关于QT中QTcpSocket接收数据的粘包问题的解决方案

TCP粘包问题:

最近做项目时,用QTcpSocket做客户端去接收数据,发现有时数据量大的时候,一次只能接收到该次发送的部分数据,或者数据量少的时候,会一次读取到对方两次发送的数据包,查了一些资料发现,这个就是Tcp的粘包问题。因为Tcp发送的数据是以流的形式传送的,就像水龙头放水一样,中间会也有断开的地方,我们需要怎么做才能取得我们需要的数据包呢?也就是说数据该从何处切分?

那么什么时候才需要考虑粘包问题?

1、如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如”hello give me sth about yourself”,然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符。
2、如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包。
3、如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
a、”hello give me about your message”
b、”Don’t give me about your message”
这样的话,如果发送方连续发送两个这样的包出去,接收方一次接收可能会是”hello give me abour your messageDon’t give me about your message”,这样接收方就傻眼了,到底应该怎么分了?因为没有协议规定怎么拆分这段字符串,所以要处理好分包,需要双方组织一个比较好的包结构,一般会在头上加上消息类型,消息长度等以确保正常接收。

粘包出现原因

粘包只可能出现在流传输中,TCP是基于流传输的,而UDP是不会出现粘包,因为他是基于报文的,也就是说UDP发送端调用几次write,

接收端必须调用相同次数的read读完,他每次最多只能读取一个报文,报文与报文是不会合并的,如果缓冲区小于报文长度,则多出来的部分会被丢掉。TCP不同了,他会合并消息,并且以不确定方式合并,这样就需要我们去粘包处理了,TCP造成粘包主要原因:

1、发送端需要等缓冲区满了才发送出去,造成粘包。

2、接收方不及时接收缓冲区的包,造成多个包一起接收。

解决方法:

为了避免粘包现象,可采取以下几种措施:

1、对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,

   就立即将本段数据发送出去,而不必等待发送缓冲区满;

2、是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽

   量避免出现粘包现象;

3、是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。

一般大多数都是使用第三种方法,自己定义包协议格式,然后人为粘包,那么我们就需要知道TCP发送时,大概会有哪几种包情况产生:

1、先接收到data1,然后接收到data2。 这是我们希望的,但是往往不是这样的。

2、先接收到data1的部分数据,然后接收到data1余下的部分以及data2的全部。

3、先接收到了data1的全部数据和data2的部分数据,然后接收到了data2的余下的数据。

4、一次性接收到了data1和data2的全部数据。

上面就是主要的几种情况,一般就是这几种,对于2、3、4就需要我们粘包处理了。

下面是我们处理粘包的案例:

我们采取一种特殊字符数组作为分隔符,每次读取数据时会进行拼接,并且检查数据是否以该分隔符结尾,如超时则结束等待。所以我们接受到的数据可能有以下几种情形:(分隔符 char endTag_m[4] = { 0xF0,0xE1,0xC2,0xB3})
1:“you are my sunshine” //无分隔符 一般是等待数据直到有第一个分隔符出现,超时结束本轮接收
2: “you are my sunshine 分隔符”//理想状态
3: “you are my sunshine 分隔符 you”//跟后一个粘包了,一般是等待数据直到有第二个分隔符出现,超时结束本轮接收
4:“you are my sunshine 分隔符 you are my sunshine too 分隔符”//两个数据包粘在一块,这种数据根据分隔符做一下处理即可

在该代码中,我们遇到比较多是接收到两个数据包粘在一块,原因可能是客户端处理不及时导致的,因为服务端抓包可见数据是分两次发送的,不存在一次发送两个数据。

希望大神能否解决客户端这边处理得更快一点?也就是一旦服务端发送数据,客户端立即处理掉,不会有数据滞留的情况。可以提高线程运行优先级?或者放到主线程中去?

void tcpSocket::slotMsgRecved()//;//接收到客户端消息
{
	QByteArray recvMsg;//当前拼接数据流
	int cnt = 0;
	while (cnt<1000)
	{
		cnt++;
		QByteArray datagram;
		datagram.resize(sock_m->bytesAvailable());
		sock_m->read(datagram.data(),datagram.size());	
		recvMsg.append(datagram);
		if (recvMsg.endsWith(endTag_m))
		{//遇到分隔符就结束
			break;
		}
		if (""==datagram&&""==recvMsg)
		{//当前数据是空的并且没有新的数据到来
			break;
		}
		sleepMsec(1);
	}
	//将数据包放入lstMsgRecv链表中
	mutex.lock();
	lstMsgRecv.append(recvMsg);
	mutex.unlock();
}
//调用线程    一直处理数据接收和拆包分发。
void tcpSocket::run()
{
	while(1)
	{
		if (recvDataFromThread)
		{//一直接收数据
			slotMsgRecved();
		}

		if (lstMsgRecv.size()>0)
		{//做数据分发
			mutex.lock();
			QByteArray byteArr = lstMsgRecv.takeFirst();
			mutex.unlock();

			MsVerify msVerify;
			
			//拆包处理 以分隔符进行分割 放入链表中
			byteArr.replace(endTag_m,",");
			QList<QByteArray> lstByteArr = byteArr.split(',');

			for (int i=0;i<lstByteArr.size();i++)
			{
				QByteArray temp = lstByteArr.at(i);
				if (temp=="")
				{
					continue;
				}
				//分发数据到不同的关联数组中
				QByteArray byteNoEndTag= msVerify.removeEndTagAndUnicToMByte(temp);
				QByteArray respCmd = msVerify.getDataByXmlContent(byteNoEndTag,"Response");
				QByteArray CS = msVerify.getDataByXmlContent(byteNoEndTag,"ModuleId");
				if (""!=CS)
				{
					if ("ManipulateData"==respCmd)
					{
						mapPrepareMsgRecv[CS] = temp + endTag_m;
					}
					else if ("ModuleStart"==respCmd)
					{
						mapModuleStartMsgRecv[CS] = temp + endTag_m;
					}
				}	
			}
		}
		sleepMsec(1);
	}
}
void sleepMsec(unsigned int millseconds)
{
	QTime reachTime = QTime::currentTime().addMSecs(millseconds);
	while (QTime::currentTime() < reachTime)
	{
		QCoreApplication::processEvents(QEventLoop::AllEvents,100);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小鱼儿LY

一切随缘

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值