网络编程之黏包

一、黏包现象:

一次只能接收到部分信息

 

黏包原理:

tcp:客户端每次发送数据给服务器端,服务器端都会给客户端一个回复

tcp的合包机制:对tcp的原理做了优化,客户端短时间内发送了两条很短的信息,将两条信息合并为一条,但只返回一个回复

 

tcp的拆包机制:

网络上传输的字节数是有限的,网络上传输最大字节数是1500个字节,超过1500个字节就会拆包,拆成N个包分别发送过去,每收到一个包都会给回复,没收到的包就会再发送一次没收到的包,然后再合包

 

内核的缓存机制,将很小的两个包合并。

内存的缓存机制,包超过了1500个字节,会拆包后分别发送,另一端要求接收,没发送完先接收包

 

造成黏包的实现原理:

tcp合包现象:①数据很短 ②时间间隔短

tcp拆包现象:大数据会发生拆分,不会一次性全部发送给对方,对方在接收的时候很可能没有一次性接收到所有的信息,那么没有接收完的信息很可能跟后面的信息黏在一起

 

黏包现象只发生在tcp协议:tcp协议的传输是流式传输,每一条信息与信息之间式没有边界的,因为没有区分信息数目的机制

udp协议中不会发送黏包现象:upd协议适合短数据的发送(接收端不会回复,而且有分界线的概念),udp发送大数据会拆包,另一端会合包,假如数据丢包,不会再重新发送。而且不能发送过长的数据(增大了数据丢失的几率)

 

在程序中会出现黏包的原因:接收数据边界不清晰,即接收端不清楚接收数据的长度

解决方案:在接收端限制接收的长度

 

二、解决黏包方案

解决黏包现象的方案1:

自定义协议:接收端知道数据的长度,根据接收的长度来控制

接收端先获取接收字节的长度,然后再根据长度取接收

# server
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',9090))
sk.listen()
conn,addr = sk.accept()
num = conn.recv(2)          # 接收数据的长度
print(num)                  # b'16'
num = num.decode().zfill(4)
msg = conn.recv(int(num))   # 根据数据的长度来接收
print(msg.decode())         # character string
# client
import socket
sk = socket.socket()
sk.connect(('127.0.0.1',9090))

sk.send(b'16character string')      # 在数据的前面定义数据的长度

此方案的弊端:

① 复杂

② 最多只能一次传递9999个字节

 

解决黏包现象的方案二:

不管数据的长度是多少,都转化为4个字节,利用struct模块自定义协议。

先统计bytes数据的长度,然后通过struct模块将数据长度转化为4个字节,接收端通过struct模块获得是一个元组,按0索引取值,还原数据的长度

 

server端:统计数据的长度,通过struct模块,处理数据的长度,得到一个4个字节的结果,先发送4个字节的长度,再发送数据

client端:先接收4个字节,通过struct来处理这4个字节,拿到数据的长度,按照数据的长度来接收数据

# server端
import socket
import struct
sk = socket.socket()
sk.bind(('127.0.0.1',9091))
sk.listen()

conn,addr = sk.accept()
num = conn.recv(4)                  # 接收数据的长度
num = struct.unpack('i',num)        # 转化为原来数据的长度
msg = conn.recv(num[0])             # 接收数据
print(msg.decode())
# client端
import socket
import struct
sk = socket.socket()
sk.connect(('127.0.0.1',9091))

msg = 'asdfghjkl'.encode('utf-8')
num = struct.pack('i',len(msg)) # 将数据长度转化为4个字节
sk.send(num)                    # 发送数据长度
sk.send(msg)                    # 发送数据

 

原文地址:https://www.cnblogs.com/st-st/p/9650095.html