轉載:http://blog.csdn.net/qdx411324962/article/details/42499535
函數作用:
系統提供select函數來實現多路復用輸入/輸出模型。select系統調用是用來讓我們的程序監視多個文件句柄的狀態變化的。程序會停在select這里等待,直到被監視的文件句柄有一個或多個發生了狀態改變。關于文件句柄,其實就是一個整數,我們最熟悉的句柄是0、1、2三個,0是標準輸入,1是標準輸出,2是標準錯誤輸出。0、1、2是整數表示的,對應的FILE *結構的表示就是stdin、stdout、stderr。
函數原型:
- int?select(int?maxfd,fd_set?*rdset,fd_set?*wrset,?\??
- ???????????fd_set?*exset,struct?timeval?*timeout);??
參數說明:
參數maxfd是需要監視的最大的文件描述符值+1;rdset,wrset,exset分別對應于需要檢測的可讀文件描述符的集合,可寫文件描述符的集 合及異常文件描述符的集合。struct timeval結構用于描述一段時間長度,如果在這個時間內,需要監視的描述符沒有事件發生則函數返回,返回值為0。
下面的宏提供了處理這三種描述詞組的方式:
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;//second??
- ????time_t?tv_usec;//minisecond??
- };??
如果參數timeout設為:
NULL,則表示select()沒有timeout,select將一直被阻塞,直到某個文件描述符上發生了事件。
0:僅檢測描述符集合的狀態,然后立即返回,并不等待外部事件的發生。
特定的時間值:如果在指定的時間段里沒有事件發生,select將超時返回。
函數返回值:
執行成功則返回文件描述詞狀態已改變的個數,如果返回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){……}
理解select模型:
理解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(1)可以有效突破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模型的服務器模型:
- array[slect_len];??
- nSock=0;??
- array[nSock++]=listen_fd;(之前listen?port已綁定并listen)??
- maxfd=listen_fd;??
- ??
- while(1){??
- ??
- FD_ZERO(&set);??
- ??
- foreach?(fd?in?array)??
- {??
- ???? fd大于maxfd,則maxfd=fd??
- ???? FD_SET(fd,&set)??
- }??
- ??
- res=select(maxfd+1,&set,0,0,0);??
- ??
- if(FD_ISSET(listen_fd,&set))??
- {??
- ???? newfd=accept(listen_fd);??
- ???? array[nsock++]=newfd;??
- ???? if(--res<=0)?continue;??
- }??
- ??
- foreach?下標1開始?(fd?in?array)??
- {??
- ???? if(FD_ISSET(fd,&tyle="COLOR:?#ff0000">set))??
- ???? 執行讀等相關操作??
- ???? 如果錯誤或者關閉,則要刪除該fd,將array中相應位置和最后一個元素互換就好,nsock減一??
- ???? if(--res<=0)?continue;??
- }??
- ??
- }??
- #include<sys/time.h>??
- #include<sys/types.h>??
- #include<unistd.h>??
- #include<string.h>??
- #include<stdlib.h>??
- #include<stdio.h>??
- int?main()??
- {??
- ????????char?buf[10]="";??
- ????????fd_set?rdfds;??
- ????????struct?timeval?tv;??
- ????????int?ret;??
- ????????FD_ZERO(&rdfds);??
- ????????FD_SET(0,&rdfds);???//文件描述符0表示stdin鍵盤輸入??
- ????????tv.tv_sec?=?3;??
- ????????tv.tv_usec?=?500;??
- ????????ret?=?select(1,&rdfds,NULL,NULL,&tv);??
- ????????if(ret<0)??
- ??????????????printf("\n?selcet");??
- ????????else?if(ret?==?0)??
- ??????????????printf("\n?timeout");??
- ????????else??
- ??????????????printf("\n?ret?=?%d",ret);??
- ??
- ????????if(FD_ISSET(1,&rdfds))??//如果有輸入,從stdin中獲取輸入字符??
- ????????{??
- ??????????????printf("\n?reading");??
- ??????????????fread(buf,9,1,stdin);??
- ?????????}??
- ?????????write(1,buf,strlen(buf));??
- ?????????printf("\n?%d?\n",strlen(buf));??
- ?????????return?0;??
- }??
- //執行結果ret?=?1.??
利用Select模型,設計的web服務器:
- #include?<stdio.h>??
- #include?<stdlib.h>??
- #include?<unistd.h>??
- #include?<errno.h>??
- #include?<string.h>??
- #include?<sys/types.h>??
- #include?<sys/socket.h>??
- #include?<netinet/in.h>??
- #include?<arpa/inet.h>??
- ??
- #define?MYPORT?88960????//?the?port?users?will?be?connecting?to??
- ??
- #define?BACKLOG?10?????//?how?many?pending?connections?queue?will?hold??
- ??
- #define?BUF_SIZE?200??
- ??
- int?fd_A[BACKLOG];????//?accepted?connection?fd??
- int?conn_amount;????//?current?connection?amount??
- ??
- void?showclient()??
- {??
- ????int?i;??
- ????printf("client?amount:?%d\n",?conn_amount);??
- ????for?(i?=?0;?i?<?BACKLOG;?i++)?{??
- ????????printf("[%d]:%d??",?i,?fd_A[i]);??
- ????}??
- ????printf("\n\n");??
- }??
- ??
- int?main(void)??
- {??
- ????int?sock_fd,?new_fd;??//?listen?on?sock_fd,?new?connection?on?new_fd??
- ????struct?sockaddr_in?server_addr;????//?server?address?information??
- ????struct?sockaddr_in?client_addr;?//?connector's?address?information??
- ????socklen_t?sin_size;??
- ????int?yes?=?1;??
- ????char?buf[BUF_SIZE];??
- ????int?ret;??
- ????int?i;??
- ??
- ????if?((sock_fd?=?socket(AF_INET,?SOCK_STREAM,?0))?==?-1)?{??
- ????????perror("socket");??
- ????????exit(1);??
- ????}??
- ??
- ????if?(setsockopt(sock_fd,?SOL_SOCKET,?SO_REUSEADDR,?&yes,?sizeof(int))?==?-1)?{??
- ????????perror("setsockopt");??
- ????????exit(1);??
- ????}??
- ??????
- ????server_addr.sin_family?=?AF_INET;?????????//?host?byte?order??
- ????server_addr.sin_port?=?htons(MYPORT);?????//?short,?network?byte?order??
- ????server_addr.sin_addr.s_addr?=?INADDR_ANY;?//?automatically?fill?with?my?IP??
- ????memset(server_addr.sin_zero,?'\0',?sizeof(server_addr.sin_zero));??
- ??
- ????if?(bind(sock_fd,?(struct?sockaddr?*)&server_addr,?sizeof(server_addr))?==?-1)?{??
- ????????perror("bind");??
- ????????exit(1);??
- ????}??
- ??
- ????if?(listen(sock_fd,?BACKLOG)?==?-1)?{??
- ????????perror("listen");??
- ????????exit(1);??
- ????}??
- ??
- ????printf("listen?port?%d\n",?MYPORT);??
- ??
- ????fd_set?fdsr;??
- ????int?maxsock;??
- ????struct?timeval?tv;??
- ??
- ????conn_amount?=?0;??
- ????sin_size?=?sizeof(client_addr);??
- ????maxsock?=?sock_fd;??
- ????while?(1)?{??
- ????????//?initialize?file?descriptor?set??
- ????????FD_ZERO(&fdsr);??
- ????????FD_SET(sock_fd,?&fdsr);??
- ??
- ????????//?timeout?setting??
- ????????tv.tv_sec?=?30;??
- ????????tv.tv_usec?=?0;??
- ??
- ????????//?add?active?connection?to?fd?set??
- ????????for?(i?=?0;?i?<?BACKLOG;?i++)?{??
- ????????????if?(fd_A[i]?!=?0)?{??
- ????????????????FD_SET(fd_A[i],?&fdsr);??
- ????????????}??
- ????????}??
- ??
- ????????ret?=?select(maxsock?+?1,?&fdsr,?NULL,?NULL,?&tv);??
- ????????if?(ret?<?0)?{??
- ????????????perror("select");??
- ????????????break;??
- ????????}?else?if?(ret?==?0)?{??
- ????????????printf("timeout\n");??
- ????????????continue;??
- ????????}??
- ??
- ????????//?check?every?fd?in?the?set??
- ????????for?(i?=?0;?i?<?conn_amount;?i++)?{??
- ????????????if?(FD_ISSET(fd_A[i],?&fdsr))?{??
- ????????????????ret?=?recv(fd_A[i],?buf,?sizeof(buf),?0);??
- ??????????????????
- ????????????????char?str[]?=?"Good,very?nice!\n";??
- ??????????????????
- ????????????????send(fd_A[i],str,sizeof(str)?+?1,?0);??
- ??????????????????
- ??????????????????
- ????????????????if?(ret?<=?0)?{????????//?client?close??
- ????????????????????printf("client[%d]?close\n",?i);??
- ????????????????????close(fd_A[i]);??
- ????????????????????FD_CLR(fd_A[i],?&fdsr);??
- ????????????????????fd_A[i]?=?0;??
- ????????????????}?else?{????????//?receive?data??
- ????????????????????if?(ret?<?BUF_SIZE)??
- ????????????????????????memset(&buf[ret],?'\0',?1);??
- ????????????????????printf("client[%d]?send:%s\n",?i,?buf);??
- ????????????????}??
- ????????????}??
- ????????}??
- ??
- ????????//?check?whether?a?new?connection?comes??
- ????????if?(FD_ISSET(sock_fd,?&fdsr))?{??
- ????????????new_fd?=?accept(sock_fd,?(struct?sockaddr?*)&client_addr,?&sin_size);??
- ????????????if?(new_fd?<=?0)?{??
- ????????????????perror("accept");??
- ????????????????continue;??
- ????????????}??
- ??
- ????????????//?add?to?fd?queue??
- ????????????if?(conn_amount?<?BACKLOG)?{??
- ????????????????fd_A[conn_amount++]?=?new_fd;??
- ????????????????printf("new?connection?client[%d]?%s:%d\n",?conn_amount,??
- ????????????????????????inet_ntoa(client_addr.sin_addr),?ntohs(client_addr.sin_port));??
- ????????????????if?(new_fd?>?maxsock)??
- ????????????????????maxsock?=?new_fd;??
- ????????????}??
- ????????????else?{??
- ????????????????printf("max?connections?arrive,?exit\n");??
- ????????????????send(new_fd,?"bye",?4,?0);??
- ????????????????close(new_fd);??
- ????????????????break;??
- ????????????}??
- ????????}??
- ????????showclient();??
- ????}??
- ??
- ????//?close?other?connections??
- ????for?(i?=?0;?i?<?BACKLOG;?i++)?{??
- ????????if?(fd_A[i]?!=?0)?{??
- ????????????close(fd_A[i]);??
- ????????}??
- ????}??
- ??
- ????exit(0);??
- }??
1 基本原理
注:select 原理圖,摘自?IBM iSeries 信息中心。
1 數據結構與函數原型
1.1 select
- 函數原型
int select(int nfds,fd_set *readset,fd_set *writeset,fd_set* exceptset,struct timeval *timeout);
- 頭文件
select
位于:#include <sys/select.h>
struct timeval
位于:#include <sys/time.h>
- 返回值
返回對應位仍然為1的fd的總數。
- 參數
- nfds:第一個參數是:最大的文件描述符值+1;
- readset:可讀描述符集合;
- writeset:可寫描述符集合;
- exceptset:異常描述符;
- timeout:select 的監聽時長,如果這短時間內所監聽的 socket 沒有事件發生。
1.2 fd_set
1.2.1 清空描述符集合
FD_ZERO(fd_set *)
1.2.2 向描述符集合添加指定描述符
FD_SET(int, fd_set *)
1.2.3 從描述符集合刪除指定描述符
FD_CLR(int, fd_set *)
1.2.4 檢測指定描述符是否在描述符集合中
FD_ISSET(int, fd_set *)
1.2.5 描述符最大數量
#define FD_SETSIZE 1024
1.3 描述符集合
可讀描述符集合中可讀的描述符,為1,其他為0;可寫也類似。異常描述符集合中有異常等待處理的描述符的值為1,其他為0。
1.4 ioctl
-
函數原型:
int ioctl(int handle, int cmd,[int *argdx, int argcx]);
-
頭文件:
#include <sys/ioctl.h>
-
返回值:
- 0 - 成功
- 1 - 失敗
2 示例
程序各部分的解釋在注釋中。
#include <sys/socket.h>
#include <string.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <unistd.h>#define TRUE 1
#define FALSE 0int main(int argc, char *argv[])
{int i, len, rc, on = TRUE;int listen_sd, new_sd = 0, max_sd;int desc_ready;char buffer[80];int close_conn, end_server = FALSE;struct sockaddr_in server_addr;struct timeval timeout;struct fd_set master_set, working_set;// Listenlisten_sd = socket(AF_INET, SOCK_STREAM, 0);if (listen_sd < 0){perror("socket() failed");exit(-1);}// Set socket optionsrc = setsockopt(listen_sd, SOL_SOCKET, SO_REUSEADDR, (char *) &on, sizeof(on));if (rc < 0){perror("setsockopt() failed");close(listen_sd);exit(-1);}// Set IO controlrc = ioctl(listen_sd, FIONBIO, (char *) &on);if (rc < 0){perror("ioctl() failed");close(listen_sd);exit(-1);}// Bindmemset(&server_addr, 0, sizeof(server_addr));server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = htonl(INADDR_ANY);server_addr.sin_port = htons(atoi(argv[1]));rc = bind(listen_sd, (struct sockaddr *) &server_addr, sizeof(server_addr));if (rc < 0){perror("bind() failed\n");close(listen_sd);exit(-1);}// Listenrc = listen(listen_sd, 32);if (rc < 0){perror("listen() failed\n");close(listen_sd);exit(-1);}// Intialize sd setFD_ZERO(&master_set);max_sd = listen_sd;FD_SET(listen_sd, &master_set);timeout.tv_sec = 3 * 60;timeout.tv_usec = 0;// Startdo{// Copy master_set into working_setmemcpy(&working_set, &master_set, sizeof(master_set));printf("Waiting on select()...\n");rc = select(max_sd + 1, &working_set, NULL, NULL, &timeout);if (rc < 0){perror(" select() failed\n");break;}if (rc == 0){printf(" select() timed out. End program.\n");break;}desc_ready = rc; // number of sds ready in working_set// Check each sd in working_setfor (i = 0; i <= max_sd && desc_ready > 0; ++i){// Check to see if this sd is readyif (FD_ISSET(i, &working_set)){--desc_ready;// Check to see if this is the listening sdif (i == listen_sd){printf(" Listeing socket is readable\n");do{// Acceptnew_sd = accept(listen_sd, NULL, NULL);// Nothing to be acceptedif (new_sd < 0){// All have been acceptedif (errno != EWOULDBLOCK){perror(" accept() failed\n");end_server = TRUE;}break;}// Insert new_sd into master_setprintf(" New incoming connection - %d\n", new_sd);FD_SET(new_sd, &master_set);if (new_sd > max_sd){max_sd = new_sd;}}while (new_sd != -1);}// This is not the listening sdelse{close_conn = FALSE;printf(" Descriptor %d is avaliable\n", i);do{rc = recv(i, buffer, sizeof(buffer), 0);// Receive data on sd "i", until failure occursif (rc < 0){// Normal failureif (errno != EWOULDBLOCK){perror(" recv() failed\n");close_conn = TRUE;}break;}// The connection has been closed by the clientif (rc == 0){printf(" Connection closed\n");close_conn = TRUE;break;}/* Receiving data succeeded and echo it backthe to client */len = rc;printf(" %d bytes received\n", len);rc = send(i, buffer, len, 0);if (rc < 0){perror(" send() failed");close_conn = TRUE;break;}}while (TRUE);// If unknown failure occuredif (close_conn){// Close the sd and remove it from master_setclose(i);FD_CLR(i, &master_set);// If this is the max sdif (i == max_sd){// Find the max sd in master_set nowwhile (FD_ISSET(max_sd, &master_set) == FALSE){--max_sd;}} // End of if (i == max_sd)} // End of if (close_conn)}}}}while (end_server == FALSE);/* Close each sd in master_set */for (i = 0; i < max_sd; ++i){if (FD_ISSET(i, &master_set)){close(i);}}return 0;
}