一、選擇模型(select)

█ 選擇(select)模型是Winsock中最常見的 I/O模型。核心便是利用 select 函數,實現對 I/O的管理!利用 select 函數來判斷某Socket上是否有數據可讀,或者能否向一個套接字寫入數據,防止程序在Socket處於阻塞模式中時,在一次 I/O 調用(如send或recv、accept等)過程中,被迫進入“鎖定”狀態;同時防止在套接字處於非阻塞模式中時,產生WSAEWOULDBLOCK錯誤。

█ select 的函數原型如下:

int select(
  __in          int nfds,
  __in_out      fd_set* readfds,
  __in_out      fd_set* writefds,
  __in_out      fd_set* exceptfds,
  __in          const struct timeval* timeout
);


其中,第一個參數nfds會被忽略。之所以仍然要提供這個參數,只是爲了保持與Berkeley套接字兼容。
後面大家看到有三個 fd_set類型的參數:
一個用於檢查可讀性(readfds),
一個用於檢查可寫性(writefds),
一個用於例外數據(exceptfds)。


fd_set 結構的定義如下:
typedef struct fd_set { 
 u_int fd_count;
 SOCKET fd_array[FD_SETSIZE];
} fd_set;

#define FD_SETSIZE      64
所以 fd_set 結構中最多隻能監視64個套接字。


fdset 代表着一系列特定套接字的集合。其中, readfds 集合包括符合下述任何一個條件的套接字:
● 有數據可以讀入。
● 連接已經關閉、重設或中止。
● 假如已調用了listen,而且一個連接正在建立,那麼accept函數調用會成功。


writefds 集合包括符合下述任何一個條件的套接字:
● 有數據可以發出。
● 如果已完成了對一個非鎖定連接調用的處理,連接就會成功。


exceptfds 集合包括符合下述任何一個條件的套接字:
● 假如已完成了對一個非鎖定連接調用的處理,連接嘗試就會失敗。
● 有帶外(Out-of-band,OOB)數據可供讀取。


舉個例子,假設我們想測試一個套接字是否“可讀”,必須將自己的套接字增添到readfds集合中,
然後調用 select 函數並等待其完成。select 完成之後,再次判斷自己的套接字是否仍爲 readfds 集合的一部分。
若答案是肯定的,則表明該套接字“可讀”,可立即着手從它上面讀取數據。


在三個參數中(readfds、writefds 和 exceptfds),任何兩個都可以是空值( NULL);
但是,至少有一個不能爲空值!在任何不爲空的集合中,必須包含至少一個套接字句柄;
否則, select 函數便沒有任何東西可以等待。最後一個參數 timeout 對應的是一個指針,它指向一個timeval 結構,
用於決定select 最多等待 I/O操作完成多久的時間。如 timeout 是一個空指針,那麼 select 調用會無限
期地“鎖定”或停頓下去,直到至少有一個描述符符合指定的條件後結束。


對 timeval 結構的定義如下:
tv_sec 字段以秒爲單位指定等待時間;
tv_usec 字段則以毫秒爲單位指定等待時間。
1秒 = 1000毫秒


若將超時值設置爲(0 , 0),表明 select 會立即返回,出於對性能方面的考慮,應避免這樣的設置。


█ 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初始化成空集合。


例如,假定我們想知道是否可從一個套接字中安全地讀取數據,同時不會陷於無休止的
“鎖定”狀態,便可使用 FDSET 宏,將自己的套接字分配給 fdread 集合,再來調用 select。要
想檢測自己的套接字是否仍屬 fdread 集合的一部分,可使用 FD_ISSET 宏。採用下述步驟,便
可完成用 select 操作一個或多個套接字句柄的全過程:
1) 使用FDZERO宏,初始化一個fdset對象;
2) 使用FDSET宏,將套接字句柄加入到fdset集合中;
3) 調用 select 函數,等待其返回……select 完成後,會返回在所有 fdset 集合中設置的套接字句柄總數,
並對每個集合進行相應的更新。
4) 根據 select的返回值和 FDISSET宏,對 fdset 集合進行檢查。
5) 知道了每個集合中“待決”的 I/O操作之後,對 I/O進行處理,
然後返回步驟1 ),繼續進行 select 處理。


select 函數返回後,會修改 fdset 結構,刪除那些不存在待決 I/O 操作的套接字句柄。

這正是我們在上述的步驟 ( 4 ) 中,爲何要使用 FDISSET 宏來判斷一個特定的套接字是否仍在集合中的原因。



封裝select函數

BOOL SOCKET_Select(SOCKET hSocket, int nTimeOut, BOOL bRead)
{
	fd_set fdset;
	timeval tv;
	FD_ZERO(&fdset);
	FD_SET(hSocket, &fdset);
	nTimeOut = nTimeOut > 1000 ? 1000 : nTimeOut;
	tv.tv_sec  = 0;
	tv.tv_usec = nTimeOut;

	int iRet = 0;
	if ( bRead ) {
		iRet = select(0, &fdset, NULL , NULL, &tv);
	}else{
		iRet = select(0, NULL , &fdset, NULL, &tv);
	}

	if(iRet <= 0) {
		return FALSE;
	} else if (FD_ISSET(hSocket, &fdset)){
		return TRUE;
	}
	return FALSE;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章