13 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. 需要对每一个操作的描述符进行判断

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

在这里插入图片描述


总结

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

参考

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

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
`select` 函数是一种 I/O 多路复用的机制,用于同时监听多个文件描述符的状态变化。它可以使用单个系统调用同时监视多个文件描述符,并在有一个或多个文件描述符就绪时通知应用程序。 `select` 函数的原型如下: ```c #include <sys/select.h> int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` 参数说明: - `nfds`:待监视的最大文件描述符加 1。 - `readfds`:可读文件描述符集合。 - `writefds`:可写文件描述符集合。 - `exceptfds`:异常条件文件描述符集合。 - `timeout`:超时时间,如果为 `NULL` 则为阻塞模式,即一直等待直到有文件描述符就绪;如果为零时间(`tv_sec` 和 `tv_usec` 均为 0),则为非阻塞模式,即立即返回;否则为指定超时时间。 `select` 函数的返回值表示就绪文件描述符的数量,如果返回值为 0,则表示超时;如果返回值为 -1,则表示出错。 使用 `select` 函数的一般流程如下: 1. 初始化需要监视的文件描述符集合。 2. 调用 `select` 函数等待文件描述符就绪。 3. 检查返回值确定哪些文件描述符已经就绪。 4. 处理就绪的文件描述符。 5. 重复上述步骤。 需要注意的是,`select` 函数有一些限制,比如只能监视的文件描述符数量有限,一般为 1024 或更小。此外,在某些平台上,使用 `select` 函数可能会有性能上的限制,可以考虑使用更高效的机制,如 `poll` 或 `epoll`。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值