粘包:
传输层协议有tcp和udp两种
tcp:Transmission Control Protocol
传输控制协议,基于数据流,收发的消息不能为空,需要在客户端和服务端都添加空消息的处理机制
tcp是可靠性协议,数据的收发都需要确认信息,这就降低了传输效率,故为了减少确认次数,tcp采用了nagle算法
将多次间隔短且数据小的数据合成一个数据流,然后发送,tcp的数据没有明确的界限,无法区分数据的开始和结束,
这就导致了可能将多个信息并为一条信息,也就是粘包
还有一种粘包情况是当一个数据包过大时,操作系统的缓存只会提供一部分数据给应用程序,导致我们只读了包的一部分内容,其余内容当我们再次接收时会发送过来
综上 tcp粘包问题的产生,是因为传输数据没有具体的界限,所以解决粘包问题需要知道数据的明确长度,当长度小时我们明确接受该长度的数据,当长度过大时,我们采用循环接受的方法来取
具体操作:1.发送方获取消息的具体长度
2.发送方先将该长度发送给接收方
3.接收方通过此长度准确接收消息
上述操作使用了第三方模块struct
struct的作用,pack方法将一个整数,转换成固定长度的bytes类型
unpack方法将一个bytes类型的长度转换为整数
解决粘包问题的核心,给数据流添加自定义报头
报头可以存放发送时间,真实信息大小,通过json模块保存成字典或列表,以便我们可以更方便的取值
具体代码(以cmd实现客户端系统命令为例子):
服务端
from socket import *
import json
import struct
import subprocess
import datetime
s = socket()#创建套接字对象
s.bind(("127.0.0.1",8888))#绑定ip和端口
s.listen(5)#监听,规定半连接池大小
while True:
conn,addr = s.accept()#接收客户端连接,三次握手
while True:
try:
cmd = conn.recv(1024)#接收cmd命令
if not cmd:#linux系统客户端强退时会不停发空消息,故此处为防止客户端突然退出,跨平台性
conn.close()
break
obj = subprocess.Popen(cmd.decode("utf-8"),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)#使用subprocess模块完成系统命令
stdout = obj.stdout.read()
stderr = obj.stderr.read()
msg = stdout +stderr
s_dic = {}#自定义报头
s_dic["time"] = str(datetime.datetime.now())#添加key time
s_dic["size"] = len(msg)#添加key size
head_data = json.dumps(s_dic)#使用json序列化成json格式的字符串
head_len = struct.pack("i",len(head_data))#使用struct模块获取报头长度,将整形转换为二进制,固定为4个字节大小
conn.send(head_len)#先发送报头长,此时使用的“i”模式为4字节,故客户端以4个字节接收,接收后使用struct模块反解出报头信息长准确接收报头信息,
#防止报头信息和具体信息粘包
conn.send(head_data.encode("utf-8"))#将json格式的字符串转换成二进制,发送报头信息,报头信息的作用,客户端可以通过报头信息获取具体信息长度
conn.send(msg)#发送具体信息
except ConnectionResetError:
conn.close()
break
客户端:
c = socket()#创建客户端套接字对象
c.connect(("127.0.0.1",8888))#链接服务器
while True:
cmd = input(">>>:").strip().encode("utf-8")#接收命令
c.send(cmd)#发送命令给服务器
lenth = c.recv(4)#接收报头长度
head_lenth = struct.unpack("i",lenth)[0]#unpack出整形报头信息的长度
head_data = c.recv(head_lenth)#准确接收报头信息
dic = json.loads(head_data.decode("utf-8"))#获取报头信息
print(dic)
rev_size = 0
all_data = b""
while rev_size < dic["size"]:#由报头信息或得的具体信息长
data = c.recv(1024)#循环接收
rev_size += len(data)
all_data += data
print(all_data.decode("gbk"))
udp协议
user datagram protocol,用户数据报协议
没有链接,面向消息,效率高,udp数据包有报头,故不会产生粘包问题