<kbd id='woaibaidu'></kbd><address id='woaibaidu'><style id='woaibaidu'></style></address><button id='woaibaidu'></button>

          当前位置:主页 > 脚本专栏 > python >
            python TCP Socket的粘包和分包的处理详解
            2018-02-12 22:09 发布 次浏览

          概述

          在停止TCP Socket开发时,都需求处置数据包粘包和分包的状况。本文详细解说处理该成绩的步骤。运用的言语是Python。实践上处理该成绩很复杂,在使用层下,界说1个协议:音讯头部+音讯长度+音讯注释便可。

          那甚么是粘包和分包呢?

          关于分包和粘包

          粘包:发送方发送两个字符串”hello”+”world”,接纳方却1次性接纳到了”helloworld”。

          分包:发送方发送字符串”helloworld”,接纳方却接纳到了两个字符串”hello”和”world”。

          虽然socket情况有以上成绩,可是TCP传输数据能保证几点:

          • 顺序稳定。例如发送方发送hello,接纳方也1定顺序接纳到hello,这个是TCP协议许诺的,因而这点成为我们处理分包、黏包成绩的要害。
          • 联系的包两头不会拔出其他数据。

          因而假如要运用socket通讯,就1定要本人界说1份协议。现在最经常使用的协议规范是:音讯头部(包头)+音讯长度+音讯注释

          TCP为何会分包

          TCP是以段(Segment)为单元发送数据的,树立TCP链接后,有1个最大音讯长度(MSS)。假如使用层数据包超越MSS,就会把使用层数据包拆分,分红两个段来发送。这个时分接纳真个使用层就要拼接这两个TCP包,才干正确处置数据。

          相干的,路由器有1个MTU( 最大传输单元),普通是1500字节,除去IP头部20字节,留给TCP的就只要MTU⑵0字节。所以普通TCP的MSS为MTU⑵0=1460字节。

          当使用层数据超越1460字节时,TCP会分多个数据包来发送。

          扩大浏览

          TCP的RFC界说MSS的默许值是536,这是由于 RFC 791里说了任何1个IP装备都得最少接纳576尺寸的巨细(实践下去说576是拨号的网络的MTU,而576减去IP头的20个字节就是536)。
          TCP为何会粘包

          有时分,TCP为了进步网络的应用率,会运用1个叫做Nagle的算法。该算法是指,发送端即便有要发送的数据,假如很少的话,会延迟发送。假如使用层给TCP传送数据很快的话,就会把两个使用层数据包“粘”在一同,TCP最初只发1个TCP数据包给接纳端。

          开发情况

          • Python版本:3.5.1
          • 操作零碎:Windows 10 x64

          音讯头部(包括音讯长度)

          音讯头部纷歧定只能是1个字节比方0xAA甚么的,也能够包括协议版本号,指令等,固然也能够把音讯长度兼并到音讯头部里,独一的要求是包头长度要牢固的,包体则可变长。上面是我自界说的1个包头:

          版本号(ver) 音讯长度(bodySize) 指令(cmd)

          版本号,音讯长度,指令数据类型都是无符号32位整型变量,因而这个音讯长度牢固为4×3=12字节。在Python由于没有类型界说,所以普通是运用struct模块生成包头。示例:

          import struct
          import json
          
          ver = 1
          body = json.dumps(dict(hello="world"))
          print(body) # {"hello": "world"}
          cmd = 101
          header = [ver, body.__len__(), cmd]
          headPack = struct.pack("!3I", *header)
          print(headPack) # b'\x00\x00\x00\x01\x00\x00\x00\x12\x00\x00\x00e'

          关于用自界说完毕符联系数据包

          有的人会想用自界说的完毕符联系每个数据包,这样传输数据包时就不需求指定长度乃至也不需求包头了。可是假如这样做,网络传输功能损失十分大,由于每读取1个字节都要做1次if判别能否是完毕符。所以建议照旧选择音讯头部+音讯长度+音讯注释这类方式。

          而且,运用自界说完毕符的时分,假如音讯注释中呈现这个符号,就会把前面的数据停止,这个时分还需求处置符号本义,类比于\r\n的反斜杠。所以十分不建议运用完毕符联系数据包。

          音讯注释

          音讯注释的数据花样可使用Json花样,这里普通是用来寄存共同信息的数据。在上面代码中,我运用{"hello","world"}数据来测试。在Python运用json模块来生成json数据

          Python示例

          上面运用Python代码展现如那边理TCP Socket的粘包和分包。中心在于用1个FIFO行列接纳缓冲区dataBuffer和1个小while循环来判别。

          详细流程是这样的:把从socket读取出来的数据放到dataBuffer前面(入队),然落后入小循环,假如dataBuffer内容长度小于音讯长度(bodySize),则跳出小循环持续接纳;大于音讯长度,则从缓冲区读取包头并获得包体的长度,再判别全部缓冲区能否大于音讯头部+音讯长度,假如小于则跳出小循环持续接纳,假如大于则读取包体的内容,然后处置数据,最初再把这次的音讯头部和音讯注释从dataBuffer删掉(出队)。

          上面用Markdown画了1个流程图。

          效劳器端代码

          # Python Version:3.5.1
          import socket
          import struct
          
          HOST = ''
          PORT = 1234
          
          dataBuffer = bytes()
          headerSize = 12
          
          sn = 0
          def dataHandle(headPack, body):
            global sn
            sn += 1
            print("第%s个数据包" % sn)
            print("ver:%s, bodySize:%s, cmd:%s" % headPack)
            print(body.decode())
            print("")
          
          if __name__ == '__main__':
            with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
              s.bind((HOST, PORT))
              s.listen(1)
              conn, addr = s.accept()
              with conn:
                print('Connected by', addr)
                while True:
                  data = conn.recv(1024)
                  if data:
                    # 把数据存入缓冲区,相似于push数据
                    dataBuffer += data
                    while True:
                      if len(dataBuffer) < headerSize:
                        print("数据包(%s Byte)小于音讯头部长度,跳出小循环" % len(dataBuffer))
                        break
          
                      # 读取包头
                      # struct中:!代表Network order,3I代表3个unsigned int数据
                      headPack = struct.unpack('!3I', dataBuffer[:headerSize])
                      bodySize = headPack[1]
          
                      # 分包状况处置,跳出函数持续接纳数据
                      if len(dataBuffer) < headerSize+bodySize :
                        print("数据包(%s Byte)不完好(总共%s Byte),跳出小循环" % (len(dataBuffer), headerSize+bodySize))
                        break
                      # 读取音讯注释的内容
                      body = dataBuffer[headerSize:headerSize+bodySize]
          
                      # 数据处置
                      dataHandle(headPack, body)
          
                      # 粘包状况的处置
                      dataBuffer = dataBuffer[headerSize+bodySize:] # 获得下1个数据包,相似于把数据pop出

          测试效劳器真个客户端代码

          上面附上测试粘包和分包的客户端代码

          # Python Version:3.5.1
          import socket
          import time
          import struct
          import json
          
          host = "localhost"
          port = 1234
          
          ADDR = (host, port)
          
          if __name__ == '__main__':
            client = socket.socket()
            client.connect(ADDR)
          
            # 正常数据包界说
            ver = 1
            body = json.dumps(dict(hello="world"))
            print(body)
            cmd = 101
            header = [ver, body.__len__(), cmd]
            headPack = struct.pack("!3I", *header)
            sendData1 = headPack+body.encode()
          
            # 分包数据界说
            ver = 2
            body = json.dumps(dict(hello="world2"))
            print(body)
            cmd = 102
            header = [ver, body.__len__(), cmd]
            headPack = struct.pack("!3I", *header)
            sendData2_1 = headPack+body[:2].encode()
            sendData2_2 = body[2:].encode()
          
            # 粘包数据界说
            ver = 3
            body1 = json.dumps(dict(hello="world3"))
            print(body1)
            cmd = 103
            header = [ver, body1.__len__(), cmd]
            headPack1 = struct.pack("!3I", *header)
          
            ver = 4
            body2 = json.dumps(dict(hello="world4"))
            print(body2)
            cmd = 104
            header = [ver, body2.__len__(), cmd]
            headPack2 = struct.pack("!3I", *header)
          
            sendData3 = headPack1+body1.encode()+headPack2+body2.encode()
          
          
            # 正常数据包
            client.send(sendData1)
            time.sleep(3)
          
            # 分包测试
            client.send(sendData2_1)
            time.sleep(0.2)
            client.send(sendData2_2)
            time.sleep(3)
          
            # 粘包测试
            client.send(sendData3)
            time.sleep(3)
            client.close()

          效劳器端打印后果

          上面是测试出来的打印后果,可见接纳方曾经完满的处置粘包和分包成绩了。

          Connected by ('127.0.0.1', 23297)
          第1个数据包
          ver:1, bodySize:18, cmd:101
          {"hello": "world"}
          
          数据包(0 Byte)小于包头长度,跳出小循环
          数据包(14 Byte)不完好(总共31 Byte),跳出小循环
          第2个数据包
          ver:2, bodySize:19, cmd:102
          {"hello": "world2"}
          
          数据包(0 Byte)小于包头长度,跳出小循环
          第3个数据包
          ver:3, bodySize:19, cmd:103
          {"hello": "world3"}
          
          第4个数据包
          ver:4, bodySize:19, cmd:104
          {"hello": "world4"}

          在框架下处置粘包和分包

          其实不管是运用阻塞照旧异步socket开发框架,框架自身都市提供1个接纳数据的办法提供应开发者,普通来讲开发者都要覆写这个办法。上面是在Twidted开发框架处置粘包和分包的示例,只上中心顺序:

          # Twiested
          class MyProtocol(Protocol):
            _data_buffer = bytes()
          
            # 代码省略
          
            def dataReceived(self, data):
              """Called whenever data is received."""
              self._data_buffer += data
              headerSize = 12
          
              while True:
                if len(self._data_buffer) < headerSize:
                  return
          
                # 读取音讯头部
                # struct中:!代表Network order,3I代表3个unsigned int数据
                headPack = struct.unpack('!3I', self._data_buffer[:headerSize])
                # 获得音讯注释长度
                bodySize = headPack[1]
          
                # 分包状况处置
                if len(self._data_buffer) < headerSize+bodySize :
                  return
          
                # 读取音讯注释的内容
                body = self._data_buffer[headerSize:headerSize+bodySize]
                # 处置数据
                self.dataHandle(headPack, body)
                # 粘包状况的处置
                self._data_buffer = self._data_buffer[headerSize+bodySize:]

          总结

          以上就是本文关于python TCP Socket的粘包和分包的处置详解的全部内容,希望对各人有所协助。感兴味的冤家可以持续参阅本站其他相干专题,如有缺乏的地方,欢送留言指出。感激冤家们对本站的支持!