多路转接原理与应用

目录

一.前言:

1.为什么需要多路转接:

[a.传统阻塞 I/O 的并发困境:](#a.传统阻塞 I/O 的并发困境:)

b.多路转接解决了什么:

2.事件探视机制的工作模型:

3.注意事项:

[二.Linux 核心多路复用 API 使用:](#二.Linux 核心多路复用 API 使用:)

[1.Linux 下基于 select 的 TCP 服务:](#1.Linux 下基于 select 的 TCP 服务:)

a.底层结构刨析:

b.核心接口解析:

c.完整代码示例:

d.技术复盘:

[2.基于 poll 的 TCP 服务](#2.基于 poll 的 TCP 服务)

a.底层结构刨析:

b.核心接口剖析:

c.完整代码示例

d.技术复盘:

3.基于epoll实现tcp服务

a.底层原理:

b.核心接口剖析:

c.完整代码示例:

d.技术复盘:

三.总结:

1.时代意义:

2.工业界现状抉择:

一.前言:

1.为什么需要多路转接:

a.传统阻塞 I/O 的并发困境:

  • 阻塞 I/O 会导致线程"死等"数据,高并发时必须开辟海量线程,极易耗尽内存并引发灾难性的上下文切换开销;
  • 非阻塞 I/O 依靠死循环不断轮询探查数据状态,会白白榨干 CPU 算力;
  • 信号驱动 I/O 机制复杂且信号触发过于频繁,尤其是在基于字节流的 TCP 协议下;
  • 异步 I/O(AIO) 看着是解决了上述大部分问题,但开发难度高与跨平台优势低。

b.多路转接解决了什么:

  • 主要解决了原先需要依赖多进程或多线程处理的多 fd 的 I/O 情况,变为仅使用一个线程就可以完成,即**"用最少的系统资源,去支撑最大量级的并发连接"**。
  • 也就是说,多路转接不存在 CPU 空转,不会有多线程开销,线程多次触发等问题,完美兼顾了高并发处理能力 与极低的系统资源消耗。

2.事件探视机制的工作模型:

  • 主要分为:fd 注册 ,事件探视 ,进程唤醒 以及读写四大部分。
  • 在这个模型中,进程不会主动去遍历每个 fd,而是将 fd 交给操作系统内核 ,此时进程只需要查看注册好的 fd 中是否有事件准备就绪(期间阻塞休眠),一旦有事件发生可以立刻找到对应的 fd,进行数据操作。

3.注意事项:

本文的高度围绕代码,帮助读者学习与使用 select 、poll 、epoll 等。

二.Linux 核心多路复用 API 使用:

1.Linux 下基于 select 的 TCP 服务:

a.底层结构刨析:

  • 结构承载: select 采用固定大小的位图 来承载文件描述符。位图 的索引 直接对应文件描述符 的数值,将第 n 个 bit 位置 1,即代表指示内核监控第 n 号文件描述符。
  • 入核与挂载: 调用 select 时,系统会将整个位图从用户空间全量拷贝 到内核空间。内核对位图 进行第一次线性遍历 ,将当前文件描述符挂载 到等待队列上。若此时没有检测到任何事件就绪,进程会主动让出 CPU,进入休眠阻塞状态。
  • 唤醒与出核: 当网卡接收到数据触发硬件中断 后,会唤醒该进程。进程醒来后,内核必须进行第二次线性遍历 ,将位图中未就绪的 bit 清零。最后,将修改后的位图从内核空间覆盖拷贝回用户空间,程序据此判断事件归属。

b.核心接口解析:

cpp 复制代码
fd_set master_set;  // 保存所有需要监控的 fd 的结构体
FD_ZERO(&master_set);  // 初始化位图
FD_SET(fd, &master_set);  //把 _fd 加入位图集合
struct timeval tv; //设置阻塞超时时间
tv.tv_sec = 3;   // 秒
tv.tv_usec = 0;  //微妙
/*
参数:最大fd序号+1,需要监控读事件的fd,需要监控写事件的fd,需要监控异常事件的fd,超时设置
返回值:小于零出现错误,等于零超时,大于零则是事件就绪的链接个数
*/
select(max_fd + 1, nullptr, nullptr, nullptr, &tv);
/*
参数:当前遍历到的fd,判断fd在哪个队列就绪
返回值:存在返回1,不存在返回0
*/
FD_ISSET(_fd, &read_fds)

c.完整代码示例:

cpp 复制代码
#include <iostream>
#include <vector>
#include <cstring>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/select.h>
int main()
{
//创建 TCP 监听 socket
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
perror("socket");
return 1;
}
//允许地址复用
int opt = 1;
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &amp;opt, sizeof(opt));

//绑定 IP 与 端口
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(8080);
if (bind(listen_fd, (sockaddr*)&amp;addr, sizeof(addr)) &lt; 0) 
{
    perror("bind");
    return 1;
}

//开始监听
if (listen(listen_fd, 5) &lt; 0) 
{
    perror("listen");
    return 1;
}

std::cout &lt;&lt; "Server started on port 8080...\n";

//设置select
fd_set master_set;              // 保存所有需要监控的 fd
FD_ZERO(&amp;master_set);           // 清空位图
FD_SET(listen_fd, &amp;master_set); // 把 listen_fd 加入集合

int max_fd = listen_fd;         // 当前监控的最大 fd 数值
std::vector&lt;int&gt; client_fds;    // 保存所有已连接的客户端 fd

// 5. 事件循环
while (true) 
{
    // select 会修改传入的集合,因此必须每次用临时变量备份
    fd_set read_fds = master_set;
    // 阻塞等待,直到有 socket 变为可读
    struct timeval tv;
    tv.tv_sec = 3;   // 秒
    tv.tv_usec = 0;
    int ret = select(max_fd + 1, &amp;read_fds, nullptr, nullptr, &amp;tv);
    if (ret &lt; 0) 
    {
        perror("select");
        break;
    }
    else if(ret == 0)
    {
        //超时
        std::cout &lt;&lt; "执行自己的任务中..." &lt;&lt; std::endl;
        sleep(1);
    }
    else
    {
        //返回值大于零,此时有 socket 变读事件
        // 监听 socket 可读 -&gt; 代表有新客户端连接
        if (FD_ISSET(listen_fd, &amp;read_fds)) 
        {
            sockaddr_in client_addr{};
            socklen_t len = sizeof(client_addr);
            int new_fd = accept(listen_fd, (sockaddr*)&amp;client_addr, &amp;len);

            if (new_fd &gt;= 0) 
            {
                std::cout &lt;&lt; "新客户端接入, fd = " &lt;&lt; new_fd &lt;&lt; "\n";
                FD_SET(new_fd, &amp;master_set);    // 加进监控集合
                client_fds.push_back(new_fd);   // 存起来供后续遍历
                if (new_fd &gt; max_fd) max_fd = new_fd;
            }
        }

        //普通客户端 socket
        for (auto it = client_fds.begin(); it != client_fds.end(); ) 
        {
            int fd = *it;
            if (FD_ISSET(fd, &amp;read_fds)) 
            {
                char buf[1024];
                ssize_t n = recv(fd, buf, sizeof(buf) - 1, 0);

                if (n &gt; 0)
                {
                    // 收到数据,打印并原样回显 (Echo)
                    buf[n] = '\0';
                    std::cout &lt;&lt; "收到客户端[" &lt;&lt; fd &lt;&lt; "]: " &lt;&lt; buf;
                    send(fd, buf, n, 0);
                    ++it;
                }
                else 
                {
                    // n == 0 表示对端正常关闭连接,n &lt; 0 表示读取出错
                    std::cout &lt;&lt; "客户端[" &lt;&lt; fd &lt;&lt; "] 断开连接\n";
                    close(fd);
                    FD_CLR(fd, &amp;master_set);   // 从位图中移除
                    it = client_fds.erase(it); // 从容器中删除

                    // 如果关掉的是最大的 fd,重新找一次最大值
                    if (fd == max_fd) 
                    {
                        max_fd = listen_fd;
                        for (int c : client_fds) 
                        {
                            if (c &gt; max_fd) max_fd = c;
                        }
                    }
                }
            } 
            else
            {
                ++it;
            }
            //
        }
        //
    }

    //
}

close(listen_fd);
return 0;
}

d.技术复盘:

  • 由于底层采用定长的 unsigned long 数组 实现位图 ,导致承载文件描述符数量存在上限。
  • 由于每次调用都会破坏传入参数,把没有事件就绪的 bit 直接清零,需要重新对整个位图进行赋值 和恢复 ,导致用户态维护成本高。
  • 由于每次调用的双向全量拷贝,导致内存开销巨大。
  • 无论内核维护文件描述符,还是用户判断哪个文件描述符事件就绪,都需要线性遍历文件描述符,导致海量并发下 CPU 性能急剧衰退。
  • 跨平台 兼容性极佳,几乎所有操作系统均原生支持。
  • 超时时间可以设置到微秒级别,精度较高。
  • 支持单独设置异常监控,在 BMC等领域效果出众。

2.基于 poll 的 TCP 服务

a.底层结构刨析:

  • 用户层注册: 采用 pollfd结构数组记录文件描述符以及对应监听事件。
  • 入核与挂载: 通过系统调用 poll 进入内核以后,拷贝 pollfd ,创建 poll_list 跟踪监控文件描述符,为其注册唤醒回调,并首次遍历判断事件状态。
  • 唤醒与出核:线程 被唤醒 后,遍历监控 fd,发现存在事件 准备就绪后,将更新好的 pollfd拷贝回用户空间,遍历其并进行业务操作。

b.核心接口剖析:

cpp 复制代码
//poll 文件描述符维护结构
struct pollfd fds[MAX_CLIENTS];
//初始化与添加fd
for (int i = 0; i < MAX_CLIENTS; ++i)
{
fds[i].fd = -1;
fds[i].events = 0;
}
fds[0].fd = listen_fd;
fds[0].events = POLLIN;
//参数:文件描述符数组,监控下标0到nfds之间的数据,超时设置0不阻塞,-1永远等待,大于0则是等待事件,单位ms
int ret = poll(fds, nfds, -1);

c.完整代码示例

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <poll.h>
#include <errno.h>
#define MAX_CLIENTS 1024        // 最多同时连接的客户端数量
#define BUFFER_SIZE 4096        // 读写缓冲区大小
int main()
{
int listen_fd, new_fd;                      // 监听套接字和新连接套接字
struct sockaddr_in server_addr, client_addr;
socklen_t addr_len = sizeof(client_addr);
char buffer[BUFFER_SIZE];
ssize_t nread;
/* 创建监听套接字 */
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) 
{
    perror("socket");
    exit(EXIT_FAILURE);
}

/* 设置地址重用,避免 "Address already in use" */
int optval = 1;
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &amp;optval, sizeof(optval)) == -1) 
{
    perror("setsockopt");
    close(listen_fd);
    exit(EXIT_FAILURE);
}

/*绑定地址 */
memset(&amp;server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);   // 监听所有网络接口
server_addr.sin_port = htons(8888);

if (bind(listen_fd, (struct sockaddr*)&amp;server_addr, sizeof(server_addr)) == -1) {
    perror("bind");
    close(listen_fd);
    exit(EXIT_FAILURE);
}

/* 开始监听 */
if (listen(listen_fd, 5) == -1) {   // 5 是未完成连接队列的大小
    perror("listen");
    close(listen_fd);
    exit(EXIT_FAILURE);
}

printf("Echo server listening on port %d\n", 8888);

/* 准备 poll 所需的数据结构 */
struct pollfd fds[MAX_CLIENTS];     // poll 监控的文件描述符数组
int nfds = 1;                       // 当前有效的 fd 数量(初始只有监听套接字)

// 初始化所有槽位,fd 设为 -1 表示空闲
for (int i = 0; i &lt; MAX_CLIENTS; ++i) 
{
    fds[i].fd = -1;
    fds[i].events = 0;
}

// 将监听套接字放入第一个位置
fds[0].fd = listen_fd;
fds[0].events = POLLIN;             // 关心可读事件(有新连接到来)

/* 主循环:不断调用 poll 处理事件 */
while (1) 
{
    int ret = poll(fds, nfds, -1);  // -1 表示无限等待
    if (ret == -1) 
    {
        perror("poll");
        break;
    }

    /* 检查监听套接字是否有新连接 */
    if (fds[0].revents &amp; POLLIN) 
    {
        new_fd = accept(listen_fd, (struct sockaddr*)&amp;client_addr, &amp;addr_len);
        if (new_fd == -1) 
        {
            perror("accept");
            continue;
        }
        printf("New connection from %s:%d (fd=%d)\n",
               inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), new_fd);

        // 找一个空闲槽位存放新客户端的 fd
        int slot = -1;
        for (int i = 1; i &lt; MAX_CLIENTS; ++i) 
        {   // 从下标 1 开始找
            if (fds[i].fd == -1) 
            {
                slot = i;
                break;
            }
        }
        if (slot == -1) {
            fprintf(stderr, "Too many clients, rejecting new connection.\n");
            close(new_fd);
        } 
        else 
        {
            fds[slot].fd = new_fd;
            fds[slot].events = POLLIN;           // 只关心可读事件
            if (slot &gt;= nfds) 
            {
                nfds = slot + 1;                 // 更新有效 fd 上限
            }
        }
    }

    /* 遍历其余 fd,处理客户端数据 */
    for (int i = 1; i &lt; nfds; ++i) 
    {
        if (fds[i].fd == -1) continue;          // 跳过空槽位

        if (fds[i].revents &amp; POLLIN) 
        {
            // 有数据可读
            nread = read(fds[i].fd, buffer, sizeof(buffer) - 1);
            if (nread &gt; 0) 
            {
                buffer[nread] = '\0';           // 方便调试打印
                printf("Received %zd bytes from fd %d: %s", nread, fds[i].fd, buffer);
                // Echo 回显:把收到的数据原样写回
                write(fds[i].fd, buffer, nread);
            } 
            else if (nread == 0) 
            {
                // 客户端关闭连接
                printf("Client fd %d disconnected.\n", fds[i].fd);
                close(fds[i].fd);
                fds[i].fd = -1;                 // 标记槽位为空
                // 可选:调整 nfds 以缩小 poll 扫描范围(非必须)
                // 如果当前 i 正好是 nfds-1,则向前收缩 nfds
                while (nfds &gt; 1 &amp;&amp; fds[nfds-1].fd == -1) 
                {
                    --nfds;
                }
            } 
            else 
            {
                // 读取错误
                perror("read");
                close(fds[i].fd);
                fds[i].fd = -1;
                while (nfds &gt; 1 &amp;&amp; fds[nfds-1].fd == -1) 
                {
                    --nfds;
                }
            }
        }

        // 如果发生错误或挂断,也关闭连接
        if (fds[i].revents &amp; (POLLERR | POLLHUP | POLLNVAL)) 
        {
            printf("Error/Hangup on fd %d, closing.\n", fds[i].fd);
            close(fds[i].fd);
            fds[i].fd = -1;
            while (nfds &gt; 1 &amp;&amp; fds[nfds-1].fd == -1) 
            {
                --nfds;
            }
        }
    }
}

/* 清理:关闭所有客户端连接和监听套接字 */
for (int i = 0; i &lt; nfds; ++i) 
{
    if (fds[i].fd != -1) 
    {
        close(fds[i].fd);
    }
}
close(listen_fd);
return 0;
}

d.技术复盘:

  • 可独立设置管理上限,但需要手动维护数组,系统底层采用线性扫描的方式,运行时间复杂度高(On)。
  • 支持多种事件,如对端关闭 、挂起 等,但不支持边缘触发。
  • 每次处理完事件后,不需要重新设置 poll_fd,但仍然要线性遍历所有文件描述符。
  • POSIX 标准内容之一,可移植性高。

3.基于epoll实现tcp服务

a.底层原理:

相比于 poll 与 select ,epoll 主要在描述符管理方式和事件通知机制上进行了根本性优化(epoll 并非 **poll**的简单增强,而是一种全新的 I/O 多路复用模型)。

  • epoll 在内核中使用红黑树 存储所有注册的文件描述符及其关注事件,使得增、删、改、查操作的时间复杂度均为 O(log n)。
  • 每个注册的 fd 注册了一个回调函数 。当 fd 上发生感兴趣的事件时(如数据到达),内核驱动会调用该回调函数,将对应的节点直接添加到内核维护的就绪链表 中,该操作的时间复杂度为 O(1)。
  • epoll_wait 调用时,只需检查就绪链表是否为空。若非空,则将链表中的事件批量拷贝到用户空间的 events 数组,并清空链表(水平触发模式下保留就绪状态)。

b.核心接口剖析:

cpp 复制代码
//创建epoll实例:
//注:epoll_create1是Linux 2.6.27后代替epoll_create的新接口,不是写错了
// 参数:0 表示无特殊标志;EPOLL_CLOEXEC 表示执行 exec 时自动关闭该 fd,防止泄漏
epfd = epoll_create1(0);

//设置监听属性
struct epoll_event
{
  uint32_t events;	/* 监听的事件 */
  epoll_data_t data;	/* 用户数据联合体 */
} __EPOLL_PACKED; //采用紧凑对齐方式编译

//控制epollfd接口
//参数:epoll文件描述符,操作类型,目标文件描述符,监听设置类型
epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev);

//参数:epoll文件描述符,用于接收就绪事件的 events 数组,队列最大值,等待事件(-1为阻塞等待)
//返回值:event输出的有效数量
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1); 

c.完整代码示例:

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>

#define PORT        8888                // 监听端口
#define MAX_EVENTS  1024                // epoll_wait 一次最多返回的事件数
#define BUFFER_SIZE 4096                // 读写缓冲区大小

// 设置非阻塞模式
static int set_nonblocking(int fd) 
{
    int flags = fcntl(fd, F_GETFL, 0);
    if (flags == -1) return -1;
    return fcntl(fd, F_SETFL, flags | O_NONBLOCK);
}

int main() 
{
    int listen_fd, epfd, nfds, i;
    struct sockaddr_in server_addr, client_addr;
    socklen_t addr_len = sizeof(client_addr);
    char buffer[BUFFER_SIZE];

    /* 创建监听套接字 */
    listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (listen_fd == -1) {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    /* 设置地址重用 */
    int optval = 1;
    setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));

    /* 3. 绑定地址 */
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sin_family = AF_INET;
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    server_addr.sin_port = htons(PORT);

    if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) 
    {
        perror("bind");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    /* 开始监听 */
    if (listen(listen_fd, 5) == -1) 
    {
        perror("listen");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    /* 将监听套接字设为非阻塞(推荐,但不是必须) */
    if (set_nonblocking(listen_fd) == -1) 
    {
        perror("set_nonblocking");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    /* 创建 epoll 实例 */
    epfd = epoll_create1(0);    // 参数 0 等同于 epoll_create(1),但更现代
    if (epfd == -1) 
    {
        perror("epoll_create1");
        close(listen_fd);
        exit(EXIT_FAILURE);
    }

    /* 将监听套接字加入 epoll,关注可读事件(新连接) */
    struct epoll_event ev;
    ev.events = EPOLLIN;                // 水平触发模式(默认)
    ev.data.fd = listen_fd;
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_fd, &ev) == -1) 
    {
        perror("epoll_ctl: listen_fd");
        close(listen_fd);
        close(epfd);
        exit(EXIT_FAILURE);
    }

    printf("Epoll echo server started on port %d\n", PORT);

    /* 事件循环 */
    struct epoll_event events[MAX_EVENTS];   // 用于接收就绪事件

    while (1) 
    {
        nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);   // -1 表示无限等待
        if (nfds == -1) 
        {
            perror("epoll_wait");
            break;
        }

        for (i = 0; i < nfds; ++i) 
        {
            int fd = events[i].data.fd;

            /* 处理新连接 */
            if (fd == listen_fd) 
            {
                int conn_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &addr_len);
                if (conn_fd == -1) 
                {
                    perror("accept");
                    continue;
                }
                printf("New connection from %s:%d (fd=%d)\n",
                       inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), conn_fd);

                // 将新连接设为非阻塞(重要:epoll 配合非阻塞更高效)
                if (set_nonblocking(conn_fd) == -1) 
                {
                    perror("set_nonblocking");
                    close(conn_fd);
                    continue;
                }

                // 将新连接加入 epoll,关注可读事件
                ev.events = EPOLLIN | EPOLLRDHUP;   // EPOLLRDHUP 可检测对端关闭
                ev.data.fd = conn_fd;
                if (epoll_ctl(epfd, EPOLL_CTL_ADD, conn_fd, &ev) == -1) 
                {
                    perror("epoll_ctl: add client");
                    close(conn_fd);
                }
            }
            /* 处理客户端数据或关闭 */
            else 
            {
                /* 检查是否发生错误或挂起 */
                if (events[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)) 
                {
                    printf("Client fd %d closed or error.\n", fd);
                    close(fd);
                    continue;
                }

                /* 可读事件 */
                if (events[i].events & EPOLLIN) 
                {
                    ssize_t nread = read(fd, buffer, sizeof(buffer) - 1);
                    if (nread > 0) 
                    {
                        buffer[nread] = '\0';
                        printf("Received %zd bytes from fd %d: %s", nread, fd, buffer);
                        // Echo 回显
                        write(fd, buffer, nread);
                    } 
                    else if (nread == 0) 
                    {
                        // 客户端关闭(read 返回 0)
                        printf("Client fd %d disconnected.\n", fd);
                        close(fd);
                    } 
                    else 
                    {
                        // 读错误
                        if (errno != EAGAIN) 
                        {  
                            // EAGAIN 是非阻塞的正常情况,忽略
                            perror("read");
                            close(fd);
                        }
                    }
                }
            }
        }
    }

    /* 清理 */
    close(listen_fd);
    close(epfd);
    return 0;
}

d.技术复盘:

  • epoll 的触发、添加速度极快,监控上限只受内存影响,但不支持普通文件描述符的读写。
  • 专用接口与 epoll_fd 管理,但维护与编写相对复杂。
  • 支持多种事件类型设置,但跨平台能力差。

三.总结:

1.时代意义:

select:传统阻塞 I/O 模型(每连接一线程)在并发量上升时,线程开销巨大,导致性能低下、资源浪费。select 引入了多路复用思想,允许单线程同时监控多个文件描述符,初步解决了并发瓶颈。

poll:解决了 select 文件描述符数量存在明显上限,每次调用需要重新设置 fd 相关配置,返回后仍需要遍历所有文件描述符的问题。

epoll:为解决 select 与 poll 线性遍历所有监控的 fd,时间复杂度高,返回后仍需要全量扫描文件描述符等问题,做到了真正高并发高性能服务。

2.工业界现状抉择:

select: 通常用在连接数量少,不需要高并发性能,需要跨平台的场景。

poll:通常用在连接数量较多的并发,且限于Linux等系统下。

epoll:追求高并发高性能,且需要边缘触发或长连接的情况下。

相关推荐
himobrinehacken3 小时前
位运算揭秘:左右移与符号位陷阱
c语言·c++·算法·安全
Android小码家5 小时前
Vehicle 实战 自定义属性+测试demo
java·c++·framework·vhal·vehicle
hetao17338375 小时前
2026-10-19 hetao1733837 的刷题记录
c++·算法
夏虫不可与之言冰6 小时前
第三课 点亮 LED 与定时器非阻塞闪烁
c语言·c++·vscode·单片机·嵌入式硬件·物联网
辛苦才能6 小时前
C++ 哈希表深度解析:删除为什么必须打标记,负载因子为什么卡 0.7
开发语言·数据结构·c++·算法
miller-tsunami6 小时前
STL简介&string相关知识点
开发语言·c++·string
坤坤子吖6 小时前
Linux 线程同步与互斥——互斥锁
linux·服务器·c++·笔记·学习
TDTC_XQ6 小时前
Linux网络编程accept的讲解
linux·服务器·c++
FlightYe7 小时前
音视频修炼之容器与协议(一):MP4和MOV的box剖析
android·linux·c++·ffmpeg·音视频