Socket+TCP粘包现象以及解决方案

Socket+TCP粘包现象以及解决方案

粘包现象

​ tcp在传输过程中为了保证效率,会在连接建立以后,将传往同一地址的包合并在一起,同时发送过去(Nagle算法)。因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。

​ 具体过程如下:

在这里插入图片描述

​ 假设现在有三个100b的数据分三次要发,调用socket的策略是每次可以发1024b,这时,根据nagle算法的优化原则,会将三个数据打成一个包一起发给server,假如server没有对应的策略去处理的话,就会出现三个数据在一个包中显现出来,这样就出现了粘包现象。

​ 另一种情况是待发送3000b大小的数据,接收策略为每次接收1024b,这样就存在一个问题,只要缓存里面的数据没有全部被读走,下次有数据传输进来时,会将上次残留的缓存数据一并读入到socket工作内存中去,也会导致两个数据包粘连。

总结:

1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合并到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一部分,服务端下一次接收的时候还是从缓冲区取上次遗留的数据,产生粘包)

如何解决粘包

​ 需要用户自己定义传输时应用层的协议,自定义一个固定长度报头,服务器每次接收数据时先根据接收到的定长报头来决定每次接收的数据大小。

server

import  socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)

# 防止端口被占用
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)


# 端口号在0-65535之间 0-1024给操作系统在用,绑定需要连接的端口
phone.bind(('127.0.0.1',8888))

# 开启监听,5代表同时监听的最大链接数
phone.listen(5)

# 等待连接
res = phone.accept()
conn,client_addr = res

# 通讯循环
while True:
    try:
        # 1.接受命令
        cmd = conn.recv(8096)
        if not cmd: break

        # 2.执行命令 拿到结果
        obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        stdout = obj.stdout.read()
        stderr = obj.stderr.read()

        # 3.把执行的命令返回给客户端
        # 第一步:做一个报头
        header_dict = {
            'filename': 'xxx.txt',
            'md5': 'xxxxxxx',
            'total_size': len(stdout)+len(stderr)
        }
        header_json = json.dumps(header_dict)
        header_bytes = header_json.encode('utf-8')
        
        # 第二步:发送报头的长度
        header = struct.pack('i',len(header_bytes))

        # 第三步:发送报头
        conn.send(header)
        conn.send(header_bytes)
        print(stdout.decode('gbk'),type(stdout),stderr.decode('gbk'))
        # 第四步:发送数据
        conn.send(stdout)
        conn.send(stderr)

    except ConnectionResetError:
        break

conn.close()

phone.close()

client

import  socket
import struct
import json
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 建立连接
client.connect(('127.0.0.1',8888))

while True:
    #1. 发送命令
    cmd = input('>>:').strip()
    if not cmd: continue
    client.send(cmd.encode('utf-8'))

    # 2. 拿命令的结果,并打印
    # 第一步:先拿到协议报头长度
    obj = client.recv(4)
    header_size = struct.unpack('i', obj)[0]

    # 第二步:接收报头
    header_byte = client.recv(header_size)
    header_byte = b''+ header_byte
    # 第三步:从报头取得有用信息
    header_json = header_byte.decode('gbk')
    print(header_json)
    header_dic = json.loads(header_json)
    total_size = header_dic['total_size']


    # 第四步:接收真实的数据
    recv_size = 0
    recv_data = b''

    while recv_size < total_size:
        res = client.recv(1024)
        recv_data += res
        recv_size += len(res)

    print(recv_data.decode('gbk'))

phone.close()
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值