由於socket recv()方法是堵塞式的,當多個客戶端連接服務器時,其中一個socket的recv調用時,會產生堵塞,使其他連接不能繼續。
如果想改變這種一直等下去的焦急狀態,可以多線程來實現(不再等待,同時去recv,同時阻塞,呵呵),每個socket連接使用一個線程,這樣效率十分低下,根本不可能應對負荷較大的情況(是啊,佔用各種資源,電腦啊,你耗不起)。
這時候我們便可以採取select模型。select允許進程指示內核等待多個事件中的任何一個發生,並僅在有一個或多個時間發生或經歷一段指定時間後才喚醒它。select告訴內核對哪些描述子感興趣以及等待多長時間。這就是所謂的非阻塞模型,就是進程或線程執行此函數時不必非要等待事件的發生,一旦執行肯定返回,以返回值的不同來反映函數的執行情況,如果事件發生則與阻塞方式相同,若事件沒有發生則返回一個代碼來告知事件未發生,而進程或線程繼續執行,所以效率較高。
select的函數原型:
int select ( int nfds,
//Winsock中此參數無意義 fd_set*
readfds, //進行可讀檢測的Socket fd_set*
writefds, //進行可寫檢測的Socket fd_set*
exceptfds, //進行異常檢測的Socket const struct timeval*
timeout //非阻塞模式中設置最大等待時間 ) |
/*參數列表int maxfdp是一個整數值,是指集合中所有文件描述符的範圍,即所有文件描述符的最大值加1,不能錯!在Windows中這個參數的值無所謂,可以設置不正確。 fd_set *readfds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的讀變化的,即我們關心是否可以從這些文件中讀取數據了,如果這個集合中有一個文件可讀,select就會返回一個大於0的值,表示有文件可讀,如果沒有可讀的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的讀變化。 fd_set *writefds是指向fd_set結構的指針,這個集合中應該包括文件描述符,我們是要監視這些文件描述符的寫變化的,即我們關心是否可以向這些文件中寫入數據了,如果這個集合中有一個文件可寫,select就會返回一個大於0的值,表示有文件可寫,如果沒有可寫的文件,則根據timeout參數再判斷是否超時,若超出timeout的時間,select返回0,若發生錯誤返回負值。可以傳入NULL值,表示不關心任何文件的寫變化。 fd_set *errorfds同上面兩個參數的意圖,用來監視文件錯誤異常。 struct timeval* timeout是select的超時時間,這個參數至關重要,它可以使select處於三種狀態:第一,若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;第二,若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;第三,timeout的值大於0,這就是等待的超時時間,即 select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,返回值同上述。*/
/*返回值:
負值:select錯誤正值:某些文件可讀寫或出錯0:等待超時,沒有可讀寫或錯誤的文件*/第二種解釋版本:
參數解釋:
參數1 nfds:這個參數是一個被忽略的參數,我們在程序中傳入0即可,其目的是與伯克利套接字兼容。
參數2 readfds:可讀性監視集合,可讀性指有連接到來、有數據到來、連接已關閉、重置或終止。
參數3 writefds:可寫性監視集合,可寫性指數據可以發送、連接可以成功。
參數4 exceptfds:例外性監視集合,例外性指連接會失敗、外帶數據到來
參數4 timeout:該參數指定select會等待的時間,者結構很簡單,要是有興趣可以看看msdn
注意:select參數中的三個監視集合至少有一個不能爲空,任何其它兩個都可爲空
fd_set的結構,這個結構是用來裝SOCKET的,把要監視SOCKET傳給這個結構,然後同select函數進行監視。
struct fd_set
{ u_int
fd_count; //
how many are SET? SOCKET
fd_array[FD_SETSIZE]; //
an array of SOCKETs }
; |
和select模型緊密結合的四個宏:
FD_CLR( s,*set) 從隊列set刪除句柄s。
FD_ISSET( s, *set) 檢查句柄s是否存在與隊列set中。
FD_SET( s,*set )把句柄s添加到隊列set中。
FD_ZERO( *set ) 把set隊列初始化成空隊列。
select選擇模式倚賴於select函數,其思想就是讓select函數對傳入fd_set進行監視(fd_set中裝有你的SOCKET句柄),如果沒什麼事發生select就將fd_set中的SOCKET清除。
如:
timeval
outTime; outTime.tv
= 1; //設置等待時間爲1s outTime.usec
= 0; //毫秒 fd_set
fdread; while ( true ) { FD_ZERO(&fdread); FD_SET(sessionSock,
&fdread) //sessionSock爲之前創建的會話套接字 select(0,
&fdread, NULL, NULL, &outTime); if (FD_ISSET(sessionSock,
&fdread)) //判斷套接字是否還在集合中 { recv_cnt
= recv(sessionSock, buf, bufSize, 0); } else { //沒有數據寫入,進行其他操作 } } |
結束了嗎,有問題嗎?是否有人覺得奇怪,我這說的是異步I/O,但這個select依然會阻塞進程啊,不是設置了outTime的嗎?此問題很好,select本身是會阻塞的,我們可以使用select實現阻塞式套接字(如上),也可以實現異步套接字。我個人對實現異步套接字的理解是:你可以單獨使用一個線程來進行你select,也就是說select阻塞你單獨的線程,說白了就是讓線程來完成異步。
參考:
1.http://blog.csdn.net/style_2009/article/details/4931009
2.http://www.cnblogs.com/jiayongzhi/archive/2011/05/13/2045833.html