Back to VNotes

C++ WebServer / 06

ET 与非阻塞 I/O:为什么必须循环 read

v3.4 切换到 ET 模式。ET 模式只在状态变化时通知一次,所以必须配合非阻塞 I/O,并循环 read 到 EAGAIN。

这一阶段要解决什么问题

LT 模式下,只要 fd 上还有数据,epoll_wait 还会继续通知。ET 模式下,如果这一次没有把数据读干净,剩余数据可能不会再次触发 EPOLLIN。

原来的实现有什么缺陷

如果仍然只 read 一次,ET 模式下内核缓冲区里可能还剩数据,但事件不再触发,连接表现得像卡住。

我是怎么改的

把客户端 fd 设置为非阻塞,并在读事件触发后循环 read。n > 0 就追加到 Connection 的 read_buffer;n == 0 表示对端关闭;n < 0 且 errno 为 EAGAIN/EWOULDBLOCK 表示本轮读完。

核心代码 / 关键逻辑

while (true) {
    ssize_t n = read(fd, buffer, sizeof(buffer));
    if (n > 0) {
        conn.append_to_read_buffer(buffer, n);
    } else if (n == 0) {
        close_connection(fd);
        break;
    } else {
        if (errno == EAGAIN || errno == EWOULDBLOCK) break;
        close_connection(fd);
        break;
    }
}

踩坑记录

一开始很容易以为“触发一次 EPOLLIN,read 一次就够”。但 ET 的关键是必须读到 EAGAIN,否则连接里剩下的数据可能一直没有下一次通知。