
为什么会出现粘包现象?
是因为TCP协议是面向流的协议,在发送的数据传输的过程中还有缓存机制来避免数据丢失,因此在连续发送小数据的时候,以及接受大小不符的时候都容易出现粘包现象,本质还是因为我们在接受数据的时候不知道发送的数据的长短
情况一 发送方的缓存机制
# server.py
from socket import socket
ser_socket = socket()
ser_socket.bind(('0.0.0.0',8000))
ser_socket.listen()
conn,addr = ser_socket.accept()
data = conn.recv(12)
print(data)
data = conn.recv(12)
print(data)
conn.close()
ser_socket.close()
# client.py
from socket import *
cli_socket = socket()
cli_socket.connect(('127.0.0.1',8000))
cli_socket.send(b'hello')
cli_socket.send(b'world')
cli_socket.close()
我们会发现sever端接受的是b'helloworld'
和b''
,这就是粘包现象,这是由于客户端发送的数据包太小并且是连续的,那么协议栈内部会采用Nagle算法对连续发送的小数据包进行合并然后再发送。如果我们在上述代码的client端两次send之间sleep1秒,则server端会接受到b'hello'
和b'world'
情况二 接收方的缓存机制
接收方不及时接收缓冲区的包,造成多个包接收(客户端发送了一段数据,服务端只收了一小部分,服务端下次再收的时候还是从缓冲区拿上次遗留的数据,产生粘包)
# server.py
from socket import socket
import time
ser_socket = socket()
ser_socket.bind(('0.0.0.0',8000))
ser_socket.listen()
conn,addr = ser_socket.accept()
data = conn.recv(2)
print(data) # b'he'
time.sleep(5)
data = conn.recv(10)
print(data) # b'lloworld'
conn.close()
ser_socket.close()
# client.py
from socket import *
cli_socket = socket()
cli_socket.connect(('127.0.0.1',8000))
cli_socket.send(b'hello')
cli_socket.send(b'world')
cli_socket.close()
客户端很快发送结束了,服务端首先接受2个字节的数据,之后sleep 5秒之后,会继续从缓存里取剩下的数据。
怎么解决粘包问题?
问题的根源在于,接收端不知道发送端将要传送的字节流的长度,所以解决粘包的方法就是围绕,如何让发送端在发送数据前,把自己将要发送的字节流总大小让接收端知晓,然后接收端来一个死循环接收完所有数据。
struct模块
该模块可以把一个类型,如数字,转成固定长度的bytes

通常我们只用到int型,它会将int型整数转成4个字节
>>>import struct
>>>res = struct.pack('i',2048)
>>>res
b'\x00\x08\x00\x00'
>>>len(res)
4
>>>struct.unpack('i',res)
(2048,) # 元祖
简单的SSH服务示例(server和client倒置了,这不是重点):
server.py
import struct
import socket
sk = socket.socket()
sk.bind(('0.0.0.0',8080))
sk.listen()
conn,addr = sk.accept()
while True:
cmd = input('>>>')
if cmd == 'q':
conn.send(b'q')
break
conn.send(cmd.encode('utf-8'))
num = conn.recv(4) # 确认需要发送的数据长度
num = struct.unpack('i',num)[0]
res = conn.recv(int(num)).decode('utf-8')
print(res)
conn.close()
sk.close()
client.py
import struct
import socket
import subprocess
sk = socket.socket()
sk.connect(('127.0.0.1',8080))
while True:
cmd = sk.recv(1024).decode('utf-8')
if cmd == 'q':
break
res = subprocess.Popen(cmd,shell=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
std_out = res.stdout.read()
std_err = res.stderr.read()
len_num = len(std_out)+len(std_err)
num_byte = struct.pack('i',len_num)
sk.send(num_byte)
sk.send(std_out)
sk.send(std_err)
sk.close()
网友评论