TCP粘包问题以及解决方法

粘包问题分析与对策

TCP粘包是指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
出现粘包现象的原因是多方面的,它既可能由发送方造成,也可能由接收方造成。

什么时候需要考虑粘包问题?

  1. 如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要是要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。

  2. 如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包

  3. 如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:

    1)“hellogive me sth abour yourself”

    2)“Don’tgive me sth abour yourself”

    那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hellogive me sth abour yourselfDon’t give me sth abour yourself"这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。

粘包出现原因

简单得说,在流传输中出现,UDP不会出现粘包,因为它有消息边界(参考Windows网络编程)

  1. 发送端需要等缓冲区满才发送出去,造成粘包
  2. 接收方不及时接收缓冲区的包,造成多个包接收

具体点:

  1. 发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。

  2. 接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
    参考: TCP粘包问题分析和解决(全)

模拟粘包问题 demo:

Server.py

"""
先启动套接字服务端
    注意:客户端一次发送,服务端先一次接收,再发送
"""
import socket

server = socket.socket()

server.bind(("127.0.0.1", 9527))
server.listen(5)

conn, addr = server.accept()
data = conn.recv(1024)	
print(data)

Client.py

# 启动服务端后再启动客户端
import socket
client = socket.socket()
client.connect(("127.0.0.1", 9527))

'''
发送三次请求,理论上结果应该是
b"hello"
b"hello"
b"hello"
'''
client.send(b"hello")
client.send(b"hello")
client.send(b"hello")

Server.py 执行结果:
在这里插入图片描述

解决粘包问题(struct 模块)

1、struct模块是什么

struct模块是一个python内置模块,他可以将固定长度的数据,打包成固定格式的长度

" i "模式:可以将数据打包成 4 个bytes

2、struct模块的作用

可以将真实数据,做成一个固定长度的报头,客户端发送给服务端,服务端可以接受报头,然后对报头进行解包,获取真实数据的长度,进行接收即可

3、struct模块的使用

import struct

data = b'11111111111111'
# 打包制作报头
header = struct.pack("i", len(data))
print(header)

# 解包获取真实数据长度 ---> 得到一个元组,元组中第一个值是真实数据的长度
res = struct.unpack("i", header)[0]
print(res)

执行结果:	
b'\x0e\x00\x00\x00'
14

4、粘包问题解决方法
只要确认对方数据的大小(长度),根据大小进行接收即可解决
  - 无论哪一端先发送数据(例 客户端先行发送数据)
    - 客户端
      - 先制作报头并发送  struct.pack()
      - 发送真实数据  
    - 服务端:
      - 接收报头,并解包获取真实数据长度  struct.unpack()
      - 根据真实数据长度 接收真实数据

server_adv.py

import socket
import subprocess
import struct

server = socket.socket()
server.bind(("127.0.0.1", 8001))
server.listen(5)

while True:
    conn, addr = server.accept()
    # print(addr)
    while True:
        try:
            # 获取客户端传过来的报头
            client_headers = conn.recv(4)
            # 解包获取真实数据长度
            data_len = struct.unpack("i", client_headers)[0]
            # 准备接收真实数据
            data = conn.recv(data_len)

            if len(data) == 0:
                continue

            data = data.decode("utf-8")
            if data == "q":
                break

            # 调用subprocess,对终端进行操作,并获取操作后正确或错误的结果
            # 接收转码后的字符串
            obj = subprocess.Popen(data, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
            # 将结果交给result变量
            result = obj.stdout.read() + obj.stderr.read()

            # 服务端做一个报头发送给客户端
            server_headers = struct.pack('i', len(result))
            # 传送报头数据
            conn.send(server_headers)
            # 待客户端确认长度后,发送真实数据返回给客户端
            conn.send(result)

        except Exception as e:
            print(e)
            break
    conn.close()

client_adv.py

import socket
import struct

client = socket.socket()
client.connect(("127.0.0.1", 8001))

while True:

    cmd = input("请输入服务端命令:")

    cmd_bytes = cmd.encode("utf-8")

    # 做一个报头
    client_headers = struct.pack("i", len(cmd_bytes))
    # 想服务端传送报头
    client.send(client_headers)
    # 待服务端确认数据长度后,发送真实数据
    client.send(cmd_bytes)

    # 获取服务端的传过来的报头
    server_headers = client.recv(4)
    # 解包获取数据的真实长度
    data_len = struct.unpack('i', server_headers)[0]
    # 准备接受真实数据
    data = client.recv(data_len)

    if len(data) == 0:
        continue

    if data == "q":
        break

    print(data.decode('gbk'))
  • 2
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值