粘包問題是全部語言中都會有的問題,由於只要使用了TCP協議,即便是經過socket編程也都會產生的問題。linux
注意:只有TCP有粘包現象,UDP永遠不會粘包,爲什麼,且聽我娓娓道來。算法
首先須要掌握一個socket收發消息的原理shell
發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。編程
例如基於TCP的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束。json
所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。windows
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。服務器
udp的recvfrom是阻塞的,一個recvfrom(x)必須對惟一一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠socket
TCP的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。tcp
假設客戶端分別發送了兩個數據包D1和D2給服務端,因爲服務端一次讀取到的字節數是不肯定的,故可能存在如下4種狀況。
特例:若是此時服務端TCP接收滑窗很是小,而數據包D1和D2比較大,頗有可能會發生第五種可能,即服務端分屢次才能將D1和D2包接收徹底,期間發生屢次拆包。
使用方法:
import struct #把一個數字打包成固定長度的4字節,獲得字節格式數據 obj=struct.pack('i',1024) # 'i'是格式 print(obj) print(len(obj)) # 解包,獲得元祖類型數據 l=struct.unpack('i',obj)[0] print(l)
問題的根源在於,接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。
服務器:
import socket import subprocess import struct HOST = "192.168.11.237" PORT = 8082 soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind((HOST,PORT)) soc.listen(5) while 1: print("等待鏈接。。。") conn,addr = soc.accept() print("鏈接成功。。。\n") while 1: try: data = conn.recv(1024) if len(data)==0: # 長度0說明斷開了鏈接。在windows中沒用,在linux中才有用 break # 把執行正確的內容放到管道中 obj = subprocess.Popen(str(data, encoding="utf8"), shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 執行的結果 b 格式,gbk編碼(windows平臺) suc_data = obj.stdout.read() fail_data = obj.stderr.read() if suc_data: # 1.先打包數據長度,爲四個字節,在發過去 head = struct.pack("i", len(suc_data)) conn.send(head) # 做爲頭信息發過去,字節格式 # 2.發真正的數據信息 conn.send(suc_data) else: # 1.先打包數據長度,爲四個字節,在發過去 head = struct.pack("i", len(fail_data)) conn.send(head) # 做爲頭信息發過去,字節格式 # 發錯誤信息 conn.send(fail_data) except: print("客戶機鏈接中斷。。。") break conn.close() soc.close()
客戶端:
import socket import struct HOST = "192.168.11.237" PORT = 8082 soc = socket.socket() soc.connect((HOST,PORT)) while 1: try: cmd = input("請輸入須要執行的命令") soc.send(cmd.encode("utf8")) # 1.獲得數據的頭信息並解包 head = soc.recv(4) data_len = struct.unpack("i",head)[0] if head == 0: break # 2.根據頭信息,拿到數據長度來接收數據 str_data = b"" while data_len: if data_len > 1024: data = soc.recv(1024) # 你要接收的數據長度必須等於真實接收到的數據長度才能夠中止 str_data += data data_len -= len(data) else: data = soc.recv(data_len) str_data += data data_len -= len(data) print(str_data.decode("gbk")) except Exception as a: print("服務器關閉了:", a) break # 4.關閉鏈接 soc.close()
簡單版解決方案確實能解決粘包問題,可是若是當要傳輸的數據太大則就會發生問題。下面給出個人解決方案
服務器:
import socket import subprocess import struct import json HOST = "192.168.11.24" PORT = 8080 soc = socket.socket(socket.AF_INET,socket.SOCK_STREAM) soc.bind((HOST,PORT)) soc.listen(5) while 1: print("等待鏈接。。。") conn,addr = soc.accept() print(f"主機{addr}鏈接成功,準備接收消息中。。。") while 1: try: # 接收命令 cmd = conn.recv(1024) # 處理命令 obj = subprocess.Popen(cmd.decode("utf8"), shell=True, stdout=subprocess.PIPE,stderr=subprocess.PIPE) suc_data = obj.stdout.read() fail_data = obj.stderr.read() if suc_data: # 1.先構造一個字典,把頭信息放到字典中 head_dic = {"data_size": len(suc_data), "md5":None, "file_name": None} # 2.對該字典序列化,獲得字符串(json串) head_dic_json_byte = (json.dumps(head_dic)).encode("utf8") # 3.將 序列化後的字典 長度打包,做爲頭部 head = struct.pack("i",len(head_dic_json_byte)) # 4.先發頭部 conn.send(head) # 5.再發 序列化後的字典(字節類型) conn.send(head_dic_json_byte) # 6.最後再發真正的數據 conn.send(suc_data) else: # 1.先構造一個字典,把頭信息放到字典中 head_dic = {"data_size": len(fail_data), "md5": None, "file_name": None} # 2.對該字典序列化,獲得字符串(json串) head_dic_json_byte = (json.dumps(head_dic)).encode("utf8") # 3.將 序列化後的字典 長度打包,做爲頭部(字節類型) head = struct.pack("i", len(head_dic_json_byte)) # 4.先發頭部 conn.send(head) # 5.再發 序列化後的字典(字節類型) conn.send(head_dic_json_byte) # 6.最後再發真正的數據 conn.send(fail_data) except: print("客戶機鏈接中斷。。。\n") break conn.close() soc.close()
客戶端
import socket import struct import json HOST = "192.168.11.24" PORT = 8080 soc = socket.socket() soc.connect((HOST,PORT)) while 1: try: cmd = input("請輸入須要執行的命令") soc.send(cmd.encode("utf8")) # 1.先拿到頭數據,並解包 head = soc.recv(4) head_dic_len = struct.unpack("i",head)[0] if len(head) == 0: # 服務器關閉了 break # 2.根據頭字典長度拿到頭字典信息,進行反序列化,拿到頭字典 head_dic_btye = soc.recv(head_dic_len) head_dic = json.loads(head_dic_btye) # json能夠直接反序列化bytes類型 # 3.根據頭字典中的數據長度拿到真正的數據 data_len= head_dic.get("data_size") str_data = b"" while data_len: if data_len > 1024: data = soc.recv(1024) # 你要接收的數據長度必須等於真實接收到的數據長度才能夠中止 str_data += data data_len -= len(data) else: data = soc.recv(data_len) str_data += data data_len -= len(data) print(str_data.decode("gbk")) except Exception as a: print("服務器關閉了:", a) break # 4.關閉鏈接 soc.close()