socket select模型

由於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

發佈了53 篇原創文章 · 獲贊 30 · 訪問量 21萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章