缓冲区 粘包

缓冲区

​ 每个 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()

原文地址:https://www.cnblogs.com/beichen123/p/11366213.html