1.傳統阻塞的Socket中存在的問題
-
每個Socket連接配接在Recv過程中,存在無資料一直阻塞問題;
(當然,這個可以通過ioctlsocket()函數修改Socket屬性為非阻塞,但會産生WSAEWOULDBLOCK錯誤。)
-
多個Socket管理不友善問題,不能在一個線程中同時監視多個Socket;
(注意,我們可以通過為每個Socket都建立一個線程的辦法實作同步監視(即一Socket一線程的方法),但這樣過于消耗系統資源。)
基于傳統的Socket中存在的問題,我們就要就要設計出相應的解決方案。在Windows下,主要有以下5種I/O模型:
- Select模型;
- 異步選擇(WSAAsyncSelect)模型;
- 事件選擇(WSAEventSelect)模型;
- 重疊I/O(Overlapped I/O);
- 完成端口(Completion Port);
本文主要介紹最基本的Select模型。
2.Select模型設計思路
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsIiM3YTN0IzMwITMxATM1EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
Select模型是Winsock中最常見的 I/O模型。核心便是利用 select 函數,實作對 I/O的管理。
每次調用Select之前,需将需要監聽的Socket放入一個fd_set結構中,利用 select 函數來判斷某Socket上是否有資料可讀/可寫,其(Select)内部通過輪詢的方式,實作了同時等待多個套接字,當某個或者多個套接字滿足可讀寫條件時,Select函數傳回。這樣,同時防止了在套接字處于非阻塞模式中時,産生WSAEWOULDBLOCK錯誤。
//fd_set 結構的定義如下:
typedef struct fd_set
{
u_int fd_count;
SOCKET fd_array[FD_SETSIZE]; // 其實是将Socket放入數組中
} fd_set;
// Select模型實作 - 核心代碼如下:
//(因與Linux下的Select相似,此處直接借用Linux下的C代碼)
// C語言代碼實作
while ()
{
// 定義fd_set集合
fd_set client;
FD_ZERO(&client);
/* fill client 将所有需要監視的Socket填充進fd_set集合(内含Socket數組)
* 變量說明:client_count為client_set中所包含的Socket數量。
* 變量說明:client_set數組中是用戶端已連接配接的Socket。
*/
for (i = ; i < *(p->client_count);++i)
{
FD_SET(p->client_set[i] , &client);
}
/* call select
* 如果有Socket發生了事件,就将那些沒有發生事件的Socket從fd_set集合中清除 (注意這裡是把集合的位址傳入的 &client)
*
* Q:Select内部是如何實作這種監視的?是通過Socket主動發送信号相應信号麼?還是輪詢?
* 這裡留了這麼一個疑問,如果哪位夥伴知道,請在下方留言 ~~ 感激 ~~
*/
ret = select(get_max(p->client_set , *(p->client_count)) + , &client , NULL , NULL , &time_val);
if ( SOCKET_ERROR == ret)
{
if (errno == EINVAL)
{
usleep();
continue;
}
printf("select fail![%s]\n" , strerror(errno));
break;
}
// check 檢視傳回的集合中,有哪些Socket還在,就說明有關于ta的事件發生。
for (i = ; i < *(p->client_count); ++i)
{
if (FD_ISSET(p->client_set[i] , &client) > )
{
// recv
ret = recv(p->client_set[i] , buf , sizeof(buf) , );
if (DEF_STD_ERROR == ret || == ret)
{
// TODO:clear current client
continue;
}
printf("recv data:%s\n" , buf);
}
}
// 處理完畢,下次循環将會重新填充fd_set集合,并再次輪詢。
}
select 函數傳回值:
select 成功完成後,會在 fdset 結構中,傳回剛好有未完成的 I/O操作的所有套接字句柄的總量。
若超過 timeval 設定的時間,便會傳回0。若 select 調用失敗,都會傳回 SOCKET_ERROR,應該調用 WSAGetLastError 擷取錯誤碼!
用 select 對套接字進行監視之前,必須将套接字句柄配置設定給一個fdset的結構集合,之後再來調用 select,便可知道一個套接字上是否正在發生上述的 I/O 活動。
Winsock 提供了下列宏操作,可用來針對 I/O活動,對 fdset 進行處理與檢查:
● FD_CLR(s, *set):從set中删除套接字s。
● FD_ISSET(s, *set):檢查s是否set集合的一名成員;如答案是肯定的是,則傳回TRUE。
● FD_SET(s, *set):将套接字s加入集合set。
● FD_ZERO( * set):将set初始化成空集合。
3.select模式的優勢和不足
優勢:
1.可以同時對多個建立起來的套接字進行有序的管理。
不足:
1.每次調用Select之前都需要将所有Socket放入fd_set結構集合中,這樣每次都需要周遊一次數組,但可能每次這個數組都沒有變,或隻有少部分變化;
2.Select函數傳回時,并不知道是哪個Socket發生了事件,是以需要再次周遊Socket數組,找到發生事件的Socket,這個過程中會對沒有發生事件的Socket也進行判斷,而這個判斷是無效的;
(是以,一次Select過程需要輪詢周遊兩次Socket數組,這樣大大降低了CPU的有效效率。)
PS:以上内容如有不足,請多多指正! –netosoul