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
    
  • 相关阅读:
    OpenCV中的绘图函数
    整理不错的opencv博客
    opencv中的函数
    这是一个学习前端技术的网站
    HDU1520 Anniversary party(树形DP入门)
    CF1255C League of Leesins(图论)
    HDU4725 The Shortest Path in Nya Graph(最短路分层)
    1288C Two Arrays
    CF1294D MEX maxiszing
    CF1295C Obtain the String
  • 原文地址:https://www.cnblogs.com/mahedong/p/11240442.html
Copyright © 2011-2022 走看看