Linux 多路轉接I/O select函數

網絡管理考試最後一道大題涉及socket和多路轉接I/O的select函數。哎,感覺複習的時候還是沒有把握到精髓,那道題就有些遺憾了……

多路轉接I/O

先構造一張有關描述符的列表,然後調用一個函數,直到這些描述符中的一個已準備好進行I/O時,該函數才返回。在返回時,它告訴進程哪些描述符已準備好可以進行I/O。

select函數

作用:允許程序監視多個文件描述符,以等待一個或多個文件描述符變爲ready,以便進行I/O操作。

Ready:一個文件描述符可以執行相應I/O操作而不會被blocking時,該文件描述符被認爲是ready

 格式:#inlucde <sys/select.h>

         int select(int maxfdp1, 

                     fd_set *restrict readfds,  

                     fd_set *restrict writefds,

                     fd_set *restrict exceptfds,

                     struct timeval *restrict  tvptr);

返回值:
返回準備就緒的描述符,所以正返回值表示已經準備好的描述符
若超時則返回0,表示沒有描述符準備好
若出錯則返回-1

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

有三種情況:
  • tvptr == NULL
            永遠等待。如果捕捉到一個信號則中斷此無限期等待。當所指定的描述符中的一個已準備好或捕捉到一個信號則返回。如果捕捉到一個信號,則select返回-1,errno設置爲EINTR
  • tvptr->tv_sec == 0 && tvptr->tv_usec == 0
             完全不等待。測試所有指定的描述符並立即返回。
  • tvptr->tv_sec != 0 || tvptr->tv_usec != 0
             等待指定的秒數和微妙數。當指定的描述符之一已準備好或當指定的時間值已經超過時立即返回。如果在超時時還沒有一個描述符準備好,則返回值是0.與第一種情況一樣,這種等待可被捕捉到的信號中斷。


中間三個參數readfds、writefds和exceptfds是指向描述符集的指針。這三個描述符集說明了我們關心的可讀、可寫或處於異常條件的各個描述符。每個描述符集存放在一個fd_set數據類型中。這種數據類型爲每一可能的描述符保持了一位。



    select的中間三個參數(指向描述符集的指針)中的任意一個或全部都可以是空指針這表示對相應狀態並不關心如果所有三個指針都是空指針,則select提供了較sleep更精確的計時器。
     select的第一個參數maxfdp1的意思是“最大描述符加1”。在三個描述符集中找出最大描述符值,然後加1,這就是第一個參數。也可以將第一個參數設置爲FD_SETSIZE,這是<sys/select.h>中的一個常量,它說明了最大的描述符(經常是1024)。如果將第一個參數設置爲我們關注的最大描述符編號值加1,內核就只需在此範圍內尋找打開的位,而不必在三個描述符集中的數百位內搜索


描述符集的處理函數:
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的全部位

使用select函數的過程一般是:
先調用宏FD_ZERO將指定的fd_set清零,然後調用宏FD_SET將需要測試的fd加入fd_set,接着調用函數select測試fd_set中的所有fd,最後用宏FD_ISSET檢查某個fd在函數select調用後,相應位是否仍然爲1。
以下是一個測試單個文件描述字可讀性的例子:
int isready(int fd)
{
      int rc;
      fd_set fds;
      struct tim tv;
      FD_ZERO(&fds);
      FD_SET(fd,&fds);
      tv.tv_sec = tv.tv_usec = 0;
      rc = select(fd+1, &fds, NULL, NULL, &tv);
      if (rc < 0) //error
      return -1;
      return FD_ISSET(fd,&fds) ? 1 : 0;
}


基於select實現的網絡服務器和客戶端:

server.c

[cpp] view plain copy
  1. #include<stdio.h>    
  2. #include<sys/types.h>    
  3. #include<sys/socket.h>    
  4. #include<unistd.h>    
  5. #include<stdlib.h>    
  6. #include<errno.h>    
  7. #include<arpa/inet.h>    
  8. #include<netinet/in.h>    
  9. #include<string.h>    
  10. #include<signal.h>    
  11. #include<sys/wait.h>    
  12.   
  13. /* 
  14.  *網絡服務器,select參與調度 
  15.  * */  
  16.   
  17. //ERR_EXIT(M)是一個錯誤退出宏  
  18. #define ERR_EXIT(m) \    
  19.     do { \    
  20.         perror(m); \    
  21.         exit(EXIT_FAILURE); \    
  22.     } while (0)    
  23.     
  24.     
  25. int main(void)    
  26. {      
  27.     signal(SIGPIPE, SIG_IGN);  
  28.   
  29.     //1.創建套接字  
  30.     int listenfd;                 //被動套接字(文件描述符),即只可以accept, 監聽套接字    
  31.     if ((listenfd = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)      
  32.         ERR_EXIT("socket error"); //調用上邊的宏   
  33.     
  34.     struct sockaddr_in servaddr;  
  35.   
  36.     //memset(&servaddr, 0, sizeof(servaddr));    
  37.     //三個結構體成員  
  38.     //設置本地IP 和端口  
  39.     servaddr.sin_family = AF_INET;    
  40.     servaddr.sin_port = htons(8080);    
  41.     servaddr.sin_addr.s_addr = htonl(INADDR_ANY);     
  42.     /* servaddr.sin_addr.s_addr = inet_addr("127.0.0.1"); */    
  43.     /* inet_aton("127.0.0.1", &servaddr.sin_addr); */    
  44.         
  45.     //2.設置套接字屬性  
  46.     int on = 1;    
  47.     if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0)    
  48.         ERR_EXIT("setsockopt error");    
  49.       
  50.     //3.綁定  
  51.     if (bind(listenfd, (struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)    
  52.         ERR_EXIT("bind error");    
  53.     
  54.     //4.監聽  
  55.     if (listen(listenfd, SOMAXCONN) < 0) //listen應在socket和bind之後,而在accept之前    
  56.         ERR_EXIT("listen error");    
  57.         
  58.     struct sockaddr_in peeraddr; //傳出參數    
  59.     socklen_t peerlen = sizeof(peeraddr); //傳入傳出參數,必須有初始值    
  60.         
  61.     int conn; // 已連接套接字(變爲主動套接字,即可以主動connect)    
  62.     int i;    
  63.     int client[FD_SETSIZE];    
  64.     int maxi = 0; // client數組中最大不空閒位置的下標    
  65.     for (i = 0; i < FD_SETSIZE; i++)    
  66.         client[i] = -1;    
  67.     
  68.     int nready;    
  69.     int maxfd = listenfd;    
  70.     fd_set rset;    
  71.     fd_set allset;    
  72.     FD_ZERO(&rset);    
  73.     FD_ZERO(&allset);    
  74.     FD_SET(listenfd, &allset);    
  75.     
  76.     while (1) {    
  77.         rset = allset;    
  78.         nready = select(maxfd + 1, &rset, NULL, NULL, NULL);    
  79.         if (nready == -1) {    
  80.             if (errno == EINTR)    
  81.                 continue;    
  82.             ERR_EXIT("select error");    
  83.         }    
  84.     
  85.         if (nready == 0)    
  86.             continue;    
  87.     
  88.         if (FD_ISSET(listenfd, &rset)) {    
  89.             
  90.             conn = accept(listenfd, (struct sockaddr*)&peeraddr, &peerlen);  //accept不再阻塞    
  91.             if (conn == -1)    
  92.                 ERR_EXIT("accept error");    
  93.                 
  94.             for (i = 0; i < FD_SETSIZE; i++) {    
  95.                 if (client[i] < 0) {    
  96.                     client[i] = conn;    
  97.                     if (i > maxi)    
  98.                         maxi = i;    
  99.                     break;    
  100.                 }     
  101.             }    
  102.                 
  103.             if (i == FD_SETSIZE) {    
  104.                 fprintf(stderr, "too many clients\n");    
  105.                 exit(EXIT_FAILURE);    
  106.             }    
  107.     
  108.             printf("recv connect ip=%s port=%d\n", inet_ntoa(peeraddr.sin_addr),    
  109.                 ntohs(peeraddr.sin_port));    
  110.     
  111.             FD_SET(conn, &allset);    
  112.             if (conn > maxfd)    
  113.                 maxfd = conn;    
  114.     
  115.             if (--nready <= 0)    
  116.                 continue;    
  117.         }    
  118.     
  119.         for (i = 0; i <= maxi; i++) {    
  120.             conn = client[i];    
  121.             if (conn == -1)    
  122.                 continue;    
  123.     
  124.             if (FD_ISSET(conn, &rset)) {    
  125.                     
  126.                 char recvbuf[1024] = {0};    
  127.                 int ret = read(conn, recvbuf, 1024);    
  128.                 if (ret == -1)    
  129.                     ERR_EXIT("readline error");    
  130.                 else if (ret  == 0) { //客戶端關閉     
  131.                     printf("client close \n");    
  132.                     FD_CLR(conn, &allset);    
  133.                     client[i] = -1;    
  134.                     close(conn);    
  135.                 }    
  136.             
  137.                 fputs(recvbuf, stdout);    
  138.                 write(conn, recvbuf, strlen(recvbuf));    
  139.                     
  140.                 if (--nready <= 0)    
  141.                     break;     
  142.             }    
  143.         }    
  144.     }           
  145.     return 0;    
  146. }   
client.c

[cpp] view plain copy
  1. #include<stdio.h>    
  2. #include<sys/types.h>    
  3. #include<sys/socket.h>    
  4. #include<unistd.h>    
  5. #include<stdlib.h>    
  6. #include<errno.h>    
  7. #include<arpa/inet.h>    
  8. #include<netinet/in.h>    
  9. #include<string.h>    
  10.     
  11.     
  12. #define ERR_EXIT(m) \    
  13.     do { \    
  14.         perror(m); \    
  15.         exit(EXIT_FAILURE); \    
  16.     } while (0)    
  17.     
  18.     
  19.     
  20.     
  21. int main(void)    
  22. {    
  23.     int sock;    
  24.     if ((sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0)    
  25.         //  listenfd = socket(AF_INET, SOCK_STREAM, 0)    
  26.         ERR_EXIT("socket error");    
  27.     
  28.     
  29.     struct sockaddr_in servaddr;    
  30.     memset(&servaddr, 0, sizeof(servaddr));    
  31.     servaddr.sin_family = AF_INET;    
  32.     servaddr.sin_port = htons(8080);    
  33.     servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");    
  34.     /* inet_aton("127.0.0.1", &servaddr.sin_addr); */    
  35.     
  36.     if (connect(sock, (struct sockaddr *)&servaddr, sizeof(servaddr)) < 0)    
  37.         ERR_EXIT("connect error");    
  38.     struct sockaddr_in localaddr;    
  39.     char cli_ip[20];    
  40.     socklen_t local_len = sizeof(localaddr);    
  41.     memset(&localaddr, 0, sizeof(localaddr));    
  42.     if( getsockname(sock,(struct sockaddr *)&localaddr,&local_len) != 0 )    
  43.         ERR_EXIT("getsockname error");    
  44.     inet_ntop(AF_INET, &localaddr.sin_addr, cli_ip, sizeof(cli_ip));    
  45.     printf("host %s:%d\n", cli_ip, ntohs(localaddr.sin_port));     
  46.     
  47.     fd_set rset;    
  48.     FD_ZERO(&rset);    
  49.     int nready;    
  50.     int maxfd;    
  51.     int fd_stdin = fileno(stdin); //    
  52.     if (fd_stdin > sock)    
  53.         maxfd = fd_stdin;    
  54.     else    
  55.         maxfd = sock;    
  56.     char sendbuf[1024] = {0};    
  57.     char recvbuf[1024] = {0};    
  58.         
  59.     while (1)    
  60.     {    
  61.     
  62.         FD_SET(fd_stdin, &rset);    
  63.         FD_SET(sock, &rset);    
  64.         nready = select(maxfd + 1, &rset, NULL, NULL, NULL); //select返回表示檢測到可讀事件    
  65.         if (nready == -1)    
  66.             ERR_EXIT("select error");    
  67.     
  68.         if (nready == 0)    
  69.             continue;    
  70.     
  71.         if (FD_ISSET(sock, &rset))    
  72.         {    
  73.     
  74.             int ret = read(sock, recvbuf, sizeof(recvbuf));     
  75.             if (ret == -1)    
  76.                 ERR_EXIT("read error");    
  77.             else if (ret  == 0)   //服務器關閉    
  78.             {    
  79.                 printf("server close\n");    
  80.                 break;    
  81.             }    
  82.     
  83.             fputs(recvbuf, stdout);    
  84.             memset(recvbuf, 0, sizeof(recvbuf));    
  85.         }    
  86.     
  87.         if (FD_ISSET(fd_stdin, &rset))    
  88.         {    
  89.     
  90.             if (fgets(sendbuf, sizeof(sendbuf), stdin) == NULL)    
  91.                 break;    
  92.     
  93.             write(sock, sendbuf, strlen(sendbuf));    
  94.             memset(sendbuf, 0, sizeof(sendbuf));    
  95.         }    
  96.     }    
  97.     
  98.     close(sock);    
  99.     return 0;    
  100. }    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章