Qt 使用UDPSocket遇到的丢包问题
2014-11-05 16:51
253 查看
最近烦人的事情很多,所以博客一直被落下了。这样不好,希望可以敦促自己不要懒惰。
前些日子接下了一个撂摊子的项目,这个项目中大量的使用udp socket进行多软件多硬件的来回通讯过程,但说实话通信量不是特别大。但是经常遇到各种各样奇怪的丢包现象。在解决这些问题过程中,也算加强了一些基础知识的学习,在此也顺便记录下解决步骤,以便下次项目中使用。
该项目中软件部分有A、B两个软件。其中A和B都有各自的发送和接受UDP功能。
代码实现很简单:
发送端:
接收端:
以上是原程序。
刚开始测试的时候,另一程序以每500ms速度发送长度为512长度的报文,发现丢包现象比较严重。开始怀疑是否是因为收包程序while中有大量的逻辑判断逻辑而造成报文并没有及时的接受到。于是新建一个数据缓存池CDataPool类,并继承于Qthread类。接受到报文后,不进行任何处理,直接放至CDataPool类的队列中,然后另起线程进行报文解析操作,代码如下:
CDataPool.h
CDataPool.cpp
CDataPool 是一个单例,同时使用线程在不停的执行报文解析,当有新的报文进来后,唤醒线程,否则线程休眠。
然后修改原来的接受部分程序:
改完以上的程序后,我满心以为,应该不会丢包了吧?使用100ms的发送次数,依然丢包,只是稍微好了一点点。但至少有些进步。大概1000包数据中至少会丢400包。伤心。
通过上述的实践说明,在报文接受过程中,报文解析逻辑比较复杂时,如果在同一线程处理,会对造成一定程度的丢包现象。但绝不是主要原因。
客户几个电话打过来催促解决问题的时间点。无奈继续没有注释阅读代码,突然发现了一个比较有趣的逻辑,很值得分享下。大家都知道到udp报文大于一定值时,将会被截包或者丢包。之前写这个项目的人写了以下的代码:
上面的代码主要是将要发送的报文拼接成字符串时,按照1000的长度进行分割,分割完毕后在发送到socket中。想法是好的,但是问题就来了,他在一个for循环中发送报文,这个速度可不是100ms或者其他值,可能都是微秒级别的。
我很想将上面的代码改动下,但是发现几乎所有的类全部都关联这个里面,改动实在太大,所以我使用了一个临时解决方法在foreach循环中加上Sleep(100).
这个操作完成后,惊喜很大,在发送频率为150ms的时候,就从没有出现丢包现象,但是如果设置成100ms,基本还是会丢10包左右。
于是继续寻找可能出现的问题,基本上所有接受和发送的地方我都检查过,依然不知道为什么会丢包。后来打电话问了某研究所的博士,他对我说了一个可能的原因,就是如果两个软件部署在同一台机器上自发自收,可能会因为socket缓存池的满的问题造成上述丢包现象。于是将两个软件分开部署后,即使调整至50ms的发送频率也不会出现丢包现象。不过调整socket的缓存大小目前我还没有去实现,毕竟已经可以和客户说原因了。
这篇文章应该没有什么技术含量,但是却记录着一些解决问题的方法和思路,希望可以互相共享并且请大牛们指点不足。谢谢
前些日子接下了一个撂摊子的项目,这个项目中大量的使用udp socket进行多软件多硬件的来回通讯过程,但说实话通信量不是特别大。但是经常遇到各种各样奇怪的丢包现象。在解决这些问题过程中,也算加强了一些基础知识的学习,在此也顺便记录下解决步骤,以便下次项目中使用。
该项目中软件部分有A、B两个软件。其中A和B都有各自的发送和接受UDP功能。
代码实现很简单:
发送端:
void UdpServer::send(const QString &message) { qDebug() << "message not null" << message; QByteArray byte; byte.append(message.toAscii()); _socket->writeDatagram(byte,mcast_addr,tempport); } UdpServer::UdpServer(QObject *parent):QObject(parent) { _socket = new QUdpSocket(); }
接收端:
void UdpClient::recv() { while(_socket->hasPendingDatagrams()) { QByteArray data; data.resize(_socket->pendingDatagramSize()); _socket->readDatagram(data.data(),(qint64)data.size()); // QString byteStr = QString(data.toHex()); // qDebug() << "此次接受的报文内容是:" << byteStr; if(byteStr.left(4)=="5555"){ ..... }else if(byteStr.left(4)=="6666"){ ..... } .... } }
以上是原程序。
刚开始测试的时候,另一程序以每500ms速度发送长度为512长度的报文,发现丢包现象比较严重。开始怀疑是否是因为收包程序while中有大量的逻辑判断逻辑而造成报文并没有及时的接受到。于是新建一个数据缓存池CDataPool类,并继承于Qthread类。接受到报文后,不进行任何处理,直接放至CDataPool类的队列中,然后另起线程进行报文解析操作,代码如下:
CDataPool.h
/*********************************************************************** * Module: CDataPool.h * Author: 振星 * Modified: 2014年11月4日星期二 09:57:45 * Purpose: Declaration of the class CDataPool * Comment: 数据管理类 ***********************************************************************/ #ifndef CDATAPOOL_H #define CDATAPOOL_H #include <QtCore/qglobal.h> #include <QMutex> #include <QString> #include <QWaitCondition> #include <QThread> #include <QQueue> #include <QSharedPointer> #define CONTAINER_MAX_NUM 10000 // 缓存队列中最大数目 typedef struct msghead { unsigned short headFst; unsigned short headSnd; }_MSGHEAD; #if defined(DATAPOOL_LIBRARY) # define DATAPOOLSHARED_EXPORT Q_DECL_EXPORT #else # define DATAPOOLSHARED_EXPORT Q_DECL_IMPORT #endif class DATAPOOLSHARED_EXPORT CDataPool : public QThread { Q_OBJECT public: static CDataPool * instance(); ~CDataPool(); //void insert(char*); bool recv(const QByteArray); // 接收数据并缓存 /* 线程停止 */ void stop(); void init(); // 设置报文回发页面 void setMark(int); protected: void run(); // 线程 signals: private: explicit CDataPool(); static CDataPool * m_instance; // 唯一实例 bool handleData(); // 处理队列中数据数据 // 线程停止 volatile bool stopped; QMutex mutex; QWaitCondition queueWait; // 队列数组 QQueue<QByteArray> qqByteArr; }; #endif // CDATAPOOL_H
CDataPool.cpp
CDataPool.cpp /*********************************************************************** * Module: CDataPool.cpp * Author: 振星 * Modified: 2014年11月4日星期二 09:57:45 * Purpose: Declaration of the class CDataPool * Comment: 数据管理类 ***********************************************************************/ #include "cdatapool.h" #include "monitorDefine.h" #include "picDefine.h" #include "QDebug" CDataPool * CDataPool::m_instance = NULL; //////////////////////////////////////////////////////////////////////// // Name: CDataPool::instance() // Purpose: Implementation of CDataPool::instance() // Comment: 应用实例 // Return: static CDataPool * //////////////////////////////////////////////////////////////////////// CDataPool * CDataPool::instance() { if ( NULL == m_instance ){ m_instance = new CDataPool; } return m_instance; } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::CDataPool() // Purpose: Implementation of CDataPool::CDataPool() // Return: //////////////////////////////////////////////////////////////////////// CDataPool::CDataPool() { stopped = false; qqByteArr.clear(); } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::handleData // Purpose: 处理数据池中的数据 // Comment: 应用实例 // Return: -1:fail; >=0:success //////////////////////////////////////////////////////////////////////// bool CDataPool::handleData() { if(qqByteArr.length() < 1) return -1; // QMutexLocker locker(&mutex); QByteArray byteArrFirst = qqByteArr.takeFirst(); QString byteStr = QString(byteArrFirst.toHex()); qDebug() << "即将处理的报文是:" << byteStr; // 为什么这么做?我也不知道,这个是之前的代码,看不懂,很麻烦的判断 if( 0x3333 == mh->headFst || 0x4444 == mh->headFst){// .... }else if(0x5555 == mh->headFst && 0xAAAA ==mh->headSnd){// switch (mark) { case MARK_1: { ..... } break; case MARK_2: { ..... } break; case MARK_3: { ..... } break; case MARK_4: { ..... } break; default: break; } }else{ // 其他处理逻辑 if(mark == MARK_1) { ..... } else if(mark == MARK_3) { ..... } else if(mark == MARK_4) { ..... } return 1; } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::~CDataPool() // Purpose: Implementation of CDataPool::~CDataPool() // Return: //////////////////////////////////////////////////////////////////////// CDataPool::~CDataPool() { //delete m_instance; QMutexLocker locker(&mutex); SFDelete(mh); qqByteArr.clear(); quit(); wait(); stopped = true; mark = 0; // TODO : implement } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::recv // Purpose: 接收数据并缓存 // Parameters: 1. buf:数据首址,2.len :长度 // Comment: 应用实例 // Return: -1:fail; >=0:success //////////////////////////////////////////////////////////////////////// bool CDataPool::recv(const QByteArray byte) { QMutexLocker locker(&mutex); // 锁定队列对象,禁止同时操作 if(qqByteArr.size() >CONTAINER_MAX_NUM){ // 当队列大于限定值时, qqByteArr.removeFirst(); } // QString byteStr = QString(byte.toHex()); // qDebug() << "放到队列里面的报文是:" << byteStr; qqByteArr.enqueue(byte); // 插入指针到队列尾部 queueWait.wakeOne(); // 唤醒睡着中的线程 return 1; } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::run() // Purpose: Implementation of CDataPool::run() // Comment: 线程 用于推送解析信号至显示模块 // Return: void //////////////////////////////////////////////////////////////////////// void CDataPool::run() { forever{ QMutexLocker locker(&mutex); if(qqByteArr.isEmpty()){ queueWait.wait(&mutex); }else{ handleData(); } } } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::init() // Purpose: Implementation of CDataPool::init() // Comment: 启动线程 // Return: void //////////////////////////////////////////////////////////////////////// void CDataPool::init() { if(!stopped){ start(); } } //////////////////////////////////////////////////////////////////////// // Name: CDataPool::setMark(int m) // Purpose: Implementation of CDataPool::setMark(int m) // Comment: 启动线程 // Return: void //////////////////////////////////////////////////////////////////////// void CDataPool::setMark(int m) { mark = m; }
CDataPool 是一个单例,同时使用线程在不停的执行报文解析,当有新的报文进来后,唤醒线程,否则线程休眠。
然后修改原来的接受部分程序:
void UdpClient::recv() { while(_socket->hasPendingDatagrams()) { QByteArray data; data.resize(_socket->pendingDatagramSize()); _socket->readDatagram(data.data(),(qint64)data.size()); // QString byteStr = QString(data.toHex()); // qDebug() << "此次接受的报文内容是:" << byteStr; cDataPool->recv(data); } }
改完以上的程序后,我满心以为,应该不会丢包了吧?使用100ms的发送次数,依然丢包,只是稍微好了一点点。但至少有些进步。大概1000包数据中至少会丢400包。伤心。
通过上述的实践说明,在报文接受过程中,报文解析逻辑比较复杂时,如果在同一线程处理,会对造成一定程度的丢包现象。但绝不是主要原因。
客户几个电话打过来催促解决问题的时间点。无奈继续没有注释阅读代码,突然发现了一个比较有趣的逻辑,很值得分享下。大家都知道到udp报文大于一定值时,将会被截包或者丢包。之前写这个项目的人写了以下的代码:
foreach (PICINFO info, selectedMoubleInfoList) { QString tempStr = info.module_userId + BACKSLASH + info.module_FactoryId + BACKSLASH + mnMap.value(info.module_userId); if(updUseData.size() + tempStr.size() < maxUpdlength){ updUseData += tempStr + COLON; }else{ updUseDataList.append(updUseData); updUseData = STR_EAMPTY; updUseData += tempStr + COLON; } selectedMoubleList.append(info.module_FactoryId); } if(updUseData.size() > 0){ updUseDataList.append(updUseData); } if(updUseDataList.size() > 0){ ui->progressBar->setRange(0,selectedMoubleInfoList.size()); //字符>1000的场合,分段截取 foreach (QString str, updUseDataList) { int index = str.lastIndexOf(COLON); QString sendStr = useCode + DOLLAR + QString::number(selectedMoubleInfoList.size())+ COMMA + doConnParamStr(paramMap) + str.left(index); udpServer->send(sendStr); }
上面的代码主要是将要发送的报文拼接成字符串时,按照1000的长度进行分割,分割完毕后在发送到socket中。想法是好的,但是问题就来了,他在一个for循环中发送报文,这个速度可不是100ms或者其他值,可能都是微秒级别的。
我很想将上面的代码改动下,但是发现几乎所有的类全部都关联这个里面,改动实在太大,所以我使用了一个临时解决方法在foreach循环中加上Sleep(100).
这个操作完成后,惊喜很大,在发送频率为150ms的时候,就从没有出现丢包现象,但是如果设置成100ms,基本还是会丢10包左右。
于是继续寻找可能出现的问题,基本上所有接受和发送的地方我都检查过,依然不知道为什么会丢包。后来打电话问了某研究所的博士,他对我说了一个可能的原因,就是如果两个软件部署在同一台机器上自发自收,可能会因为socket缓存池的满的问题造成上述丢包现象。于是将两个软件分开部署后,即使调整至50ms的发送频率也不会出现丢包现象。不过调整socket的缓存大小目前我还没有去实现,毕竟已经可以和客户说原因了。
这篇文章应该没有什么技术含量,但是却记录着一些解决问题的方法和思路,希望可以互相共享并且请大牛们指点不足。谢谢
相关文章推荐
- QT中UDPSocket丢包问题
- linux下使用udpsocket时遇到的问题
- QT中UDPSocket丢包问题(续)
- 使用eclipse编译qt程序遇到问题的解决方法
- Qt&QML使用过程中遇到的问题(还是声明篇)
- 使用QT遇到问题!
- SOCKET客户端与服务端长时间通信后,会连接不上服务端的问题,以及server端UDP丢包的问题
- Java使用Socket传输文件遇到的问题
- Qt下使用QtSql连接sqlite3所遇到的问题总结
- 使用designer-qt3设计界面,生成对应.h和.cpp文件时遇到的问题
- QT 5使用QNetworkAccessManager类遇到的问题
- VS2005中使用Qt4.8.5遇到的问题
- Qt 笔记(记录使用Qt中遇到的一些问题)
- QML使用WebView遇到的QSslSocket问题解决
- AS3使用SOCKET遇到的安全沙箱问题
- QT线程初次使用。遇到的问题。
- QT线程初次使用。遇到的问题。
- QT线程初次使用。遇到的问题。
- QT 实现屏幕共享,遇到udp掉包的问题
- Java使用Socket传输文件遇到的问题