缓冲区
每个 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()