tcp粘包现象和解决方式

底层原理分析:

数据流向:

客户端应用程序
客户端操作系统内存
客户端网卡
基于tcp协议发送/接收
服务器网卡
服务器操作系统内存
服务器应用程序
  1. 不管是recv()还是send(),都不是直接接收对方的数据,而是操作自己的操作系统内存。

  2. 不是一个send()对应一个recv()

  3. 通信流程:

    recv:
    	wait data 耗时非常长
    	copy data
    send:
    	copy data
    

Nagle算法:(减少网络IO,提高程序效率,同时会有粘包的问题)

​ TCP(transport control protocol,传输控制协议)是面向连接的,面向流的,提供高可靠性服务。收发两端(客户端和服务端)都要有一一成对的socket,因此,发送端为了将多个发往接收端的包更有效的发给对方,使用了优化算法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制,即面向流的通信是无消息保护边界的。

解决粘包问题:

简单版本:发送固定长度的报头(只包含数据长度信息)

客户端 服务器 请求数据 使用struct制作固定长度的报头 发送报头 报头(数据的长度(total_size)) 拿到数据的长度(total_size) 使用数据长度作为数据块大小进行接收 若recv_size<total_size,则循环接收再拼接 发送真实数据 真实数据 接收真实数据 请求数据 ... 客户端 服务器 标准时序图
  • 服务器制作固定长度报头:

    import struct
    
    res = struct.pack("i",1230) # i:int 4字节
    
    print(res,type(res),len(res))
    
    --------------------------------------
    
    b'\xce\x04\x00\x00' <class 'bytes'> 4
    
  • 客户端解包报头:

    import struct
    
    recv = struct.unpack("i",res)
    
    print(recv,recv[0])
    
    --------------------------------------
    
    (1230,) 1230
    

终极版本:发送固定长度的报头(用字典封装,包含多种信息)

客户端 服务器 请求数据 字典封装报头信息 字典-->>字符串-->>字节-->>len(字节)-->>获得报头长度 struct.pack('i',len(字节)) 发送固定字节的报头长度 报头长度 拿到报头长度 使用报头长度作为数据块大小接收报头 发送报头 报头 拿到报头 从报头中解析出真实数据的描述信息 拿到total_size 使用total_size作为数据块大小准备接收真实数据 发送真实数据 真实数据 接收真实数据 请求数据 ... 客户端 服务器 标准时序图
  • 服务器发送报头:

    import struct
    import json
    
    header_dic = {
        'filename':'a.txt',
        'md5':'xxxxxxx',
        'total_size':6516161646513131
    }
    
    # 序列化
    header_json = json.dumps(header_dic)
    print(header_json,'\n',type(header_json))
    header_bytes = header_json.encode('utf-8')
    print(header_bytes,'\n',type(header_bytes),'\n',len(header_bytes))
    
    socket.send(struct.pack('i',len(header_bytes))) # 发送报头长度
    
    ---------------------------------------------
    {"filename": "a.txt", "md5": "xxxxxxx", "total_size": 6516161646513131}
     <class 'str'>
    b'{"filename": "a.txt", "md5": "xxxxxxx", "total_size": 6516161646513131}'    
     <class 'bytes'>
    71
    
  • 客户端接收报头和数据:

    import struct
    import json
    
    obj = socket.recv(4)
    # 收报头长度
    header_size = struct.unpack('i',obj)
    # 收报头
    header_bytes = socket.recv(header_size)
    
    # 从报头中解析出真实数据的描述信息
    # 反序列化
    header_json = header_bytes.decode('utf-8')
    header_dic = json.loads(head_json)
    print(header_dic,'\n',type(header_dic))
    
    # 拿到真实数据长度
    total_size = header_dic['total_size']
    
    # 接收真实数据
    recv_size = 0
    recv_data = b''
    while recv_size < total_size:
        res = socket.recv(1024)
        recv_data += res
        recv_size += len(res)
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值