一、黏包现象:
一次只能接收到部分信息
黏包原理:
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) # 发送数据