缓冲区
每个 socket 被创建后, 都会分配两个缓冲区, 输入缓冲区和输出缓冲区
write( )/send( ) 并不立即向网络中传输数据, 而是先将数据写入缓冲区中, 再由TCP协议将数据从缓冲区发送到目标机器. 一旦将数据写入到缓冲区, 函数就可以成功返回, 不管他们有没有到达目标机器, 也不管它们何时被发送到网络, 这些都是TCP协议负责的事情.
TCP协议独立于 write( )/send( ) 函数, 数据有可能刚被写入缓冲区就发送到网络, 也可能在缓冲区中不断积压, 多次写入的数据被一次性发送到网络, 这取决于当时的网络情况, 当前线程是否空闲等诸多因素, 不由程序员控制.
read( )/recv( ) 函数也是如此, 也从输入缓冲区中读取数据, 而不是直接从网络中读取.
I/O 缓冲区特性
I/O 缓冲区在每个TCP套接字中单独存在
I/O 缓冲区在创建套接字时自动生成
即使关闭套接字也会继续传送输出缓冲区中遗留的数据
关闭套接字将丢失输入缓冲区中的数据
输入输出缓冲区的默认大小一般都是 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()