Socket+TCP粘包现象以及解决方案
粘包现象
tcp在传输过程中为了保证效率,会在连接建立以后,将传往同一地址的包合并在一起,同时发送过去(Nagle算法)。因为接收方不知道消息之间的界限,不知道一次性提取多少字节的数据所造成的。
具体过程如下:
假设现在有三个100b的数据分三次要发,调用socket的策略是每次可以发1024b,这时,根据nagle算法的优化原则,会将三个数据打成一个包一起发给server,假如server没有对应的策略去处理的话,就会出现三个数据在一个包中显现出来,这样就出现了粘包现象。
另一种情况是待发送3000b大小的数据,接收策略为每次接收1024b,这样就存在一个问题,只要缓存里面的数据没有全部被读走,下次有数据传输进来时,会将上次残留的缓存数据一并读入到socket工作内存中去,也会导致两个数据包粘连。
总结:
1.发送端需要等缓冲区满才发送出去,造成粘包(发送数据时间间隔很短,数据量很小,会合并到一起,产生粘包)
2.接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一部分,服务端下一次接收的时候还是从缓冲区取上次遗留的数据,产生粘包)
如何解决粘包
需要用户自己定义传输时应用层的协议,自定义一个固定长度报头,服务器每次接收数据时先根据接收到的定长报头来决定每次接收的数据大小。
server
import socket
import subprocess
import struct
import json
phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 防止端口被占用
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 端口号在0-65535之间 0-1024给操作系统在用,绑定需要连接的端口
phone.bind(('127.0.0.1',8888))
# 开启监听,5代表同时监听的最大链接数
phone.listen(5)
# 等待连接
res = phone.accept()
conn,client_addr = res
# 通讯循环
while True:
try:
# 1.接受命令
cmd = conn.recv(8096)
if not cmd: break
# 2.执行命令 拿到结果
obj = subprocess.Popen(cmd.decode('gbk'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = obj.stdout.read()
stderr = obj.stderr.read()
# 3.把执行的命令返回给客户端
# 第一步:做一个报头
header_dict = {
'filename': 'xxx.txt',
'md5': 'xxxxxxx',
'total_size': len(stdout)+len(stderr)
}
header_json = json.dumps(header_dict)
header_bytes = header_json.encode('utf-8')
# 第二步:发送报头的长度
header = struct.pack('i',len(header_bytes))
# 第三步:发送报头
conn.send(header)
conn.send(header_bytes)
print(stdout.decode('gbk'),type(stdout),stderr.decode('gbk'))
# 第四步:发送数据
conn.send(stdout)
conn.send(stderr)
except ConnectionResetError:
break
conn.close()
phone.close()
client
import socket
import struct
import json
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
# 建立连接
client.connect(('127.0.0.1',8888))
while True:
#1. 发送命令
cmd = input('>>:').strip()
if not cmd: continue
client.send(cmd.encode('utf-8'))
# 2. 拿命令的结果,并打印
# 第一步:先拿到协议报头长度
obj = client.recv(4)
header_size = struct.unpack('i', obj)[0]
# 第二步:接收报头
header_byte = client.recv(header_size)
header_byte = b''+ header_byte
# 第三步:从报头取得有用信息
header_json = header_byte.decode('gbk')
print(header_json)
header_dic = json.loads(header_json)
total_size = header_dic['total_size']
# 第四步:接收真实的数据
recv_size = 0
recv_data = b''
while recv_size < total_size:
res = client.recv(1024)
recv_data += res
recv_size += len(res)
print(recv_data.decode('gbk'))
phone.close()