解决客户端与服务端传输数据黏包问题
黏包原因
1.因为发送数据包时,每次发送的包小,因为系统进行优化算法,就将两次的包放在一起发送,减少了资源的重复占用。多次发送会经历多次网络延迟,一起发送会减少网络延迟的次数。因此在发送小数据时会将两次数据一起发送,而客户端接收时,则会一并接收。#即出现多次send会出现黏包
2.是因为接收数据时,又多次接收,第一次接收的数据量小,导致数据还没接收完,就停下了,剩余的数据会缓存在内存中,然后等到下次接收时和下一波数据一起接收。
方案1
客户端将数据分为两个阶段发给服务端
- 将报头内容组成结构体 ,发给服务端;报头:报头标识"package"+报文长度Len
- 发送完报头,紧接着发送长度为Len的报文(json串形式)发给服务端;
客户端代码示例
struct pkg_header_t
{
char cType[8];//package
unsigned int len;
};
INT64 ClientSocket::SendData(QString qMsg)
{
/*send to server*/
QTextCodec *codec = QTextCodec::codecForName("utf-8");
codec->setCodecForLocale(codec);
QString qMsg = codec->toUnicode(msg.toLocal8Bit());
qDebug()<<qMsg.size()<<endl;
pkg_header_t pkg;
strncpy(pkg.cType,"package",8);
QByteArray q = qMsg.toLocal8Bit();
pkg.len = q.size();
qDebug()<<q.size()<<endl;
INT64 iRet = client->write((const char*)((void *)&pkg),sizeof(pkg_header_t));
if (-1 == iRet)
{
qCritical() << "Message send head error";
}
iRet = client->write(q);
if (-1 == iRet)
{
qCritical() << "Message send error";
}
return iRet;
}
服务端将数据分为两个阶段接收
- 接收报头结构体 ,校验报头标识"package",获取报文长度Len
- 根据报头信息紧接着循环接收,直到接收长度为Len的报文(json串);
方案2
服务端代码示例
import json
import socket
import struct
sk =socket.socket()#创建一个socket对象
sk.bind(('127.0.0.1',8080))#绑定本地ip地址与端口
sk.listen()#开启监听
buffer =1024 #设置buffer值大小
conn,addr =sk.accept()#等待客户端连接服务端,得到地址与双共工通道
head_len=conn.recv(4)#接收用struck将数字转长度为4的bytes
head_len =struct.unpack('i',head_len)[0]#调用struct模块来解包,得到原来的数字(数字为报头的长度)
json_head =conn.recv(head_len).decode('utf-8')#接收json序列化的报头进行解码
head =json.loads(json_head)#将json序列化的报头进行反序列化
filesize =head['filesize']#拿到head字典中键filesize所对应的值
print(filesize)#打印filesize
with open(r'dir\%s'%head['filename'],'wb')as f:#dir\文件名,拿到文件的路径,以wb模式打开
while filesize:#当filesize(文件内剩余内容的大小)有值时
if filesize >=buffer:#如果filesize>= buffer值,buffer值是设定的一次接收多少字节的内容
print(filesize) #打印filesize大小
content =conn.recv(buffer)#接收buffer值大小的内容
f.write(content)#写入文件
filesize -=buffer#原来的文件大小减去接收的内容,等于剩余文件的大小
else:#如果文件剩余的内容大小<buffer设定的大小,就全部接收
content =conn.recv(filesize)
f.write(content)
filesize =0
print('=====>',len(content))
print(filesize)
print('服务器端')
conn.close()
sk.close()
客户端代码示例
import struct
import os
import json
import socket
sk =socket.socket
sk.connect(('127.0.0.1',8090))
buffer =1024
head ={'filepath':r'D:\Documents\oCam',
'filename':r'test.mp4',
'filesize':None}#定义一个报头
file_path =os.path.join(head['filepath'],head['filename'])#将文件名与文件路径加载进目录中
filesize = os.path.getsize(file_path)#得到目录中文件的大小
head['filesize'] =filesize#将文件大小赋值回列表中
json_head =json.dumps(head)#将head字典序列化
bytes_head =json_head.encode('utf-8')#将序列化之后的字典进行解码
head_len =len(bytes_head)#计算转码之后字典的长度
pack_len =struct.pack('i',head_len)#调用struct模块将长度转换成长度为4的bytes类型
sk.send(pack_len)#发送pack_len
sk.send(bytes_head)#发送bytes_head
with open(file_path,'rb')as f:
while filesize:
print(filesize)
if filesize>=buffer:
content =f.read(buffer)
print('====>',len(content))
sk.send(content)
filesize-=buffer
else:
content =f.read(filesize)
sk.send(content)
filesize=0
sk.close()