一、select模型是什么
select模型是Windows sockets中最常見的IO模型。它利用select函數實現IO 管理。通過對select函數的調用,應用程序可以判斷套接字是否存在數據、能否向該套接字寫入據。
二、為什么要使用select模型?
解決基本C/S模型中,accept()、recv()、send()阻塞的問題,以及C/S模型需要創建大量線程,客戶端過多就會增加服務器運行壓力
三、select模型與C/S模型的不同點
• C/S模型中accept()會阻塞一直傻等socket來鏈接
• select模型只解決accept()傻等的問題,不解決recv(),send()執行阻塞問題
其實select模型解決了實現多個客戶端鏈接,與多個客戶端分別通信
兩個模型都存在recv(),send()執行阻塞問題
• 由於服務器端,客戶端不需要(客戶端只有一個socket,可以通過加線
程解決同時recv和send)
select函數決定一個或者多個套接字(socket)的狀態,如果需要的話,等待執行異步I/O。
四、select模型的API函數
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函數檢索指定的錯誤碼。
五、代碼示例
select模型下的客戶端服務器簡單收發數據通信:
客戶端:
int main() { // 1) 創建socket SOCKET sockServer = socket( AF_INET, SOCK_STREAM, //流式 IPPROTO_TCP);//tcp協議 // 2) 綁定端口 sockaddr_in siServer; siServer.sin_family = AF_INET; siServer.sin_port = htons(9527); siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&siServer, sizeof(siServer)); if (nRet == SOCKET_ERROR) { printf("綁定失敗 \r\n"); return 0; } // 3) 監聽 nRet = listen(sockServer, SOMAXCONN); if (nRet == SOCKET_ERROR) { printf("監聽失敗 \r\n"); return 0; } //創建線程, 檢測socket是否有數據可讀, 並且處理 vector<SOCKET> vctClients; HANDLE hTread = CreateThread(NULL, 0, HandleClientsThread, (LPVOID)&vctClients, 0, NULL); CloseHandle(hTread); while (true) { // 4) 接受連接 sockaddr_in siClient; int nSize = sizeof(siClient); SOCKET sockClient = accept(sockServer, (sockaddr*)&siClient, &nSize); if (sockClient == SOCKET_ERROR) { printf("接受連接失敗 \r\n"); return 0; } printf("IP:%s port:%d 連接到服務器. \r\n", inet_ntoa(siClient.sin_addr), ntohs(siClient.sin_port)); g_lock.Lock(); //加上線程鎖 vctClients.push_back(sockClient); g_lock.UnLock(); }
return 0;
}
服務器:
bool HandleData(SOCKET sockClient) { // 5) 收發數據 char aryBuff[MAXWORD] = { 0 }; int nRet = recv(sockClient, aryBuff, sizeof(aryBuff), 0); if (nRet == 0 || nRet == SOCKET_ERROR) { printf("接受數據失敗 \r\n"); return false; } printf("收到數據: %s \r\n", aryBuff); char szBuff[] = { "recv OK " }; nRet = send(sockClient, szBuff, sizeof(szBuff), 0); if (nRet == SOCKET_ERROR) { printf("數據發送失敗 \r\n"); return false; } return true; } //線程, 用來處理客戶端, 和客戶端進行數據的收發 DWORD WINAPI HandleClientsThread(LPVOID pParam) { vector<SOCKET>& vctClients = *(vector<SOCKET>*)pParam; while (TRUE) { fd_set fdRead; FD_ZERO(&fdRead); //初始化 //將所有的socket放入數組 g_lock.Lock(); for (auto sock:vctClients) { FD_SET(sock, &fdRead); } g_lock.UnLock(); //檢測指定的socket timeval tv = {1, }; int nRet = select(fdRead.fd_count, &fdRead, NULL, NULL, &tv); if (nRet == 0 || nRet == SOCKET_ERROR) { continue; } //處理數據 g_lock.Lock(); for (auto itr = vctClients.begin(); itr != vctClients.end(); ++itr) { //判斷socket是否是可以讀數據了 if (FD_ISSET(*itr, &fdRead)) { if (!HandleData(*itr)) { //連接斷開 vctClients.erase(itr); break; } } } g_lock.UnLock(); } return 0; }