TCP黏包
发布日期:2021-05-09 05:58:46 浏览次数:10 分类:博客文章

本文共 3076 字,大约阅读时间需要 10 分钟。

黏包现象

在使用TCP协议进行数据传输的时候,会有以下问题出现。

client:

import socketsk = socket.socket()sk.connect(("127.0.0.1", 8101))# 连续发送数据s = "我爱你"sk.send(s.encode("utf-8"))sk.send(s.encode("utf-8"))print("发送完毕")sk.close()

server:

import socketsk = socket.socket()sk.bind(("127.0.0.1", 8101))sk.listen()conn, addr = sk.accept()msg1 = conn.recv(1024)print(msg1.decode("utf-8"))msg2 = conn.recv(1024)print(msg2.decode("utf-8"))sk.close()

运行结果

我们发现,打印来的效果是两个数据包合在一起了。为什么会这样呢? 在数据传输的时候客户端发送两次数据。这两个数据并不是直接发送出去的。首先会存放在缓冲区。等缓冲区数据装满或者经过一段时间后。会把缓冲区中的数据一起发送出去。 这就导致了一个很坑的现象。明明是两次发送的数据。被合在了一起。这就是典型的黏包现象。

注意,黏包现象只有TCP才会出现。UDP是不会出现黏包的。因为UDP的不连续性。每次发送的数据都会立刻打包成数据包然后发出去。数据包与数据包之间是有边界隔离的。你可以认为是一个sendto对应一个recvfrom。因此UDP不会出现黏包.

如何解决黏包问题

很简单。之所以出现黏包就是因为数据没有边界,直接把两个包混合成了一个包。那么我可以在发送数据的时候,指定边界,告诉对方,我接下来这个数据包有多大。 对面接收数据的时候呢,先读取该数据包的大小,然后再读取数据,就不会产生黏包了。

通俗的说,发送数据的时候制定数据的格式:长度+数据。 接收的时候就知道有多少是当前这个数据包的大小了。也就相当于定义了分隔边界了。

client:

import socketsk = socket.socket()sk.connect(("127.0.0.1", 8101))# 连续发送数据s = "我爱你"bs = s.encode("utf-8")# 计算数据长度. 格式化成4位数字bs_len = format(len(bs), "04d").encode("utf-8")# 发送数据之前. 先发送长度# 整个数据包: 0009\x\x\x\x\x\x...sk.send(bs_len)sk.send(bs)sk.send(bs_len)sk.send(bs)print("发送完毕")sk.close()

server:

import socketsk = socket.socket()sk.bind(("127.0.0.1", 8101))sk.listen()conn, addr = sk.accept()# 整个数据包: 0009\x\x\x\x\x\x...# 接收4个字节. 转换成数字bs_len = int(conn.recv(4).decode('Utf-8'))# 读取数据msg1 = conn.recv(bs_len)print(msg1.decode("utf-8"))bs_len = int(conn.recv(4).decode('Utf-8'))msg2 = conn.recv(bs_len)print(msg2.decode("utf-8"))sk.close()

如果每次发送数据都要经过这么一次,属实有点儿累。没关系,python提供了一个很好用的模块来帮我们解决这个恶心的问题

ret = struct.pack("i", 123456789)print(ret)print(len(ret))  # 4  不论数字大小, 定死了4个字节# 把字节还原回数字bs = b'\x15\xcd[\x07'num = struct.unpack("i", bs)[0]print(num)

优雅的解决黏包问题

client:

import socketimport structsk = socket.socket()sk.connect(("127.0.0.1", 8123))msg_bs = "我爱你".encode("utf-8")msg_struct_len = struct.pack("i", len(msg_bs))# 发一次sk.send(msg_struct_len)sk.send(msg_bs)# 发两次sk.send(msg_struct_len)sk.send(msg_bs)

server:

import socketimport structsk = socket.socket()sk.bind(("127.0.0.1", 8123))sk.listen()conn, addr = sk.accept()# 接收一个数据包msg_struct_len = conn.recv(4)msg_len = struct.unpack("i", msg_struct_len)[0]data = conn.recv(msg_len)print(data.decode('utf-8'))# 接收第二个数据包msg_struct_len = conn.recv(4)msg_len = struct.unpack("i", msg_struct_len)[0]data = conn.recv(msg_len)print(data.decode('utf-8'))

看着还是别扭?提取一个模块试试看

my_socket_util

import structdef my_send(sk, msg):    msg_bs = msg.encode("utf-8")    msg_struct_len = struct.pack("i", len(msg_bs))    sk.send(msg_struct_len)    sk.send(msg_bs)def my_recv(sk):    # 接收一个数据包    msg_struct_len = sk.recv(4)    msg_len = struct.unpack("i", msg_struct_len)[0]    data = sk.recv(msg_len)    return data.decode("utf-8")

client:

import socketimport my_socket_util as msusk = socket.socket()sk.connect(("127.0.0.1", 8123))msu.my_send(sk, "我爱你")msu.my_send(sk, "我爱你")

server:

import socketimport my_socket_util as msusk = socket.socket()sk.bind(("127.0.0.1", 8123))sk.listen()conn, addr = sk.accept()print(msu.my_recv(conn))print(msu.my_recv(conn))
上一篇:文件的上传和下载
下一篇:Socket与三挥四握

发表评论

最新留言

第一次来,支持一个
[***.219.124.196]2025年04月18日 16时31分58秒