天天看点

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手册上是怎么写的。