【计算机网络】多路转接之select

系统提供select()来实现多路转接

IO = 等 + 拷贝 -> select()只负责等待,可以一次等待多个fd

select()本身没有数据拷贝的能力,拷贝要read()/write()来完成

一、select的使用

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


① int nfds:select要监视多个fd中的最大的fd+1(比如要监视3,4,5 那nfds就是5+1=6)


fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout 这四个参数都是输入输出型参数

② struct timeval *timeout

设为nullptr -> 阻塞式监视;设为struct timbal timeout = {0, 0} -> 非阻塞式监视

设为struct timbal timeout = {5, 0} -> 5s以内阻塞式,超过5s,非阻塞返回一次(每隔5s返回一次)

假设到第3s时就返回了,那struct timbal timeout作为输出型参数就返回剩余时间2s(5-3=2)

返回值:ret>0:返回有几个fd就绪了;ret==0:超时返回了;ret<0:select调用失败


fd_set *readfds(读事件)  fd_set *writefds(写事件)  ⑤fd_set *exceptfds(异常事件)

select未来只关心三类事件:a. 读 b.写 c.异常 — 对于任何一个fd,都是这三种

fd_set:位图结构,表示文件描述符集合

输入:表示用户告诉内核,你要帮我关心一下,我给你的集合中的所有的fd的读事件(哪些fd上的读事件内核要关心)

比特位的位置,表示fd的数值;比特位的内容,表示是否关心

输出:内核告诉用户,你说要关心的多个fd中,有哪些已经就绪了

比特位的位置,表示fd的数值;比特位的内容,表示哪些fd所对应的事件已经就绪了

输入输出型参数的意义:让用户和内核之间互相沟通,互相知晓对方想要的或者关心的(这三个参数分别对应三类事件)


我们不能直接操作fd_set位图,系统提供了专门的接口去操作fd_set位图

void FD_CLR(int fd, fd_set *set); // 删除fd_set位图中的fd

int  FD_ISSET(int fd, fd_set *set); // 检测fd_set位图中是否存在fd

void FD_SET(int fd, fd_set *set); // 把fd设置进fd_set位图

void FD_ZERO(fd_set *set); // 清空fd_set位图

注意⚠️:能够添加的fd的个数一定是有上限的(1024个)

二、select的特点

1.select能同时等待的文件fd是有上限的,除非重新改内核,否则无法解决

2.必须借助第三方数组,来维护合法的fd

3.select的大部分参数是输入输出型的。调用select前,要重新设置所有的fd;调用之后,我们还有检查更新所有的fd(遍历成本)

4.select为什么第一个参数是最大fd+1呢?确定遍历范围 — 内核层面

5.select采用位图,用户->内核,内核->用户,来回的进行数据拷贝(拷贝成本)

### 多路转接中 `select` 函数的使用方法与实现原理 #### 1. `select` 函数的基本概念 `select` 是一种经典的 I/O 多路复用机制,允许应用程序同时监视多个文件描述符的状态变化。它通过等待一组文件描述符中的任意一个变为可操作状态来提高程序效率[^1]。 #### 2. `select` 函数的定义 `select` 的函数原型如下: ```c int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout); ``` - **`nfds`**: 表示要监听的最大文件描述符编号加一(即最高文件描述符值+1)。这是为了优化内核内部的数据处理逻辑。 - **`readfds`**: 指向一个 `fd_set` 类型的指针,表示需要监听其是否有数据可读的一组文件描述符集合。 - **`writefds`**: 指向一个 `fd_set` 类型的指针,表示需要监听其是否可以写入的一组文件描述符集合。 - **`exceptfds`**: 指向一个 `fd_set` 类型的指针,表示需要监听其是否存在异常情况的一组文件描述符集合。 - **`timeout`**: 设置超时时间,如果为 NULL 则表示阻塞直到某个文件描述符准备好;如果指定时间为零则立即返回并检查当前状态。 每次调用 `select` 后,传入的 `fd_set` 结构会被修改,只有那些满足条件的文件描述符才会保留下来[^4]。 #### 3. `select` 的工作流程 以下是 `select` 的典型执行过程: - 用户初始化三个 `fd_set` 集合(分别对应读、写和异常),并将感兴趣的文件描述符加入这些集合。 - 调用 `select` 并将上述集合以及超时参数传递给内核。 - 内核接收请求后,在指定的时间范围内持续监测所有文件描述符的状态。 - 当某些文件描述符发生变化(如可读、可写或发生异常)时,内核更新对应的 `fd_set` 集合并通知用户进程。 - 用户再次检查更新后的 `fd_set` 来确认具体哪个文件描述符发生了变化,并采取相应措施。 这种设计使得单线程能够高效管理大量连接而不必频繁切换上下文[^5]。 #### 4. 示例代码:基于 `select` 的简单 TCP 服务器 下面是一个简单的例子展示如何利用 `select` 构建一个多客户端支持的服务端程序: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #define MAX_CLIENTS 64 #define BUFFER_SIZE 1024 int main() { int server_fd, new_socket; struct sockaddr_in address; int addrlen = sizeof(address); // 创建 socket 文件描述符 if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { perror("Socket failed"); exit(EXIT_FAILURE); } // 绑定地址信息到套接字 address.sin_family = AF_INET; address.sin_addr.s_addr = INADDR_ANY; address.sin_port = htons(8080); bind(server_fd, (struct sockaddr *)&address, sizeof(address)); listen(server_fd, 3); fd_set readfds; FD_ZERO(&readfds); FD_SET(server_fd, &readfds); while (1) { fd_set tmp_fds = readfds; int activity = select(MAX_CLIENTS, &tmp_fds, NULL, NULL, NULL); if ((activity < 0) && (errno != EINTR)) { printf("Select error\n"); } // 如果有新的连接到来 if (FD_ISSET(server_fd, &tmp_fds)) { if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen))<0){ perror("Accept failed"); continue; } FD_SET(new_socket, &readfds); } // 检查其他现有的连接是否有数据到达 for (int i = 0; i < MAX_CLIENTS; ++i) { int sd = i; if (sd == server_fd || !FD_ISSET(sd, &tmp_fds)) continue; char buffer[BUFFER_SIZE]; memset(buffer, '0', BUFFER_SIZE); ssize_t valread = recv(sd , buffer, BUFFER_SIZE, 0); if(valread <= 0){ close(sd); FD_CLR(sd, &readfds); }else{ send(sd , buffer , strlen(buffer), 0 ); } } } return 0; } ``` 此代码片段展示了如何创建一个基本的回显服务器,其中每个新建立的连接都会被添加至 `select` 所监控的集合之中[^3]。 #### 5. 缺点分析 尽管功能强大,但 `select` 存在一些固有的局限性: - 它所能处理的最大文件描述符数目受限于系统架构下的固定大小(通常不超过 1024)。 - 每次调用都需重新构建整个 `fd_set` 数据结构,增加了不必要的计算负担。 - 运行期间涉及大量的用户空间与核心空间之间的数据交换,进一步降低了性能表现。 --- ###
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值