之前描述了Qt中编写UDP收发程序的丢包问题,
见http://blog.csdn.net/rabbitjerry/article/details/72674458
后来终于得到了彻底解决,并且在Windows操作系统和Linux操作系统下均得到了验证。
一、解决思路
1.在程序中利用QThread类开辟一个用来接收UDP包的新线程;
2.在Windows操作系统下使用Windows封装的Socket,在Linux下使用Linux的Socket,摒弃了Qt的QSocket;
3.在新线程中使用while死循环,并采用Socket默认的阻塞模式接收数据;
4.为了避免维护多个程序,使用宏控制是使用Windows的Socket还是Linux的Socket,在不同的环境下更改宏定义后重新编译即可,便于使用和维护。
由此一来,不再丢包,且CPU占用率也较低(因为采用了阻塞模式)。
二、核心代码
1.宏定义和头文件的引用
#define _WIN_SOCKET_ 1
#define _QT_SOCKET_ 0
#define _LINUX_SOCKET_ 0
#if _WIN_SOCKET_ // for windows OS
#include
#include
#endif
#if _LINUX_SOCKET_ // for Linux OS
#include
#include
#include
#include
#include
#include
#include
#endif
2.头文件中相关代码
#if _QT_SOCKET_
private:
QUdpSocket * p_echo_socket;
#endif
#if _WIN_SOCKET_ // for windows OS
private:
WSADATA wsaData;
WORD sockVersion;
SOCKET echo_socket_WIN;
sockaddr_in addr_WIN;
sockaddr_in src_addr_WIN;
int src_addr_len = sizeof(src_addr_WIN);
#endif
#if _LINUX_SOCKET_
private:
int socket_len;
int socket_descriptor;
struct sockaddr_in echo_socket_LINUX;
#endif
3. 构造函数中与socket相关的内容
/************* socket **************/
#if _QT_SOCKET_
p_echo_socket = new QUdpSocket(this);
p_echo_socket->setSocketOption(QAbstractSocket::ReceiveBufferSizeSocketOption, 4*1024*1024);//设置缓存
if(!p_echo_socket->bind(DRY_ECHO_NET_PORT)) // 端口绑定
{
qDebug()<<"BIND failed for receiving echo port.";
}
#endif
#if _WIN_SOCKET_ // for windows OS
sockVersion = MAKEWORD(2,2);
if(WSAStartup(sockVersion, &wsaData) != 0)
{
printf("winsock initialization FAILED.");
}
echo_socket_WIN = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if(echo_socket_WIN == INVALID_SOCKET)
{
printf("winsocket error !");
}
addr_WIN.sin_family = AF_INET;
addr_WIN.sin_port = htons(DRY_ECHO_NET_PORT);
addr_WIN.sin_addr.S_un.S_addr = INADDR_ANY;
if(bind(echo_socket_WIN, (sockaddr *)&addr_WIN, sizeof(addr_WIN)) == SOCKET_ERROR)
{
printf("bind error !");
closesocket(echo_socket_WIN);
}
// set socket buffer size
int optVal = 0;
int optLen = sizeof(optVal);
optVal = 4*1024*1024;
setsockopt(echo_socket_WIN, SOL_SOCKET, SO_RCVBUF, (char*)&optVal, optLen);
#endif
#if _LINUX_SOCKET_
bzero(&echo_socket_LINUX,sizeof(echo_socket_LINUX));
echo_socket_LINUX.sin_family=AF_INET;
echo_socket_LINUX.sin_addr.s_addr=htonl(INADDR_ANY);
echo_socket_LINUX.sin_port=htons(DRY_ECHO_NET_PORT);
socket_len=sizeof(echo_socket_LINUX);
socket_descriptor=socket(AF_INET,SOCK_DGRAM,0);
bind(socket_descriptor,(struct sockaddr *)&echo_socket_LINUX,sizeof(echo_socket_LINUX));
int buffer_size_LINUX=0;
socklen_t optlen_LINUX;
optlen_LINUX = sizeof(buffer_size_LINUX);
getsockopt(socket_descriptor,SOL_SOCKET,SO_RCVBUF,&buffer_size_LINUX,&optlen_LINUX);
buffer_size_LINUX = 4*1024*1024;
if(setsockopt(socket_descriptor, SOL_SOCKET, SO_RCVBUF, &buffer_size_LINUX, optlen_LINUX) < 0)
{
qDebug()<<"set recv buffer size FAILED.";
}
getsockopt(socket_descriptor,SOL_SOCKET,SO_RCVBUF,&buffer_size_LINUX,&optlen_LINUX);
#endif
while(1)
{
net_pack_size = 0;
#if _QT_SOCKET_
if( p_echo_socket->hasPendingDatagrams()) // 有数据
{
net_pack_size = p_echo_socket->pendingDatagramSize();
p_echo_socket->readDatagram((char*)p_echo_net_pack,net_pack_size);
}
#endif
#if _WIN_SOCKET_
net_pack_size = recvfrom(echo_socket_WIN, (char*)p_echo_net_pack, 1600, 0, (sockaddr *)&src_addr_WIN, &src_addr_len);
#endif
#if _LINUX_SOCKET_
net_pack_size = recvfrom(socket_descriptor,(char*)p_echo_net_pack,1600,0,(struct sockaddr *)&echo_socket_LINUX,(socklen_t*)&socket_len);
#endif
...
1.pro文件中在Windows操作系统下要添加如下库,但在Linux系统下则要注释掉该行代码
LIBS+=-lpthreadlibwsock32libws2_32#forwindowsOS
2.Ubuntu操作系统下,设置缓存大小的上限受到操作系统中某个文件的限制,此时需要手动修改默认的接收缓存最大值:
打开/proc/sys/net/core/rmem_max:改为4194304
Ubuntu 16.0默认是212992
[20170601]