2022年3月18日 星期五

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;
}
}
}

沒有留言:

張貼留言