C++ Windows非阻塞UDP通信源码

时间:2022-09-09 11:55:49

UDP通信中,recvfrom或recv等函数默认都是阻塞方式进行的,即如果没有收到消息,那么程序会一直卡在recv()这个函数这里,使得该线程不能进行后续的操作。但有时候我们需要该线程在有UDP数据发送过来的时候才进行数据接收,而在其他时间该线程还有别的任务进行处理,那么我们就需要将Sokcet设置为非阻塞通信的方式。

非阻塞通信中,需要用到select()函数,select函数用于在非阻塞中,当一个套接字或一组套接字有信号时通知你,系统提供select函数来实现多路复用输入/输出模型,原型:

int select(int maxfdp, fd_set *readfds, fd_set *writefds, fd_set *errorfds, struct timeval *timeout);

其中:

  • maxfd没有太大意义,一般是给赋值为最大描述符个数+1,只是起到兼容作用,大多数情况下会被系统忽略;
  • struct fd_set可以理解为一个集合,这个集合中存放的是文件描述符(file descriptor),即文件句柄,这可以是我们所说的普通意义的文件,当然Unix下任何设备、管道、FIFO等都是文件形式,全部包括在内,所以毫 无疑问一个socket就是一个文件,socket句柄就是一个文件描述符。fd_set集合可以通过一些宏由人为来操作,比如清空集合 FD_ZERO(fd_set * ),将一个给定的文件描述符加入集合之中FD_SET(int ,fd_set * ),将一个给定的文件描述符从集合中删除FD_CLR(int ,fd_set* ),检查集合中指定的文件描述符是否可以读写FD_ISSET(int ,fd_set* )。
  • 在select函数中,三个fd_set分别用于检查该文件句柄是否可读/是否可写/是否有文件错误异常。返回值大于0表示可操作,返回值等于0表示超时,返回值小于0表示有错误异常
  • struct timeval是一个大家常用的结构,用来代表时间值,有两个成员,一个是秒数,另一个是毫秒数。

下面给出一个非阻塞UDP通信的实例:

#include <WinSock2.h>
#pragma comment(lib, "WS2_32")
#include <Windows.h>
#include <iostream>
#include <string>

#define Port 5500
#define CACHE_LENGTH 1024
using namespace std;

// 非阻塞UDP通信
/************************************************************************/
/* 初始化Socket */
/************************************************************************/
bool InitWinsock()
{
    int Error;
    WORD VersionRequested;
    WSADATA WsaData;
    VersionRequested=MAKEWORD(2,2);
    Error=WSAStartup(VersionRequested,&WsaData); //启动WinSock2
    if(Error!=0)
    {
        return FALSE;
    }
    else
    {
        if(LOBYTE(WsaData.wVersion)!=2||HIBYTE(WsaData.wHighVersion)!=2)
        {
            WSACleanup();
            return FALSE;
        }
    }
    return TRUE;
}

int main()
{
    fd_set rfd;                     // 描述符集 这个将用来测试有没有一个可用的连接
    struct timeval timeout;
    timeout.tv_sec=0;               //等下select用到这个
    timeout.tv_usec=0;              //timeout设置为0,可以理解为非阻塞
    char UDP_buffer[CACHE_LENGTH];
    int rev=0;
    int SelectRcv;

#pragma region Windows Socket start
    // Windows Socket start:
    BasicFunction::InitWinsock();
    SOCKET sockListen;
    sockListen=socket(AF_INET,SOCK_DGRAM,0);
    if (sockListen == -1)
    {
        cout<<"Socket Fail"<<endl;
        return 0;
    }
    int recvbuf = 1;
    setsockopt(sockListen,SOL_SOCKET,SO_RCVBUF,(char*)&recvbuf,sizeof(int));
    //设置为非阻塞模式 
    int imode=1;  
    rev=ioctlsocket(sockListen,FIONBIO,(u_long *)&imode);  
    if(rev == SOCKET_ERROR)  
    {  
        printf("ioctlsocket failed!");  
        closesocket(sockListen);  
        WSACleanup();  
        return -1;  
    }  
    struct sockaddr_in serverPCaddr;
    memset(&serverPCaddr,0,sizeof(sockaddr_in));
    serverPCaddr.sin_family=AF_INET;
    serverPCaddr.sin_port=htons(Port);             ///监听端口
    serverPCaddr.sin_addr.s_addr=INADDR_ANY; //inet_addr("192.168.0.11"); ///本机
    if(0!= ::bind(sockListen,(struct sockaddr*)&serverPCaddr,sizeof(struct sockaddr)))
    {
        cout<<"recv_bind()失败,error: "<<GetLastError()<<endl;
        closesocket(sockListen);
        WSACleanup();
        return 0;
    }
    int fromlen = sizeof(struct sockaddr_in);
#pragma endregion Windows Socket start

    while(1)
    {
        // UDP数据接收
        FD_ZERO(&rfd);           //总是这样先清空一个描述符集
        FD_SET(sockListen,&rfd); //把sock放入要测试的描述符集
        SelectRcv = select(sockListen+1,&rfd,0,0, &timeout); //检查该套接字是否可读
        if(SelectRcv<0) 
            cout<<"监听失败"<<GetLastError()<<endl;
// if(SelectRcv==0) 
// {
// //返回值为0,表示超时,
// }
        if (SelectRcv > 0)
        {
            memset(UDP_buffer,'\0',(CACHE_LENGTH)*sizeof(char));
            rev=0;
            rev=recvfrom(sockListen,UDP_buffer,(CACHE_LENGTH)*sizeof(char),0,(struct sockaddr*)&serverPCaddr,&fromlen);
            if (rev!=SOCKET_ERROR)
            {
            //数据接收成功,下面为对UDP接收的数据的处理
                string udpstr(UDP_buffer);
                cout<<udpstr<<endl;
            }
        }
    //如果select()函数返回-1,表示无可以接收/写入的数据,那么程序就会执行下面的代码
    // .........
    // 代码段
    // .........
    }

return 0;
}