粘包及解决方案

一、 粘包

1. 粘包现象

基于tcp协议的socket,客户端一次接受不完,下一次继续接受(如果间隔时间相对过长,后续的数据会与之前剩余的数据黏在一起),send数据时,连续的发送少量的数据(时间间隔很短),这些数据会积压在一起发送出去.

2. 粘包现象

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

3. 粘包产生的原因

  1. TCP协议是基于数据流的,无论底层是怎样分段分片的,TCP协议不会根据消息的界限传输数据,而是把构成整条消息的数据段排序完成后呈现到内核缓冲区,等到发送方的缓冲区有足够的数据后才发送一个TCP段
  2. 当socket的传输的数据大于接收的数据时,多余的数据会被放置在缓冲区,接收方的recv在下一次接收数据时,它会继续接受之前放置在缓冲的数据,而不是发送端最新发送过来的数据

4. 缓冲区

每个 socket 被创建后,都会分配两个缓冲区,输入缓冲区和输出缓冲区。

write()/send() 并不立即向网络中传输数据,而是先将数据写入缓冲区中,再由TCP协议将数据从缓冲区发送到目标机器。一旦将数据写入到缓冲区,函数就可以成功返回,不管它们有没有到达目标机器,也不管它们何时被发送到网络,这些都是TCP协议负责的事情。

TCP协议独立于 write()/send() 函数,数据有可能刚被写入缓冲区就发送到网络,也可能在缓冲区中不断积压,多次写入的数据被一次性发送到网络,这取决于当时的网络情况、当前线程是否空闲等诸多因素,不由程序员控制。

read()/recv() 函数也是如此,也从输入缓冲区中读取数据,而不是直接从网络中读取。

这些I/O缓冲区特性可整理如下:

1.I/O缓冲区在每个TCP套接字中单独存在;
2.I/O缓冲区在创建套接字时自动生成;
3.即使关闭套接字也会继续传送输出缓冲区中遗留的数据;
4.关闭套接字将丢失输入缓冲区中的数据。

二、粘包的解决方案

1. low版

服务端

import socket
import subprocess
import struct
server_side = socket.socket()
server_side.bind(("127.0.0.1", 8848))
server_side.listen(5)
while 1:
    conn, addr = server_side.accept()
    while 1:
        try:
            cmd = conn.recv(1024)
            if cmd.decode("utf-8") == 'q':
                break
            obj = subprocess.Popen(cmd.decode("utf-8"),
                                   shell=True,
                                   stdout=subprocess.PIPE,
                                   stderr=subprocess.PIPE,
                                   )
            result = obj.stdout.read() + obj.stderr.read()
            # 制作报头
            total_size = len(result)
            # 将不固定长度的int数据类型的报头,转化成固定长度的4bytes
            total_size_bytes = struct.pack("i", total_size)
            # 发送报头
            conn.send(total_size_bytes)
            # 发送数据
            conn.send(result)
        except ConnectionResetError:
            break
    conn.close()
server_side.close()

客户端

import socket
import struct
client_side = socket.socket()
client_side.connect(("127.0.0.1", 8848))
while 1:
    to_send = input("请输入你要执行的命令:")
    if to_send.upper() == "Q":
        client_side.send("q".encode("utf-8"))
        break
    client_side.send(to_send.encode("utf-8"))
    # 接收报头
    head_bytes = client_side.recv(4)
    # 反解报头
    total_size = struct.unpack("i", head_bytes)[0]
    # 循环接收数据
    data = b""
    while len(data) < total_size:
        data += client_side.recv(1024)
    print(data.decode("gbk"))
client_side.close()

2. 旗舰版

服务端

import socket
import subprocess
import json
import struct
phone = socket.socket()
phone.bind(("127.0.0.1", 8848))
phone.listen(5)
conn, addr = phone.accept()
while 1:
    try:
        cmd = conn.recv(1024)
        obj = subprocess.Popen(cmd.decode("utf-8"),
                               shell=True,
                               stdout=subprocess.PIPE,
                               stderr=subprocess.PIPE
                               )
        result = obj.stdout.read() + obj.stderr.read()
        result = result.decode("gbk").encode("utf-8")
        # 1.制作报头
        head_dict = {"md5": "df",
                     "file_name": "新建文件夹",
                     "file_size": len(result)}

        # 2. 将报头字典转化成json字符串
        head_dict_json = json.dumps(head_dict)
        # 3. 将json字符串转化成bytes
        head_dict_json_bytes = head_dict_json.encode("utf-8")
        # 4. 获取报头的长度
        head_len = len(head_dict_json_bytes)
        # 5. 将报头长度转化成固定的4个bytes
        head_len_bytes = struct.pack("i", head_len)
        # 6. 发送固定的4个字节
        conn.send(head_len_bytes)
        # 7. 发送报头
        conn.send(head_dict_json_bytes)
        # 8. 发送原数据
        conn.send(result)
    except ConnectionResetError:
        break
conn.close()
phone.close()

客户端

import socket
import struct
import json
phone = socket.socket()
phone.connect(("127.0.0.1", 8848))
while 1:
    cmd = input("请输入指令")
    phone.send(cmd.encode("utf-8"))
    # 1. 接收报头长度
    head_len_bytes = phone.recv(4)
    # 2. 将报头数字转化成int类型
    head_len = struct.unpack("i", head_len_bytes)[0]
    # 3. 接收bytes类型的报头字典
    head_dict_json_bytes = phone.recv(head_len)
    # 4. 将bytes类型的字典转化成json字符串
    head_dict_json = head_dict_json_bytes.decode("utf-8")
    # 5. 将json字符串转化成字典
    head_dict = json.loads(head_dict_json)
    # 6. 循环接收原数据
    total_data = b''
    while len(total_data) < head_dict["file_size"]:
        total_data += phone.recv(1024)
    print(total_data.decode("utf-8"))
phone.close

猜你喜欢

转载自www.cnblogs.com/mahedong/p/11240442.html