当前位置: 首页 > news >正文

UNIX下C语言编程与实践64-UNIX 并发 Socket 编程:I/O 多路复用 select 函数与并发处理

在 UNIX 并发 Socket 编程中,I/O 多路复用是解决“单进程处理多套接字”的核心技术,而 select 函数是其中最经典、最基础的实现。select 函数通过“批量监控多个文件描述符(含套接字)的事件状态”,让单个进程无需创建多进程/线程,即可并发处理“接收连接”“收发数据”等多种事件,有效平衡了 CPU 资源占用与并发效率。本文将详细解析 select 函数的功能、参数与使用方法,结合实例演示其在并发 Socket 处理中的应用,分析优缺点与常见问题,并拓展对比其他 I/O 多路复用方案。

一、核心概念:I/O 多路复用与 select 函数

I/O 多路复用的本质是“让内核帮忙监控多个 I/O 对象(如套接字),当其中任意一个或多个 I/O 对象就绪(有事件发生)时,内核通知进程进行处理”。select 函数是 UNIX 系统中最早实现 I/O 多路复用的接口,支持监控“读”“写”“异常”三类事件。

1.1 为什么需要 select 函数?

在 select 出现之前,处理多套接字并发主要有两种方案,但均存在明显缺陷:

  • 阻塞套接字+多进程/线程:为每个连接创建一个进程/线程,虽能实现并发,但进程/线程切换开销大、内存占用高,难以支撑大规模并发;
  • 非阻塞套接字+轮询:单个进程循环查询所有非阻塞套接字,虽无切换开销,但无事件时仍频繁调用函数,导致 CPU 占用率居高不下(如 100%)。

select 函数的价值select 结合了两种方案的优点——通过内核监控套接字事件,进程仅在“有事件发生”时才被唤醒处理,既避免了进程/线程切换开销,又不会浪费 CPU 资源,是早期 UNIX 系统中实现轻量级高并发的首选方案。

1.2 select 函数的核心作用

对 select 函数的定义是:“监控多个文件描述符的读、写、异常事件,阻塞等待直到有事件发生或超时,返回就绪的文件描述符数量”。具体来说,select 可实现以下核心功能:

  • 同时监控多个套接字(如侦听套接字、连接套接字);
  • 区分“读就绪”(如客户端发送数据、新连接请求)、“写就绪”(如套接字发送缓冲区空闲)、“异常就绪”(如连接异常);
  • 支持设置超时时间,避免进程永久阻塞;
  • 仅通知进程“有事件发生的套接字”,无需遍历所有套接字。

二、select 函数详解:原型、参数与返回值

详细给出了 select 函数的原型、参数含义及使用规范,这是正确使用 select 的基础。

2.1 函数原型

#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);

2.2 参数详细说明

select 函数的参数较多,且部分参数为“输入输出型”(如文件描述符集合),需精确理解每个参数的作用:

参数数据类型功能说明关键注意事项
nfdsint需要监控的“最大文件描述符编号 + 1”必须正确设置——若监控的套接字描述符为 3、5、7,则 nfds 需设为 8(7+1);若设置过小,编号超过 nfds-1 的套接字将无法被监控
readfdsfd_set *“读事件”监控集合——需监控“读就绪”的文件描述符(如套接字)加入此集合输入:告知内核需要监控哪些套接字的读事件;
输出:内核修改集合,仅保留“读就绪”的套接字
writefdsfd_set *“写事件”监控集合——需监控“写就绪”的文件描述符加入此集合输入:告知内核需要监控哪些套接字的写事件;
输出:内核修改集合,仅保留“写就绪”的套接字;
若无需监控写事件,可设为 NULL
exceptfdsfd_set *“异常事件”监控集合——需监控“异常就绪”的文件描述符加入此集合输入:告知内核需要监控哪些套接字的异常事件;
输出:内核修改集合,仅保留“异常就绪”的套接字;
若无需监控异常事件,可设为 NULL
timeoutstruct timeval *超时时间——设置 select 函数的阻塞时长
  • NULL:永久阻塞,直到有事件发生才返回;
  • 成员全为 0(tv_sec=0, tv_usec=0):立即返回,不阻塞(轮询模式);
  • 非零值:阻塞 tv_sec 秒 + tv_usec 微秒,超时后无论有无事件均返回;
  • 注意select 调用后会修改 timeout 的值,若需重复调用,需重新初始化。

2.3 关键数据类型:fd_set 与 struct timeval

使用 select 函数需依赖两个核心数据类型,对其定义与作用有明确说明:

2.3.1 fd_set:文件描述符集合

fd_set 是一个“位图结构”(或动态数组),用于存储需要监控的文件描述符。由于 fd_set 的实现依赖系统,用户无法直接操作其成员,需通过系统提供的宏函数进行管理(见下文 2.4 节)。

重要限制fd_set 的大小由宏 FD_SETSIZE 定义(默认通常为 1024),这意味着 select 最多只能监控 1024 个文件描述符,是 select 函数的核心缺陷之一。

2.3.2 struct timeval:超时时间结构

struct timeval 用于定义 select 函数的超时时间,结构定义如下:

struct timeval {long tv_sec;   // 秒数long tv_usec;  // 微秒数(1 秒 = 1000000 微秒)
};

示例:设置超时时间为 100 毫秒(0.1 秒):

struct timeval timeout;
timeout.tv_sec = 0;    // 0 秒
timeout.tv_usec = 100000; // 100000 微秒 = 100 毫秒

2.4 文件描述符集合操作宏

fd_set 集合的操作必须通过系统提供的宏函数完成,不可直接修改其成员。常用的宏函数有 4 个,功能如下表所示:

宏函数原型功能说明示例
FD_ZEROvoid FD_ZERO(fd_set *fdset);清空 fdset 集合,移除所有文件描述符fd_set readfds; FD_ZERO(&readfds);
FD_SETvoid FD_SET(int fd, fd_set *fdset);将文件描述符 fd 加入 fdset 集合FD_SET(listen_sock, &readfds); // 监控侦听套接字的读事件
FD_CLRvoid FD_CLR(int fd, fd_set *fdset);将文件描述符 fd 从 fdset 集合中移除FD_CLR(conn_sock, &readfds); // 停止监控连接套接字
FD_ISSETint FD_ISSET(int fd, fd_set *fdset);判断 fd 是否在 fdset 集合中(即是否就绪),返回非 0 表示就绪,0 表示未就绪if (FD_ISSET(listen_sock, &readfds)) { /* 处理新连接 */ }

常见错误:调用 select 前未使用 FD_ZERO 清空集合,直接调用 FD_SET,导致集合中残留旧的文件描述符,引发“误判就绪事件”的问题。

2.5 select 函数的返回值

select 函数的返回值是判断“事件处理结果”的关键,对其有明确说明:

  • 返回值 > 0:成功,返回“就绪的文件描述符总数”(读、写、异常集合中就绪的总数);
  • 返回值 = 0:超时,无任何文件描述符就绪;
  • 返回值 = -1:失败,设置 errno(如 EINTR 表示调用被信号中断,EBADF 表示存在无效的文件描述符)。

示例:处理 select 返回值:

int ready = select(nfds, &readfds, NULL, NULL, &timeout);
if (ready == -1) {if (errno == EINTR) {continue; // 被信号中断,重新调用 select}perror("select() failed");exit(EXIT_FAILURE);
} else if (ready == 0) {printf("select timeout, no event\n");continue;
} else {printf("ready file descriptors: %d\n", ready);// 处理就绪事件
}

三、使用 select 实现并发 Socket 处理:完整实例

给出了使用 select 函数实现并发 Socket 处理的核心思路:“监控侦听套接字的读事件(处理新连接)和连接套接字的读事件(处理数据接收),通过 FD_ISSET 判断就绪套接字并处理”。以下基于该思路实现一个完整的 TCP 服务器实例。

3.1 实例需求

实现一个 TCP 服务器,支持以下功能:

  • 使用 select 同时监控“侦听套接字”(处理新连接)和“连接套接字”(处理数据接收);
  • 支持多个客户端同时连接,接收客户端发送的数据并回复“已收到”;
  • 设置超时时间为 100 毫秒,避免永久阻塞;
  • 客户端关闭连接时,及时清理资源,停止监控该套接字。

3.2 完整代码实现

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>// 配置参数
#define PORT 9001          // 侦听端口
#define MAX_CONN 1024      // 最大连接数(不超过 FD_SETSIZE)
#define BUF_SIZE 1024      // 数据缓冲区大小
#define SELECT_TIMEOUT 100 // select 超时时间(毫秒)// 错误处理宏
#define ERROR_CHECK(ret, msg) \if (ret == -1) { \perror(msg); \exit(EXIT_FAILURE); \}// 将套接字设为非阻塞模式(select 配合非阻塞套接字,避免处理事件时阻塞)
int set_nonblock(int sockfd) {int flags = fcntl(sockfd, F_GETFL, 0);ERROR_CHECK(flags, "fcntl(F_GETFL) failed");if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) {perror("fcntl(F_SETFL) failed");return -1;}return 0;
}int main() {int listen_sock;                  // 侦听套接字struct sockaddr_in serv_addr;     // 服务器地址结构int conn_socks[MAX_CONN] = {0};   // 已连接套接字列表(0 表示无效)int conn_count = 0;               // 当前已连接客户端数量fd_set readfds;                  // select 读事件集合int max_fd;                       // 监控的最大文件描述符struct timeval timeout;           // select 超时时间// 步骤1:创建侦听套接字listen_sock = socket(AF_INET, SOCK_STREAM, 0);ERROR_CHECK(listen_sock, "socket() failed");printf("创建侦听套接字成功,listen_sock = %d\n", listen_sock);// 步骤2:设置端口复用(避免 TIME_WAIT 状态占用端口)int reuse = 1;ERROR_CHECK(setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)), "setsockopt(SO_REUSEADDR) failed");// 步骤3:绑定地址与端口memset(&serv_addr, 0, sizeof(serv_addr));serv_addr.sin_family = AF_INET;serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡serv_addr.sin_port = htons(PORT);ERROR_CHECK(bind(listen_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)), "bind() failed");// 步骤4:设置侦听ERROR_CHECK(listen(listen_sock, MAX_CONN), "listen() failed");printf("服务器已启动,监听端口 %d(最大连接数:%d)\n", PORT, MAX_CONN);// 步骤5:将侦听套接字设为非阻塞(避免 accept 时阻塞)ERROR_CHECK(set_nonblock(listen_sock), "set_nonblock(listen_sock) failed");// 步骤6:进入 select 循环,处理并发事件while (1) {// -------------------------- 阶段1:初始化 select 参数 --------------------------// 1.1 清空并初始化读事件集合FD_ZERO(&readfds);// 将侦听套接字加入集合(监控新连接请求)FD_SET(listen_sock, &readfds);max_fd = listen_sock; // 初始最大文件描述符为侦听套接字// 将所有已连接套接字加入集合(监控数据接收)for (int i = 0; i < conn_count; i++) {int curr_sock = conn_socks[i];if (curr_sock > 0) {FD_SET(curr_sock, &readfds);// 更新最大文件描述符(select 要求)if (curr_sock > max_fd) {max_fd = curr_sock;}}}// 1.2 初始化超时时间(每次调用 select 前需重新设置,因 select 会修改其值)timeout.tv_sec = 0;timeout.tv_usec = SELECT_TIMEOUT * 1000; // 毫秒 → 微秒// -------------------------- 阶段2:调用 select 监控事件 --------------------------int ready = select(max_fd + 1, &readfds, NULL, NULL, &timeout);if (ready == -1) {if (errno == EINTR) {printf("select 被信号中断,重新监控\n");continue;}perror("select() failed");break;} else if (ready == 0) {// 超时无事件,继续循环(无需处理)continue;}// -------------------------- 阶段3:处理就绪事件 --------------------------// 3.1 处理侦听套接字的读事件(新连接请求)if (FD_ISSET(listen_sock, &readfds)) {struct sockaddr_in client_addr;socklen_t client_addr_len = sizeof(client_addr);// 非阻塞 accept:此时必有新连接,不会返回 EAGAINint new_conn = accept(listen_sock, (struct sockaddr*)&client_addr, &client_addr_len);if (new_conn > 0) {// 检查是否超过最大连接数if (conn_count >= MAX_CONN) {printf("警告:已达最大连接数(%d),拒绝新客户端连接\n", MAX_CONN);close(new_conn);continue;}// 将新连接套接字设为非阻塞(避免 recv 时阻塞)if (set_nonblock(new_conn) == -1) {close(new_conn);continue;}// 将新连接加入列表conn_socks[conn_count++] = new_conn;printf("新客户端连接:IP=%s, Port=%d, conn_sock=%d(当前连接数:%d)\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_conn, conn_count);} else {perror("accept() failed");}}// 3.2 处理已连接套接字的读事件(数据接收)for (int i = 0; i < conn_count; i++) {int curr_sock = conn_socks[i];// 判断当前套接字是否就绪(读事件)if (curr_sock > 0 && FD_ISSET(curr_sock, &readfds)) {char buf[BUF_SIZE] = {0};// 非阻塞 recv:此时必有数据或连接关闭,不会返回 EAGAINssize_t recv_len = recv(curr_sock, buf, BUF_SIZE - 1, 0);if (recv_len > 0) {// 成功接收数据,打印并回复客户端printf("从 conn_sock=%d 接收数据:%s(长度:%zd 字节)\n", curr_sock, buf, recv_len);// 回复客户端(非阻塞 send,此处省略错误处理)const char *reply = "Server received: ";send(curr_sock, reply, strlen(reply), 0);send(curr_sock, buf, recv_len, 0);} else if (recv_len == 0) {// 客户端正常关闭连接printf("conn_sock=%d 客户端正常关闭连接(当前连接数:%d→%d)\n", curr_sock, conn_count, conn_count - 1);close(curr_sock);// 清理连接列表:将最后一个有效套接字移到当前位置,填补空缺conn_socks[i] = conn_socks[--conn_count];conn_socks[conn_count] = 0; // 标记为无效i--; // 重新检查当前位置(因已替换为新套接字)} else {// 连接异常(如客户端强制关闭)perror("recv() failed");printf("conn_sock=%d 连接异常,关闭(当前连接数:%d→%d)\n", curr_sock, conn_count, conn_count - 1);close(curr_sock);conn_socks[i] = conn_socks[--conn_count];conn_socks[conn_count] = 0;i--;}}}}// 步骤7:清理资源(理论上不会到达此处)close(listen_sock);for (int i = 0; i < conn_count; i++) {if (conn_socks[i] > 0) {close(conn_socks[i]);}}return 0;
}

3.3 实例代码解析

代码“select 并发处理”的核心流程,关键步骤解析如下:

3.3.1 套接字非阻塞设置

特别指出,select 函数虽能通知“事件就绪”,但后续的 acceptrecv 等函数仍可能阻塞(如极端情况下事件被其他进程抢占)。因此,建议将所有套接字设为非阻塞模式,避免单个套接字的阻塞影响整个进程的并发处理。

示例中,listen_sock 和 new_conn 均通过 set_nonblock 函数设为非阻塞,确保 accept 和 recv 不会阻塞。

3.3.2 select 参数的动态初始化

强调,select 函数存在两个“输入输出型参数”的特性,需特别注意:

  • 文件描述符集合(readfds)select 调用后会修改集合,仅保留“就绪的套接字”,因此每次调用 select 前,需重新调用 FD_ZERO 清空集合,并通过 FD_SET 重新加入需要监控的套接字;
  • 超时时间(timeout)select 调用后会将超时时间修改为“剩余时间”(如设置 100ms 超时,50ms 后有事件发生,timeout 会被改为 50ms),因此每次调用 select 前需重新初始化 timeout 的值。

示例中,每次循环都会重新初始化 readfds 和 timeout,避免因参数残留导致功能异常。

3.3.3 就绪事件的处理逻辑

对“select 就绪事件处理”的建议是:“先处理侦听套接字的新连接,再处理连接套接字的数据接收”,示例严格遵循该逻辑:

  1. 新连接处理:若 listen_sock 就绪(FD_ISSET(listen_sock, &readfds)),调用 accept 接收新连接,设为非阻塞并加入连接列表;
  2. 数据接收处理:遍历连接列表,若 curr_sock 就绪,调用 recv 接收数据,根据返回值处理“正常接收”“客户端关闭”“连接异常”三种情况;
  3. 连接列表清理:客户端关闭或连接异常时,及时 close 套接字,调整连接列表(用最后一个有效套接字填补空缺),避免无效套接字占用资源。

3.4 运行结果

编译并运行服务器,使用多个 telnet 或 nc 客户端连接并发送数据,运行结果如下:

创建侦听套接字成功,listen_sock = 3
服务器已启动,监听端口 9001(最大连接数:1024)
新客户端连接:IP=127.0.0.1, Port=54321, conn_sock=4(当前连接数:1)
新客户端连接:IP=127.0.0.1, Port=54322, conn_sock=5(当前连接数:2)
从 conn_sock=4 接收数据:Hello select!(长度:13 字节)
从 conn_sock=5 接收数据:I/O multiplexing(长度:16 字节)
conn_sock=4 客户端正常关闭连接(当前连接数:2→1)
从 conn_sock=5 接收数据:Goodbye(长度:7 字节)

结果表明,服务器成功通过 select 同时处理了“新连接请求”和“多客户端数据接收”,实现了并发处理。

四、select 函数的优缺点分析

对 select 函数的优缺点有客观总结,这是选择是否使用 select 的关键依据。

4.1 优点

  • 并发处理能力:支持同时监控多个套接字,单个进程即可实现多客户端并发,避免了多进程/线程的切换开销;
  • 阻塞模式节省 CPU:仅在“有事件发生”或“超时”时返回,无事件时进程休眠,CPU 占用率极低(如 0%~1%);
  • 跨平台兼容性好select 是 POSIX 标准接口,几乎所有 UNIX-like 系统(如 Linux、BSD、macOS)和 Windows 系统均支持,代码可移植性高;
  • 支持多种事件类型:可同时监控“读”“写”“异常”三类事件,满足大多数 Socket 编程需求(如发送数据前检查写就绪,避免发送阻塞);
  • 实现简单:接口设计清晰,配合文件描述符集合宏函数,上手门槛低,适合初学者学习 I/O 多路复用原理。

4.2 缺点(重点强调)

核心缺陷select 函数的最大缺陷是“文件描述符数量限制”和“效率随套接字数量增长而下降”,这使其在大规模并发场景(如 1000+ 客户端)中逐渐被 epollkqueue 等新接口取代。

  • 文件描述符数量限制:受 FD_SETSIZE 宏限制(默认 1024),最多只能监控 1024 个文件描述符,无法支撑大规模并发;
  • 每次调用需重新初始化集合select 会修改文件描述符集合,每次调用前需重新调用 FD_ZERO 和 FD_SET,增加了代码复杂度和开销;
  • 遍历效率低select 仅返回“就绪的文件描述符总数”,不告知“具体哪些套接字就绪”,需遍历所有监控的套接字(通过 FD_ISSET),效率随套接字数量增长而急剧下降;
  • 内核/用户空间数据拷贝:每次调用 select 时,需将文件描述符集合从用户空间拷贝到内核空间;返回时,内核需将修改后的集合拷贝回用户空间,增加了内存拷贝开销。

五、使用 select 函数的常见错误与解决方案

总结,使用 select 函数时的常见错误,结合实际编程经验,以下是关键错误及解决方案:

常见错误错误原因解决方案
部分套接字未被监控,事件未触发nfds 参数设置过小,导致编号超过 nfds-1 的套接字未被监控每次调用 select 前,计算“所有监控套接字中的最大编号 + 1”,赋值给 nfds(如示例中通过 max_fd = max(max_fd, curr_sock) 动态更新)
文件描述符数量超过 FD_SETSIZE,监控失败FD_SETSIZE 是 fd_set 的最大容量(默认 1024),超过该值的套接字无法加入集合1. 若在 Linux 系统,改用 epoll 函数(无数量限制);
2. 若必须使用 select,可修改内核参数 FD_SETSIZE(不推荐,可能导致兼容性问题);
3. 采用“多进程+select”方案,每个进程监控 1024 个套接字
未重新初始化文件描述符集合,导致事件误判select 会修改文件描述符集合,仅保留就绪的套接字;若下次调用前未重新 FD_ZERO 和 FD_SET,会监控残留的旧套接字,导致误判每次调用 select 前,必须重新执行:
1. FD_ZERO(&readfds) 清空集合;
2. FD_SET(listen_sock, &readfds) 和 FD_SET(conn_sock, &readfds) 重新加入需要监控的套接字
未重新初始化超时时间,导致超时异常select 会修改 timeout 的值(将其设为“剩余时间”),若下次调用前未重新赋值,可能导致超时时间变短或为 0(立即返回)每次调用 select 前,重新初始化 timeout 的 tv_sec 和 tv_usec 成员(如示例中每次循环都设置 timeout.tv_usec = SELECT_TIMEOUT * 1000
忽略 EINTR 错误,导致 select 调用终止进程在 select 阻塞期间收到信号(如 SIGINT),select 会返回 -1,errno 设为 EINTR,若未处理该错误,进程会退出处理 selecterrno == EINTR,若成立则重新调用 select,避免进程异常终止(如示例中 if (errno == EINTR) continue;
套接字未设为非阻塞,导致处理事件时阻塞虽 select 通知“事件就绪”,但极端情况下(如事件被其他进程抢占),acceptrecv 等函数仍可能阻塞,导致进程无法处理其他事件将所有监控的套接字(侦听套接字、连接套接字)设为非阻塞模式(通过 fcntl 添加 O_NONBLOCK 标志),确保事件处理函数不会阻塞

六、拓展:select 与 poll、epoll 的对比

随着 UNIX 系统的发展,出现了 poll 和 epoll(Linux 特有)等新的 I/O 多路复用接口,它们针对 select 的缺陷进行了优化。以下是三者的核心区别及应用选择建议:

6.1 核心区别对比

对比维度selectpollepoll(Linux 特有)
文件描述符数量限制受 FD_SETSIZE 限制(默认 1024)无硬限制(仅受系统内存限制)无硬限制(仅受系统内存限制)
事件集合存储方式fd_set 位图(固定大小)struct pollfd 数组(动态大小)内核维护的事件表(红黑树)
每次调用是否需重新初始化是(select 会修改集合)否(pollfd 数组不会被内核修改)否(事件表只需初始化一次,后续通过 epoll_ctl 增删)
就绪事件通知方式仅返回总数,需遍历所有监控套接字仅返回总数,需遍历所有 pollfd 检查 revents返回就绪的套接字列表,无需遍历(高效)
内核/用户空间数据拷贝每次调用均需拷贝(集合)每次调用均需拷贝(pollfd 数组)仅初始化时拷贝一次(事件表),后续无需拷贝(高效)
事件类型支持读、写、异常读、写、异常(支持更多细分事件,如 POLLINPOLLOUT读、写、异常(支持边缘触发/水平触发,事件类型更丰富)
跨平台性好(POSIX 标准,支持 UNIX、Windows)较好(POSIX 标准,支持多数 UNIX,Windows 不支持)差(仅 Linux 系统支持)
适用场景小规模并发(≤1024 客户端)、跨平台需求中等规模并发(无数量限制)、跨 UNIX 平台需求大规模高并发(如 10000+ 客户端)、Linux 专属场景(如服务器)

6.2 应用选择建议(实践总结)

根据不同的业务场景和技术需求,选择合适的 I/O 多路复用接口:

  • 选择 select 的场景
    • 客户端数量较少(≤1024),如小型工具、测试程序;
    • 需要跨平台(如同时支持 Linux 和 Windows);
    • 业务逻辑简单,无需大规模并发。
  • 选择 poll 的场景
    • 客户端数量超过 1024,但未达到大规模(如 thousands 级别);
    • 仅需支持 UNIX-like 系统,无需 Windows 兼容性;
    • 需要更丰富的事件类型,且避免 select 的数量限制。
  • 选择 epoll 的场景
    • 高并发场景(如 10000+ 客户端),如 Web 服务器、即时通信服务器;
    • 仅运行在 Linux 系统(如后端服务器);
    • 对性能要求高,需减少内存拷贝和遍历开销(如边缘触发模式)。

七、总结

基于《精通UNIX下C语言编程与项目实践笔记.pdf》的内容,select 函数是 UNIX 并发 Socket 编程中“承上启下”的关键接口——它解决了早期阻塞/非阻塞模型的缺陷,为 I/O 多路复用技术奠定了基础。通过本文的讲解,可总结出以下核心要点:

  1. select 函数的核心是“内核监控+事件通知”:通过内核监控多个套接字事件,仅在有事件发生时唤醒进程,平衡了并发效率与 CPU 资源占用;
  2. 正确使用需关注三个关键点nfds 需设为“最大文件描述符+1”,每次调用前需重新初始化文件描述符集合和超时时间,套接字建议设为非阻塞;
  3. 优缺点明确,场景受限select 跨平台性好、实现简单,但存在文件描述符数量限制和效率问题,仅适用于小规模并发场景;
  4. 进阶方向是更高效的 I/O 多路复用接口:大规模并发场景下,Linux 系统优先选择 epoll,其他 UNIX 系统可选择 kqueue,以突破 select 的性能瓶颈。

对于初学者,掌握 select 函数不仅能实现基础的并发 Socket 处理,更能深入理解 I/O 多路复用的核心原理——这是学习 epoll 等高级接口的基础,也是成为 UNIX 网络编程工程师的关键一步。

http://www.dtcms.com/a/465312.html

相关文章:

  • 世界杯哪个网站做代理跨境电商网站系统开发
  • SNK施努卡CCD视觉检测系统
  • 杨和勒流网站建设网站建设制作设计
  • SQLite架构
  • 初识Linux和Linux基础指令详细解析及shell的运行原理
  • Python容器内存三要素
  • NumPy 矩阵库(numpy.matlib)用法与作用详解
  • Web 开发 26
  • 正规app软件开发费用漯河网站优化
  • 人工智能学习:线性模型,损失函数,过拟合与欠拟合
  • 开篇词:为何要懂攻防?—— 实战化安全思维的建立
  • 怎么在qq上自己做网站wordpress是一款强大的
  • 网站建设公司 成本结转ppt之家模板免费下载
  • Android Vibrator学习记录
  • pop、push、unshift、shift的作用?
  • 大模型激活值相关公式说明(114)
  • unity升级对ab变更的影响
  • 谁是远程控制软件的“最优选”?UU远程、ToDesk、向日葵深度横测
  • 天机学堂升级版,海量新功能加入
  • vuedraggable拖拽任意组件并改变数据排序
  • {MySQL查询性能优化索引失效的八大场景与深度解决方案}
  • 网站整体建设方案360网站免费推广怎么做
  • 方舟优品:生产型撮合模式如何推动电商行业创新发展
  • 无人机芯片模块技术要点分析
  • 使用手机检测的智能视觉分析技术与应用 加油站使用手机 玩手机检测
  • 门户网站建设的重要性如何优化网页
  • 怎么在工商网站做实名认证海淀商城网站建设
  • 加餐 结束语
  • 做网站都需要用到什么3d建模一般学费多少
  • 深入解析 Conda、Anaconda 与 Miniconda:Python 环境管理的完整指南