Python黏包问题

黏包问题

概念

黏包问题只存在于基于TCP协议的Socket通信过程中,其是指发送者将多个数据包一起发送给接收者,造成接收者不能区分每个数据包的分界。


原因

发送者因素

基于TCP协议进行通信时,TCP协议默认会使用Nagle算法。Nagle算法是以减少数据包发送量来增进TCP/IP网络的性能。其工作方式是合并一定数量的输出数据后一次提交。特别的是,只要有已提交的数据包尚未确认,发送者会持续缓冲数据包,直到累积一定数量的数据才提交。
所以,基于TCP协议的Nagle算法,就引发了发送者所导致的黏包现象。

接收者因素

接收者受到通信数据后,类似上面所说的Nagle算法,接收者也会首先将数据保存至缓存中,由应用层程序主动从缓存中获取数据进行读取。这样就会造成如果接收数据的速度大于读取数据的速度,数据会被暂存至缓存,也就是多个数据包的分界不能区分,造成黏包问题。


处理办法

发送者处理

由于发送者造成的黏包问题主要是因为Nagle算法所导致,所以我们解决的方式也就是将Nagle算法禁用,进而解决由于发送者所造成的的黏包问题。使用TCP_NODELAY选项来关闭Nagle算法。

应用程序处理

由于接收端没有可以处理黏包问题的方法,我们只能从应用程序进行逻辑上的处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
# ---------- 服务端 ----------
# 1、发送报头长度
# 2、发送编码后的报头内容
# 3、发送消息体
# ------------------------------
import socket, struct, json
import subprocess

sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sk.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 重复利用端口
sk.bind(('127.0.0.1', 8080))
sk.listen(5)

while True:
conn, addr = sk.accept()
while True:
cmd = conn.recv(1024)
if not cmd:
break
print('cmd: %s' % cmd)
res = subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
err = res.stderr.read()
print(err)
if err:
back_msg = err
else:
back_msg = res.stdout.read()

headers={'data_size':len(back_msg)} # 使用dict整理报头
head_json=json.dumps(headers) # 将报头序列化
head_json_bytes=bytes(head_json,encoding='utf-8') # 编码序列化报头

conn.send(struct.pack('i', len(head_json_bytes))) # 发送报头长度
conn.send(head_json_bytes) # 发送编码后的报头内容
conn.sendall(back_msg) # 发送消息体
conn.close()

# ---------- 客户端 ----------
# 1、接收报头长度
# 2、根据报头长度接收报头内容→解码→反序列化
# 3、根据报头内容获取消息体
# ------------------------------

from socket import *
import struct, json

ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)

while True:
cmd=input('>>: ')
if not cmd:continue
client.send(bytes(cmd,encoding='utf-8'))

head=client.recv(4)
head_json_len=struct.unpack('i',head)[0]
head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
data_len=head_json['data_size']

recv_size=0
recv_data=b''
while recv_size < data_len:
recv_data+=client.recv(1024)
recv_size+=len(recv_data)

print(recv_data.decode('utf-8'))