select與poll的使用

 2.1. 如何管理多個連接?
“我想同時監控一個以上的文件描述符(fd)/連接(connection)/流(stream),應該怎麼辦?”

使用 select() 或 poll() 函數。

注意:select() 在BSD中被引入,而poll()是SysV STREAM流控制的產物。因此,這裏就有了平臺移植上的考慮:純粹的BSD系統可能仍然缺少poll(),而早一些的SVR3系統中可能沒有select(),儘管在SVR4中將其加入。目前兩者都是POSIX. 1g標準,(譯者注:因此在Linux上兩者都存在)

select()和poll()本質上來講做的是同一件事,只是完成的方法不一樣。兩者都通過檢驗一組文件描述符來檢測是否有特定的時間將在上面發生並在一定的時間內等待其發生。

[重要事項:無論select()還是poll()都不對普通文件起很大效用,它們着重用於套接口(socket)、管道(pipe)、僞終端(pty)、終端設備(tty)和其他一些字符設備,但是這些操作都是系統相關(system-dependent)的。]

2.1.1. 我如何使用select()函數?
select()函數的接口主要是建立在一種叫'fd_set'類型的基礎上。它('fd_set') 是一組文件描述符(fd)的集合。由於fd_set類型的長度在不同平臺上不同,因此應該用一組標準的宏定義來處理此類變量:

     fd_set set;
     FD_ZERO(&set);        /* 將set清零 */
     FD_SET(fd, &set);     /* 將fd加入set */
     FD_CLR(fd, &set);     /* 將fd從set中清除 */
     FD_ISSET(fd, &set);   /* 如果fd在set中則真 */
      
在過去,一個fd_set通常只能包含少於等於32個文件描述符,因爲fd_set其實只用了一個int的比特矢量來實現,在大多數情況下,檢查 fd_set能包括任意值的文件描述符是系統的責任,但確定你的fd_set到底能放多少有時你應該檢查/修改宏FD_SETSIZE的值。*這個值是系統相關的*,同時檢查你的系統中的select() 的man手冊。有一些系統對多於1024個文件描述符的支持有問題。[譯者注: Linux就是這樣的系統!你會發現sizeof(fd_set)的結果是128(*8 = FD_SETSIZE=1024) 儘管很少你會遇到這種情況。]

select的基本接口十分簡單:

     int select(int nfds, fd_set *readset, fd_set *writeset,
                fd_set *exceptset, struct timeval *timeout);
      
其中:

nfds     
      需要檢查的文件描述符個數,數值應該比是三組fd_set中最大數
      更大,而不是實際文件描述符的總數。
readset    
      用來檢查可讀性的一組文件描述符。
writeset
      用來檢查可寫性的一組文件描述符。
exceptset
      用來檢查意外狀態的文件描述符。(注:錯誤並不是意外狀態)
timeout
      NULL指針代表無限等待,否則是指向timeval結構的指針,代表最
      長等待時間。(如果其中tv_sec和tv_usec都等於0, 則文件描述符
      的狀態不被影響,但函數並不掛起)
      
函數將返回響應操作的對應操作文件描述符的總數,且三組數據均在恰當位置被修改,只有響應操作的那一些沒有修改。接着應該用FD_ISSET宏來查找返回的文件描述符組。

這裏是一個簡單的測試單個文件描述符可讀性的例子:

      int isready(int fd)
      {
          int rc;
          fd_set fds;
          struct timeval 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)
            return -1;
    
          return FD_ISSET(fd,&fds) ? 1 : 0;
      }
      
當然如果我們把NULL指針作爲fd_set傳入的話,這就表示我們對這種操作的發生不感興趣,但select() 還是會等待直到其發生或者超過等待時間。

[譯者注:在Linux中,timeout指的是程序在非sleep狀態中度過的時間,而不是實際上過去的時間,這就會引起和非Linux平臺移植上的時間不等問題。移植問題還包括在System V風格中select()在函數退出前會把timeout設爲未定義的 NULL狀態,而在BSD中則不是這樣, Linux在這點上遵從System V,因此在重複利用timeout指針問題上也應該注意。]

2.1.2. 我如何使用poll()?
poll ()接受一個指向結構'struct pollfd'列表的指針,其中包括了你想測試的文件描述符和事件。事件由一個在結構中事件域的比特掩碼確定。當前的結構在調用後將被填寫並在事件發生後返回。在SVR4(可能更早的一些版本)中的 "poll.h"文件中包含了用於確定事件的一些宏定義。事件的等待時間精確到毫秒 (但令人困惑的是等待時間的類型卻是int),當等待時間爲0時,poll()函數立即返回,-1則使poll()一直掛起直到一個指定事件發生。下面是pollfd的結構。

      struct pollfd {
          int fd;         /* 文件描述符 */
          short events;   /* 等待的事件 */
          short revents; /* 實際發生了的事件 */
      };
      
於select()十分相似,當返回正值時,代表滿足響應事件的文件描述符的個數,如果返回0則代表在規定事件內沒有事件發生。如發現返回爲負則應該立即查看 errno,因爲這代表有錯誤發生。

如果沒有事件發生,revents會被清空,所以你不必多此一舉。

這裏是一個例子

    /* 檢測兩個文件描述符,分別爲一般數據和高優先數據。如果事件發生
       則用相關描述符和優先度調用函數handler(),無時間限制等待,直到
       錯誤發生或描述符掛起。*/
   
    #include <stdlib.h>
    #include <stdio.h>
  
    #include <sys/types.h>
    #include <stropts.h>
    #include <poll.h>
  
    #include <unistd.h>
    #include <errno.h>
    #include <string.h>
  
    #define NORMAL_DATA 1
    #define HIPRI_DATA 2
  
    int poll_two_normal(int fd1,int fd2)
    {
        struct pollfd poll_list[2];
        int retval;
  
        poll_list[0].fd = fd1;
        poll_list[1].fd = fd2;
        poll_list[0].events = POLLIN|POLLPRI;
        poll_list[1].events = POLLIN|POLLPRI;
  
        while(1)
        {
            retval = poll(poll_list,(unsigned long)2,-1);
            /* retval 總是大於0或爲-1,因爲我們在阻塞中工作 */
  
            if(retval < 0)
            {
                fprintf(stderr,"poll錯誤: %s/n",strerror(errno));
                return -1;
            }
    
            if(((poll_list[0].revents&POLLHUP) == POLLHUP) ||
               ((poll_list[0].revents&POLLERR) == POLLERR) ||
               ((poll_list[0].revents&POLLNVAL) == POLLNVAL) ||
               ((poll_list[1].revents&POLLHUP) == POLLHUP) ||
               ((poll_list[1].revents&POLLERR) == POLLERR) ||
               ((poll_list[1].revents&POLLNVAL) == POLLNVAL))
              return 0;
  
            if((poll_list[0].revents&POLLIN) == POLLIN)
              handle(poll_list[0].fd,NORMAL_DATA);
            if((poll_list[0].revents&POLLPRI) == POLLPRI)
              handle(poll_list[0].fd,HIPRI_DATA);
            if((poll_list[1].revents&POLLIN) == POLLIN)
              handle(poll_list[1].fd,NORMAL_DATA);
            if((poll_list[1].revents&POLLPRI) == POLLPRI)
              handle(poll_list[1].fd,HIPRI_DATA);
        }
    }
      
2.1.3. 我是否可以同時使用SysV IPC和select()/poll()?
*不能。* (除非在AIX上,因爲它用一個無比奇怪的方法來實現這種組合)

一般來說,同時使用select()或poll()和SysV 消息隊列會帶來許多麻煩。SysV IPC的對象並不是用文件描述符來處理的,所以它們不能被傳遞給select()和 poll()。這裏有幾種解決方法,其粗暴程度各不相同:


完全放棄使用SysV IPC。 :-)

用fork(),然後讓子進程來處理SysV IPC,然後用管道或套接口和父進程 說話。父進程則使用select()。

同上,但讓子進程用select(),然後和父親用消息隊列交流。

安排進程發送消息給你,在發送消息後再發送一個信號。*警告*:要做好 這個並不簡單,非常容易寫出會丟失消息或引起死鎖的程序。

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