Qt 使用UDPSocket遇到的丢包问题

X11/Qt/Qt quick/Qml界面技术 专栏收录该内容
307 篇文章 0 订阅

原文地址::https://blog.csdn.net/bsbsxll/article/details/40824755

 

相关文章

1、QT中UDPSocket丢包问题----https://blog.csdn.net/rabbitjerry/article/details/72674458

2、QT中UDPSocket丢包问题(续)----https://blog.csdn.net/rabbitjerry/article/details/72830004

3、UDP主要丢包原因及具体问题分析----https://blog.csdn.net/kobejayandy/article/details/35797609

4、解决UDP丢包问题的经验----https://blog.csdn.net/kristpan/article/details/50553592

5、Linux网络编程-UDP接收数据丢包解决方案----https://blog.csdn.net/baidu_35692628/article/details/76165337

6、Linux UDP严重丢包问题的解决----https://blog.csdn.net/u013267687/article/details/52594126

 

最近烦人的事情很多,所以博客一直被落下了。这样不好,希望可以敦促自己不要懒惰。

前些日子接下了一个撂摊子的项目,这个项目中大量的使用udp socket进行多软件多硬件的来回通讯过程,但说实话通信量不是特别大。但是经常遇到各种各样奇怪的丢包现象。在解决这些问题过程中,也算加强了一些基础知识的学习,在此也顺便记录下解决步骤,以便下次项目中使用。

 

该项目中软件部分有A、B两个软件。其中A和B都有各自的发送和接受UDP功能。

代码实现很简单:

发送端:

 
  1. void UdpServer::send(const QString &message)

  2. {

  3. qDebug() << "message not null" << message;

  4. QByteArray byte;

  5. byte.append(message.toAscii());

  6. _socket->writeDatagram(byte,mcast_addr,tempport);

  7. }

  8.  
  9. UdpServer::UdpServer(QObject *parent):QObject(parent)

  10. {

  11. _socket = new QUdpSocket();

  12. }


 

接收端:

 
  1. void UdpClient::recv()

  2. {

  3. while(_socket->hasPendingDatagrams())

  4. {

  5. QByteArray data;

  6. data.resize(_socket->pendingDatagramSize());

  7. _socket->readDatagram(data.data(),(qint64)data.size());

  8. // QString byteStr = QString(data.toHex());

  9. // qDebug() << "此次接受的报文内容是:" << byteStr;

  10. if(byteStr.left(4)=="5555"){

  11. .....

  12. }else if(byteStr.left(4)=="6666"){

  13. .....

  14. }

  15. ....

  16. }

  17. }

 

以上是原程序。

 

刚开始测试的时候,另一程序以每500ms速度发送长度为512长度的报文,发现丢包现象比较严重。开始怀疑是否是因为收包程序while中有大量的逻辑判断逻辑而造成报文并没有及时的接受到。于是新建一个数据缓存池CDataPool类,并继承于Qthread类。接受到报文后,不进行任何处理,直接放至CDataPool类的队列中,然后另起线程进行报文解析操作,代码如下:

CDataPool.h

 

 
  1. /***********************************************************************

  2. * Module: CDataPool.h

  3. * Author: 振星

  4. * Modified: 2014年11月4日星期二 09:57:45

  5. * Purpose: Declaration of the class CDataPool

  6. * Comment: 数据管理类

  7. ***********************************************************************/

  8.  
  9. #ifndef CDATAPOOL_H

  10. #define CDATAPOOL_H

  11. #include <QtCore/qglobal.h>

  12. #include <QMutex>

  13. #include <QString>

  14. #include <QWaitCondition>

  15. #include <QThread>

  16. #include <QQueue>

  17. #include <QSharedPointer>

  18.  
  19. #define CONTAINER_MAX_NUM 10000 // 缓存队列中最大数目

  20. typedef struct msghead

  21. {

  22. unsigned short headFst;

  23. unsigned short headSnd;

  24. }_MSGHEAD;

  25.  
  26. #if defined(DATAPOOL_LIBRARY)

  27. # define DATAPOOLSHARED_EXPORT Q_DECL_EXPORT

  28. #else

  29. # define DATAPOOLSHARED_EXPORT Q_DECL_IMPORT

  30. #endif

  31.  
  32. class DATAPOOLSHARED_EXPORT CDataPool : public QThread {

  33. Q_OBJECT

  34. public:

  35. static CDataPool * instance();

  36. ~CDataPool();

  37. //void insert(char*);

  38. bool recv(const QByteArray); // 接收数据并缓存

  39. /* 线程停止 */

  40. void stop();

  41. void init();

  42. // 设置报文回发页面

  43. void setMark(int);

  44. protected:

  45. void run(); // 线程

  46.  
  47. signals:

  48.  
  49. private:

  50. explicit CDataPool();

  51. static CDataPool * m_instance; // 唯一实例

  52. bool handleData(); // 处理队列中数据数据

  53. // 线程停止

  54. volatile bool stopped;

  55. QMutex mutex;

  56. QWaitCondition queueWait;

  57. // 队列数组

  58. QQueue<QByteArray> qqByteArr;

  59. };

  60.  
  61. #endif // CDATAPOOL_H


CDataPool.cpp

 

 
  1. CDataPool.cpp

  2. /***********************************************************************

  3. * Module: CDataPool.cpp

  4. * Author: 振星

  5. * Modified: 2014年11月4日星期二 09:57:45

  6. * Purpose: Declaration of the class CDataPool

  7. * Comment: 数据管理类

  8. ***********************************************************************/

  9. #include "cdatapool.h"

  10. #include "monitorDefine.h"

  11. #include "picDefine.h"

  12. #include "QDebug"

  13.  
  14.  
  15. CDataPool * CDataPool::m_instance = NULL;

  16. // Name: CDataPool::instance()

  17. // Purpose: Implementation of CDataPool::instance()

  18. // Comment: 应用实例

  19. // Return: static CDataPool *

  20. CDataPool * CDataPool::instance()

  21. {

  22. if ( NULL == m_instance ){

  23. m_instance = new CDataPool;

  24. }

  25. return m_instance;

  26. }

  27.  
  28. // Name: CDataPool::CDataPool()

  29. // Purpose: Implementation of CDataPool::CDataPool()

  30. // Return:

  31. CDataPool::CDataPool()

  32. {

  33. stopped = false;

  34. qqByteArr.clear();

  35. }

  36.  
  37. // Name: CDataPool::handleData

  38. // Purpose: 处理数据池中的数据

  39. // Comment: 应用实例

  40. // Return: -1:fail; >=0:success

  41. bool CDataPool::handleData()

  42. {

  43. if(qqByteArr.length() < 1)

  44. return -1;

  45. // QMutexLocker locker(&mutex);

  46. QByteArray byteArrFirst = qqByteArr.takeFirst();

  47. QString byteStr = QString(byteArrFirst.toHex());

  48. qDebug() << "即将处理的报文是:" << byteStr;

  49.  
  50. // 为什么这么做?我也不知道,这个是之前的代码,看不懂,很麻烦的判断

  51. if( 0x3333 == mh->headFst || 0x4444 == mh->headFst){//

  52. ....

  53. }else if(0x5555 == mh->headFst && 0xAAAA ==mh->headSnd){//

  54. switch (mark) {

  55. case MARK_1:

  56. {

  57. .....

  58. }

  59. break;

  60. case MARK_2:

  61. {

  62. .....

  63. }

  64. break;

  65. case MARK_3:

  66. {

  67. .....

  68. }

  69. break;

  70. case MARK_4:

  71. {

  72. .....

  73. }

  74. break;

  75. default:

  76. break;

  77. }

  78.  
  79. }else{ // 其他处理逻辑

  80. if(mark == MARK_1)

  81. {

  82. .....

  83. }

  84. else if(mark == MARK_3)

  85. {

  86. .....

  87. }

  88. else if(mark == MARK_4)

  89. {

  90. .....

  91. }

  92. return 1;

  93. }

  94.  
  95. // Name: CDataPool::~CDataPool()

  96. // Purpose: Implementation of CDataPool::~CDataPool()

  97. // Return:

  98. CDataPool::~CDataPool()

  99. {

  100. //delete m_instance;

  101. QMutexLocker locker(&mutex);

  102. SFDelete(mh);

  103. qqByteArr.clear();

  104. quit();

  105. wait();

  106. stopped = true;

  107. mark = 0;

  108. // TODO : implement

  109. }

  110.  
  111. // Name: CDataPool::recv

  112. // Purpose: 接收数据并缓存

  113. // Parameters: 1. buf:数据首址,2.len :长度

  114. // Comment: 应用实例

  115. // Return: -1:fail; >=0:success

  116. bool CDataPool::recv(const QByteArray byte)

  117. {

  118. QMutexLocker locker(&mutex); // 锁定队列对象,禁止同时操作

  119. if(qqByteArr.size() >CONTAINER_MAX_NUM){ // 当队列大于限定值时,

  120. qqByteArr.removeFirst();

  121. }

  122.  
  123. // QString byteStr = QString(byte.toHex());

  124. // qDebug() << "放到队列里面的报文是:" << byteStr;

  125.  
  126. qqByteArr.enqueue(byte); // 插入指针到队列尾部

  127. queueWait.wakeOne(); // 唤醒睡着中的线程

  128. return 1;

  129. }

  130.  
  131. // Name: CDataPool::run()

  132. // Purpose: Implementation of CDataPool::run()

  133. // Comment: 线程 用于推送解析信号至显示模块

  134. // Return: void

  135. void CDataPool::run()

  136. {

  137. forever{

  138. QMutexLocker locker(&mutex);

  139. if(qqByteArr.isEmpty()){

  140. queueWait.wait(&mutex);

  141. }else{

  142. handleData();

  143. }

  144. }

  145. }

  146.  
  147. // Name: CDataPool::init()

  148. // Purpose: Implementation of CDataPool::init()

  149. // Comment: 启动线程

  150. // Return: void

  151. void CDataPool::init()

  152. {

  153. if(!stopped){

  154. start();

  155. }

  156. }

  157.  
  158. // Name: CDataPool::setMark(int m)

  159. // Purpose: Implementation of CDataPool::setMark(int m)

  160. // Comment: 启动线程

  161. // Return: void

  162. void CDataPool::setMark(int m)

  163. {

  164. mark = m;

  165. }

 

 

CDataPool 是一个单例,同时使用线程在不停的执行报文解析,当有新的报文进来后,唤醒线程,否则线程休眠。

 

然后修改原来的接受部分程序:

 
  1. void UdpClient::recv()

  2. {

  3. while(_socket->hasPendingDatagrams())

  4. {

  5. QByteArray data;

  6. data.resize(_socket->pendingDatagramSize());

  7. _socket->readDatagram(data.data(),(qint64)data.size());

  8. // QString byteStr = QString(data.toHex());

  9. // qDebug() << "此次接受的报文内容是:" << byteStr;

  10. cDataPool->recv(data);

  11. }

  12. }

 

 

改完以上的程序后,我满心以为,应该不会丢包了吧?使用100ms的发送次数,依然丢包,只是稍微好了一点点。但至少有些进步。大概1000包数据中至少会丢400包。伤心。

通过上述的实践说明,在报文接受过程中,报文解析逻辑比较复杂时,如果在同一线程处理,会对造成一定程度的丢包现象。但绝不是主要原因。

 

客户几个电话打过来催促解决问题的时间点。无奈继续没有注释阅读代码,突然发现了一个比较有趣的逻辑,很值得分享下。大家都知道到udp报文大于一定值时,将会被截包或者丢包。之前写这个项目的人写了以下的代码:

 
  1. foreach (PICINFO info, selectedMoubleInfoList) {

  2. QString tempStr = info.module_userId + BACKSLASH

  3. + info.module_FactoryId + BACKSLASH

  4. + mnMap.value(info.module_userId);

  5. if(updUseData.size() + tempStr.size() < maxUpdlength){

  6. updUseData += tempStr + COLON;

  7. }else{

  8. updUseDataList.append(updUseData);

  9. updUseData = STR_EAMPTY;

  10. updUseData += tempStr + COLON;

  11. }

  12. selectedMoubleList.append(info.module_FactoryId);

  13. }

  14.  
  15. if(updUseData.size() > 0){

  16. updUseDataList.append(updUseData);

  17. }

  18.  
  19. if(updUseDataList.size() > 0){

  20. ui->progressBar->setRange(0,selectedMoubleInfoList.size());

  21.  
  22. //字符>1000的场合,分段截取

  23. foreach (QString str, updUseDataList) {

  24.  
  25. int index = str.lastIndexOf(COLON);

  26.  
  27. QString sendStr = useCode + DOLLAR + QString::number(selectedMoubleInfoList.size())+ COMMA

  28. + doConnParamStr(paramMap)

  29. + str.left(index);

  30.  
  31. udpServer->send(sendStr);

  32.  
  33. }


 

上面的代码主要是将要发送的报文拼接成字符串时,按照1000的长度进行分割,分割完毕后在发送到socket中。想法是好的,但是问题就来了,他在一个for循环中发送报文,这个速度可不是100ms或者其他值,可能都是微秒级别的。

 

我很想将上面的代码改动下,但是发现几乎所有的类全部都关联这个里面,改动实在太大,所以我使用了一个临时解决方法在foreach循环中加上Sleep(100).

这个操作完成后,惊喜很大,在发送频率为150ms的时候,就从没有出现丢包现象,但是如果设置成100ms,基本还是会丢10包左右。

于是继续寻找可能出现的问题,基本上所有接受和发送的地方我都检查过,依然不知道为什么会丢包。后来打电话问了某研究所的博士,他对我说了一个可能的原因,就是如果两个软件部署在同一台机器上自发自收,可能会因为socket缓存池的满的问题造成上述丢包现象。于是将两个软件分开部署后,即使调整至50ms的发送频率也不会出现丢包现象。不过调整socket的缓存大小目前我还没有去实现,毕竟已经可以和客户说原因了。

 

这篇文章应该没有什么技术含量,但是却记录着一些解决问题的方法和思路,希望可以互相共享并且请大牛们指点不足。谢谢

  • 1
    点赞
  • 0
    评论
  • 8
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值