IO复用之select函数

前面我们所有的代码都是通过read和write操作, 每次只能处理一个IO请求. 但是IO复用可以在同时处理多个socket的IO请求. 而且IO复用与线程连用是最常见的搭配.

本节介绍IO多路复用select函数.

函数原型

#include <sys/types.h>
#include <sys/times.h>
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);

成功 : 返回事件发生的个数, 并且将返回改变的文件符保存在对应的集合中.

失败 : 返回负数. 如果被中断不可重启, 会设置errno的值为EINTR.

超时 : 返回0.

函数参数

函数参数有点多, 可能一时间掌握不了可以慢慢做实验体验每个参数的意义.

  • nfds : 打开的最大文件描述符 + 1 (注意 : 是最大 + 1)
  • readfds : 读描述符集合
  • writefds : 写描述符集合
  • exceptfds : 错误描述符集合
  • timeout : 定时
    1. timeout == NULL : select函数永远阻塞等待监视文件描述符集合中某个文件描述符发生变化为止.
    2. timeout == 0 : 函数为非阻塞函数, 不管有无等待的文件描述符发生变化都会返回
    3. timeout > 0 : 等待的超时时间, 即函数在timeout时间内阻塞, 超时时间之内有事件到来就返回了, 否则在超时后不管怎样一定返回.

描述符集合

select函数有一个叫文件描述符集合的数据类型fd_set, fd_set很像信号集sigset_t而且两者的实现都很相似. sigset_t信号集有32位之多, 每一位表示一个信号. 同样fd_set描述符集默认有1024位(最大值用FD_SETSIZE表示), 每一位表示一个文件描述符.

sigset_t有一系列sigset函数, fd_set也同样有一系列的操作函数 :

// 从描述符集中删除fd
FD_CLR(int fd, fd_set *fdset);

// 清除描述符集所有设置的描述符
FD_ZERO(fd_set *fdset);

// 将fd添加到描述符集中
FD_SET(int fd, fd_set *fdset);

// 验证fd是否在描述符集中
FD_ISSET(int fd, fd_set *fdset);

函数调用

我们使用本节介绍的select函数来修改之前的代码, 实验验证与write函数的不同.

将客户端的代码做了修改, 这里只将修改的代码贴出分析. 完整的代码 select_client.c

// 客户端
int client(int port, const char *cli_addr)
{
    int sockfd;
    sockfd = Socket(0);
    Connect(sockfd, port, cli_addr);

    char buf[1024];
    int n;
	
    // 1
    fd_set rfds, testrfds;
    FD_ZERO(&rfds);
    FD_SET(sockfd, &rfds);
    FD_SET(STDIN_FILENO, &rfds);
    while(1)
    {
        // 2
		testrfds = rfds;
		if(select(sockfd + 1, &testrfds, NULL, NULL, NULL) < 0)
		    EXIT("select");
	
        // 3
		if(FD_ISSET(STDIN_FILENO, &testrfds))
		{
		    n = read(STDIN_FILENO, buf, sizeof(buf));
		    if(n == 0)
				break;
		    send(sockfd, &buf, strlen(buf), 0);
		}
        // 4
		if(FD_ISSET(sockfd, &testrfds))
		{
		    n = recv(sockfd, buf, sizeof(buf), 0);
		    if(n == 0)
				break;
		    write(STDOUT_FILENO, buf, n);
		}
    }
    close(sockfd);

    return 0;
}

需要注意几点

  1. 将新的描述符集zero再添加文件描述符
  2. 因为select返回会重置所有的描述符集, 所以必须将原描述符集保存下来, 并且每次循环都要重新设置
  3. 需要对每一个操作的描述符进行判断

最终运行的结果可以看出正常的回射任何问题, 并且服务端关闭后客户端立马也就关闭了.

IO复用之select函数

总结

  • 掌握select函数的应用
  • 用select函数将服务器的多进程改成单进程的IO多路复用. 完整代码select_service.c.

参考

I/O多路复用技术(multiplexing)是什么?

上一篇:肥猫学习日记------------------Linux下的简单UDP协议建立


下一篇:网络编程:linux下的socket套接字编程之TCP服务器