<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>异步 IO on JasperSao的博客</title><link>https://jasperstonnne.github.io/MyBlog/tags/%E5%BC%82%E6%AD%A5-io/</link><description>Recent content in 异步 IO on JasperSao的博客</description><generator>Hugo -- gohugo.io</generator><language>zh-cn</language><lastBuildDate>Sun, 30 Aug 2026 08:12:59 +0000</lastBuildDate><atom:link href="https://jasperstonnne.github.io/MyBlog/tags/%E5%BC%82%E6%AD%A5-io/index.xml" rel="self" type="application/rss+xml"/><item><title>IOCP：Windows 异步机制</title><link>https://jasperstonnne.github.io/MyBlog/p/iocp-windows-async-io/</link><pubDate>Thu, 20 Aug 2026 00:00:00 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/iocp-windows-async-io/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/iocp-windows-async-io/cover.svg" alt="Featured image of post IOCP：Windows 异步机制" /&gt;&lt;h2 id="一网络编程要解决什么问题"&gt;一、网络编程要解决什么问题&#10;&lt;/h2&gt;&lt;p&gt;在 C/S 模式中，服务端通常先执行：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;socket -&amp;gt; bind -&amp;gt; listen&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;客户端也会先创建 socket。各种网络模型前面的准备过程基本相同，主要区别在后续如何处理连接和 I/O。&lt;/p&gt;&#10;&lt;p&gt;网络编程中有四件无法预先确定的事情：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;客户端什么时候建立连接；&lt;/li&gt;&#10;&lt;li&gt;客户端什么时候断开连接；&lt;/li&gt;&#10;&lt;li&gt;客户端什么时候发送数据；&lt;/li&gt;&#10;&lt;li&gt;数据什么时候能够写入发送缓冲区。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;因此，网络编程主要解决四类问题：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;连接的建立；&lt;/li&gt;&#10;&lt;li&gt;连接的断开；&lt;/li&gt;&#10;&lt;li&gt;数据的接收；&lt;/li&gt;&#10;&lt;li&gt;数据的发送。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;每个 socket 都对应接收缓冲区（receive buffer）和发送缓冲区（send buffer）。&lt;code&gt;write&lt;/code&gt; 只负责把数据从用户态复制到内核态的发送缓冲区；数据什么时候到达对端、如何到达对端，属于网络协议栈要解决的问题。&lt;/p&gt;&#10;&lt;p&gt;常见的网络模型包括：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;阻塞 I/O 模型；&lt;/li&gt;&#10;&lt;li&gt;Reactor；&lt;/li&gt;&#10;&lt;li&gt;Proactor（IOCP）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="二阻塞-io-与-reactor"&gt;二、阻塞 I/O 与 Reactor&#10;&lt;/h2&gt;&lt;p&gt;一次 I/O 可以分成两个阶段：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;I/O 检测&lt;/strong&gt;：判断 I/O 是否就绪；&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;I/O 操作&lt;/strong&gt;：真正执行 &lt;code&gt;accept&lt;/code&gt;、&lt;code&gt;read&lt;/code&gt;、&lt;code&gt;write&lt;/code&gt;、&lt;code&gt;connect&lt;/code&gt; 等操作。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="1-阻塞-io"&gt;1. 阻塞 I/O&#10;&lt;/h3&gt;&lt;p&gt;阻塞 I/O 通过阻塞当前线程来等待 I/O 就绪，从而解决“不知道事件什么时候发生”的问题。&lt;/p&gt;&#10;&lt;p&gt;阻塞和非阻塞主要描述 I/O 检测阶段：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;阻塞：I/O 没有就绪时，线程会等待；&lt;/li&gt;&#10;&lt;li&gt;非阻塞：I/O 没有就绪时，函数立即返回。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="2-reactor"&gt;2. Reactor&#10;&lt;/h3&gt;&lt;p&gt;Reactor 使用：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;I/O 多路复用（检测 I/O）+ 非阻塞 I/O（操作 I/O）&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它先注册事件。当事件触发时，系统发出就绪通知，用户层收到通知后，再调用 POSIX I/O 函数完成操作。因此，Reactor 属于：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;同步 I/O + 异步事件通知&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;select&lt;/code&gt;、&lt;code&gt;poll&lt;/code&gt;、&lt;code&gt;epoll&lt;/code&gt; 用于同时检测多路 I/O 是否就绪，它们只负责 I/O 检测；Reactor 则包含 I/O 检测和后续的 I/O 操作。&lt;/p&gt;&#10;&lt;p&gt;所以它们的关系是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;Reactor = select/poll/epoll 等 I/O 多路复用 + 非阻塞 I/O 操作&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="三iocp-与-reactor-的区别"&gt;三、IOCP 与 Reactor 的区别&#10;&lt;/h2&gt;&lt;p&gt;IOCP（I/O Completion Port，I/O 完成端口）是 Windows 提供的一种高效异步 I/O 机制。&lt;/p&gt;&#10;&lt;p&gt;Reactor 的过程：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;注册事件&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;内核检测到 I/O 就绪&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户层收到就绪通知&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户层调用 accept/read/write 等函数完成 I/O&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这是一种&lt;strong&gt;就绪通知&lt;/strong&gt;。&lt;/p&gt;&#10;&lt;p&gt;IOCP 的过程：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户层投递异步 I/O 请求&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;内核检测并完成 I/O&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;用户层收到完成通知&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因此：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Reactor：同步 I/O、异步事件；&lt;/li&gt;&#10;&lt;li&gt;IOCP：异步 I/O、异步事件。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;两者都是为了解决网络 I/O 问题，因此 IOCP 在整体概念上更接近 Reactor；但 IOCP 并不是 &lt;code&gt;select&lt;/code&gt;、&lt;code&gt;poll&lt;/code&gt;、&lt;code&gt;epoll&lt;/code&gt; 这样的 I/O 多路复用机制。&lt;/p&gt;&#10;&lt;p&gt;在 Reactor 中，即使 socket 已经就绪，真正的数据复制仍由用户线程调用 I/O 函数完成。IOCP 则由用户层发起异步操作，由内核完成 I/O；在此期间，用户线程可以处理其他任务。&lt;/p&gt;&#10;&lt;h2 id="四iocp-的工作原理"&gt;四、IOCP 的工作原理&#10;&lt;/h2&gt;&lt;p&gt;网络 I/O 属于外设资源，相关数据需要通过系统调用，由内核进行操作。&lt;/p&gt;&#10;&lt;p&gt;IOCP 的基本流程如下：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;使用 &lt;code&gt;CreateIoCompletionPort&lt;/code&gt; 创建完成端口；&lt;/li&gt;&#10;&lt;li&gt;将 socket 与 IOCP 绑定；&lt;/li&gt;&#10;&lt;li&gt;投递异步 I/O 操作；&lt;/li&gt;&#10;&lt;li&gt;内核进行 I/O 检测和 I/O 操作；&lt;/li&gt;&#10;&lt;li&gt;操作完成后，内核把完成信息放入完成队列；&lt;/li&gt;&#10;&lt;li&gt;用户层通过 &lt;code&gt;GetQueuedCompletionStatus&lt;/code&gt; 取得完成通知。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;常见的异步操作包括：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AcceptEx&lt;/code&gt;：接受连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;ConnectEx&lt;/code&gt;：主动建立连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;DisconnectEx&lt;/code&gt;：主动断开连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WSARecv&lt;/code&gt;：接收数据；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WSASend&lt;/code&gt;：发送数据。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;例如建立连接时：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;socket 绑定 IOCP&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;投递 AcceptEx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;内核完成连接接收&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;GetQueuedCompletionStatus 取得完成通知&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;与传统 &lt;code&gt;accept&lt;/code&gt; 不同，&lt;code&gt;AcceptEx&lt;/code&gt; 是预先投递的异步请求。服务器可以在启动时投递多个 &lt;code&gt;AcceptEx&lt;/code&gt;，从而并发接收多条连接。&lt;/p&gt;&#10;&lt;h3 id="iocp-与线程池"&gt;IOCP 与线程池&#10;&lt;/h3&gt;&lt;p&gt;IOCP 通常会绑定一个线程池。&lt;/p&gt;&#10;&lt;p&gt;当线程调用 &lt;code&gt;GetQueuedCompletionStatus&lt;/code&gt; 时，该线程会与 IOCP 产生关联。线程退出或关闭后，关联取消；一个线程只能关联一个 IOCP。&lt;/p&gt;&#10;&lt;p&gt;&lt;code&gt;GetQueuedCompletionStatus&lt;/code&gt; 是阻塞接口：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;等待完成队列出现数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;取得完成状态&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;在用户态处理完成事件&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;最后可以使用 &lt;code&gt;CloseHandle&lt;/code&gt; 关闭 I/O 完成端口句柄。&lt;/p&gt;&#10;&lt;h2 id="五完成事件如何与连接关联"&gt;五、完成事件如何与连接关联&#10;&lt;/h2&gt;&lt;p&gt;收到完成通知后，需要知道是哪一条连接、哪一次操作完成了。&lt;/p&gt;&#10;&lt;h3 id="1-reactor-中的关联方式"&gt;1. Reactor 中的关联方式&#10;&lt;/h3&gt;&lt;p&gt;以 &lt;code&gt;epoll&lt;/code&gt; 为例，注册事件时可以把一个值交给内核保存：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;也可以关联用户态指针：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ev&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ptr&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;user_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 返回事件时会带回这个值，因此可以判断是哪一条连接发生了事件。&lt;/p&gt;&#10;&lt;h3 id="2-iocp-中的关联方式"&gt;2. IOCP 中的关联方式&#10;&lt;/h3&gt;&lt;p&gt;IOCP 有两种关联信息。&lt;/p&gt;&#10;&lt;p&gt;第一种是 &lt;code&gt;CreateIoCompletionPort&lt;/code&gt; 的第三个参数 &lt;code&gt;CompletionKey&lt;/code&gt;。它可以保存一个用户态指针，并在其中记录 socket 等连接信息。&lt;/p&gt;&#10;&lt;p&gt;第二种是异步函数最后传入的 &lt;code&gt;OVERLAPPED&lt;/code&gt; 指针。可以把 &lt;code&gt;OVERLAPPED&lt;/code&gt; 放进自定义结构体：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-cpp" data-lang="cpp"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="nc"&gt;OverlappedPerIO&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;OVERLAPPED&lt;/span&gt; &lt;span class="n"&gt;overlapped&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;SOCKET&lt;/span&gt; &lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="c1"&gt;// 其他与本次 I/O 相关的数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;};&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;内核使用其中的 &lt;code&gt;OVERLAPPED&lt;/code&gt;，用户层则可以在结构体中附加需要关联的数据。&lt;/p&gt;&#10;&lt;p&gt;可以这样理解：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;CompletionKey&lt;/code&gt;：关联连接级信息；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;OVERLAPPED*&lt;/code&gt;：关联某一次 I/O 操作的信息。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h2 id="六重叠-io"&gt;六、重叠 I/O&#10;&lt;/h2&gt;&lt;p&gt;异步 I/O 是投递请求后无需原地等待，可以由其他线程等待完成通知。&lt;/p&gt;&#10;&lt;p&gt;重叠 I/O 是指：无需等待上一个 I/O 完成，就可以继续投递下一个操作，让多个操作同时处于进行状态。&lt;/p&gt;&#10;&lt;p&gt;例如，如果每次只投递一个 &lt;code&gt;AcceptEx&lt;/code&gt;，完成一次后再投递下一次，那么客户端较多时，建立连接的效率会比较低。服务器可以在启动时投递多个 &lt;code&gt;AcceptEx&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AcceptEx 请求 1 ─┐&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AcceptEx 请求 2 ─┼─→ 同时等待多个客户端连接&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;AcceptEx 请求 3 ─┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;每个重叠 I/O 都使用一个独立的 &lt;code&gt;OVERLAPPED&lt;/code&gt; 结构。操作完成后，&lt;code&gt;GetQueuedCompletionStatus&lt;/code&gt; 会返回对应的 &lt;code&gt;OVERLAPPED*&lt;/code&gt;，用户层据此找到这次操作的上下文。&lt;/p&gt;&#10;&lt;h2 id="七连接断开的判断"&gt;七、连接断开的判断&#10;&lt;/h2&gt;&lt;p&gt;客户端主动断开连接时，可以通过以下方式判断。&lt;/p&gt;&#10;&lt;h3 id="reactor"&gt;Reactor&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;epoll_wait&lt;/code&gt; 返回 &lt;code&gt;EPOLLHUP&lt;/code&gt; 或 &lt;code&gt;EPOLLRDHUP&lt;/code&gt;；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;read&lt;/code&gt; / &lt;code&gt;recv&lt;/code&gt; 返回 &lt;code&gt;0&lt;/code&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;h3 id="iocp"&gt;IOCP&#10;&lt;/h3&gt;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;GetLastError&lt;/code&gt; 返回相关错误，例如异常断开时的 &lt;code&gt;ERROR_NETNAME_DELETED&lt;/code&gt;；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;GetQueuedCompletionStatus&lt;/code&gt; 返回的完成字节数 &lt;code&gt;dwBytes == 0&lt;/code&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;超时情况下还可能出现 &lt;code&gt;WAIT_TIMEOUT&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h2 id="八投递操作"&gt;八、投递操作&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AcceptEx&lt;/code&gt; 可以多次投递，以并发接收连接；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WSARecv&lt;/code&gt; 涉及接收顺序和线程安全问题；&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;WSASend&lt;/code&gt; 可以多次投递。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>io_uring（一）：与 epoll 一较高下</title><link>https://jasperstonnne.github.io/MyBlog/p/io-uring-vs-epoll/</link><pubDate>Wed, 19 Aug 2026 00:00:00 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/io-uring-vs-epoll/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/io-uring-vs-epoll/cover.svg" alt="Featured image of post io_uring（一）：与 epoll 一较高下" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;核心目标：能从代码上说清楚 &lt;code&gt;epoll&lt;/code&gt; 和 &lt;code&gt;io_uring&lt;/code&gt; 的区别，而不是只记 API。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;h2 id="1-今天最重要的结论"&gt;1. 今天最重要的结论&#10;&lt;/h2&gt;&lt;p&gt;一句话区分：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Reactor / epoll：内核通知“现在可以做 I/O 了”，真正的 &lt;code&gt;accept/recv/send&lt;/code&gt; 由应用执行。&lt;/strong&gt;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Proactor / io_uring：应用先提交 I/O 操作，内核完成后通知“这个操作已经做完了”。&lt;/strong&gt;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;最值得记住的一组对照：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;epoll 的 EPOLLIN = 可以读了，但数据还没被应用读出&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring 的 READ CQE = 读操作已结束，结果在 cqe-&amp;gt;res，数据已进入指定 buffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;epoll 的 EPOLLOUT = 现在可以写，但应用还要调用 send/write&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring 的 WRITE CQE = 写操作已结束，cqe-&amp;gt;res 表示实际写出的字节数&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;因此，二者虽然都有“注册/提交 → 等待 → 处理事件”的循环，但事件的含义完全不同：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;epoll 返回的是&lt;strong&gt;就绪事件&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;li&gt;io_uring 返回的是&lt;strong&gt;完成事件&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="2-异步与异步-io不是一回事"&gt;2. “异步”与“异步 I/O”不是一回事&#10;&lt;/h2&gt;&lt;h3 id="21-一般意义上的异步"&gt;2.1 一般意义上的异步&#10;&lt;/h3&gt;&lt;p&gt;发起任务后，不原地等待它结束，当前执行流可以继续做别的事；任务完成后再通过回调、消息、事件或协程恢复等方式取得结果。&lt;/p&gt;&#10;&lt;p&gt;例如：把日志写盘任务放入线程池。业务线程只投递任务，工作线程实际调用同步 &lt;code&gt;write()&lt;/code&gt;。从业务流程看它是异步的，但底层 &lt;code&gt;write()&lt;/code&gt; 本身仍可能是同步 I/O。&lt;/p&gt;&#10;&lt;h3 id="22-同步-io"&gt;2.2 同步 I/O&#10;&lt;/h3&gt;&lt;p&gt;普通的：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;length&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;一次函数调用既发起操作，也返回该次操作的结果。即使 fd 被设为非阻塞，调用者仍然要亲自执行 &lt;code&gt;recv/send&lt;/code&gt;；“非阻塞”不自动等于“异步 I/O”。&lt;/p&gt;&#10;&lt;h3 id="23-epoll-做到的是什么"&gt;2.3 epoll 做到的是什么&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;epoll_wait()&lt;/code&gt; 可以高效等待大量 fd 的&lt;strong&gt;就绪状态&lt;/strong&gt;，避免逐个 fd 轮询。它通常与非阻塞 socket 配合，构成 Reactor。&lt;/p&gt;&#10;&lt;p&gt;但 &lt;code&gt;epoll&lt;/code&gt; 不替应用搬运数据：收到 &lt;code&gt;EPOLLIN&lt;/code&gt; 后，代码仍要调用 &lt;code&gt;recv()&lt;/code&gt;；收到 &lt;code&gt;EPOLLOUT&lt;/code&gt; 后，仍要调用 &lt;code&gt;send()&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h3 id="24-io_uring-做到的是什么"&gt;2.4 io_uring 做到的是什么&#10;&lt;/h3&gt;&lt;p&gt;应用把“请替我执行 accept/read/write……”描述成 SQE，提交给内核；操作完成后，内核写入 CQE。应用处理 CQE 时，看到的是操作结果，而不只是“可以开始操作”。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="3-io_uring-的两个环"&gt;3. io_uring 的两个环&#10;&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用准备 SQE → SQ（Submission Queue）→ 内核执行 I/O&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;应用处理结果 ← CQ（Completion Queue）← 内核生成 CQE&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="sq--sqe"&gt;SQ / SQE&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;SQ：提交队列。&lt;/li&gt;&#10;&lt;li&gt;SQE：一次待执行 I/O 的描述，例如操作类型、fd、buffer、长度和用户数据。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_get_sqe()&lt;/code&gt;：取得一个可填写的 SQE。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_prep_accept/read/write(...)&lt;/code&gt;：只是在填写 SQE，&lt;strong&gt;不会立刻执行 I/O&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_submit()&lt;/code&gt;：把准备好的请求提交给内核。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="cq--cqe"&gt;CQ / CQE&#10;&lt;/h3&gt;&lt;ul&gt;&#10;&lt;li&gt;CQ：完成队列。&lt;/li&gt;&#10;&lt;li&gt;CQE：一个已经完成的 I/O 结果。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cqe-&amp;gt;res&lt;/code&gt;：操作结果。成功时通常是新 fd 或字节数；失败时是负的 errno 值。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;cqe-&amp;gt;user_data&lt;/code&gt;：应用提交时附带的标识，用来判断这是哪个连接、哪类操作。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_cq_advance()&lt;/code&gt;：告诉 ring，这批 CQE 已处理，可以回收。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="为什么使用环形队列和-mmap"&gt;为什么使用环形队列和 mmap&#10;&lt;/h3&gt;&lt;p&gt;环形队列可以复用固定槽位，减少频繁分配；SQ/CQ 通过 &lt;code&gt;mmap&lt;/code&gt; 在用户态和内核态之间共享队列元数据，减少提交、取结果时的额外系统调用和元数据复制。&lt;/p&gt;&#10;&lt;p&gt;但要注意：这不代表所有 I/O 数据都天然“零拷贝”。例如普通 socket 接收的数据通常仍需进入应用提供的 buffer。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="4-io_uring-三个底层系统调用"&gt;4. io_uring 三个底层系统调用&#10;&lt;/h2&gt;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_setup&lt;/code&gt;：创建并配置 ring，建立 SQ/CQ 所需资源。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_enter&lt;/code&gt;：提交请求和/或等待完成事件。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_register&lt;/code&gt;：预注册文件、buffer、事件等资源，减少热路径开销；它不是每个普通请求都必须调用。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;课堂代码使用的是 &lt;code&gt;liburing&lt;/code&gt; 封装：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_queue_init_params&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ENTRIES_LENGTH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_get_sqe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_prep_accept&lt;/span&gt;&lt;span class="p"&gt;(...);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_submit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_wait_cqe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;可近似理解为：初始化 ring → 取得请求槽位 → 描述操作 → 提交 → 等结果。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="5-你的-io_uring-echo-server-怎么走"&gt;5. 你的 io_uring Echo Server 怎么走&#10;&lt;/h2&gt;&lt;p&gt;关键入口：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;set_event_accept&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;sockfd&lt;/span&gt;&lt;span class="p"&gt;,(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;sockaddr&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;clientaddr&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;len&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;set_event_accept()&lt;/code&gt; 做两件事：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;用 &lt;code&gt;io_uring_get_sqe()&lt;/code&gt; 取得 SQE。&lt;/li&gt;&#10;&lt;li&gt;用 &lt;code&gt;io_uring_prep_accept()&lt;/code&gt; 填写 accept 请求，并在 &lt;code&gt;user_data&lt;/code&gt; 中记录 &lt;code&gt;fd + EVENT_ACCEPT&lt;/code&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;p&gt;此时只是&lt;strong&gt;准备请求&lt;/strong&gt;，并没有等待客户端。&lt;/p&gt;&#10;&lt;p&gt;主循环：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring_submit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 提交当前准备好的 accept/recv/send 请求&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring_wait_cqe&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 没有完成结果时在这里等待&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring_peek_batch_cqe&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 批量取得已完成操作&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;根据 user_data 判断 EVENT_ACCEPT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;再次 set_event_accept，同时为新 clientfd 提交 recv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;io_uring_cq_advance&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;你测试时发现程序阻塞在 &lt;code&gt;io_uring_wait_cqe()&lt;/code&gt;，这是合理的：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_prep_accept()&lt;/code&gt; 只准备请求，很快返回。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring_submit()&lt;/code&gt; 提交请求。&lt;/li&gt;&#10;&lt;li&gt;在客户端真正连接前，accept 尚未完成，CQ 中没有结果，所以 &lt;code&gt;wait_cqe()&lt;/code&gt; 等待。&lt;/li&gt;&#10;&lt;li&gt;客户端连接后，CQE 到达，&lt;code&gt;cqe-&amp;gt;res&lt;/code&gt; 应是新连接的 client fd（失败则为负数）。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;h3 id="51-三种事件不是就绪事件而是操作身份"&gt;5.1 三种事件不是“就绪事件”，而是操作身份&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define EVENT_ACCEPT 0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define EVENT_READ 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="cp"&gt;#define EVENT_WRITE 2&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里的 &lt;code&gt;EVENT_*&lt;/code&gt; 是应用自己定义的标签。准备 SQE 时，代码把 &lt;code&gt;fd + event&lt;/code&gt; 写入 &lt;code&gt;sqe-&amp;gt;user_data&lt;/code&gt;；操作完成后，再从 CQE 的 &lt;code&gt;user_data&lt;/code&gt; 还原标签，据此判断完成的是 accept、recv 还是 send。&lt;/p&gt;&#10;&lt;p&gt;三种 SQE 的含义：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;函数&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;提交给内核的操作&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;CQE 到达时 &lt;code&gt;entries-&amp;gt;res&lt;/code&gt; 的含义&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;set_event_accept&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;接收一个新连接&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;新连接 fd；负数表示失败&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;set_event_recv&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把数据读进指定 buffer&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;实际读取字节数；0 表示对端关闭；负数表示失败&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;set_event_send&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;发送指定 buffer 中的数据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;实际发送字节数；负数表示失败&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;h3 id="52-完整状态循环"&gt;5.2 完整状态循环&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;启动：准备 ACCEPT SQE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ submit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ACCEPT 完成&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ entries-&amp;gt;res 得到 connfd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ 再准备一个 ACCEPT，继续接收其他客户端&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ 为 connfd 准备 RECV&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ submit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; READ 完成&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ├─ res == 0：对端断开，close(fd)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └─ res &amp;gt; 0：buffer 中已有数据，准备 SEND&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ submit&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; WRITE 完成&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; 再为该 fd 准备 RECV&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;对单个连接而言，核心循环就是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;RECV 完成 → SEND 完成 → 再次 RECV&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这份代码没有额外解析数据，而是把收到的 &lt;code&gt;buffer&lt;/code&gt; 原样发送回去，因此它是一个 io_uring Echo Server。&lt;/p&gt;&#10;&lt;h3 id="53-为什么循环开头的-submit-能提交刚刚准备的操作"&gt;5.3 为什么循环开头的 submit 能提交刚刚准备的操作&#10;&lt;/h3&gt;&lt;p&gt;在处理 CQE 时调用的 &lt;code&gt;set_event_accept/recv/send()&lt;/code&gt; 都只是取得并填写新的 SQE。它们不会立刻执行；本轮处理结束并 &lt;code&gt;cq_advance&lt;/code&gt; 后，程序回到 &lt;code&gt;while&lt;/code&gt; 开头，由下一次 &lt;code&gt;io_uring_submit()&lt;/code&gt; 统一提交。&lt;/p&gt;&#10;&lt;p&gt;因此时间顺序是：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;本轮处理 CQE → 准备下一批 SQE → advance → 下一轮 submit&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这种批量准备、批量提交正是 io_uring 的重要思路。&lt;/p&gt;&#10;&lt;h3 id="为什么处理后要再次设置-accept"&gt;为什么处理后要再次设置 accept&#10;&lt;/h3&gt;&lt;p&gt;普通的 &lt;code&gt;io_uring_prep_accept()&lt;/code&gt; 描述的是&lt;strong&gt;一次 accept 操作&lt;/strong&gt;。完成一次就消费掉一次请求；若想继续接收连接，需要再准备并提交新的 accept。&lt;/p&gt;&#10;&lt;p&gt;这与 epoll 的常规 LT 模式不同：监听 fd 注册 &lt;code&gt;EPOLLIN&lt;/code&gt; 后，只要仍处于可读状态，epoll 可以继续报告就绪，无需每完成一次 accept 就重新 &lt;code&gt;EPOLL_CTL_ADD&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h3 id="为什么必须-advance"&gt;为什么必须 advance&#10;&lt;/h3&gt;&lt;p&gt;CQE 被读取不等于被消费：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;io_uring_cq_advance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="n"&gt;nready&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这一步更新 CQ 的消费位置。漏掉它，旧 CQE 会一直被当成尚未处理，表现为循环反复看到同一结果。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="6-你的-reactor-代码怎么走"&gt;6. 你的 Reactor 代码怎么走&#10;&lt;/h2&gt;&lt;h3 id="61-初始化"&gt;6.1 初始化&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;epoll_create&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;创建 20 个监听 socket（端口 2000～2019）&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;把监听 fd 以 EPOLLIN 加入 epoll&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;进入 epoll_wait 主循环&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;监听 fd 的回调被设置为 &lt;code&gt;accept_cb&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;conn_list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;sockfd&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;r_action&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;recv_callback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;accept_cb&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="62-建立连接"&gt;6.2 建立连接&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;监听 fd 出现 EPOLLIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;主循环调用 accept_cb&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;accept_cb 主动执行 accept()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;event_register(clientfd, EPOLLIN)&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;关键点：&lt;code&gt;EPOLLIN&lt;/code&gt; 只表示监听 socket 可以执行 accept；真正建立并取出 client fd 的动作仍由 &lt;code&gt;accept()&lt;/code&gt; 完成。&lt;/p&gt;&#10;&lt;h3 id="63-接收数据"&gt;6.3 接收数据&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clientfd 出现 EPOLLIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;调用 recv_cb&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;recv() 把数据读进 rbuffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ws_request() 处理请求&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;把关注事件修改为 EPOLLOUT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 &lt;code&gt;recv()&lt;/code&gt; 返回 0，说明对端正常断开；返回负数则表示错误。当前代码随后关闭 fd 并从 epoll 删除。&lt;/p&gt;&#10;&lt;h3 id="64-发送数据"&gt;6.4 发送数据&#10;&lt;/h3&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clientfd 出现 EPOLLOUT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;调用 send_cb&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ws_response() 生成响应&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;send() 主动发送 wbuffer&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;把关注事件改回 EPOLLIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;整个连接状态近似为：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;等待读 EPOLLIN → recv + 处理请求 → 等待写 EPOLLOUT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↑ ↓&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; └──────── send + 改回读 ──────────┘&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="7-逐段对照reactor-与-io_uring"&gt;7. 逐段对照：Reactor 与 io_uring&#10;&lt;/h2&gt;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;阶段&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;epoll / Reactor&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;io_uring / Proactor 思路&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;接收连接&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;注册监听 fd 的 &lt;code&gt;EPOLLIN&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;提交一个 accept SQE&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;通知到达&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;“监听 fd 可以 accept”&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;“accept 已完成”&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;新连接 fd&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;应用调用 &lt;code&gt;accept()&lt;/code&gt; 得到&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;从 accept CQE 的 &lt;code&gt;res&lt;/code&gt; 得到&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;接收数据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;EPOLLIN&lt;/code&gt; 后应用调用 &lt;code&gt;recv()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;预先提交 recv/read SQE，完成后 buffer 已有数据&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;发送数据&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;EPOLLOUT&lt;/code&gt; 后应用调用 &lt;code&gt;send()&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;预先提交 send/write SQE，完成后得到实际写入量&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;事件身份&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;events[i].data.fd&lt;/code&gt; + 回调表&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;cqe-&amp;gt;user_data&lt;/code&gt; 标识 fd/事件/上下文&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;一次操作结束&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;修改下一次关注的就绪事件&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;消费 CQE，再准备下一次 I/O SQE&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;批量处理&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;epoll_wait(..., events, 1024, ...)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;io_uring_peek_batch_cqe(..., cqes, 128)&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;代码上的一一对应：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;epoll_ctl ADD/MOD ↔ io_uring_get_sqe + prep_xxx&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;epoll_wait ↔ io_uring_wait_cqe / peek_batch_cqe&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;events[i].data.fd ↔ cqe-&amp;gt;user_data&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EPOLLIN 后 recv ↔ READ CQE 到达时数据已读完&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;EPOLLOUT 后 send ↔ WRITE CQE 到达时写操作已完成&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;下一轮 set_event ↔ 下一轮重新准备 SQE&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↔ io_uring_cq_advance 消费完成项&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="8-对学习笔记中三个说法的校正"&gt;8. 对学习笔记中三个说法的校正&#10;&lt;/h2&gt;&lt;h3 id="-sqe-与-cqe-是同一个节点共用一块内存不准确"&gt;① “SQE 与 CQE 是同一个节点，共用一块内存”——不准确&#10;&lt;/h3&gt;&lt;p&gt;SQE 和 CQE 是不同队列中的不同结构：SQE 描述请求，CQE 描述结果。它们通过 &lt;code&gt;user_data&lt;/code&gt; 关联。共享内存指的是这些 ring 能被用户态与内核态共同访问，不是 SQE 原地变成 CQE。&lt;/p&gt;&#10;&lt;h3 id="-io_uring_prep_accept-底层调用-register不准确"&gt;② “io_uring_prep_accept 底层调用 register”——不准确&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;io_uring_prep_accept()&lt;/code&gt; 主要是填写 SQE。&lt;code&gt;io_uring_register&lt;/code&gt; 是独立的资源注册系统调用，普通 accept 请求不要求每次 register。&lt;/p&gt;&#10;&lt;h3 id="-mmap-后就没有-copy范围过大"&gt;③ “mmap 后就没有 copy”——范围过大&#10;&lt;/h3&gt;&lt;p&gt;&lt;code&gt;mmap&lt;/code&gt; 主要减少 SQ/CQ 控制信息在用户态和内核态之间的复制及系统调用开销；普通网络 I/O 的 payload 是否复制，是另一层问题。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="9-最小记忆框架"&gt;9. 最小记忆框架&#10;&lt;/h2&gt;&lt;p&gt;只背下面四句话：&lt;/p&gt;&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;code&gt;epoll&lt;/code&gt; 给我的是&lt;strong&gt;就绪&lt;/strong&gt;，我还要自己 &lt;code&gt;accept/recv/send&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;io_uring&lt;/code&gt; 给我的是&lt;strong&gt;完成&lt;/strong&gt;，结果看 &lt;code&gt;cqe-&amp;gt;res&lt;/code&gt;，身份看 &lt;code&gt;cqe-&amp;gt;user_data&lt;/code&gt;。&lt;/li&gt;&#10;&lt;li&gt;io_uring 的路径是：&lt;strong&gt;取 SQE → prep → submit → 等 CQE → 处理 → advance&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;li&gt;普通 io_uring 请求通常是一次性的：&lt;strong&gt;完成一次，消费一次，再提交下一次&lt;/strong&gt;。&lt;/li&gt;&#10;&lt;/ol&gt;&#10;</description></item></channel></rss>