TCP粘包现象以及struct模块

image

为什么会出现粘包现象?

是因为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


image

通常我们只用到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()

参考

http://www.cnblogs.com/Eva-J/articles/8244551.html#_label5

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

友情链接更多精彩内容