处理多个客户端
select 与 poll
处理多个客户端 是 CoddyKit 上的免费 C Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C Academy 课程共包含 4 节课。
并发问题
简单的服务器一次只处理一个客户端。如果某个客户端响应缓慢,其他所有客户端都必须等待。要同时服务多个客户端,您需要采用不同的模型。
并发处理方法
三种常见策略:
- 每个客户端对应一个线程或进程:简单,但规模扩大后开销很大
- 使用
select或poll进行输入/输出多路复用:一个线程监视多个套接字 - 使用
epoll/kqueue实现事件循环:可扩展到数千个连接
select 的作用
select() 监视一组文件描述符,并通过一次阻塞调用告知您哪些描述符已就绪,可以读取、写入或存在错误。这样,一个线程就能服务多个套接字。
fd_set 基础
select 使用 fd_set 位掩码。请使用以下宏进行管理:FD_ZERO、FD_SET、FD_CLR 和 FD_ISSET。
#include <sys/select.h>
void setup_set(fd_set *set, int listen_fd) {
FD_ZERO(set); /* clear all */
FD_SET(listen_fd, set); /* watch the listener */
}调用 select
select 需要传入最大 fd 加一,并会原地修改集合,以标记已就绪的描述符。由于该操作具有破坏性,您必须在每次调用前重新构建集合。
#include <sys/select.h>
#include <stdio.h>
int wait_ready(int maxfd, fd_set *read_set) {
int n = select(maxfd + 1, read_set, NULL, NULL, NULL);
if (n < 0) perror("select");
return n; /* number of ready fds */
}select 服务器循环
基本模式是:重新构建集合,调用 select,然后逐个处理已就绪的 fd:如果它是监听描述符,就接收新客户端;如果它是客户端描述符,就读取数据。
#include <sys/select.h>
void loop_skeleton(int listen_fd, fd_set *master, int maxfd) {
fd_set work;
for (;;) {
work = *master; /* copy, select destroys it */
select(maxfd + 1, &work, NULL, NULL, NULL);
for (int fd = 0; fd <= maxfd; fd++) {
if (!FD_ISSET(fd, &work)) continue;
/* fd == listen_fd -> accept; else -> recv */
}
}
}跟踪客户端
接收新连接时,将其 fd 添加到主集合,并更新 maxfd。当客户端断开连接时(recv 返回 0),请对其执行 close,并使用 FD_CLR 将它从集合中移除。
#include <sys/select.h>
#include <unistd.h>
void add_client(fd_set *master, int *maxfd, int conn) {
FD_SET(conn, master);
if (conn > *maxfd) *maxfd = conn;
}
void drop_client(fd_set *master, int conn) {
close(conn);
FD_CLR(conn, master);
}select 的局限
select 存在以下缺点:
- 最多只能处理
FD_SETUP个描述符(通常为 1024 个) - 每次调用都要对所有 fd 进行 O(n) 扫描
- 每次循环都必须重新构建集合
对于大量连接,poll 或 epoll 的扩展性更好。
poll 提供的功能
poll() 接收一个 struct pollfd 数组,而不是固定的位掩码,因此没有 1024 个描述符的限制,也不会在每次调用时破坏输入内容。
#include <poll.h>
void setup_poll(struct pollfd *pfd, int listen_fd) {
pfd[0].fd = listen_fd;
pfd[0].events = POLLIN; /* notify when readable */
}调用 poll
poll 会阻塞,直到至少有一个 fd 就绪(或超时),然后为每个已就绪的条目设置 revents。
#include <poll.h>
#include <stdio.h>
int poll_ready(struct pollfd *pfds, int count) {
int n = poll(pfds, count, -1); /* -1 = block forever */
if (n < 0) perror("poll");
return n;
}扩展规模:epoll
对于数千个连接,Linux 的 epoll(以及 BSD 的 kqueue)每个事件只返回已就绪的描述符,单个事件的复杂度为 O(1),避免了 select 和 poll 所执行的完整扫描。
快速检查
测试您对处理多个客户端的理解。
回顾
您学会了如何使用一个线程服务多个客户端。
select监视多个 fd;使用 FD_ 宏进行管理- 由于 select 具有破坏性,因此每次循环都要重新构建集合
- 将接收的客户端添加到集合中;断开连接时将其移除
poll避免了 1024 个描述符的限制;epoll可扩展到数千个连接
常见问题解答
「处理多个客户端」课时是免费的吗?
是的 — 「处理多个客户端」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C Academy 课程的其余内容,请升级到 CoddyKit PRO。 C Academy 课程共包含 4 节课。
「处理多个客户端」这节课中我会学到什么?
select 与 poll 你通过在浏览器中直接运行的动手代码来练习 C Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「处理多个客户端」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C Academy 课中编写并运行代码吗?
能。每节 C Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。