天天看點

select()——老版的同步IO多路複用

  • 英文小冊原文位址:​​beej.us/guide/bgnet…​​
  • 作者:Beej
  • 中文翻譯位址:​​www.chanmufeng.com/posts/netwo…​​

我假設你已經讀過​

​poll()​

​的用法了,是以直接進入主題。

​select()​

​​可以同時監聽多個socket,當有你感興趣的(多個)事件中的任何一個發生,核心才會喚醒​

​select()​

​​。如果你真的想知道的話,​

​select()​

​會告訴你哪些socket是可以讀取的,哪些是可以寫入的,哪些引發了異常。

警告:随着連接配接數越來越多,​

​select()​

​函數會變得巨慢!這種情況下,推薦你使用​​libevent​​這樣的事件庫。它會嘗試使用你系統上可用的最快方法,獲得更好的性能。

看一下​

​select()​

​的文法:

#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

int select(int numfds, fd_set *readfds, fd_set *writefds,
           fd_set *exceptfds, struct timeval *timeout);      

​select()​

​​函數監聽多種類型檔案描述符的集合,尤其是​

​readfd​

​​、​

​writefd​

​​和​

​exceptfd​

​​。如果你想要知道你是否能從标準輸入(standard input)及某個socket descriptor(用 ​

​sockfd​

​​ 表示)中進行讀取,隻要将标準輸入的檔案描述符表——​

​0​

​​ 與 ​

​sockfd​

​​ 新增到 ​

​readfds​

​​ 中。參數​

​numfds​

​​應設定為最高檔案描述符的值加1。在本例中,它應該設定為​

​sockfd+1​

​​,因為它肯定高于标準輸入——​

​0​

​。

當​

​select()​

​​傳回時,​

​readfds​

​​将被修改,來反映你選擇的哪些file descriptor可以讀取。你可以使用下面的宏​

​FD_ISSET()​

​測試它們。

在進一步讨論之前,我先說一下如何操作這些file descriptor集合,每個集合都是​

​fd_set​

​類型,下面的宏在此類型上運作:

函數 描述
FD_SET(int fd, fd_set *set); 将fd加入到set
FD_CLR(int fd, fd_set *set); 從set種移除fd
FD_ISSET(int fd, fd_set *set); 若fd在set中,傳回true
FD_ZERO(fd_set *set); 清空set

最後,這個奇怪的​

​struct timeval​

​是什麼?

有時候,你不想永遠一直等着别人給你發送資料。也許每隔一段時間你就想在終端上列印“Still Going…”,即使什麼都沒有發生。這個struct允許你指定逾時時間段。如果超過了時間,​

​select()​

​仍然沒有找到任何就緒的file descriptor,它将傳回以便你可以繼續進行處理。

​struct timeval​

​ 長這樣:

struct timeval {
  int tv_sec;     // seconds
  int tv_usec;    // microseconds
};      

隻需将​

​tv_sec​

​​設定為等待的秒數,将​

​tv_usec​

​設定成等待的微秒數。你沒看錯,這是_micro_seconds,而不是毫秒。一毫秒有1000微秒,一秒鐘有1000毫秒。是以,每秒有1000000微秒。為什麼是“usec”?“u”應該看起來像我們用來表示“micro”的希臘字母μ(Mu)。

此外,當函數傳回時,可能會更新​

​timeout​

​以顯示剩餘時間。這取決于你正在運作的Unix類型。

哇!我們有一個微秒級别的計時器!好吧,别指望它。無論你将​

​struct timeval​

​設定得多麼小,你可能還是要等待一小段的 standard Unix timeslice(标準 Unix 時間片段)。

另一件有意思的事情是:如果将​

​struct timeval​

​​中的字段設定為0,​

​select()​

​​會在輪詢過 sets 中的每個 file descriptor 之後立即timeout。如果将參數​

​timeout​

​​設定為​

​NULL​

​​,它将永遠不會timeout,而是陷入等待狀态,直到至少一個file descriptor已經就緒。如果你不在乎等待時間,可以在​

​select()​

​​中将其設定為​

​NULL​

​。

下面的代碼段等待2.5秒,等待标準輸入中出現某些内容:

/*
** select.c -- a select() demo
*/

#include <stdio.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define STDIN 0  // file descriptor for standard input

int main(void)
{
    struct timeval tv;
    fd_set readfds;

    tv.tv_sec = 2;
    tv.tv_usec = 500000;

    FD_ZERO(&readfds);
    FD_SET(STDIN, &readfds);

    // don't care about writefds and exceptfds:
    select(STDIN+1, &readfds, NULL, NULL, &tv);

    if (FD_ISSET(STDIN, &readfds))
        printf("A key was pressed!\n");
    else
        printf("Timed out.\n");

    return 0;
}      

如果你用的是行緩沖(line buffered)的終端,那麼你從鍵盤輸入資料後應該要盡快按下 Enter,否則程式就會發生 timeout。

行緩存:标準輸出流遇到換行符\n時沖刷緩存。

你現在可能在想,這個方法用在需要等待資料的 datagram socket 上應該會很棒,你是對的:這可能确實是個不錯的方法。有些Unix可以以這種方式使用​

​select()​

​,有些則不能。如果你想嘗試的話,你應該參考一下你系統上的man手冊上是怎麼寫的。