1. 程式人生 > >socket select函式的詳細講解

socket select函式的詳細講解

原型

int select(
int nfds,
fd_set* ,
fd_set* ,
fd_set* ,
const struct timeval* );

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

timeout為結構timeval,用來設定select()的等待時間,其結構定義如下
struct timeval
{
time_t tv_sec; //second 秒
time_t tv_usec; //microsecond 微妙
};

註釋:
   本函式用於確定一個或多個套介面的狀態。對每一個套介面,呼叫者可查詢它的可讀性、可寫性及錯誤狀態資訊。用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。

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

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

範例 :
sock= socket(AF_INET,SOCK_STREAM,0);

struct sockaddr_in addr;      //告訴sock 應該再什麼地方licence
memset(&addr,0,sizeof(addr));
addr.sin_family=AF_INET;
addr.sin_port=htons(11111);  //埠啦
addr.sin_addr.s_addr=htonl(INADDR_ANY);        //在本機的所有ip上開始監聽

bind (sock,(sockaddr *)&addr,sizeof(addr));//bind....

listen(sock,5);                   //最大5個佇列

SOCKET socka;                    //這個用來接受一個連線
fd_set rfd;                     // 描述符集 這個將用來測試有沒有一個可用的連線
struct timeval timeout;

FD_ZERO(&rfd);                     //總是這樣先清空一個描述符集

timeout.tv_sec=60;                //等下select用到這個
timeout.tv_usec=0;

u_long ul=1;

ioctlsocket(sock,FIONBIO,&ul);    //用非阻塞的連線

//現在開始用select


FD_SET(sock,&rfd);    //把sock放入要測試的描述符集 就是說把sock放入了rfd裡面 這樣下一步呼叫select對rfd進行測試的時候就會測試sock了(因為我們將sock放入的rdf) 一個描述符集可以包含多個被測試的描述符,
if(select(sock+1,&rfd,0,0, &timeout)==0) { //這個大括號接上面的,返回0那麼就超過了timeout預定的時間

//處理....

}

if(FD_ISSET(sock,&rfd))
{      //有一個描述符準備好了

socka=accept(sock,0,0);     //一個用來測試讀 一個用來測試寫

FD_ZERO(&rfd);

FD_ZERO(&wfd);

FD_SET(socka,&rfd);//把socka放入讀描述符集

FD_SET(sockb,&rfd);//把sockb放入讀描述符集

FD_SET(socka,&wfd);把socka放入寫描述符集

FD_SET(sockb,&wfd);把sockb放入寫描述符集

if(SOCKET_ERROR!=select(0,&rfd,&wfd,0,0))      //測試這兩個描述符集,永不超時 其中rfd只用來測試讀 wfd只用來測試寫

{      //沒有錯誤

if(FD_ISSET(socka,&rfd))    //socka可讀

{...}

if(FD_ISSET(sockb,&rfd)   //sockb可讀

{...}

if(FD_ISSET(socka,&wfd) //socka 可寫

{...}

if(FD_ISSET(sockb,&wfd) //sockb可寫

{...}

}

二、linux c中

select(I/O多工機制)


表頭檔案

#i nclude<sys/time.h>
#i nclude<sys/types.h>
#i nclude<unistd.h>

定義函式

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

函式說明

select()用來等待檔案描述詞狀態的改變。引數n代表最大的檔案描述詞加1,引數readfds、writefds 和exceptfds 稱為描述片語,是用來回傳該描述詞的讀,寫或例外的狀況。底下的巨集提供了處理這三種描述片語的方式:
FD_CLR(inr fd,fd_set* set);用來清除描述片語set中相關fd 的位
FD_ISSET(int fd,fd_set *set);用來測試描述片語set中相關fd 的位是否為真
FD_SET(int fd,fd_set*set);用來設定描述片語set中相關fd的位
FD_ZERO(fd_set *set); 用來清除描述片語set的全部位

引數

timeout為結構timeval,用來設定select()的等待時間,其結構定義如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};

返回值

如果引數timeout設為NULL則表示select()沒有timeout。

錯誤程式碼

執行成功則返回檔案描述詞狀態已改變的個數,如果返回0代表在描述詞狀態改變前已超過timeout時間,當有錯誤發生時則返回-1,錯誤原因存於errno,此時引數readfds,writefds,exceptfds和timeout的值變成不可預測。
EBADF 檔案描述詞為無效的或該檔案已關閉
EINTR 此呼叫被訊號所中斷
EINVAL 引數n 為負值。
ENOMEM 核心記憶體不足

範例

常見的程式片段:fs_set readset;
FD_ZERO(&readset);
FD_SET(fd,&readset);
select(fd+1,&readset,NULL,NULL,NULL);
if(FD_ISSET(fd,readset){……}

下面是linux環境下select的一個簡單用法

#i nclude <sys/time.h>
#i nclude <stdio.h>
#i nclude <sys/types.h>
#i nclude <sys/stat.h>
#i nclude <fcntl.h>
#i nclude <assert.h>

int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY | O_NONBLOCK);
assert(keyboard>0);
while(1)
    {
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout);
if(FD_ISSET(keyboard,&readfd))
    {
      i=read(keyboard,&c,1);
          if('/n'==c)
          continue;
      printf("hehethe input is %c/n",c);
    
       if ('q'==c)
      break;
      }
}
}