粘包
粘包
【一】什么是粘包
- 须知:只有TCP有粘包现象,UDP永远不会粘包
【二】什么是粘包问题
- 客户端发送需要执行的代码
- 服务端接收到客户端传过来的代码
- 服务端调用方法执行代码并拿到执行后的结果
- 服务端将执行后的结果进行返回
- 客户端接收到服务端返回的结果并做打印输出
【1】服务端
from socket import *
import subprocess
server = socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
while True:
conn, client_addr = server.accept()
while True:
try:
cmd_from_client = conn.recv(1024)
if len(cmd_from_client) == 0:
break
msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
true_msg = msg_server.stdout.read()
false_msg = msg_server.stderr.read()
conn.send(true_msg)
conn.send(false_msg)
except Exception as e:
break
conn.close()
【2】客户端
from socket import *
while True:
client = socket()
client.connect(('127.0.0.1', 8888))
msg = input('enter msg :>>>').strip()
if len(msg) == 0: continue
msg = msg.encode('utf-8')
client.send(msg)
msg_from_server = client.recv(1024)
msg_from_server = msg_from_server.decode('gbk')
print(msg_from_server)
client.close()
【3】问题引入
- 服务端:
- 执行代码,代码为空会报错
- 执行代码,返回的数据可能存在空/报错信息
- 客户端:
- 输入的指令长度,可能会超出范围
- 接受到的服务端反馈的结果可能会特别多
- 如何打印超出数据范围(缓存到系统里)的数据
【4】粘包问题
- 在 TCP 协议中是流式协议,数据是源源不断的传入到客户端中,但是客户端可以接受到的信息的长度是有限的
- 当接收到指定长度的信息后,客户端进行打印输出
- 剩余的其他数据会被缓存到 内存中
- 当再次执行其他命令时
- 新的数据的反馈结果,会叠加到上一次没有完全打印完全的信息的后面,造成数据的错乱
- 当客户端想打印新的命令的数据时,打印的其实是上一次没有打印完的数据
- 对数据造成的错乱
【5】粘包问题解决思路
- 拿到数据的总大小
recv_total_size
recv_size = 0
,循环接收,每接收一次,recv_size += 接收的长度
- 直到
recv_size = recv_total_size
表示接受信息完毕,结束循环
【三】UDP协议不存在粘包问题
-
粘包问题出现的原因
- TCP 协议是流式协议,数据像水流一样粘在一起,没有任何边界之分
- 收数据没有接收干净,有残留,就会和下一次的结果混淆在一起
-
解决粘包问题的核心法门就是
- 每次都收干净
- 不造成数据的混淆
-
当我们启动udp服务端后,由udp客户端向服务端发送两条数据
-
但是在udp服务端只接收到了一条数据
-
这是因为 udp 是报式协议,传送数据过程中会将数据打包直接发走,不会对数据进行拼接操作(没有Nagle算法)
【四】TCP协议解决粘包问题
【1】解决思路
- 利用
struct
模块将传输过去的数据的总长度 打包 + 到头部进行发送
【2】服务端
import json
from socket import *
import subprocess
import struct
server = socket()
server.bind(('127.0.0.1', 8888))
server.listen(5)
while True:
conn, client_addr = server.accept()
while True:
try:
cmd_from_client = conn.recv(1024)
if len(cmd_from_client) == 0:
break
msg_server = subprocess.Popen(cmd_from_client.decode('utf-8'),
shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
)
true_msg = msg_server.stdout.read()
false_msg = msg_server.stderr.read()
total_size_len = len(true_msg) + len(false_msg)
headers = {
'total_size': total_size_len
}
json_data = json.dumps(headers)
json_data_bytes = json_data.encode('utf-8')
json_data_size_pack = struct.pack('i', len(json_data_bytes))
conn.send(json_data_size_pack)
conn.send(json_data_bytes)
conn.send(true_msg)
conn.send(false_msg)
except Exception as e:
break
conn.close()
【3】客户端
from socket import *
import struct,json
while True:
client = socket()
client.connect(('127.0.0.1', 8888))
msg = input('enter msg :>>>').strip()
if len(msg) == 0: continue
msg = msg.encode('utf-8')
client.send(msg)
json_data_size_unpack = client.recv(4)
json_data_size = struct.unpack('i', json_data_size_unpack)[0]
json_data_bytes = client.recv(json_data_size)
json_dict=json_data_bytes.decode('utf8')
headers_dict=json.loads(json_dict)
total_len=headers_dict['total_size']
recv_size=0
while recv_size < total_len:
msg_from_server = client.recv(1024)
print(msg_from_server.decode('gbk'),end='')
recv_size+=1024
client.close()
【五】数据传输练习
【1】服务端
import json
import struct
from socket import *
import os
server = socket()
server.bind(('127.0.0.1', 9696))
server.listen(5)
while True:
client, addr = server.accept()
while True:
file_name_data = client.recv(1024)
file_name = file_name_data.decode('utf8')
print(f"这是来自客户端想要下载的文件 :>>>> {file_name}")
file_path = os.path.join(os.path.dirname(__file__), file_name)
with open(file_path, 'rb') as f:
video_data = f.read()
message = {
"data_length": len(video_data)
}
header_bytes = bytes(json.dumps(message), encoding='utf-8')
header_len_bytes = struct.pack('i', len(header_bytes))
# 发送消息头长度
client.send(header_len_bytes)
# 发送消息头
client.send(header_bytes)
# 发送视频数据
client.sendall(video_data)
conn.close()
【2】客户端
import os
from socket import *
import struct
import json
while True:
client = socket()
client.connect(('127.0.0.1', 9696))
cmd_data = input("需要下载的文件名 :>>>> ").strip()
new_file_name = input("请输入需要保存的文件名 :>>>> ").strip()
file_path = os.path.join(os.path.dirname(__file__), new_file_name)
cmd_data = cmd_data.encode('utf8')
client.send(cmd_data)
head_len_bytes = client.recv(4)
head_len = struct.unpack('i', head_len_bytes)[0]
head_bytes = client.recv(head_len)
json_data = head_bytes.decode('utf-8')
data = json.loads(json_data)
video_size = data['data_length']
video_data = b''
while len(video_data) < video_size:
video_data += client.recv(1024)
with open(file_path, 'wb') as f:
f.write(video_data)