1. 程式人生 > >socket通訊中select函式的使用和詳解

socket通訊中select函式的使用和詳解

---------------------------面向連線
     #include <winsock.h>
     #include <stdio.h>
     #define PORT       5150
     #define MSGSIZE     1024
     #pragma comment(lib, "ws2_32.lib")
     int     g_iTotalConn = 0;
     SOCKET g_CliSocketArr[FD_SETSIZE];
     DWORD WINAPI WorkerThread(LPVOID lpParameter);
     int main()
     {  
         WSADATA     wsaData;  
         SOCKET       sListen, sClient;  
         SOCKADDR_IN local, client;  
         int         iaddrSize = sizeof(SOCKADDR_IN);  
         DWORD       dwThreadId;  
         // Initialize Windows socket library  
         WSAStartup(0x0202, &wsaData);  
         // Create listening socket  
         sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);  
         // Bind          
         local.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
         local.sin_family = AF_INET;
         local.sin_port = htons(PORT);  
         bind(sListen, (struct sockaddr *)&local, sizeof(SOCKADDR_IN));  
         // Listen   listen(sListen, 3);  
         // Create worker thread  
         CreateThread(NULL, 0, WorkerThread, NULL, 0, &dwThreadId);    
         while (TRUE)  
         {               // Accept a connection    
             sClient = accept(sListen, (struct sockaddr *)&client, &iaddrSize);    
             printf("Accepted client:%s:%d\n", inet_ntoa(client.sin_addr), ntohs(client.sin_port));    
             // Add socket to g_CliSocketArr    
             g_CliSocketArr[g_iTotalConn++] = sClient;  
         }    
         return 0;
     }
     DWORD WINAPI WorkerThread(LPVOID lpParam)
     {  
         int             i;  
         fd_set         fdread;  
         int             ret;  
         struct timeval tv = {1, 0};  
         char           szMessage[MSGSIZE];    
         while (TRUE)  
         {    
             FD_ZERO(&fdread);    
             for (i = 0; i < g_iTotalConn; i++)
             {
                 FD_SET(g_CliSocketArr, &fdread);
             }                     // We only care read event
             ret = select(0, &fdread, NULL, NULL, &tv);
             if (ret == 0)
             {       // Time expired
                 continue;
             }
             for (i = 0; i < g_iTotalConn; i++)
             {
                 if (FD_ISSET(g_CliSocketArr, &fdread))
                   {         // A read event happened on g_CliSocketArr
                       ret = recv(g_CliSocketArr, szMessage, MSGSIZE, 0);
                       if (ret == 0 || (ret == SOCKET_ERROR && WSAGetLastError() == WSAECONNRESET))
                         {
                             // Client socket closed          
                             printf("Client socket %d closed.\n", g_CliSocketArr);
                             closesocket(g_CliSocketArr);
                             if (i < g_iTotalConn - 1)
                             {
                                 g_CliSocketArr[i--] = g_CliSocketArr[--g_iTotalConn];
                             }
                         }
                         else
                         {
                               // We received a message from client
                               szMessage[ret] = '\0';
                               send(g_CliSocketArr, szMessage, strlen(szMessage), 0);
                         }
                   } //if
             }//for
         }//while    
         return 0;
     }
     伺服器的幾個主要動作如下:
     1.建立監聽套接字,繫結,監聽;
     2.建立工作者執行緒;
     3.建立一個套接字陣列,用來存放當前所有活動的客戶端套接字,每accept一個連線就更新一次陣列;
     4.接受客戶端的連線。
     這裡有一點需要注意的,就是我沒有重新定義FD_SETSIZE巨集,所以伺服器最多支援的併發連線數為64。而且,這裡決不能無條件的ccept,伺服器應該根據當前的連線數來決定
是否接受來自某個客戶端的連線。一種比較好的實現方案就是採用WSAAccept函式,而且讓WSAAccept回撥自己實現的Condition Function。
     如下所示:
     int CALLBACK ConditionFunc(LPWSABUF lpCallerId,LPWSABUF lpCallerData, LPQOS lpSQOS,LPQOS lpGQOS,LPWSABUF lpCalleeId, LPWSABUF lpCalleeData,GROUP FAR *
g,DWORD dwCallbackData)
     {
         if (當前連線數 < FD_SETSIZE)
             return CF_ACCEPT;
         else  
             return CF_REJECT;
     }
     工作者執行緒裡面是一個死迴圈,一次迴圈完成的動作是:
     1.將當前所有的客戶端套接字加入到讀集fdread中;
     2.呼叫select函式;
     3.檢視某個套接字是否仍然處於讀集中,如果是,則接收資料。如果接收的資料長度為0,或者發生WSAECONNRESET錯誤,則表示客戶端套接字主動關閉,這時需要將伺服器中
對應的套接字所繫結的資源釋放掉,然後調整我們的套接字陣列(將陣列中最後一個套接字挪到當前的位置上)。
     除了需要有條件接受客戶端的連線外,還需要在連線數為0的情形下做特殊處理,因為如果讀集中沒有任何套接字,select函式會立刻返回,這將導致工作者執行緒成為一個毫無
停頓的死迴圈,CPU的佔用率馬上達到100%。
     關係到套接字列表的操作都需要使用迴圈,在輪詢的時候,需要遍歷一次,再新的一輪開始時,將列表加入佇列又需要遍歷一次.也就是說,Select在工作一次時,需要至少遍歷2次
列表,這是它效率較低的原因之一.
     在大規模的網路連線方面,還是推薦使用IOCP或EPOLL模型.但是Select模型可以使用在諸如對戰類遊戲上,比如類似星際這種,因為它小巧易於實現,且對戰類遊戲的網路連線量
並不大. 對於Select模型想要突破Windows 64個限制的話,可以採取分段輪詢,一次輪詢64個.例如套接字列表為128個,在第一次輪詢時,將前64個放入佇列中用Select進行狀態查詢,
待本次操作全部結束後.將後64個再加入輪詢佇列中進行輪詢處理.這樣處理需要在非阻塞式下工作.以此類推,Select也能支援無限多個.