TCP Socket的粘包和分包的处理


概述

   在进行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:]
服务端解析数据包   


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM