作用:
網絡編程中的select是用來讓我們的程序監視多個文件描述符的狀態變化的。程序會停在select這裏等待,直到被監視的文件描述符有某一個或多個發生了狀態改變。而sokcet就是一個文件描述符。
在網絡編程中,利用select來遍歷所有socket的狀態,包括服務端的socket,來實現非阻塞監聽,和非阻塞接收。
函數參數解析:
int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout);
函數參數說明:
ndfs:select監視的文件句柄數,視進程中打開的文件數而定,一般設爲要監視各文件中的最大文件描述符值加1。
readfds:這個文件描述符集合監視文件集中的任何文件是否有數據可讀,當select函數返回的時候,readfds將清除其中不可讀的文件描述符,只留下可讀的文件描述符。
select()函數準備好讀的條件:
1>.套接口有數據可讀
2>.該連接的讀這一半關閉(也就是接收了FIN的TCP連接)。對這樣的套接口進行讀操作將不阻塞並返回0(也就是返回EOF)。
3>.該套接口是一個偵聽套接口且已完成的連接數不爲0。
4>.其上有一個套接口錯誤待處理,對這樣的套接口的讀操作將不阻塞並返回-1,並設置errno。
writefds:這個文件描述符集合監視文件集中的任何文件是否有數據可寫,當select函數返回的時候,writefds將清除其中不可寫的文件描述符,只留下可寫的文件描述符。
select()函數準備好寫的條件:
1>.套接口有可用於寫的空間。
2>.該連接的寫這一半關閉,對這樣的套接口進行寫操作將產生SIGPIPE信號。
3>.該套接口使用非阻塞的方式connect建立連接,並且連接已經異步建立,或則connect已經以失敗告終。
4>.其上有一個套接口錯誤待處理。
exceptfds:這個文件集將監視文件集中的任何文件是否發生錯誤,其實,它可用於其他的用途,例如,監視帶外數據OOB,帶外數據使用MSG_OOB標誌發送到套接字上。當select函數返回的時候,exceptfds將清除其中的其他文件描述符,只留下標記有OOB數據的文件描述符。
timeout:本次select()的超時結束時間。usec是微妙的簡寫,總時間就是妙+微秒。select會修改timeout的值爲剩餘時間,所以每次調用都必須重 新賦值。
struct timeval { time_t tv_sec; /* seconds */ suseconds_t tv_usec; /* microseconds */ };
這個參數至關重要,它可以使select處於三種狀態:
(1)若將NULL以形參傳入,即不傳入時間結構,就是將select置於阻塞狀態,一定等到監視文件描述符集合中某個文件描述符發生變化爲止;
(2)若將時間值設爲0秒0毫秒,就變成一個純粹的非阻塞函數,不管文件描述符是否有變化,都立刻返回繼續執行,文件無變化返回0,有變化返回一個正值;
(3)timeout的值大於0,這就是等待的超時時間,即select在timeout時間內阻塞,超時時間之內有事件到來就返回了,否則在超時後不管怎樣一定返回,返回值同上述。
函數的返回值:
正值:表示監視的文件集中有文件描述符符合要求
零值:表示select監視超時
負值:表示發生了錯誤,錯誤值由errno指定。
宏操作:
FD_ZERO(fd_set *set): 用來清除描述詞組set的全部位
FD_SET(int fd,fd_set*set): 用來設置描述詞組set中相關fd的位
FD_ISSET(int fd,fd_set *set): 用來測試描述詞組set中相關fd 的位是否爲真
FD_CLR(inr fd,fd_set* set): 用來清除描述詞組set中相關fd 的位
用法
(1)select + accept函數的非阻塞實現
將正在listen的socket設置到readfds中,調用select,如果有客戶端connect,select將返回正值,通過宏FD_ISSET可檢測到該socket可讀,此時再用accept接受新的socket,進行讀寫.
sockfd = listen_tcp(); //socket()、bind()、listen()
FD_SET(sockfd, rset);
while(1){
ret = select(sockfd + 1, rset, NULL, NULL, timeout); // 等待某個事件發生:或是新連接、或是數據、或是FIN、或是RST到達
if(select()返回TIMEOUT){ //select()超時
printf("日誌打印");
sleep(1);
continue;
}
else if(FD_ISSET(sockfd,&rset)){ //判斷句柄是否可讀,返回真代表可讀,可讀代表有新連接。
connfd = accept(sockfd, ...);
}
else if(select()返回錯誤){
return -1;
}
pthread_create(thread_recv_data, connfd, ...); // 創建線程處理新連接.
close();
}
(2)select + connect函數的非阻塞實現(TCP)
a. 將打開的socket設爲非阻塞的。
b. 發connect調用,這時返回-1,但是errno被設爲EINPROGRESS,意即connect仍舊在進行還沒有完成。
c. 將打開的socket放進被監視的可寫(注意不是可讀)文件集合中,用select進行監視,如果可寫,查看全局變量errno,如果爲零則connect成功。
while(1){
ret = connect();
if(errno == EINPROCESS){ //此時TCP的三路握手繼續進行
select(...) //等待某個事件發生:或是新連接、或是超時
if(FD_ISSET(sockfd,&wset) ){ //判斷句柄可寫,不能代表建立連接成功。
getsockopt(...);
if(errno == 0){ //建立連接成功
}
}else if(select()返回TIMEOUT){
sleep(1);
continue();
}
}
else if(ret == -1){
//Connect failed
}
pthread_create(thread_send_log, ...); //建立線程處理新連接
close();
}
(3)select檢測對方Socket連接關閉
使用select監視是否有數據可讀,當監視到可讀返回正值後,使用recv函數對套接字進行讀操作,recv函數返回正值表示正常的讀操作,若返回0或者-1表示對方連接已關閉。
int sockfd;
fd_set fdR;
int sock_fd[N];//save client_sockfd, size is N
struct timeval timeout;
while(1){
FD_ZERO(&fdR);
FD_SET(sockfd, &fdR);
for() FD_SET(sockclient);
switch (select(sockfd + 1,&fdR, NULL, &timeout)) {
case -1:error;
case 0:timeout;
default:
if (FD_ISSET(sockclient, &fdR))
{
ret = recv();
if(ret <0)
{
"the socket closed"
}
}
}
/* if sock_serv is in &fdR, now accept() */
if( FD_ISSET(sock_serv,&fdR) ){
sock_fd[..] = now accept();
}
}
更多:http://blog.163.com/li_xiang1102/blog/static/60714076201110296471767/
http://blog.csdn.net/hanchaoman/article/details/6184236
http://www.cnblogs.com/coder2012/archive/2013/06/16/3138530.html