1. 程式人生 > >套接字——粘包現象

套接字——粘包現象

粘包現象

讓我們基於tcp先製作一個遠端執行命令的程式(1:執行錯誤命令 2:執行ls 3:執行ifconfig)

注意注意注意:

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,    
stdout=subprocess.PIPE)    

的結果的編碼是以當前所在的系統為準的,如果是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道里讀一次結果

什麼是粘包

須知:只有TCP有粘包現象,UDP永遠不會粘包,為何,且聽我娓娓道來

首先需要掌握一個socket收發訊息的原理

 

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

所謂粘包問題主要還是因為接收方不知道訊息之間的界限,不知道一次性提取多少位元組的資料所造成的。

 

tcp是基於資料流的,於是收發的訊息不能為空,這就需要在客戶端和服務端都新增空訊息的處理機制,防止程式卡住,而udp是基於資料報的,即便是你輸入的是空內容(直接回車),那也不是空訊息,udp協議會幫你封裝上訊息頭,實驗略

 

 

兩種情況下會發生粘包。

 

1、傳送端需要等緩衝區滿才傳送出去,造成粘包(傳送資料時間間隔很短,資料了很小,會合到一起,產生粘包)

 

2、接收方不及時接收緩衝區的包,造成多個包接收(客戶端傳送了一段資料,服務端只收了一小部分,服務端下次再收的時候還是從緩衝區拿上次遺留的資料,產生粘包)

 

 

 

send(位元組流)和recv(1024)及sendall

 

recv裡指定的1024意思是從快取裡一次拿出1024個位元組的資料

 

send的位元組流是先放入己端快取,然後由協議控制將快取內容發往對端,如果待發送的位元組流大小大於快取剩餘空間,那麼資料丟失,用sendall就會迴圈呼叫send,資料不會丟失

 

 

 

解決粘包的方法

 

為位元組流加上自定義固定長度報頭,報頭中包含位元組流長度,然後一次send到對端,對端在接收時,先從快取中取出定長的報頭,然後再取真實資料

 

struct模組 

 

該模組可以把一個型別,如數字,轉成固定長度的bytes

 

>>> struct.pack('i',1111111111111)

 

。。。。。。。。。

 

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是範圍

 

 

import json,struct
#假設通過客戶端上傳1T:1073741824000的檔案a.txt

#為避免粘包,必須自定製報頭
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T資料,檔案路徑和md5值

#為了該報頭能傳送,需要序列化並且轉為bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化並轉成bytes,用於傳輸

#為了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個位元組
head_len_bytes=struct.pack('i',len(head_bytes)) #這4個位元組裡只包含了一個數字,該數字是報頭的長度

#客戶端開始傳送
conn.send(head_len_bytes) #先發報頭的長度,4個bytes
conn.send(head_bytes) #再發報頭的位元組格式
conn.sendall(檔案內容) #然後發真實內容的位元組格式

#服務端開始接收
head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的位元組格式
x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度

head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式
header=json.loads(json.dumps(header)) #提取報頭

#最後根據報頭的內容提取真實的資料,比如
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)