IO 多路转接之 epoll 详解:从原理到 LT/ET 实战,打造高性能网络服务

        在 Linux 高并发网络编程中,epoll 是性能最优的 IO 多路转接技术,它解决了 select/poll 的诸多痛点(如文件描述符数量限制、内核遍历开销),成为 Nginx、Redis 等高性能中间件的核心 IO 模型。本文将从 epoll 的核心系统调用入手,深入解析其 “红黑树 + 就绪链表” 的工作原理,对比水平触发(LT)与边缘触发(ET)的差异,最终通过实战案例演示 epoll 服务器的实现,帮你彻底掌握高性能 IO 的设计思路。

一、epoll 核心认知:为什么它比 select/poll 更高效?

epoll 是 Linux 2.6 内核引入的 IO 多路转接技术,专为 “大批量文件描述符(FD)监控” 设计。相比 select/poll,它的高效源于三个核心改进:

  1. 无 FD 数量限制:突破 select 的FD_SETSIZE(默认 1024)限制,支持数万甚至数十万 FD 监控;
  2. 内核事件回调机制:无需像 select/poll 那样每次遍历所有 FD,而是通过回调函数将就绪 FD 加入 “就绪链表”,epoll_wait 直接读取就绪链表,时间复杂度 O (1);
  3. 减少数据拷贝:仅在 FD 注册(epoll_ctl)时将 FD 信息从用户态拷贝到内核态,后续 epoll_wait 无需重复拷贝,大幅降低开销。

epoll 的本质是 “内核级事件驱动”—— 内核主动跟踪 FD 状态变化,仅在 FD 就绪时通知应用程序,避免应用程序轮询或遍历,是高并发服务的 “性能基石”。

二、epoll 的三个核心系统调用

epoll 的使用流程可概括为 “创建 epoll 句柄→注册 FD 事件→等待就绪事件”,对应三个系统调用:epoll_createepoll_ctlepoll_wait

2.1 epoll_create:创建 epoll 句柄

#include <sys/epoll.h>

int epoll_create(int size);

        功能:创建一个 epoll 实例(内核维护的eventpoll结构体),用于管理待监控的 FD 和就绪事件。

        参数size在 Linux 2.6.8 后被忽略,仅需传入一个大于 0 的整数(如 10)。

        返回值:成功返回 epoll 句柄(非负整数),失败返回 - 1(错误码存于errno)。

        注意:epoll 句柄需用close()关闭,避免资源泄漏。

2.2 epoll_ctl:注册 / 修改 / 删除 FD 事件

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

        功能:向 epoll 实例中添加、修改或删除 FD 的监控事件。

        参数详解

参数含义关键说明
epfdepoll 句柄epoll_create的返回值
op操作类型可选值:- EPOLL_CTL_ADD:添加 FD 到 epoll;- EPOLL_CTL_MOD:修改 FD 的监控事件;- EPOLL_CTL_DEL:从 epoll 中删除 FD(event传 NULL)
fd待监控的文件描述符如 socket、标准输入等
event监控事件配置结构体包含 “事件类型” 和 “用户数据”,见下文

  struct epoll_event结构体

typedef union epoll_data {
    void *ptr;    // 自定义数据(如FD关联的业务数据)
    int fd;       // 待监控的FD(最常用)
    uint32_t u32; // 32位整数
    uint64_t u64; // 64位整数
} epoll_data_t;

struct epoll_event {
    uint32_t events;    // 监控的事件类型(如EPOLLIN、EPOLLOUT)
    epoll_data_t data;  // 用户数据(通常存FD,用于epoll_wait后识别就绪FD)
};

        常见事件类型

事件宏含义适用场景
EPOLLINFD 可读(如 socket 接收缓冲区有数据、对端关闭连接)接收网络数据、读取标准输入
EPOLLOUTFD 可写(如 socket 发送缓冲区有空闲空间)发送网络数据
EPOLLPRIFD 有紧急数据可读(如 TCP 带外数据)处理紧急数据
EPOLLERRFD 发生错误错误处理(无需主动监控,内核自动触发)
EPOLLHUPFD 被挂断(如管道写端关闭)连接断开处理(无需主动监控,内核自动触发)
EPOLLET边缘触发(ET)模式高性能场景,需配合非阻塞 IO
EPOLLONESHOT仅监控一次事件避免多个线程同时处理同一 FD

2.3 epoll_wait:等待 FD 就绪事件

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

        功能:阻塞等待 epoll 实例中监控的 FD 就绪,将就绪事件拷贝到用户态数组。

        参数详解

参数含义关键说明
epfdepoll 句柄epoll_create的返回值
events就绪事件数组用户态预分配的struct epoll_event数组,用于存储就绪事件
maxevents数组最大长度不能超过epoll_createsize,通常设为数组元素个数(如 1000)
timeout超时时间(毫秒)-1:永久阻塞;0:非阻塞(立即返回);>0:阻塞指定毫秒后返回

返回值

        正数:就绪的 FD 数量(需循环处理前 N 个events元素);

        0:超时时间到,无 FD 就绪;

        -1:调用失败(如被信号中断),错误码存于errno。

三、epoll 工作原理:红黑树与就绪链表的协同

epoll 的高效源于内核中eventpoll结构体的设计,它通过 “红黑树管理监控 FD” 和 “双链表存储就绪 FD”,实现高效的 FD 管理和事件通知。

3.1 核心数据结构

内核为每个 epoll 实例维护一个eventpoll结构体,关键成员如下:

struct eventpoll {
    struct rb_root rbr;        // 红黑树:存储所有待监控的FD(epitem结构体)
    struct list_head rdlist;  // 双链表:存储就绪的FD(epitem结构体)
};

        红黑树(rbr)

                每个节点是epitem结构体(包含 FD、监控事件、指向eventpoll的指针);

                作用:快速添加、删除、查找 FD(时间复杂度 O (logN)),避免重复监控;

        就绪链表(rdlist)

             当 FD 就绪时(如 socket 接收数据),内核通过回调函数ep_poll_callback将对应的epitem加入该链表;

                epoll_wait直接读取该链表,无需遍历所有 FD,时间复杂度 O (1)。

3.2 完整工作流程

  1. 创建 epoll 实例:调用epoll_create,内核创建eventpoll结构体(初始化红黑树和就绪链表);
  2. 注册 FD 事件:调用epoll_ctl(EPOLL_CTL_ADD),内核创建epitem结构体,添加到红黑树,并为 FD 注册设备驱动回调(如网卡接收数据的回调);
  3. 等待就绪事件:调用epoll_wait,内核检查就绪链表是否为空:
    • 为空:阻塞等待,释放 CPU;
    • 非空:将就绪链表中的epitem拷贝到用户态events数组,返回就绪数量;
  4. FD 就绪触发
    • 当 FD 就绪(如网卡收到数据),设备驱动调用ep_poll_callback
    • 回调函数将epitem从红黑树找到,加入就绪链表;
    • epoll_wait处于阻塞状态,唤醒进程处理就绪事件。

四、epoll 的两种工作模式:LT 与 ET

epoll 支持两种事件触发模式 ——水平触发(LT,Level Triggered) 和边缘触发(ET,Edge Triggered),这是 epoll 最核心的特性之一,直接影响程序的性能和复杂度。

4.1 水平触发(LT):默认模式,“亲妈式” 通知

LT 是 epoll 的默认模式,特点是 “只要 FD 就绪(如接收缓冲区有数据),epoll_wait 就会持续返回该 FD”,直到 FD 的就绪状态消失(如数据被读完)。

工作示例(监听 socket 可读事件)
  1. 客户端向 socket 发送 2KB 数据,socket 接收缓冲区有数据,FD 变为就绪;
  2. 调用epoll_wait,返回该 FD(就绪);
  3. 应用程序调用read读取 1KB 数据,接收缓冲区剩余 1KB 数据;
  4. 再次调用epoll_wait,仍返回该 FD(因缓冲区还有数据,FD 仍就绪);
  5. 应用程序读取剩余 1KB 数据,缓冲区为空,FD 变为未就绪;
  6. 下次epoll_wait不再返回该 FD。
特点与适用场景

        优点:逻辑简单,支持阻塞 / 非阻塞 IO,无需一次处理完所有数据;

        缺点:若数据未及时处理,epoll_wait会反复返回该 FD,增加内核与用户态的交互次数;

        适用场景:对性能要求不极致、代码复杂度需控制的场景(如中小规模服务)。

4.2 边缘触发(ET):高性能模式,“后妈式” 通知

ET 模式需在epoll_ctl时显式指定EPOLLET标志,特点是 “FD 就绪时,epoll_wait 仅返回一次”,无论数据是否处理完,后续不再通知,直到 FD 的就绪状态再次变化(如再次收到数据)。

工作示例(监听 socket 可读事件)
  1. 客户端向 socket 发送 2KB 数据,FD 就绪,epoll_wait返回该 FD;
  2. 应用程序调用read读取 1KB 数据,接收缓冲区剩余 1KB 数据;
  3. 再次调用epoll_wait不再返回该 FD(因 FD 就绪状态未变化);
  4. 客户端再次发送 1KB 数据,FD 就绪状态变化,epoll_wait再次返回该 FD;
  5. 应用程序读取剩余 2KB 数据(上次 1KB + 本次 1KB),缓冲区为空。
关键要求:必须配合非阻塞 IO

ET 模式下,若使用阻塞 IO,可能导致 “数据未读完但无法再次触发 epoll_wait” 的死锁:

        例:应用程序用阻塞read读取 1KB 数据(缓冲区剩 1KB),因 ET 不再通知,read下次调用会阻塞(缓冲区无新数据),程序卡死;

        解决方案:将 FD 设为非阻塞模式,用循环read直到返回EAGAIN/EWOULDBLOCK(表示缓冲区已空),确保一次处理完所有数据。

特点与适用场景

        优点epoll_wait返回次数少,内核与用户态交互少,性能更高(Nginx 默认使用 ET);

        缺点:逻辑复杂,必须配合非阻塞 IO,需一次处理完所有数据;

        适用场景:高并发、高性能场景(如百万级连接的网关、Web 服务器)。

4.3 LT 与 ET 对比

维度水平触发(LT)边缘触发(ET)
触发频率FD 就绪时持续触发FD 就绪时仅触发一次
IO 模式支持阻塞 / 非阻塞仅支持非阻塞
数据处理要求可分多次处理必须一次处理完所有数据
代码复杂度
性能中等
适用场景中小规模服务、简单逻辑高并发、高性能服务

五、epoll 实战:从 LT 模式到 ET 模式的服务器实现

以下通过 “TCP 字典服务器” 案例,分别演示 epoll 的 LT 和 ET 模式实现,核心功能:客户端发送英文单词,服务器返回中文释义。

5.1 基础封装:TcpSocket 类

先封装 socket 的基础操作(创建、绑定、监听、接收、发送),支持非阻塞 IO(用于 ET 模式):

// tcp_socket.hpp
#pragma once
#include <string>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <cstring>
#include <cerrno>

class TcpSocket {
public:
    TcpSocket() : _fd(-1) {}
    TcpSocket(int fd) : _fd(fd) {}
    ~TcpSocket() { Close(); }

    // 创建socket
    bool Socket() {
        _fd = socket(AF_INET, SOCK_STREAM, 0);
        if (_fd < 0) { perror("socket failed"); return false; }
        return true;
    }

    // 绑定IP和端口
    bool Bind(const std::string& ip, uint16_t port) {
        struct sockaddr_in addr;
        memset(&addr, 0, sizeof(addr));
        addr.sin_family = AF_INET;
        addr.sin_addr.s_addr = inet_addr(ip.c_str());
        addr.sin_port = htons(port);
        if (bind(_fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
            perror("bind failed"); return false;
        }
        return true;
    }

    // 监听
    bool Listen(int backlog = 5) {
        if (listen(_fd, backlog) < 0) {
            perror("listen failed"); return false;
        }
        return true;
    }

    // 接受连接
    bool Accept(TcpSocket* new_sock, std::string* client_ip = nullptr, uint16_t* client_port = nullptr) {
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
        int new_fd = accept(_fd, (struct sockaddr*)&client_addr, &len);
        if (new_fd < 0) { perror("accept failed"); return false; }
        new_sock->_fd = new_fd;
        if (client_ip) *client_ip = inet_ntoa(client_addr.sin_addr);
        if (client_port) *client_port = ntohs(client_addr.sin_port);
        return true;
    }

    // 阻塞接收数据(LT模式用)
    bool Recv(std::string* buf) {
        char tmp[1024] = {0};
        ssize_t n = read(_fd, tmp, sizeof(tmp)-1);
        if (n < 0) { perror("recv failed"); return false; }
        if (n == 0) { return false; } // 对端关闭
        buf->assign(tmp, n);
        return true;
    }

    // 阻塞发送数据(LT模式用)
    bool Send(const std::string& buf) {
        ssize_t n = write(_fd, buf.c_str(), buf.size());
        if (n < 0) { perror("send failed"); return false; }
        return true;
    }

    // 设置非阻塞IO(ET模式用)
    bool SetNonBlock() {
        int fl = fcntl(_fd, F_GETFL);
        if (fl < 0) { perror("fcntl F_GETFL"); return false; }
        if (fcntl(_fd, F_SETFL, fl | O_NONBLOCK) < 0) {
            perror("fcntl F_SETFL"); return false;
        }
        return true;
    }

    // 非阻塞接收数据(ET模式用,循环读直到缓冲区空)
    bool RecvNonBlock(std::string* buf) {
        buf->clear();
        char tmp[1024*10] = {0};
        while (true) {
            ssize_t n = read(_fd, tmp, sizeof(tmp)-1);
            if (n < 0) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) break; // 缓冲区空,正常退出
                perror("recv nonblock failed"); return false;
            }
            if (n == 0) { return false; } // 对端关闭
            tmp[n] = '\0';
            *buf += tmp;
            if (n < (ssize_t)sizeof(tmp)-1) break; // 数据已读完
        }
        return true;
    }

    // 非阻塞发送数据(ET模式用,循环写直到发送完)
    bool SendNonBlock(const std::string& buf) {
        ssize_t pos = 0;
        ssize_t left = buf.size();
        while (left > 0) {
            ssize_t n = write(_fd, buf.data() + pos, left);
            if (n < 0) {
                if (errno == EAGAIN || errno == EWOULDBLOCK) continue; // 发送缓冲区满,重试
                perror("send nonblock failed"); return false;
            }
            pos += n;
            left -= n;
        }
        return true;
    }

    // 关闭socket
    void Close() {
        if (_fd != -1) { close(_fd); _fd = -1; }
    }

    // 获取FD
    int GetFd() const { return _fd; }

private:
    int _fd;
};

5.2 案例 1:epoll LT 模式服务器

LT 模式逻辑简单,无需非阻塞 IO,适合快速开发。

核心代码(epoll_lt_server.cc)
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/epoll.h>
#include "tcp_socket.hpp"

// 全局字典(英文→中文)
std::unordered_map<std::string, std::string> g_dict = {
    {"apple", "苹果"}, {"banana", "香蕉"}, {"cat", "猫"},
    {"dog", "狗"}, {"book", "书"}, {"happy", "快乐的"}
};

// 业务处理:查询字典
void DictHandler(const std::string& req, std::string* resp) {
    if (g_dict.count(req)) {
        *resp = req + ": " + g_dict[req];
    } else {
        *resp = req + ": 未找到释义";
    }
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    // 1. 创建监听socket
    TcpSocket listen_sock;
    if (!listen_sock.Socket() || !listen_sock.Bind(ip, port) || !listen_sock.Listen()) {
        return 1;
    }
    std::cout << "LT Mode Server Started: " << ip << ":" << port << std::endl;

    // 2. 创建epoll实例
    int epfd = epoll_create(10);
    if (epfd < 0) { perror("epoll_create"); return 1; }

    // 3. 添加监听socket到epoll(LT模式)
    struct epoll_event ev;
    ev.events = EPOLLIN; // 监听可读事件
    ev.data.fd = listen_sock.GetFd();
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock.GetFd(), &ev) < 0) {
        perror("epoll_ctl ADD listen_sock");
        close(epfd);
        return 1;
    }

    // 4. 事件循环
    struct epoll_event events[1000]; // 就绪事件数组
    while (true) {
        // 等待就绪事件(永久阻塞)
        int nfds = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);
        if (nfds < 0) {
            perror("epoll_wait");
            continue;
        }

        // 处理就绪的FD
        for (int i = 0; i < nfds; ++i) {
            int fd = events[i].data.fd;
            if (fd == listen_sock.GetFd()) {
                // 5. 监听socket就绪:处理新连接
                TcpSocket new_sock;
                std::string client_ip;
                uint16_t client_port;
                if (listen_sock.Accept(&new_sock, &client_ip, &client_port)) {
                    std::cout << "New Client: " << client_ip << ":" << client_port << " (fd=" << new_sock.GetFd() << ")" << std::endl;
                    // 添加新连接到epoll
                    struct epoll_event new_ev;
                    new_ev.events = EPOLLIN;
                    new_ev.data.fd = new_sock.GetFd();
                    epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock.GetFd(), &new_ev);
                    // 转移socket所有权(避免析构关闭)
                    _socks.emplace(new_sock.GetFd(), std::move(new_sock));
                }
            } else {
                // 6. 已连接socket就绪:处理请求
                auto it = _socks.find(fd);
                if (it == _socks.end()) continue;

                std::string req, resp;
                if (!it->second.Recv(&req)) {
                    // 客户端关闭,从epoll和_socks中删除
                    std::cout << "Client Disconnected: fd=" << fd << std::endl;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                    _socks.erase(it);
                    continue;
                }

                // 处理请求并返回结果
                DictHandler(req, &resp);
                it->second.Send(resp);
                std::cout << "fd=" << fd << " req: " << req << " → resp: " << resp << std::endl;
            }
        }
    }

    // 7. 释放资源(实际不会执行,因循环常驻)
    close(epfd);
    return 0;
}

5.3 案例 2:epoll ET 模式服务器

ET 模式需将 FD 设为非阻塞,并用循环读写确保数据处理完整。

核心代码(epoll_et_server.cc)
#include <iostream>
#include <vector>
#include <unordered_map>
#include <functional>
#include <sys/epoll.h>
#include "tcp_socket.hpp"

// 全局字典(同LT模式)
std::unordered_map<std::string, std::string> g_dict = {
    {"apple", "苹果"}, {"banana", "香蕉"}, {"cat", "猫"},
    {"dog", "狗"}, {"book", "书"}, {"happy", "快乐的"}
};

// 业务处理(同LT模式)
void DictHandler(const std::string& req, std::string* resp) {
    if (g_dict.count(req)) {
        *resp = req + ": " + g_dict[req];
    } else {
        *resp = req + ": 未找到释义";
    }
}

int main(int argc, char* argv[]) {
    if (argc != 3) {
        std::cerr << "Usage: " << argv[0] << " server_ip server_port" << std::endl;
        return 1;
    }

    std::string ip = argv[1];
    uint16_t port = std::stoi(argv[2]);

    // 1. 创建监听socket(LT模式,避免accept漏接)
    TcpSocket listen_sock;
    if (!listen_sock.Socket() || !listen_sock.Bind(ip, port) || !listen_sock.Listen()) {
        return 1;
    }
    std::cout << "ET Mode Server Started: " << ip << ":" << port << std::endl;

    // 2. 创建epoll实例
    int epfd = epoll_create(10);
    if (epfd < 0) { perror("epoll_create"); return 1; }

    // 3. 添加监听socket到epoll(LT模式,无需EPOLLET)
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = listen_sock.GetFd();
    if (epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock.GetFd(), &ev) < 0) {
        perror("epoll_ctl ADD listen_sock");
        close(epfd);
        return 1;
    }

    // 存储已连接socket(避免析构关闭)
    std::unordered_map<int, TcpSocket> _socks;

    // 4. 事件循环
    struct epoll_event events[1000];
    while (true) {
        int nfds = epoll_wait(epfd, events, sizeof(events)/sizeof(events[0]), -1);
        if (nfds < 0) { perror("epoll_wait"); continue; }

        for (int i = 0; i < nfds; ++i) {
            int fd = events[i].data.fd;
            if (fd == listen_sock.GetFd()) {
                // 5. 处理新连接(LT模式,无需非阻塞)
                TcpSocket new_sock;
                std::string client_ip;
                uint16_t client_port;
                if (listen_sock.Accept(&new_sock, &client_ip, &client_port)) {
                    std::cout << "New Client: " << client_ip << ":" << client_port << " (fd=" << new_sock.GetFd() << ")" << std::endl;
                    // 设置新连接为非阻塞(ET模式必需)
                    new_sock.SetNonBlock();
                    // 添加到epoll,指定EPOLLET
                    struct epoll_event new_ev;
                    new_ev.events = EPOLLIN | EPOLLET; // 边缘触发
                    new_ev.data.fd = new_sock.GetFd();
                    epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock.GetFd(), &new_ev);
                    _socks.emplace(new_sock.GetFd(), std::move(new_sock));
                }
            } else {
                // 6. 处理已连接socket(ET模式,非阻塞读写)
                auto it = _socks.find(fd);
                if (it == _socks.end()) continue;

                std::string req, resp;
                if (!it->second.RecvNonBlock(&req)) {
                    // 客户端关闭,清理资源
                    std::cout << "Client Disconnected: fd=" << fd << std::endl;
                    epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL);
                    _socks.erase(it);
                    continue;
                }

                // 处理请求并发送结果(非阻塞发送)
                DictHandler(req, &resp);
                it->second.SendNonBlock(resp);
                std::cout << "fd=" << fd << " req: " << req << " → resp: " << resp << std::endl;
            }
        }
    }

    close(epfd);
    return 0;
}

5.4 测试步骤

  1. 编译运行服务器
    # LT模式
    g++ epoll_lt_server.cc -o epoll_lt_server -std=c++11
    ./epoll_lt_server 127.0.0.1 8888
    
    # ET模式
    g++ epoll_et_server.cc -o epoll_et_server -std=c++11
    ./epoll_et_server 127.0.0.1 8889
    
  2. 启动客户端
    # 使用telnet或自定义TCP客户端
    telnet 127.0.0.1 8888
    
  3. 测试功能
    • 客户端输入 “apple”,服务器返回 “apple: 苹果”;
    • 输入 “test”,返回 “test: 未找到释义”。

六、epoll vs select/poll:全方位对比

特性selectpollepoll(Linux)
FD 数量限制有(FD_SETSIZE,默认 1024)无(受内存限制)无(支持数万 FD)
数据拷贝每次 epoll_wait 拷贝所有 FD 集合每次 poll 拷贝所有 pollfd 数组仅注册时拷贝,后续无拷贝
就绪事件查找遍历所有 FD(O (N))遍历所有 pollfd(O (N))读取就绪链表(O (1))
触发模式仅水平触发(LT)仅水平触发(LT)水平触发(LT)、边缘触发(ET)
接口复杂度高(需手动重置 FD 集合)中(pollfd 数组,输入输出分离)低(三步调用,接口清晰)
适用场景小规模 FD(≤1024)中等规模 FD大规模高并发(如 Web 服务器、网关)

七、总结与实战建议

epoll 是 Linux 高并发网络编程的 “利器”,其核心优势在于 “无 FD 限制、低数据拷贝、高效就绪事件查找”,配合 ET 模式和非阻塞 IO,可实现百万级连接的高性能服务。

核心收获

  1. 工作原理:通过 “红黑树管理监控 FD” 和 “就绪链表存储就绪事件”,实现高效 FD 管理;
  2. 触发模式:LT 模式简单易用,ET 模式性能更高(需配合非阻塞 IO);
  3. 实战要点
    • ET 模式下,FD 必须设为非阻塞,且需循环读写直到缓冲区空 / 满;
    • 监听 socket 建议用 LT 模式,避免 ET 模式下 accept 漏接连接;
    • 需手动管理已连接 socket 的生命周期(避免析构关闭)。

实战建议

        中小规模服务(≤1000 连接):用 select/poll 即可,开发成本低;

        大规模高并发服务(≥10000 连接):用 epoll ET 模式,配合非阻塞 IO 和线程池,最大化性能;

        跨平台需求:用 select/poll(epoll 仅 Linux 支持),或使用封装库(如 libevent、libuv)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值