python/socket編程之粘包

python/socket編程之粘包

粘包

  只有TCP有粘包現象,UDP永遠不會粘包。python

  首先須要掌握一個socket收發消息的原理算法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
發送端能夠是 1k 1k 的發送數據而接受端的應用程序能夠 2k 2k 的提取數據,固然也有多是 3k 或者多k提取數據,也就是說,應用程序是不可見的,所以TCP協議是面來那個流的協議,這也是容易出現粘包的緣由而UDP是面向笑死的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任一字節的數據,這一點和TCP是很同的。怎樣定義消息呢?認爲對方一次性write / send的數據爲一個消息,須要命的是當對方send一條信息的時候,不管鼎城怎麼樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。
 
例如基於TCP的套接字客戶端往服務器端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看來更笨不知道文件的字節流從何初開始,在何處結束。
 
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的
 
發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多數據後才發上一個TCP段。如連續幾回下須要send的數據都不多,一般TCP會根據優化算法把 這些數據合成一個TCP段後 一次發送出去,這樣接收方就收到了粘包數據
 
TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。
tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略
udp的recvfrom是阻塞的,一個recvfrom(x)必須對一個一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠
 
tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。

  

兩種狀況下會發生粘包:

1.發送端須要等本機的緩衝區滿了之後才發送出去,形成粘包(發送數據時間間隔很端,數據很小,會合在一個起,產生粘包)shell

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

粘包實例:

複製代碼
服務端
import socket import subprocess din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=('127.0.0.1',8080) din.bind(ip_port) din.listen(5) conn,deer=din.accept() data1=conn.recv(1024) data2=conn.recv(1024) print(data1) print(data2)
複製代碼

 

複製代碼
客戶端
import socket import subprocess din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=('127.0.0.1',8080) din.connect(ip_port) din.send('helloworld'.encode('utf-8')) din.send('sb'.encode('utf-8'))
複製代碼

 low比的解決粘包的方法:json

在客戶端發送下邊添加一個時間睡眠,就能夠避免粘包現象。在服務端接收的時候也要進行時間睡眠,纔能有效的避免粘包狀況windows

複製代碼
#客戶端 import socket import time import subprocess din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=('127.0.0.1',8080) din.connect(ip_port) din.send('helloworld'.encode('utf-8')) time.sleep(3) din.send('sb'.encode('utf-8'))
複製代碼
複製代碼
#服務端 import socket import time import subprocess din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) ip_port=('127.0.0.1',8080) din.bind(ip_port) din.listen(5) conn,deer=din.accept() data1=conn.recv(1024) time.sleep(4) data2=conn.recv(1024) print(data1) print(data2)
複製代碼

大神解決粘包的方法:

  爲字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後依次send到對端,對端在接受時,先從緩存中取出定長的報頭,而後再取真是數據。緩存

  使用struct模塊服務器

  該模塊能夠把一個類型,如數字,轉成固定長度的bytes字節網絡

struct模塊 

  該模塊能夠把一個類型,如數字,轉成固定長度的bytes
  >>> res=struct.pack('i',1111111111111)  #打包成固定長度的bytes
  >>> struct.unpack(「I」,res)             #解包

採用自定義報頭的形式:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
1 #服務端
2 import socket #導入模塊
3 import struct #導入模塊
4 import subprocess #導入模塊
5 din = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於網路家族以數據流的形式傳輸
6 ip = ( '127.0.0.1' , 8080 ) #標識服務端惟一的地址
7 din.bind(ip) #綁定地址
8 din.listen( 5 ) #設置連接緩衝池最大數量
9 while True :
10 conn,addr = din.accept() #等待連接進入
11 print ( '------>' ,addr)
12 while True :
13 try : #開始捕捉異常,在windows中使用
14 cmd = conn.recv( 1024 ) #把接收的內容賦值給變量cmd
15 res = subprocess.Popen(cmd.decode( 'utf-8' ),shell = True ,stdout = subprocess.PIPE,stderr = subprocess.PIPE) #設置一個管道,以shell的方式寫入,判斷若是是正確的命令就放到標準輸出的管道中,不然就放到錯誤輸出的管道中
16 dui = res.stdout.read() #讀取標準輸出管道中的內容賦值給dui
17 cuo = res.stderr.read() #讀取錯誤輸出管道中的內容賦值給cuo
18 data_bat = len (dui) + len (cuo) #把dui和cuo的內容長度相加賦值給data_bat
19 conn.send(struct.pack( 'i' ,data_bat)) #經過模塊模仿報頭形式發送給客戶端
20 conn.send(dui) #把dui的發送給客戶端
21 conn.send(cuo) #把cuo的發送給客戶端
22 except Exception: #結束捕捉異常<br>23 break #跳出
24 conn.close() #斷開連接
25 din.close() #關閉基於網絡家族傳輸的連接
複製代碼
複製代碼
 1 #客戶端  2 import socket  3 import struct  4 din=socket.socket(socket.AF_INET,socket.SOCK_STREAM)  5 ip_port=('127.0.0.1',8080)  6 din.connect(ip_port)  7 while True:  8 cmd=input('請輸入命令:').strip()  9 if not cmd:continue 10 din.send(bytes(cmd,encoding='utf-8')) 11 baotou=din.recv(4) 12 data_size=struct.unpack('i',baotou)[0] 13 rec_size=0 14 rec_data=b'' 15 while rec_size < data_size: 16 data=din.recv(1024) 17 rec_size+=len(data) 18 rec_data+=data 19 20 print(rec_data.decode('gbk')) 21 22 din.close()
複製代碼
複製代碼

牛逼報頭的形式:

複製代碼
 1 #服務端  2 import struct #導入模塊  3 import socket #導入模塊  4 import json #導入模塊  5 import subprocess #導入模塊  6 din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於網絡家族以流的方式建立一個連接路  7 ip=('127.0.0.1',8080) #設定服務端惟一的地址  8 din.bind(ip) #綁定地址  9 din.listen(5) #激活啓動,設置連接池最大緩數量 10 while True: 11 conn,addr=din.accept() #等待接受數據連接 12 print('------->>>') 13 while True: 14 try: #開始捕捉異常,在windows中使用 15 dr=conn.recv(1024) #接收內容並賦值給dr 16 res=subprocess.Popen(dr.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #設置一個管道,把對的內容一個對的管道中,把錯誤的放進一個錯誤的管道中。 17 dui=res.stdout.read() #讀取標準輸出的內容並賦值給dui 18 cuo=res.stderr.read() #讀取錯誤輸出的內容並賦值給cuo 19 data_lik=len(dui)+len(cuo) #計算標準輸出和錯誤輸出的總長度並賦值給data_lik 20 head_dic={'data_lik':data_lik} #本身頂一個字典 並賦值給head_dic 21 head_json=json.dumps(head_dic) #把本身定義的字典進行一個序列化 22 head_bytes=head_json.encode('utf-8') #把序列化後的內容進行轉碼 ,由於網絡傳輸都是經過字節進行傳輸 23 head_len=len(head_bytes) #把轉換成字節碼的長度拿到 並賦值給head_len 24 #發送報頭長度 25 conn.send(struct.pack('i',head_len)) #發送本身定義的報頭長度 26 # 發送報頭 27 conn.send(head_bytes) #發送本身定義的報頭 28 #發送真是數據 29 conn.send(dui) #發送真實的數據 30 conn.send(cuo) #發送真實的數據 31 except Exception: #結束捕捉異常 32 break #跳出 33 conn.close() #斷開連接 34 din.close() #關閉鏈接 
複製代碼
複製代碼
 1 #客戶端  2 import struct #導入模塊  3 import socket #導入模塊  4 import json #導入模塊  5 din=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於網絡家族和流的方式傳輸 建立鏈接  6 ip=('127.0.0.1',8080) #設置惟一的地址  7 din.connect(ip) #綁定地址  8 while True:  9 run=input('請輸入命令:') #用戶交互界面 10 if not run:continue #判斷若是run爲空就跳出 11 din.send(bytes(run,encoding='utf-8')) #經過字節形式發送用戶輸入的的內容 12 data=din.recv(4) #接收內容長度爲4,並賦值給data 13 head_len=struct.unpack('i',data)[0] #解報頭並索引出值賦值給head_len 14 head_bytes=din.recv(head_len) #接收長度爲head_len變量中的值,賦值給head_bytes 15 head_json=head_bytes.decode('utf-8') #把剛剛接收的內容進行轉換,轉換成utf-8 16 head_dic=json.loads(head_json) #在把轉換成utf-8的內容 反序列化成字典的形式 17 data_lik=head_dic['data_lik'] #在把字典的查找的值賦值給data_lik 18 recv_size=0 #設定變量並賦值爲0 19 recv_data=b'' #設定變量並建立一個空字節的 20 while recv_size < data_lik: #若是變量小於字典查找出來的值時,循環爲真。 21 data=din.recv(1024) #接收內容賦值給data 22 recv_size+=len(data) #把獲得的內容長度加到變量recv_size中,這樣就能實現若是沒有取完內容就一直取得效果 23 recv_data+=data #把拿到的值加到新建立的字節中 24 print(recv_data.decode('gbk')) #打印以GBK轉碼的內容 25 din.close() #關閉鏈接
複製代碼
相關文章
相關標籤/搜索