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;
}