粘包问题 socketserver简介
1 案例:远程控制服务端
server.py
import socket
import subprocess
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5)
while True:
conn, addr = s.accept()
while True:
try:
recv_data = conn.recv(1024)
if len(recv_data) == 0:
break
cmd = recv_data.decode('utf-8')
print(f'>>>{cmd}')
obj = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
s.close()
client.py
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8080))
while True:
send_data = input('>>>').strip()
if len(send_data) == 0:
continue
s.send(send_data.encode('utf-8'))
recv_data = s.recv(1024)
print(recv_data.decode('gbk'))
s.close()
遇到的问题:假设客户端发送的命令在服务端产生的返回值数据量很大,客户端接收来自服务端的数据时可能会出现粘包问题。
2 粘包问题
2.1 介绍
粘包问题
- TCP协议属于面向流的协议,数据像水流一样混在一起,接收方不能判断每次发送数据的界限;
- 接收方不能保证每次接收数据是完整的,可能有残留,残留数据会暂存于缓存中,和下一次接收的数据混在一起。
解决粘包问题
主要在接收端做处理
- 获取发送数据的总量;
- 循环接收数据,通过数据总量判断是否接收完全;
- 数据接收完全后,跳出循环。
TCP协议下的传输使用了Nagle算法进行优化,发送端将多次发送的间隔短且数据量小的数据合并成一个大的数据块,进行封包发送,这样做接收端就难以分辨出实际的每次发送的数据了。因此,面向流(TCP协议)的通信中不存在数据边界。
UDP协议是面向消息的协议,接收方每次接收的数据是有边界的,假设接收端没有接收完全,残留的数据会被抛弃,不会影响下一次接收的数据,因此UDP协议下的通信不会出现粘包现象。UDP协议稳定传输的数据量上限是512个字节。
server.py
import socket
import subprocess
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind(('127.0.0.1', 8080))
s.listen(5)
while True:
conn, addr = s.accept()
while True:
try:
recv_data = conn.recv(1024)
if len(recv_data) == 0:
break
cmd = recv_data.decode('utf-8')
obj = subprocess.Popen(
cmd,
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE
)
stdout_res = obj.stdout.read()
stderr_res = obj.stderr.read()
send_total_size = len(stdout_res) + len(stderr_res)
# 1. 发送固定长度的头信息,这里长度是4Bytes
# 头信息的内容是发送的数据总量
# pack的i模式将整形数据打包成长度为4的Bytes类型数据
send_head = struct.pack('i', send_total_size)
conn.send(send_head)
# 2. 发送实际数据
conn.send(stdout_res)
conn.send(stderr_res)
except Exception:
break
conn.close()
s.close()
client.py
import socket
import struct
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('127.0.0.1', 8080))
while True:
send_data = input('>>>').strip()
if len(send_data) == 0:
continue
s.send(send_data.encode('utf-8'))
# 1. 先接受固定长度的头信息,这里固定长度为4Bytes
recv_head = s.recv(4)
# 2. 从头信息中获取接收数据的总量
recv_total_size = struct.unpack('i', recv_head)[0]
recv_size = 0
while recv_size < recv_total_size:
recv_data = s.recv(1024)
recv_size += len(recv_data)
print(recv_data.decode('gbk'), end='')
else:
print()
s.close()
3 socketserver简介
3.1 TCP协议
server.py
import socketserver
class CustomRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
# 如果是tcp协议,self.request => conn连接对象
print(self.client_address) # 客户端ip地址和端口号
# 通信循环
while True:
try:
msg = self.request.recv(1024)
if len(msg) == 0: break
self.request.send(msg.upper())
except Exception:
break
self.request.close()
# 服务端应该做两件事
# 1. 不停地从半连接池中获取连接请求,并与其建立双向连接通路,获取连接对象
s = socketserver.ThreadingTCPServer(('127.0.0.1', 8889), CustomRequestHandler)
s.serve_forever()
# 2. 通过连接与客户端进行通信
# 调用自定义类CustomRequestHandle中的方法handle
3.2 UDP协议
server.py
import socketserver
class CustomRequestHandler(socketserver.BaseRequestHandler):
def handle(self):
recv_data = self.request[0]
print('客户端发送的数据为%s' % recv_data)
server = self.request[1]
client_address = self.client_address
server.sendto(recv_data.upper(), client_address)
s = socketserver.ThreadingUDPServer(("127.0.0.1", 8888), CustomRequestHandler)
s.serve_forever()