I/O多路复用之select系统调用

来源:互联网 发布:js导出excel 数字格式 编辑:程序博客网 时间:2024/06/05 21:08

I/O多路复用模型允许我们同时等待多个套接字描述符是否就绪。Linux系统为实现I/O多路复用提供的最常见的一个函数是select函数,该函数允许进程指示内核等待多个事件中的任何一个发生,并只有在一个或多个事件发生或经历一段指定的时间后才唤醒它。
作为一个例子,我们可以调用select,告知内核仅在下列情况发生时才返回:

  • 当集合{0, 4}中任意描述符准备好读时返回
  • 当集合{1, 2, 7}中任意描述符准备好写时返回
  • 已经历了10.2秒

也就是说,我们调用select可以告知内核我们对哪些描述符感兴趣以及等待多久时间。
select是一个复杂的函数,有许多不同的应用场景,我们将只讨论第一种场景:等待一组描述符准备好读。

#include <unistd.h>#include <sys/types.h>int select(int n, fd_set *fdset, NULL, NULL, struct timeval *timeout);FD_ZERO(fd_set *fdset);          // 将fdset初始为为空集合FD_CLR(int fd, fd_set *fdset);   // 从fdset清除fdFD_SET(int fd, fd_set *fdset);   // 将fd添加到fdsetFD_ISSET(int fd, fd_set *fdset); // fd是否存在于fdset 

我们来看下select函数的参数。参数n指定需要测试的描述符的数目,测试的描述符范围从0到n-1。第二个参数fdset指定需要测试的可读描述符集合。当fdset集合中有描述符可读,或者经历了timeout时间时,select将返回。当select返回时,作为一个副作用,select修改了参数fdset指向的描述符集合,这时fdset变成由读集合中准备好可以读了的描述符组成。select函数的返回值则指明了就绪集合的基数。值得注意的是,由于这个副作用,我们必须每次在调用select时都更新读集合。

#include <sys/types.h>#include <sys/socket.h>#include <stdio.h>#include <netinet/in.h>#include <sys/time.h>#include <sys/ioctl.h>#include <unistd.h>#include <stdlib.h>int main() {    int listenfd, connfd;    int server_len, client_len;    struct sockaddr_in server_address;    struct sockaddr_in client_address;    fd_set readfds, testfds;    // 创建套接字    listenfd = socket(AF_INET, SOCK_STREAM, 0);    // 命名套接字    server_address.sin_family = AF_INET;    server_address.sin_addr.s_addr = htonl(INADDR_ANY);    server_address.sin_port = htons(6240);    server_len = sizeof(server_address);    bind(listenfd, (struct sockaddr*)&server_address, server_len);    // 创建套接字队列    listen(listenfd, 5);    FD_ZERO(&readfds);    FD_SET(listenfd, &readfds);    // 等待客户请求    while (1) {        char ch;        int fd;        int nread;        // 同时检查监听套接字和已连接套接字        testfds = readfds;        printf("server waiting\n");        int result = select(FD_SETSIZE, &testfds, (fd_set*)0, (fd_set*)0, (struct timeval*)0);        if (result < 1) {            perror("select error");            exit(1);        }        for (fd = 0; fd < FD_SETSIZE; fd++) {            // 检查哪个描述符可读            if (FD_ISSET(fd, &testfds)) {                // 如果是一个新的客户连接请求                if (fd == listenfd) {                    client_len = sizeof(client_address);                    connfd = accept(listenfd, (struct sockaddr*)&client_address, &client_len);                    FD_SET(connfd, &readfds);                    printf("adding client on fd %d\n", connfd);                }                // 如果是旧的客户活动                else {                    ioctl(fd, FIONREAD, &nread);                    // 如果客户断开连接                    if (nread == 0) {                        close(fd);                        FD_CLR(fd, &readfds);                        printf("removing client on fd %d\n", fd);                    }                    // 客户请求数据到达                    else {                        read(fd, &ch, 1);                        sleep(5);                        printf("serving client on fd %d\n", fd);                        ch++;                        write(fd, &ch, 1);                    }                }            }        }    }}

上面的代码展示了如何使用select来编写多并发服务器的过程。服务器可以让select调用同时检查监听套接字和已连接套接字。一旦select指示有活动发生,就可以用FD_ISSET来遍历所有可能的文件描述符,以检查是哪个描述符上面有活动发生。
如果是监听套接字可读,这说明正有一个客户试图建立连接,此时就可以调用accept创建一个客户的已连接套接字而不用担心阻塞。如果是某个客户描述符准备好,这说明该描述符上有一个客户请求需要我们读取处理。如果读操作返回零字节,这表示有一个客户进程已结束,这时我们可以关闭该套接字并把它从描述符集合中删除。

参考资料

  1. 深入理解计算机系统,第2版,机械工业出版社
  2. Linux程序设计(第4版),Neil Matthew等著,人民邮电出版社,2010年
  3. UNIX 网络编程卷1:套接字联网API(第三版), W.Richard Stevens 等著
0 0
原创粉丝点击