CppGuide社区 CppGuide社区
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
首页
  • 🔥最新谷歌C++风格指南(含C++17/20)
  • 🔥C++17详解
  • 🔥C++20完全指南
  • 🔥C++23快速入门
🔥C++面试
  • 第1章 C++ 惯用法与Modern C++篇
  • 第2章 C++开发工具与调试进阶
  • 第3章 C++多线程编程从入门到进阶
  • 第4章 C++网络编程重难点解析
  • 第5章 网络通信故障排查常用命令
  • 第6章 网络通信协议设计
  • 第7章 高性能服务结构设计
  • 第8章 Redis网络通信模块源码分析
  • 第9章 服务其他模块设计
  • 🚀 全部章节.pdf 下载 (opens new window)
  • 🔥C++游戏编程入门(零基础学C++)
  • 🔥使用C++17从零开发一个调试器 (opens new window)
  • 🔥使用C++20从零构建一个完整的低延迟交易系统 (opens new window)
  • 🔥使用C++从零写一个C语言编译器 (opens new window)
  • 🔥从零用C语言写一个Redis
  • leveldb源码分析
  • libevent源码分析
  • Memcached源码分析
  • TeamTalk源码分析
  • 优质源码分享 (opens new window)
  • 🔥远程控制软件gh0st源码分析
  • 🔥Windows 10系统编程
  • 🔥Linux 5.x内核开发与调试 完全指南 (opens new window)
  • TCP源码实现超详细注释版.pdf (opens new window)
  • 高效Go并发编程
  • Go性能调优
  • Go项目架构设计
  • 🔥使用Go从零开发一个数据库
  • 🔥使用Go从零开发一个编译器 (opens new window)
  • 🔥使用Go从零开发一个解释器 (opens new window)
Rust编程指南
  • SQL零基础指南
  • MySQL开发与调试指南
GitHub (opens new window)
  • 从零用C语言写一个Redis 引言
  • 02. 套接字入门
  • 03. 简易服务器/客户端
  • 04. 协议解析
  • 05. 事件循环与非阻塞I/O
    • 05. 事件循环与非阻塞I/O
  • 06. 事件循环的实现
  • 07. 基础服务器:实现get、set、del功能
  • 08. 数据结构:哈希表
  • 09. 数据序列化
  • 10. AVL树:实现与测试
  • 11. AVL树和有序集合
  • 12. 事件循环和定时器
  • 13. 堆数据结构和生存时间(TTL)
  • 14. 线程池与异步任务
目录

05. 事件循环与非阻塞I/O

# 05. 事件循环与非阻塞I/O

在服务器端网络编程里,处理并发连接有三种方法:多进程(forking)、多线程(multi-threading),还有事件循环(event loops)。多进程是给每个客户端连接创建新的进程来实现并发;多线程则是用线程代替进程。事件循环呢,靠的是轮询(polling)和非阻塞I/O(Nonblocking IO),而且通常在单线程上运行。由于进程和线程开销的问题,现在大多数生产级别的软件在网络编程这块都用事件循环。

咱们服务器事件循环的简化伪代码是这样的:

all_fds =   [ ... ]
while  True :
    active_fds =  poll(all_fds)
    for  each fd in  active_fds:
        do_something_with(fd)

def  do_something_with(fd):
    if  fd是监听套接字:
        add_new_client(fd)
    elif  fd是客户端连接:
        while  work_not_done(fd):
            do_something_to_client(fd)

def  do_something_to_client(fd):
    if  should_read_from(fd):
        data =   read_until_EAGAIN(fd)
        process_incoming_data(data)
    while  should_write_to(fd):
        write_until_EAGAIN(fd)
    if  should_close(fd):
        destroy_client(fd)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

咱们不是直接对文件描述符(fd)进行读写或接收操作,而是用轮询(poll)操作来告诉我们哪些fd能直接操作还不会阻塞。当对fd进行I/O操作时,这个操作得在非阻塞模式下进行。

在阻塞模式下,如果内核里没数据,read操作会阻塞调用者;如果写缓冲区满了,write操作会阻塞;要是内核队列里没有新连接,accept操作也会阻塞。在非阻塞模式下,这些操作要么不阻塞直接成功,要么失败并返回错误码EAGAIN,这个错误码的意思就是“还没准备好”。因为EAGAIN而失败的非阻塞操作,必须在轮询通知其准备就绪后重新尝试。

在事件循环里,轮询(poll)是唯一的阻塞操作,其他所有操作都得是非阻塞的,这样单线程就能处理多个并发连接啦。所有阻塞式的网络I/O API,比如read、write和accept,都有非阻塞模式。像gethostbyname这种没有非阻塞模式的API,还有磁盘I/O操作,应该在线程池(thread pools)里执行,这部分内容后面的章节会讲到。另外,定时器(timers)也必须在事件循环里实现,因为在事件循环里可不能靠sleep等待。

把fd设置为非阻塞模式的系统调用是fcntl:

static void fd_set_nb(int fd)  {
    errno =  0;
    int flags =  fcntl(fd,  F_GETFL,  0);
    if  (errno)  {
        die("fcntl error");
        return ;
    }

    flags | =  O_NONBLOCK;

    errno =  0;
    (void)fcntl(fd,  F_SETFL,  flags);
    if  (errno)  {
        die("fcntl error");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在Linux系统里,除了poll系统调用,还有select和epoll。老古董select系统调用和poll基本差不多,就是最大文件描述符数量被限制得很小,不适用于有大量客户端连接的应用。epoll API由三个系统调用组成:epoll_create、epoll_wait和epoll_ctl。epoll API是有状态的,它不是把fd集合作为系统调用的参数,而是用epoll_ctl来操作由epoll_create创建的fd集合,epoll_wait就是对这个集合进行操作。

下一章我们会用poll系统调用,因为它的代码量比有状态的epoll API稍微少点。不过在实际项目里,epoll API更受欢迎,因为随着fd数量增加,poll的参数会变得特别大。

上次更新: 2025/03/25, 00:48:42
04. 协议解析
06. 事件循环的实现

← 04. 协议解析 06. 事件循环的实现→

最近更新
01
C++语言面试问题集锦 目录与说明
03-27
02
第四章 Lambda函数
03-27
03
第二章 关键字static及其不同用法
03-27
更多文章>
Copyright © 2024-2025 沪ICP备2023015129号 张小方 版权所有
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式