• 缓冲区 粘包


    缓冲区

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

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

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

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

    I/O 缓冲区特性

    1. I/O 缓冲区在每个TCP套接字中单独存在

    2. I/O 缓冲区在创建套接字时自动生成

    3. 即使关闭套接字也会继续传送输出缓冲区中遗留的数据

    4. 关闭套接字将丢失输入缓冲区中的数据

      ​ 输入输出缓冲区的默认大小一般都是 8K , 可以通过getsockopt( )函数获取

    缓冲区的作用

    ​ 暂时存储一些数据.

    ​ 缓冲区存在, 如果你的网络波动, 保证数据的收发稳定, 匀速

    ​ 缺点: 造成了粘包现象之一

    粘包

    发生粘包的两种情况

    接收方没有及时接收缓冲区的包, 造成多个包接收(客户端发送了一段数据, 服务端只收了一小部分, 服务端下次再收的时候还是从缓冲区拿上次遗留的数据, 产生粘包)

    server(服务器端)
    import socket
    import subprocess
    phone = socket.socket()
    phone.bind(("172.0.0.1", 8848))
    phone.listen()
    while 1:
        conn, addr = phone.accept()
        print(conn, addr)
        while 1:
        	from_client_data = conn.recv(1024)
        	# print(f"客户端传输进来的信息:{from_client_data.strip().decode('utf-8')}")
            obj = subprocess.Popen(from_client_data.decode('utf-8'),
                     shell=True,
                     stdout=subprocess.PIPE.
                     stderr=subprocess.PIPE)
        	to_client_data = obj.stdout.read() + obj.stderr.read()
        	conn.send(to_client_data)
        except ConnectionResetError:
            print("客户端中断")
            break
        conn.close()
    phone.close()
    
    client(客户端)
    import socket
    phone = socket.socket()
    phone.connect(("127.0.0.1", 8848))
    while 1:
        data = input("传向服务器端的信息:").strip().encode("utf-8")
        if not data:
            print("输入不得为空,会双向阻塞出bug")
            continue
        phone.send(data)
        if data.upper() == b"Q":
            print("退出成功")
            break
        from_server_data = phone.recv(1024)
        print(f"服务端传输进来的信息{from_server_data.strip().decode('gbk')}")
    phone.close()
    

    ​ 当客户端发的命令获取的结果大小已经超过客户端recv上限的1024, 那么下次输入命令时, 会继续取上次残留到缓存区的数据

    发送数据时间间隔很短, 数据也很小时, 会合到一起, 产生粘包

    server服务端
    import socket
    phone = socket.socket()
    phone.bind(("127.0.0.1", 8848))
    phone.listen()
    conn,addr = phone.accept()
    print(conn, addr)
    from_client_data = conn.recv(1024)
    print(f"来自客户端的消息:{from_client_data.decode('utf-8')}")
    to_client_data = input(">>>")
    conn.send(to_client_data.encode("utf-8"))
    conn.close()
    phone.close()
    
    client客户端
    import socket
    phone = socket.socket()
    phone.connect(("127.0.0.1", 8848))
    phone.send(b"he")
    phone.send(b"llo")
    from_server_data = phone.recv(1024)
    print(f"来自服务端的消息:{from_server_data.decode('utf-8')}")
    phone.close()
    

    如何解决粘包现象

    思路

    ​ 服务端发一次数据, 10000字节, 客户端接收数据时, 循环接收, 每次(至多) 接收 1024 个字节, 直至将所有的字节全部接收完毕, 将接收的数据拼凑在一起, 最后解码.

    ​ 遇到的问题: recv的次数无法确定

    ​ 发送具体的总数据之前, 先发送一个总数据的长度: 例如 5000个字节, 然后在发送总数据.

    ​ 客户端: 先接收一个长度(5000个字节), 然后我再循环recv, 控制循环的条件就是只要你接受的数据 < 5000 , 就一直接收.

    ​ 遇到的问题: 总数据的长度转化成的字节数不固定

    server服务端
    conn.send(total_size)  # 总数据长度
    conn.send(result)	   # 总数据
    
    client客户端
    total_size_bytes = phone.recv(4)
    total_size
    data = b''
    while len(data) < total_size:
        data = data + phone.recv(1024)
    

    ​ 要将 total_size int类型 转化成bytes类型 才可以发送

    ​ 387 -----> str(387) --> "387" --->bytes b'387' 长度 3 bytes

    ​ 4185 -----> str(4185) --> "387" --->bytes b'4185' 长度 4 bytes

    	18000 ----->  str(18000) --> "18000" --->bytes b'18000'  长度 5 bytes
    

    解决方法

    我们要解决:

    ​ 将不固定的长度的 int类型 转化成固定长度的bytes 并且还可以翻转回来

    ​ 所以用struct模块

    import struct
    # 将一个数字转化成等长度的bytes类型。
    ret = struct.pack('i', 183346)
    print(ret, type(ret), len(ret))
    
    # 通过unpack反解回来
    ret1 = struct.unpack('i',ret)[0]
    print(ret1, type(ret1), len(ret1))
    
    
    # 但是通过struct 处理不能处理太大
    
    ret = struct.pack('l', 4323241232132324)
    print(ret, type(ret), len(ret))  # 报错
    
    具体解决方法(加报头)
    server服务端
    
    import socket
    import struct
    import subprocess
    
    phone = socket.socket()
    
    phone.bind(("127.0.0.1", 8848))
    phone.listen()
    
    while 1:
        conn, addr = phone.accept()
        print(conn, addr)
        while 1:
            try:
                from_client_data = conn.recv(1024)
                print(f"{from_client_data.strip().decode('utf-8')}")
                obj = subprocess.Popen(from_client_data.decode("utf-8"),
                                       shell=True,
                                       stdout=subprocess.PIPE,
                                       stderr=subprocess.PIPE,
                                       )
                total_size = len(obj.stdout.read()) + len(obj.stderr.read())
                
                header = struct.pack("i", total_size)
                # 制作表头
                
                conn.send(header)
                # 发送表头
                
                to_client_data = obj.stdout.read() + obj.stderr.read()
                conn.send(to_client_data)
                # 发送总数据
                
            except ConnectionResetError:
                print("客户端中断")
                break
        conn.close()
    phone.close()
    
    
    client客户端
    
    import socket
    import struct
    
    phone = socket.socket()
    
    phone.connect(("127.0.0.1", 8848))
    
    while 1:
            data = input(">>>").strip().encode("utf-8")
            if not data:
                print("输入不能为空")
                continue
            phone.send(data)
            if data.upper() == b"Q":
                print("退出成功")
                break
            header = phone.recv(4)
            # 接收报头
            
            total_size = struct.unpack("i", header)[0]
            # 解析报头
            
            recv_size = 0
            from_server_data = b''
            while recv_size < total_size:
                recv_data = phone.recv(1024)
                from_server_data += recv_data
                recv_size += len(recv_data)
            # 根据报头信息, 拼接总数据
            
            print(f"{from_server_data.decode('gbk')}")
            # 一次性打印总数据, 解决粘包
    
    phone.close()
    
    
  • 相关阅读:
    获取父子栏目内容的知识点总结:更多信息页面的信息内容获取
    获取栏目内容的知识点总结:SingleInfoSortPortlet类型笔记(单栏目的获取)
    JBPM插件安装(MyEclipse8.5测试成功)和配置
    获取栏目内容的知识点总结:KnobInfoSortPortlet类型笔记(没有层级关系的栏目信息)
    HTML的事件说明
    正交投影、格拉姆施密特正交(一)
    子级Repeater获取父级Repeater绑定项的值
    启用IIS6的Gzip压缩功能
    属性IsLocked不可用于登录"[sa]解决办法
    在IE流览器中正确显示PNG透明图片
  • 原文地址:https://www.cnblogs.com/beichen123/p/11366213.html
Copyright © 2020-2023  润新知