select

作用:

網絡編程中的select是用來讓我們的程序監視多個文件描述符的狀態變化的。程序會停在select這裏等待,直到被監視的文件描述符有某一個或多個發生了狀態改變。而sokcet就是一個文件描述符。

  在網絡編程中,利用select來遍歷所有socket的狀態,包括服務端的socket,來實現非阻塞監聽,和非阻塞接收。


函數參數解析:

int select(int maxfdp,fd_set *readfds,fd_set *writefds,fd_set *errorfds,struct timeval *timeout); 

函數參數說明:

ndfsselect監視的文件句柄數,視進程中打開的文件數而定,一般設爲要監視各文件中的最大文件描述符值加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

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