socket基礎函數(2)

來源:互聯網
上載者:User

標籤:style   blog   http   color   使用   ar   strong   for   檔案   

一、select 

winsock中 #include <winsock.h>

原型

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

nfds:本參數忽略,僅起到相容作用。
    readfds:(可選)指標,指向一組等待可讀性檢查的套介面。
    writefds:(可選)指標,指向一組等待可寫性檢查的套介面。
    exceptfds:(可選)指標,指向一組等待錯誤檢查的套介面。
    timeout:select()最多等待時間,對阻塞操作則為NULL。

注釋:
    本函數用於確定一個或多個套介面的狀態。對每一個套介面,調用者可查詢它的可讀性、可寫性及錯誤狀態資訊。

    用fd_set結構來表示一組等待檢查的套介面。 在調用返回時,這個結構存有滿足一定條件的套介面組的子集,並且select()返回滿足條件的套介面的數目。

    有一組宏可用於對fd_set的操作,這些宏與Berkeley Unix軟體中的相容,但內部的表達是完全不同的。

參數:

   readfds:標識等待可讀性檢查的套介面。如果該套介面正處於監聽listen()狀態,則若有串連請求到達,該套介面便被標識為可讀,這樣一個accept()調用保證可以無阻塞完成。

對其他套介面而言,可讀性意味著有排隊資料供讀取。

或者對於SOCK_STREAM類型套介面來說,相對於該套介面的虛套介面已關閉,於是recv()或recvfrom()操作均能無阻塞完成。

如果虛電路被“優雅地”中止,則recv()不讀取資料立即返回;如果虛電路被強制複位,則recv()將以WSAECONNRESET錯誤立即返回。如果SO_OOBINLINE選項被設定,則將檢查帶外資料是否存在(參見setsockopt())。
    writefds:標識等待可寫性檢查的套介面。如果一個套介面正在connect()串連(非阻塞),可寫性意味著串連順利建立。如果套介面並未處於connect()調用中,可寫性意味著send()和sendto()調用將無阻塞完成。〔但並未指出這個保證在多長時間內有效,特別是在多線程環境中〕。
    exceptfds:標識等待帶外資料存在性或意味錯誤條件檢查的套介面。請注意如果設定了SO_OOBINLINE選項為假FALSE,則只能用這種方法來檢查帶外資料的存在與否。對於SO_STREAM類型套介面,遠端造成的串連中止和KEEPALIVE錯誤都將被作為意味出錯。如果套介面進行中串連connect()(非阻塞方式),則串連試圖的失敗將會表現在exceptfds參數中。
    如果對readfds、writefds或exceptfds中任一個組類不感興趣,可將它置為空白NULL。
    在winsock.h標頭檔中共定義了四個宏來操作描述字集。FD_SETSIZE變數用於確定一個集合中最多有多少描述字(FD_SETSIZE預設值為64,可在包含winsock.h前用#define FD_SETSIZE來改變該值)。對於內部表示,fd_set被表示成一個套介面的隊列,最後一個有效元素的後續元素為INVAL_SOCKET。宏為:
    FD_CLR(s,*set):從集合set中刪除描述字s。
    FD_ISSET(s,*set):若s為集合中一員,非零;否則為零。
    FD_SET(s,*set):向集合添加描述字s。
    FD_ZERO(*set):將set初始化為空白集NULL。
    timeout: 控制select()完成的時間。若timeout參數為空白指標,則select()將一直阻塞到有一個描述字滿足條件。否則的話,timeout指向一個timeval結構,其中指定了select()調用在返回前等待多長時間。如果timeval為{0,0},則select()立即返回,這可用於探詢所選套介面的狀態。如果處於這種狀態,則select()調用可認為是非阻塞的,且一切適用於非阻塞調用的假設都適用於它。舉例來說,阻塞鉤子函數不應被調用,且WINDOWS套介面實現不應yield。

設定了timeout的值之後呢,select在沒有檔案描述符監視可用的情況下,會等待這個timeout的時間,時間到了select返回0

如果timeout逾時之前有檔案描述符可用,則返回可用的數量,這時候的timeout則會依然計數,因此如果想要每次都逾時一定的時間那麼在slelect返回>0的值之後要重新裝填timeout的值一次。以保證逾時時間沒有變化。

如果tv_sec和tv_usec都是0,那麼就是逾時時間為0,那麼select就會立刻返回了。

如果timeout這裡是個NULL,那麼逾時就未被啟用,會一直阻塞在監視檔案描述符的地方。


傳回值:
    select()調用返回處於就緒狀態並且已經包含在fd_set結構中的描述字總數;如果逾時則返回0;否則的話,返回SOCKET_ERROR錯誤,應用程式可通過WSAGetLastError()擷取相應錯誤碼。

錯誤碼:
    WSANOTINITIALISED:在使用此API之前應首先成功地調用WSAStartup()。
    WSAENETDOWN:WINDOWS套介面實現檢測到網路子系統失效。
    WSAEINVAL:逾時時間值非法。
    WSAEINTR:通過一個WSACancelBlockingCall()來取消一個(阻塞的)調用。
    WSAEINPROGRESS:一個阻塞的WINDOWS套介面調用正在運行中。
    WSAENOTSOCK:描述字集合中包含有非套介面的元素。

select()函數主要是建立在fd_set類型的基礎上的。fd_set(它比較重要所以先介紹一下)是一組檔案描述字(fd)的集合,它用一位來表示一個fd(下面會仔細介紹),對於fd_set類型通過下面四個宏來操作: 

fd_set set;

FD_ZERO(&set);      

FD_SET(fd, &set);   

FD_CLR(fd, &set);   

FD_ISSET(fd, &set);        

過去,一個fd_set通常只能包含<32的fd(檔案描述字),因為fd_set其實只用了一個32位向量來表示fd;現在,UNIX系統通常會在標頭檔<sys/select.h>中定義常量FD_SETSIZE,它是資料類型fd_set的描述字數量,其值通常是1024,這樣就能表示<1024的fd。根據fd_set的位向量實現,我們可以重新理解操作fd_set的四個宏: 

fd_set set;

FD_ZERO(&set);     

FD_SET(0, &set);   

FD_CLR(4, &set);     

FD_ISSET(5, &set);   

―――――――――――――――――――――――――――――――――――――――

注意fd的最大值必須<FD_SETSIZE。

――――――――――――――――――――――――――――――――――――――― 

select函數的介面比較簡單:

int select(int nfds, fd_set *readset, fd_set *writeset,

fd_set* exceptset, struct tim *timeout); 

功能:

測試指定的fd可讀?可寫?有異常條件待處理?     

參數:

nfds: 需要檢查的檔案描述字個數(即檢查到fd_set的第幾位),數值應該比三組fd_set中所含的最大fd

       值更大,一般設為三組fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大

       的fd為5,則nfds=6,因為fd是從0開始的)。設這個值是為提高效率,使函數不必檢查fd_set的所

       有1024位。

readset:  用來檢查可讀性的一組檔案描述字。

writeset: 用來檢查可寫性的一組檔案描述字。

exceptset:用來檢查是否有異常條件出現的檔案描述字。(註:錯誤不包括在異常條件之內)

timeout:有三種可能:

         1:timeout=NULL(阻塞:直到有一個fd位被置為1函數才返回)

         2:timeout所指向的結構設為非零時間(等待固定時間:有一個fd位被置為1或者時間耗盡,函數

            均返回)

         3. timeout所指向的結構,時間設為0(非阻塞:函數檢查完每個fd後立即返回) 

傳回值:     

返回對應位仍然為1的fd的總數。 

Remarks:

三組fd_set均將某些fd位置0,只有那些可讀,可寫以及有異常條件待處理的fd位仍然為1。

使用select函數的過程一般是:

先調用宏FD_ZERO將指定的fd_set清零,然後調用宏FD_SET將需要測試的fd加入fd_set,接著調用函數  

select測試fd_set中的所有fd,最後用宏FD_ISSET檢查某個fd在函數select調用後,相應位是否仍然為1。 

以下是一個測試單個檔案描述字可讀性的例子:

int isready(int fd)

{

    int rc;

    fd_set fds;

    struct tim tv;    

    FD_ZERO(&fds);

    FD_SET(fd,&fds);

    tv.tv_sec = tv.tv_usec = 0;    

    rc = select(fd+1, &fds, NULL, NULL, &tv);

    if (rc < 0)   //error

        return -1;    

    return FD_ISSET(fd,&fds) ? 1 : 0;

 }

下面還有一個複雜一些的應用:

//這段代碼將指定測試Socket的描述字的可讀可寫性,因為Socket使用的也是fd

uint32 SocketWait(TSocket *s,bool rd,bool wr,uint32 timems)    

{

     fd_set rfds,wfds;

 #ifdef _WIN32

     TIM tv;

 #else

     struct tim tv;

 #endif    

     FD_ZERO(&rfds);

     FD_ZERO(&wfds); 

     if (rd)   //TRUE

     FD_SET(*s,&rfds);   //添加要測試的描述字 

     if (wr)     //FALSE

       FD_SET(*s,&wfds); 

     tv.tv_sec=timems/1000;     //second

     tv.tv_usec=timems00;     //ms 

     for (;;) //如果errno==EINTR,反覆測試緩衝區的可讀性

          switch(select((*s)+1,&rfds,&wfds,NULL,

              (timems==TIME_INFINITE?NULL:&tv)))  //測試在規定的時間內套介面接收緩衝區中是否有資料可讀

         {                                              //0--逾時,-1--出錯

         case 0:    

              return 0; 

         case (-1):   

              if (SocketError()==EINTR)

                   break;              

              return 0; //有錯但不是EINTR 

          default:

              if (FD_ISSET(*s,&rfds)) //如果s是fds中的一員返回非0,否則返回0

                   return 1;

              if (FD_ISSET(*s,&wfds))

                   return 2;

              return 0;

         };

}

 

Q&A:

和select模型緊密結合的四個宏:

FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);


理解select模型的關鍵在於理解fd_set,為說明方便,取fd_set長度為1位元組,fd_set中的每一bit可以對應一個檔案描述符fd。則1位元組長的fd_set最大可以對應8個fd。
(1)執行fd_set set; FD_ZERO(&set);則set用位表示是0000,0000。
(2)若fd=5,執行FD_SET(fd,&set);後set變為0001,0000(第5位置為1)
(3)若再加入fd=2,fd=1,則set變為0001,0011
(4)執行select(6,&set,0,0,0)阻塞等待
(5)若fd=1,fd=2上都發生可讀事件,則select返回,此時set變為0000,0011。注意:沒有事件發生的fd=5被清空。

基於上面的討論,可以輕鬆得出select模型的特點:
(1)可監控的檔案描述符個數取決與sizeof(fd_set)的值。我這邊伺服器上sizeof(fd_set)=512,每bit表示一個檔案描述符,則我伺服器上支援的最大檔案描述符是512*8=4096。對調整fd_set的大小可參考http://www.cppblog.com/CppExplore/archive/2008/03/21/45061.html中的模型2,可以有效突破select可監控的檔案描述符上限。
(2)將fd加入select監控集的同時,還要再使用一個資料結構array儲存放到select監控集中的fd,一是用於再select返回後,array作為來源資料和fd_set進行FD_ISSET判斷。二是select返回後會把以前加入的但並無事件發生的fd清空,則每次開始select前都要重新從array取得fd逐一加入(FD_ZERO最先),掃描array的同時取得fd最大值maxfd,用於select的第一個參數。
(3)可見select模型必須在select前迴圈array(加fd,取maxfd),select返回後迴圈array(FD_ISSET判斷是否有事件發生)。

 

另外,如果select調用中設定了等待時間,那麼每次調用時都需要重新對這個時間賦值嗎?就像對fd_set處理一樣。
例如:
fd_set readfd;
struct timval tv;
while(1) {
  FD_ZERO(&readfd);
  FD_SET(fd, &readfd);
  tv.tv_sec = 2;
  tv.tv_usec = 0;
  select(maxfd+1, &readfd, NULL, NULL, &tv);
  ......;
}

如上代碼,對fd_set需要每次調用都要重新設定,那麼對tv來說是否也是一樣呢?能不能把對tv的賦值放在while外面?

答案是不行,如果將時間的初始化放在外邊,時間初始化為2秒,假設在1秒後發上了事件,則select將會返回並將tv的時間變成上次阻塞的剩餘時間,即1秒,然後再進行監視通訊端。這是因為linux系統對select()的實現中會修改參數tv為剩餘時間。所以對於select函數中的最後一個參數,需要在迴圈中設定,每次迴圈要重新設定。如果設在迴圈外面,當迴圈執行起來後,每次迴圈select都會修改tv的值,tv的值越來越小,導致最後會產生select函數這tv時間內收不到有效時間,而返回-1,造成錯誤。

 

socket 可讀:

1. 接收緩衝區有資料,一定可讀
2. 對方正常關閉socket,也是可讀
3. 對於偵聽socket,有新連結到達也可讀

 

socket基礎函數(2)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.