0Pricing
C Academy · 课时

处理多个客户端

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 反馈 — 无需本地设置。

此课程中的所有课时

  1. 套接字概述
  2. TCP 服务器
  3. TCP 客户端
  4. 处理多个客户端
← 返回 C Academy