1. 程式人生 > >C++ Windows非阻塞UDP通訊原始碼

C++ Windows非阻塞UDP通訊原始碼

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