TCP粘包以及解決方案
阿新 • • 發佈:2018-11-06
獲取文件 打開文件 tro cmd nco fin 邊界 機制 port
TCP協議粘包現象的說明:
- TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合並成一個大的數據塊,然後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。
粘包會發生什麽?
這時候,接受不知道數據的限界,就沒有辦法正確的解析對方傳輸過來的限界。就才去了類似通信協議的解決方案,處理粘包問題。
簡單解決問題的方法。根據當前需要發送的數據的大小傳輸數據的二進制長度先發送給客戶端,在根據傳輸數據的長度來獲取的真實的數據。
實現如下服務端
import socket,subprocess,struct server = socket.socket() server.bind(("127.0.0.1",54321)) server.listen(5) while True: client, addr = server.accept() while True: try: cmd = client.recv(1024).decode("utf-8")#接受命令 if not cmd: print("client closed") client.close() break p = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) res = p.stdout.read()+p.stderr.read()#獲得返回的數據 res_len =len(res)#整形形式的需要轉換為固定長度的二進制形式。使用struct模塊 bytes_len = struct.pack("i",res_len) client.send(bytes_len) client.send(res) except ConnectionResetError: print("客戶端錯誤退出") client.close() break server.close()
客戶端
import socket,struct client =socket.socket() client.connect(("127.0.0.1",54321)) while True: cmd = input(">>>:") if not cmd:continue client.send(cmd.encode("utf-8")) #獲取數據的長度的二進制 bytes_len = client.recv(1024) #解壓成為整形 res_len = struct.unpack("i",bytes_len)[0] #接受數據的方式 final_data=b"" #當前接受數據的長度計數器 data_len = 0 while data_len<res_len: res = client.recv(1024) final_data += res data_len += len(res) print(final_data.decode("gbk")) client.close()
其實我們還以定制更加復雜的數據頭
可以字典形式表現,實現傳輸更加復雜文件,甚至可以進行校驗
服務端
import socket,struct,json server = socket.socket() server.bind(("127.0.0.1",3333)) server.listen(5) while True: client,addr = server.accept() while True: try: ml = client.recv(1024).decode("utf-8")#模擬客戶端請求報告 if not ml: client.close() break if ml != "1":continue #開始資質表頭 filename = "總結" #計算文件bytes長度 file_len = 0 f = open(r"F:\Python_exe\day34\上午總結","rb") for line in f: line_len = len(line) file_len += line_len f.close() file_dict = {"filename":filename,"length":file_len} #將字典裝換為json格式 json_dict = json.dumps(file_dict) #再json字符串轉為bytes byte_dict = json_dict.encode("utf-8") #計算字典二進制的長度 head_len = len(byte_dict) #將長度打包成一個數據頭 head = struct.pack("i", head_len) client.send(head) client.send(byte_dict) #標記內容完成 #傳輸正式內容 with open(r"F:\Python_exe\day34\上午總結","rb") as f: for line in f: client.send(line) except ConnectionResetError: print("連接錯誤連接") client.close() break
客戶端
import socket,struct模塊,json client = socket.socket() client.connect(("127.0.0.1",3333)) while True: ml = input(">>>:") if not ml:continue client.send(ml.encode("utf-8")) #獲取字典的二進制 bytes_len = client.recv(4) #還原 head_len =struct模塊.unpack(‘i‘, bytes_len)[0] #獲取數據報頭 bytes_dict = client.recv(head_len) #將bytes類型轉化為json格式 json_dict = bytes_dict.decode("utf-8") #將字典json格式還原為成python的字典 file_dict=json.loads(json_dict) #獲取文件的名稱 filename = file_dict["filename"] #獲取文件的傳輸長度 file_len = file_dict["length"] #打開文件追加寫 f = open(filename,"ab") #當前接受長度 data_len = 0 while data_len<file_len: data = client.recv(1024) data_len += len(data) f.write(data) f.close() client.close() client.close()
解決粘包的方案 自定義報頭
1.先用報頭傳輸數據的長度
對於我們遠程CMD程序來說 只要先傳輸長度就能解決粘包的問題
但是如果做得是一個文件上傳下載 除了數據的長度 還需要傳輸文件的名字 md5等等信息
又該如何?
2.自定義復雜報頭 完成發送一些額外的信息 例如文件名
1.將要發送的額外數據打包成一個字典
2.將字典轉為bytes類型
3.計算字典的bytes長度 並先發送
4.發送字典數據
5.發送真實數據
TCP粘包以及解決方案