select函數決定一個或者多個套接字(socket)的狀態,如果需要的話,等待執行異步I/O。
int select(
__in int nfds,
__inout fd_set *readfds,
__inout fd_set *writefds,
__inout fd_set *exceptfds,
__int const struct timeval *timeout
);
參數:
nfds:忽略。
readnfds: 指向檢查可讀性的套接字集合的可選的指針。
writefds: 指向檢查可寫性的套接字集合的可選的指針。
exceptfds: 指向檢查錯誤的套接字集合的可選的指針。
timeout: select函數需要等待的最長時間,需要以TIMEVAL結構體格式提供此參數,對於阻塞操作,此參數為null。
返回值:
select函數返回那些准備好並且包含在fd_set結構體的套接字的總數,如果超時,則返回0;如果錯誤發生,返回SOCKET_ERROR。如果返回值為SOCKET_ERROR,可以通過WSAGetLastError函數檢索指定的錯誤碼。
| 錯誤碼 |
解釋 |
| WSANOTINITIALISTED |
在使用此函數之前,WSAStartup函數必須成功的執行 |
| WSAEFALUT |
套接字執行時不能分配需要的資源或者readfds、writefds、exceptfds、timeval參數不是用戶地址空間的一部分。 |
| WSAENETDOWN |
網絡子系統失敗 |
| WSAEINVAL |
超時值不合法的,或者其他的三個參數為空。 |
| WSAEINTR |
阻塞的套接字1.1調用通過WSACancelBlockingCall取消 |
| WSAEINPROGRESS |
阻塞的套接字1.1調用正在處理或者服務提供者正在處理一個掉用戶函數。 |
| WSAENOTSOCK |
描述集中包括一個不是套接字的入口。 |
說明
select函數用於決定一個或者多個套接字的狀態。對於每一個套接字,調用者可以請求讀、寫或者錯誤狀態信息。一個請求給定狀態的套接字集由fd_set結構體指定。在fd_set結構體中的套接字必須和單個服務提供者聯系在一起。基於此,如果WSAPROTOCOL_INFO結構體中有相同的providerId值,套接字被認為來自同一個服務提供者。直到返回,結構體更新去反映滿足指定條件套接字子集。select函數返回滿足條件的套接字個數。fd_set集合可以通過一些宏手動操作。這些宏也適合伯克利套接字,但是它們的機理是根本不同的。
參數readfds指示檢查套接字的可讀性。當套接字在listen狀態,如果已經接收一個連接請求,這個套接字會被標記為可讀,例如一個accept會確保不會阻塞的完成。對於其他的套接字,可讀性意味着隊列中的數據適合讀,當調用recv,WSARecv,WSARecvFrom或者recvfrom后不會阻塞。
對於面向連接的套接字,可讀性也可以指示關閉套接字的從另一端接收的請求。如果虛電路正常關閉,並且所有的數據都已經接收,然后recv會立刻返回(沒有數據接收),如果虛電路重置,recv會立刻返回錯誤碼,例如WSAECONNRESET。如果套接字選項SO_OOBINLINE置位(參見setsockop),出現的OOB數據將會被檢查。
參數writefds指示檢查套接字的可寫性。如果套接字處理connect調用(非阻塞的),並且完全建立連接,這時套接字是可寫。如果套接字沒有處理connect調用,可寫性意味着擔保send,sendto或者WSASendto執行成功。但是,如果len參數超過系統的緩存空間大小,它們在阻塞套接字中是可以阻塞的。不確定多長的長度是合法的,尤其在多線程環境下。
參數exceptfds指示套接字被檢查OOB數據出現或者異常錯誤環境。
注意:OOB數據僅僅應用當SO_OOBINLINE設置為FALSE的情況下。如果一個套接字處理連接調用(非阻塞模式),試圖連接的錯誤信息在exceptfds中,這個文檔並沒有定義那些錯誤需要包含其中。
readfd,writefds或者exceptfds中任何兩個參數在調用的時候需要為null。至少一個必須為非空,並且任何一個非空描述設置必須包括至少一個套接字句柄。
總之,一個套接字將會被指定在一個特殊的集合當select返回如果:
readfds:
① 如果listen函數已經調用並且連接掛起,accept會執行成功。
② 數據適合讀(如果SO_OOBINLINE置位,包括OOB數據)
③ 連接被關/重置/終止
writefds:
① 如果處理一個connect調用(非阻塞),連接成功。
② 數據可以發送。
exceptfds:
① 如果處理一個connect調用(非阻塞),連接失敗。
② OOB數據適合讀(僅當SO_OOBINLINE未置位)
在頭文件Winsock2.h中定義四個宏來操作和檢查描述集。FD_SETSIZE決定在描述集合中最大數量(FD_SETSIZE的默認值為64,此值可以在導入Winsock2.h之前通過FD_SETSIZE修改)。
使用這些宏是為了在不同的套接字環境中維護軟件便利。這些宏操作和檢查fd_set內容為:
FD_CLR(s, *set)
從set集合中移除描述符s
FD_ISSET(s, *set)
如果s在set中,返回非0,否則返回0
FD_SET(s, *set)
增加描述符s到set中
FD_ZERO(*set)
初始化set集合為null集合
#include<WinSock2.h> #include<stdio.h> #include<Windows.h> #include<string> #include<iostream> #include<thread> #include<exception> #include<future> #include<vector> using namespace std; #pragma comment(lib,"WS2_32.lib")//顯示連接套接字庫 #define _WINSOCK_DEPRECATED_NO_WARNINGS #define SIZE 5 FILE * ffp; struct fd_set rfds; struct sockaddr_in ipadd; struct timeval timeout = { 0, 200 }; char * readbuff[10] = { 0 }; string ss; vector<SOCKET> v; char sztext[1024] = { 0 }; char sztext1[1024] = { 0 }; SOCKET s; int n; sockaddr_in addr, addr2; int ret; void Close() { ::closesocket(s); if (!v.empty()) { v.clear(); } ::WSACleanup(); } void Initialize() { WSADATA data; WORD w = MAKEWORD(2, 0);//版本號 //strcpy(sztext, lastSend.c_str()); ::WSAStartup(w, &data); //動態鏈接庫初始化 s = ::socket(AF_INET, SOCK_STREAM, 0); n = sizeof(addr2); addr.sin_family = AF_INET; addr.sin_port = htons(75); addr.sin_addr.S_un.S_addr = INADDR_ANY; ::bind(s, (sockaddr*)&addr, sizeof(addr)); ::listen(s, SIZE); printf("服務器已經啟動\n"); } void MyRecv() { try{ while (true) { for (int i = 0; i < v.size(); ++i) { FD_ZERO(&rfds); /* 清空集合 */ FD_SET(v.at(i), &rfds); /* 將fp添加到集合,后面的FD_ISSET和FD_SET沒有必然關系,這里是添加檢測 */ switch (select(0, &rfds, NULL, NULL, &timeout)) //select使用 { case -1: v.erase(v.begin() + i); printf("客服端斷開\n"); break; //select錯誤退出程序 case 0: continue; //再次輪詢 default: if (FD_ISSET(v.at(i), &rfds)) //測試sock是否可讀即是否網絡上有數據 { if (::recv(v.at(i), sztext1, sizeof(sztext1), 0) != -1) { printf("%s\r\n", sztext1); } else { v.erase(v.begin() + i); printf("客服端斷開\n"); continue; } ss = "有"; ss += sztext1; strcpy(sztext, ss.c_str()); for (int j = 0; j < v.size(); ++j) { ::send(v.at(j), sztext, sizeof(sztext), 0); } } } } } } catch (const exception& e) { cerr << "出錯了" << endl; Close(); } return; } int main() { SOCKET s1; Initialize(); auto w = async(launch::async, [&]{ while (true) { if (v.size() < SIZE) { for (int i = v.size(); i < SIZE; ++i) { s1 = ::accept(s, (sockaddr*)&addr2, &n); if (s1 != NULL) { v.push_back(s1); printf("%s已經連接上\r\n", inet_ntoa(addr2.sin_addr)); } } printf("服務器接收額已滿!\n"); } } return; }); try{ thread t1(MyRecv); t1.join(); } catch (const exception& e) { cerr << "出錯了" << endl; } Close(); system("pause"); return 0; }
參數time-out控制select函數完成的時間(超過這個時間返回超時)。如果time-out是個空指針,select會一直保持阻塞指導至少一個描述符符合指定的准則。否則,time-out指向一個TIMEVAL結構體,這個結構體指定select在返回之前應該等待最大時間。當select返回,TIMEVAL結構體中的內容是不會改變的。如果TIMEVAL初始化為{0,0},select會立刻返回;這用於得到選擇的套接字的狀態。如果select立刻返回,然后select調用認為是非阻塞的,此時非阻塞調用的標准假設適用。例如,阻塞鈎子不會調用,窗體套接字不會退出。
