zoukankan      html  css  js  c++  java
  • 粘包及解决方案

    一、 粘包

    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
    
  • 相关阅读:
    CF1539 VP 记录
    CF1529 VP 记录
    CF875C National Property 题解
    CF1545 比赛记录
    CF 1550 比赛记录
    CF1539E Game with Cards 题解
    CF1202F You Are Given Some Letters... 题解
    vmware Linux虚拟机挂载共享文件夹
    利用SOLR搭建企业搜索平台 之九(solr的查询语法)
    利用SOLR搭建企业搜索平台 之四(MultiCore)
  • 原文地址:https://www.cnblogs.com/mahedong/p/11240442.html
Copyright © 2011-2022 走看看