首页 > 其他 > 详细

TCP黏包

时间:2021-04-25 00:18:14      阅读:22      评论:0      收藏:0      [点我收藏+]

黏包现象

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

client:

import socket

sk = 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 socket


sk = 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 socket

sk = 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 socket


sk = 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 socket
import struct

sk = 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 socket
import struct

sk = 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 struct


def 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 socket
import my_socket_util as msu

sk = socket.socket()
sk.connect(("127.0.0.1", 8123))

msu.my_send(sk, "我爱你")
msu.my_send(sk, "我爱你")

server:

import socket
import my_socket_util as msu

sk = 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))

TCP黏包

原文:https://www.cnblogs.com/pure3417/p/14698434.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!