概述
在进行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:]