概述
在進行TCP Socket開發時,都需要處理數據包粘包和分包的情況.實際上解決該問題很簡單,在應用層下,定義一個協議:消息頭部+消息長度+消息正文即可。
分包和粘包
分包:發送方發送字符串”helloworld”,接收方卻接收到了兩個字符串”hello”和”world”
粘包:發送方發送兩個字符串”hello”+”world”,接收方卻一次性接收到了”helloworld”
socket環境有以上問題,但是TCP傳輸數據能保證幾點:
1. 順序不變,例如發送方發送hello,接收方也一定順序接收到hello,這個是TCP協議承諾的,因此這點成為我們解決分包,黏包問題的關鍵.
2. 分割的包中間不會插入其他數據
因此如果要使用socket通信,就一定要自己定義一份協議.目前最常用的協議標准是:消息頭部(包頭)+消息長度+消息正文
TCP分包的原理
TCP是以段(Segment)為單位發送數據的,建立TCP鏈接后,有一個最大消息長度(MSS).如果應用層數據包超過MSS,就會把應用層數據包拆分,分成兩個段來發送.
這個時候接收端的應用層就要拼接這兩個TCP包,才能正確處理數據。
相關的,路由器有一個MTU( 最大傳輸單元)一般是1500字節,除去IP頭部20字節,留給TCP的就只有MTU-20字節。所以一般TCP的MSS為MTU-20=1460字節
當應用層數據超過1460字節時,TCP會分多個數據包來發送。
TCP粘包的原理
TCP為了提高網絡的利用率,會使用一個叫做Nagle的算法.該算法是指,發送端即使有要發送的數據,如果很少的話,會延遲發送.
如果應用層給TCP傳送數據很快的話,就會把兩個應用層數據包“粘”在一起,TCP最后只發一個TCP數據包給接收端.
自定義消息頭部
消息頭部不一定只能是一個字節比如0xAA
什么的,也可以包含協議版本號,指令等,當然也可以把消息長度合並到消息頭部里,
唯一的要求是包頭長度要固定的,包體則可變長。下面是我自定義的一個包頭:
版本號,消息長度,指令數據類型 都是無符號32位整型變量,於是這個消息長度固定為4×3=12字節 每個整數類型變量占用4個字節的長度

1 import struct 2 import json 3 4 version = 1 5 body =json.dumps(dict(hello="world")) 6 cmd =101 7 8 header = [version,len(body),cmd] 9 #struct中:!代表Network order,3I代表3個unsigned int數據 10 headPack = struct.pack("3i",*header) 11 print(headPack)

1 import socket 2 import time 3 import struct 4 import json 5 6 host='localhost' 7 port=1234 8 9 ADDR=(host,port) 10 11 if __name__ == "__main__": 12 client = socket.socket() 13 client.connect(ADDR) 14 15 version=1 16 body =json.dumps(dict(hello="world",myhello="aaaaaabbbbbb")) 17 #print(body) 18 cmd =110 19 header =[version,len(body),cmd] 20 #headPack=str(header).encode() 21 headPack=struct.pack('!3i',*header) 22 print(headPack,type(headPack)) 23 sendmsg1 = headPack + body.encode() 24 25 client.send(sendmsg1) 26 27 print("******分包測試***********") 28 version2 =2 29 body2=json.dumps(dict(hello="world2")) 30 cmd2 =102 31 header2 = [version2,len(body2),cmd2] 32 headPack2=struct.pack("!3i",*header2) 33 sendmsg2_1 = headPack2 + body2[:2].encode() 34 sendmsg2_2 = body2[2:].encode() 35 36 client.send(sendmsg2_1) 37 time.sleep(0.2) 38 client.send(sendmsg2_2) 39 time.sleep(3) 40 41 print("******粘包測試***********") 42 version3=3 43 body3 = json.dumps(dict(hello="world3")) 44 cmd3 = 103 45 header3 = [version3,len(body3),cmd3] 46 headPack3 = struct.pack("!3I",*header3) 47 48 version4 = 4 49 body4 = json.dumps(dict(hello="world4444")) 50 cmd4 = 104 51 header4 = [version4,len(body4),cmd4] 52 headPack4 = struct.pack("!3i",*header4) 53 54 sendmsg3 = headPack3+body3.encode()+headPack4+body4.encode() 55 client.send(sendmsg3) 56 time.sleep(3) 57 client.close()

import socket import struct import json HOST='localhost' PORT =1234 dataBuffer = bytes() headerSize =12 sn = 0 def dataHandler(headPack,body): global sn sn +=1 print("第%s個數據包:" %sn) print("version:%s,bodysize:%s,cmd:%s" % headPack) print(json.loads(body.decode())) print("結束....") if __name__ =="__main__": with socket.socket(socket.AF_INET,socket.SOCK_STREAM) as s: s.bind((HOST,PORT)) s.listen() conn,addr = s.accept() with conn: print("connted by",addr) while True: data = conn.recv(1024) if data: dataBuffer +=data while True: if len(dataBuffer) < headerSize: print("數據包(%s Byte) 小於消息頭部長度,跳出小循環" %len(dataBuffer)) break headPack = struct.unpack("!3i",dataBuffer[:headerSize]) # print(headPack) # print(dataBuffer) # print(dataBuffer[:headerSize]) bodySize = headPack[1] print(dataBuffer[headerSize:headerSize + bodySize]) #分包 if len(dataBuffer) < headerSize+bodySize: print("數據包(%s byte)不完整(總共 %s byte),跳出循環" %(len(dataBuffer),headerSize+bodySize)) break body = dataBuffer[headerSize:headerSize+bodySize] dataHandler(headPack,body) dataBuffer = dataBuffer[headerSize+bodySize:]