python之socket編程

客戶端&服務端架構

  c/s架構:  即python

  1.硬件C/S架構(打印機)web

  2.軟件C/S架構(web服務)算法

  socket給咱們提供了一個接入c/s架構的接口,至於傳輸層使用的協議取決於咱們socket接口           使用的協議編程

socket

  socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口.在設計模式中,socket其         實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在socket接口後面,對用戶來講,一組簡               單的接口就是所有,讓socket去組織數據,以符合指定的協議設計模式

  因此,咱們無需深刻理解TCP/UDP協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的           規 定去編程,寫出的程序天然就是遵循tcp/udp標準緩存

套接字家族

基於文件類型的套接字家族

        套接字家族的名字:AF_UNIX服務器

        unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程            運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊網絡

基於網絡類型的套接字家族

        套接字家族的名字:AF_INET數據結構

        (還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平                臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族                        中,AF_INET是使用最普遍的一個,python支持不少種地址家族)架構

套接字工做流程

  服務器端先初始化Socket, 而後與端口綁定(bind), 對端口進行監聽(listen), 調用accept阻塞, 等待客戶端鏈接. 在這時若是有個客戶端初始化一個Socket, 而後鏈接服務器(connect), 若是鏈接成功, 這時客戶端與服務器端的鏈接就創建了. 客戶端發送數據請求, 服務器端接收請求並處理請求, 而後把迴應數據發送給客戶端, 客戶端讀取數據, 最後關閉鏈接, 一次交互結束

from socket import *    ##服務端
ip_port =("127.0.0.1",8082)
back_log = 5    #設定鏈接池的大小
buffer_size = 1024

phone = socket(AF_INET,SOCK_STREAM)#建立服務端套接字
phone.bind(ip_port)     #綁定地址
phone.listen(back_log)   #監聽連接
while True:#外層循環用來接收不一樣的連接
    print("服務端開始運行了!")
    conn,addr = phone.accept()  #接收客戶端連接
    print("雙向連接是",conn)
    print("客戶端地址",addr)

    while True:#內層循環用來基於一次連接循環通訊
        try:
            msg = conn.recv(buffer_size )  #接收客戶端消息
            print("客服端發來消息:",msg.decode("utf-8"))
            conn.send(msg.upper())   #發送消息至客戶端
        except Exception:#異常處理用於該次通訊退出結束該次連接
            break
    conn.close()#關閉客戶端套接字

phone.close()   #關閉服務端套接字

客戶端

 1 from socket import *
 2 ip_port = ("127.0.0.1",8082)
 3 back_log = 5
 4 buffer_size = 1024
 5 
 6 phone = socket(AF_INET,SOCK_STREAM)   #建立客戶端套接字
 7 phone.connect(ip_port)    #嘗試連接服務端
 8 
 9 while True:#通信循環
10     use_choose = input("請輸入信息》》》:").strip()
11     if not use_choose:continue
12     phone.send(use_choose.encode("utf-8"))  #發送消息至服務端
13     print("客戶端已發送消息!")
14     date = phone.recv(buffer_size)    #接收服務端返回的消息
15     print("收到服務端發來的消息:",date.decode("utf-8"))
16 phone.close()     #關閉客戶端套接字

注意:在關閉掉服務端後短期內重啓可能會遇到

 

這個是因爲你的服務端仍然存在四次揮手的time_wait狀態在佔用地址

1 #加入一條socket配置,重用ip和端口
2 
3 phone=socket(AF_INET,SOCK_STREAM)
4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
5 phone.bind(('127.0.0.1',8080))

UDP協議的套接字

  服務端: 基於UDP協議的套接字服務端與TCP協議下的套接字服務端不一樣,UDP協議的服務端            沒有鏈接池, 能夠實現即時通訊,同時與多人通訊

1 #服務端
2 ss = socket()   #建立一個服務器的套接字
3 ss.bind()       #綁定服務器套接字
4 inf_loop:       #服務器無限循環
5      cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送)
6 ss.close()                         # 關閉服務器套接字

客戶端

1 cs = socket()   # 建立客戶套接字
2 comm_loop:      # 通信循環
3     cs.sendto()/cs.recvfrom()   # 對話(發送/接收)
4 cs.close()                      # 關閉客戶套接字

簡單示例:

時間服務器

 1 服務端
 2 from  socket import *
 3 import time
 4 ip_port = ("127.0.0.1",8080)
 5 buffer_size = (1024)
 6 servier_clint = socket(AF_INET,SOCK_DGRAM)#建立服務器套接字
 7 servier_clint.bind(ip_port)   #綁定服務器套接字
 8 
 9 while True:
10     print("等待用戶接入")
11     date,addr=servier_clint.recvfrom(buffer_size)  #接收客戶端消息
12     if  not date:
13         fmt = "%Y-%m-%d-%X"
14     else:
15         fmt = date.decode("utf-8")
16     date = time.strftime(fmt)
17     servier_clint.sendto(date.encode("utf-8"),addr)
18 
19 
20 客戶端
21 from  socket import *
22 ip_port = ("127.0.0.1",8080)
23 buffer_size = (1024)
24 user_clint = socket(AF_INET,SOCK_DGRAM)
25 
26 
27 while True:
28     use_choose = input("請輸入》》").strip()
29     user_clint.sendto(use_choose.encode("utf-8"),ip_port)
30 
31     date,addr = user_clint.recvfrom(buffer_size)
32     print(date.decode("utf-8"))

recv與recvfrom的區別

  

發消息,都是將數據發送到己端的發送緩衝中,收消息都是從己端的緩衝區中收

tcp:send發消息,recv收消息

udp:sendto發消息,recvfrom收消息

1.tcp協議:

(1)若是收消息緩衝區裏的數據爲空,那麼recv就會阻塞

(2)tcp基於連接通訊,若是一端斷開了連接,那另一端的連接也跟着結束recv將不會阻塞,收到的是空

客戶端發送爲空,測試結果--->驗證:(1)

客戶端直接終止程序,測試結果--->驗證:(2)

 1 import subprocess
 2 from socket import *
 3 
 4 phone=socket(AF_INET,SOCK_STREAM)        #服務端
 5 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
 6 phone.bind(('127.0.0.1',8080))
 7 phone.listen(5)
 8 
 9 conn,addr=phone.accept()
10 
11 while True:
12     data=conn.recv(1024)
13     print('from client msg is ',data)
14     conn.send(data.upper())
 1 import subprocess
 2 from socket import *
 3 
 4 phone=socket(AF_INET,SOCK_STREAM)     #客戶端
 5 phone.connect(('127.0.0.1',8080))
 6 
 7 
 8 while True:
 9     msg=input('>>: ')
10     phone.send(msg.encode('utf-8'))
11     print('Client message has been sent')
12 
13     data=phone.recv(1024)
14     print('from server msg is ',data.decode('utf-8'))
15 phone.close()

2.udp協議

(1)若是若是收消息緩衝區裏的數據爲「空」,recvfrom不會阻塞

(2)recvfrom收的數據小於sendinto發送的數據時,數據丟失

(3)只有sendinto發送數據沒有recvfrom收數據,數據丟失

客戶端發送空,看服務端結果--->驗證(1)

 1 from socket import *
 2 
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_server=socket(AF_INET,SOCK_DGRAM)    #服務端
 7 udp_server.bind(ip_port)
 8 
 9 while True:
10     data1,addr=udp_server.recvfrom(bufsize)
11     print(data1)

  分別運行服務端,客戶端--->驗證(2)

 1 from socket import *
 2 
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_server=socket(AF_INET,SOCK_DGRAM)   #服務端
 7 udp_server.bind(ip_port)
 8 
 9 data1,addr=udp_server.recvfrom(1)
10 print('第一次收了 ',data1)
11 data2,addr=udp_server.recvfrom(1)
12 print('第二次收了 ',data2)
13 data3,addr=udp_server.recvfrom(1)
14 print('第三次收了 ',data3)
15 print('--------結束----------')
1 from socket import *
2 ip_port=('127.0.0.1',9003)
3 bufsize=1024
4 
5 udp_client=socket(AF_INET,SOCK_DGRAM)#客戶端
6 
7 udp_client.sendto(b'hello',ip_port)
8 udp_client.sendto(b'world',ip_port)
9 udp_client.sendto(b'egon',ip_port)

  不運行服務端,單獨運行客戶端,一點問題沒有,可是消息丟了--->驗證(3)

 1 from socket import *
 2 
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_server=socket(AF_INET,SOCK_DGRAM)    #服務端
 7 udp_server.bind(ip_port)
 8 
 9 data1,addr=udp_server.recvfrom(bufsize)
10 print('第一次收了 ',data1)
11 data2,addr=udp_server.recvfrom(bufsize)
12 print('第二次收了 ',data2)
13 data3,addr=udp_server.recvfrom(bufsize)
14 print('第三次收了 ',data3)
15 print('--------結束----------')
 1 from socket import *
 2 import time
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_client=socket(AF_INET,SOCK_DGRAM)#客戶端
 7 
 8 udp_client.sendto(b'hello',ip_port)
 9 udp_client.sendto(b'world',ip_port)
10 udp_client.sendto(b'egon',ip_port)
11 
12 print('客戶端發完消息啦')
13 time.sleep(100)

  注意:

  1.你單獨運行上面的udp的客戶端, 你發現並不會報錯, 相反tcp卻會報錯,由於udp協議只負責把包發出去,對方收不收,我根本無論,而tcp是基於鏈接的,必須有一個服務端先運行着,客戶端去跟服務端創建連接而後依託於連接才能傳遞消息,任何一方視圖把連接摧毀都會致使對方程序的崩潰

  2.上面的udp程序,你註釋任何一條客戶端的sendinto,服務端都會卡住,爲何?由於服務端有幾個recvfrom就要對應幾個sendinto,哪怕是sendinto(b" ")那也要有

  

      3.總結:

         1.udp的sendinto不用管是否有一個正在運行的服務端,能夠己端一個勁的發消息

         2.udp的recvfrom是阻塞的,一個recvfrom(x)必須對一個一個sendinto(y),收完了x個字節的數            據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠

         3.tcp的協議數據不會丟,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是                 會粘包。

粘包

  首先明白一點,socket不論是服務端仍是客戶端收發消息都是首先與各自的內核態進行交               互,而後再經過網卡進行數據傳輸

  

發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。

例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束

所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。

此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

    1. TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
    2. UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。
    3. tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略

  兩種狀況下會發生粘包

  發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據流很小,匯合                      到一塊兒,產生粘包)

 1 from socket import *    #服務端
 2 ip_port=('127.0.0.1',8080)
 3 
 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
 5 tcp_socket_server.bind(ip_port)
 6 tcp_socket_server.listen(5)
 7 
 8 
 9 conn,addr=tcp_socket_server.accept()
10 
11 
12 data1=conn.recv(10)
13 data2=conn.recv(10)
14 
15 print('----->',data1.decode('utf-8'))
16 print('----->',data2.decode('utf-8'))
17 
18 conn.close()
 1 import socket    #客戶端
 2 BUFSIZE=1024
 3 ip_port=('127.0.0.1',8080)
 4 
 5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 6 res=s.connect_ex(ip_port)
 7 
 8 
 9 s.send('hello'.encode('utf-8'))
10 s.send('feng'.encode('utf-8'))

接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)

 1 from socket import *    #服務端
 2 ip_port=('127.0.0.1',8080)
 3 
 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
 5 tcp_socket_server.bind(ip_port)
 6 tcp_socket_server.listen(5)
 7 
 8 
 9 conn,addr=tcp_socket_server.accept()
10 
11 
12 data1=conn.recv(2) #一次沒有收完整
13 data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的
14 
15 print('----->',data1.decode('utf-8'))
16 print('----->',data2.decode('utf-8'))
1 import socket    #客戶端
2 BUFSIZE=1024
3 ip_port=('127.0.0.1',8080)
4 
5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)        
6 res=s.connect_ex(ip_port)
7 
8 
9 s.send('hello feng'.encode('utf-8'))

  拆包的發生狀況

  當應用程序一次性發送的數據大於緩衝區的長度的時候,tcp會將此次發送的數據拆成幾個數據         包發送出去

  補充問題一:爲什麼tcp是可靠傳輸,UDP是不可靠傳輸

  

      tcp在數據傳輸時,發送端先把數據發送到本身的緩存中,而後協議控制將緩存中的數據發往          對端,對端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則從新發送數              據,因此tcp是可靠的

      而udp發送數據,對端是不會返回確認信息的,所以不可靠

  補充問題二:send(字節流)和recv(1024)及sendall

  

       recv裏指定的1024意思是從緩存裏一次拿出1024個字節的數據

      send的字節流是先放入己端緩存,而後由協議控制將緩存內容發往對端,若是待發送的字節流        大小大於緩存剩餘空間,那麼數據丟失,用sendall就會循環調用send,數據不會丟失

相關文章
相關標籤/搜索