<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Socket on JasperSao的博客</title><link>https://jasperstonnne.github.io/MyBlog/tags/socket/</link><description>Recent content in Socket 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/socket/index.xml" rel="self" type="application/rss+xml"/><item><title>POSIX API 与 TCP 连接生命周期</title><link>https://jasperstonnne.github.io/MyBlog/p/posix-api-tcp-connection-lifecycle/</link><pubDate>Mon, 10 Aug 2026 00:00:00 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/posix-api-tcp-connection-lifecycle/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/posix-api-tcp-connection-lifecycle/cover.svg" alt="Featured image of post POSIX API 与 TCP 连接生命周期" /&gt;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;本篇承接已有的《网络IO》《IO 多路复用与 Reactor》《Reactor 与 epoll：百万并发网络服务器课后笔记》。&lt;br&gt;&#10;socket、fd、服务端基础代码、select/poll/epoll 和 Reactor 不再重复；&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="一为什么网络代码可以跨-linuxunix-和-macos"&gt;一、为什么网络代码可以跨 Linux、Unix 和 macOS&#10;&lt;/h2&gt;&lt;p&gt;Linux 受到 Unix 设计影响，不同厂商和社区又形成了不同发行版。它们的内核、工具和扩展功能可能不同，但应用程序仍然可以使用一组相似的接口：&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;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="nf"&gt;bind&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;listen&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;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;connect&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;&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;close&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;重要原因是这些系统广泛支持 POSIX 和 BSD Socket 风格的接口规范。&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;函数叫什么&#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;/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;POSIX / Socket API&#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;代码可以直接迁移还要求它没有依赖某个系统的专有能力。例如 socket、bind、listen 是通用 Socket API；epoll 是 Linux 专用机制，迁移到 macOS 时通常要换成 kqueue。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="二客户端和服务端使用的-api"&gt;二、客户端和服务端使用的 API&#10;&lt;/h2&gt;&lt;h3 id="21-tcp-客户端"&gt;2.1 TCP 客户端&#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;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="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="c1"&gt;//optional&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;connect&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;&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;close&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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;socket() → 创建 socket 和 fd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;bind() → 可选，指定本地 IP 和本地端口&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;connect() → 指定对端并发起 TCP 连接&#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;span class="line"&gt;&lt;span class="cl"&gt;recv() → 从内核取出收到的数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;close() → 关闭 fd，并推动 TCP 关闭过程&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;客户端通常不主动调用 bind()。如果直接 connect()，内核会选择合适的本地 IP 和临时端口。&lt;/p&gt;&#10;&lt;p&gt;临时端口范围由系统配置决定，不固定为 1024～65535。Linux 上可以查看：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /proc/sys/net/ipv4/ip_local_port_range&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;客户端需要固定源 IP 或源端口时，才会在 connect() 前显式调用 bind()。&lt;/p&gt;&#10;&lt;h3 id="22-tcp-服务端"&gt;2.2 TCP 服务端&#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;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="nf"&gt;bind&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;listen&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;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;recv&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;close&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;与 Reactor 结合后还会使用：&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;epoll_create&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;epoll_ctl&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;epoll_wait&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;fcntl&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;这两组 API 位于不同层次：&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/bind/listen/accept/connect/send/recv/close&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 管理 TCP socket 和数据传输&#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_create/epoll_ctl/epoll_wait&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 管理大量 fd 的就绪事件&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;fcntl() 常用于把 fd 设置为非阻塞。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="三一次-tcp-连接的三个阶段"&gt;三、一次 TCP 连接的三个阶段&#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;1. 建立连接：三次握手&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 数据传输：序号、确认、流量控制、拥塞控制和重传&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 断开连接：FIN/ACK 与 TCP 状态迁移&#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;connect() / accept()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;send() / recv()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;close()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;内核真正执行的是 TCP 协议状态机。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="四socket-在应用层与内核之间建立了什么"&gt;四、socket() 在应用层与内核之间建立了什么&#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="n"&gt;fd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;socket&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;socket 的英文含义是插座。可以用“插头和插座”帮助理解应用层 fd 与内核 socket 对象的关系：&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; fd：应用程序使用的整数句柄&#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; socket/TCP相关对象：保存协议、地址、状态、缓冲区等&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;socket() 成功后主要完成两类事情。&lt;/p&gt;&#10;&lt;h3 id="41-分配-fd"&gt;4.1 分配 fd&#10;&lt;/h3&gt;&lt;p&gt;fd 是当前进程文件描述符表中的一个位置。内核找到可用位置，将它标记为正在使用，然后返回对应整数。&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;进程fd表&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;0 → 标准输入&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;1 → 标准输出&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2 → 标准错误&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3 → 新创建的socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;调用 close(fd) 后，该 fd 位置会被释放，之后可能被其他文件或 socket 再次使用。&lt;/p&gt;&#10;&lt;h3 id="42-创建并关联内核-socket-状态"&gt;4.2 创建并关联内核 socket 状态&#10;&lt;/h3&gt;&lt;p&gt;应用程序用 fd 查找内核中的 socket 对象。TCP 相关对象会逐步保存：&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;本地IP&#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;远端IP&#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;TCP状态&#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;课堂中把这组 TCP 连接状态概括为 TCB（Transmission Control Block，传输控制块）。&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;源IP&#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;目的IP&#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;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;192.168.1.10:52000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 192.168.1.20:2000&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → TCP&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;同一个服务端端口可以同时拥有大量连接，因为每条连接的五元组不同。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="五bind把本地地址写入-socket"&gt;五、bind()：把本地地址写入 socket&#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;bind&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;bind() 的作用是给 socket 指定本地 IP 和本地端口：&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;通过fd找到内核socket对象&#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;设置本地IP&#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;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;0.0.0.0:2000&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;客户端通常直接调用 connect()；如果之前没有 bind()，内核会自动选择本地地址和临时端口。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="六listen让-socket-进入监听状态"&gt;六、listen()：让 socket 进入监听状态&#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;listen&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;backlog&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;listen() 将 socket 变成监听 socket：&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;普通TCP socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ listen()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;LISTEN状态&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果服务端只完成 bind() 而没有 listen()，对应端口没有处于 TCP 监听状态，客户端连接通常会被拒绝。&lt;/p&gt;&#10;&lt;h3 id="61-两类连接队列"&gt;6.1 两类连接队列&#10;&lt;/h3&gt;&lt;p&gt;监听 socket 的连接可以从两个阶段理解：&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;收到客户端SYN&#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;SYN队列：握手尚未完成&#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;收到第三次握手ACK&#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()&#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;SYN queue / 半连接队列&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;accept queue / 已完成连接队列&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;“半连接”不是只能传一半数据，而是三次握手还没有完成。&lt;/p&gt;&#10;&lt;h3 id="62-backlog"&gt;6.2 backlog&#10;&lt;/h3&gt;&lt;p&gt;现代 Linux 中，listen(fd, backlog) 的 backlog 主要限制等待 accept() 的已完成连接队列长度：&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;listen&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;backlog&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;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;应用程序暂时没来得及 accept 时，内核允许多少条已经建立完成的连接排队等待。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;SYN 队列还有独立内核配置：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;cat /proc/sys/net/ipv4/tcp_max_syn_backlog&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;实际队列长度还受内核上限、系统配置和 SYN cookies 等机制影响。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="七tcp-三次握手"&gt;七、TCP 三次握手&#10;&lt;/h2&gt;&lt;p&gt;三次握手由内核 TCP 协议栈完成。应用程序通过 connect() 发起；服务端 listen() 后由内核接收握手；accept() 取出的是已经完成握手的连接。&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;客户端初始序号：1234&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;服务端初始序号：5647&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="71-第一次客户端发送-syn"&gt;7.1 第一次：客户端发送 SYN&#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;client → server&#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;SYN = 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SEQ = 1234&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;含义：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;客户端希望建立连接，自己的初始发送序号从 1234 开始。&lt;/p&gt;&#10;&#10; &lt;/blockquote&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;CLOSED → SYN_SENT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;SYN 会占用一个序号，所以服务端确认时使用 1234 + 1。&lt;/p&gt;&#10;&lt;h3 id="72-第二次服务端返回-syn--ack"&gt;7.2 第二次：服务端返回 SYN + ACK&#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;server → client&#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;SYN = 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK = 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SEQ = 5647&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK number = 1235&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;ACK number = 1235 表示客户端序号 1234 的 SYN 已经收到，下一次期望从 1235 开始。&lt;/p&gt;&#10;&lt;p&gt;同时，服务端告诉客户端自己的初始发送序号是 5647。&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;LISTEN → SYN_RECEIVED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="73-第三次客户端返回-ack"&gt;7.3 第三次：客户端返回 ACK&#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;client → server&#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;ACK = 1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK number = 5648&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;5648 = 5647 + 1，表示服务端的 SYN 已经收到。随后双方进入：&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;ESTABLISHED&#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;客户端 服务端&#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;SYN, SEQ=1234&#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; SYN+ACK,&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; SEQ=5647, ACK=1235&#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;ACK, ACK=5648&#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;双方进入ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="74-为什么要交换序号"&gt;7.4 为什么要交换序号&#10;&lt;/h3&gt;&lt;p&gt;双方分别告诉对方自己的初始序号，后续 TCP 才能利用序号和确认号实现：&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;初始序号不是固定从 0 开始，而是由内核生成。这样可以减少旧连接延迟报文的干扰，也提高序号的不可预测性。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="八connect握手和-accept-的关系"&gt;八、connect()、握手和 accept() 的关系&#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;connect&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;connect() 让内核发起 TCP 三次握手。阻塞 socket 上，connect() 通常要等待连接成功或失败后才返回，因此服务端不可达时可能等待较长时间。&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-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;listen&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;accept&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;三次握手不是由 accept() 执行的。握手由服务端内核协议栈在监听状态下完成：&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;listenfd出现可读事件&#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()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;accept() 主要完成：&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;为它分配新的clientfd&#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;让clientfd关联这条已连接socket&#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;listenfd → 继续监听新连接&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clientfd → 与某个客户端收发数据&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="81-reactor-中的对应关系"&gt;8.1 Reactor 中的对应关系&#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;listenfd出现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()&#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;得到clientfd&#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;把clientfd注册到epoll&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;LT 下，如果队列中还有连接，监听 fd 会继续报告可读。&lt;/p&gt;&#10;&lt;p&gt;ET 下应循环 accept()，直到当前队列已经取空。课堂代码：&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="k"&gt;while&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;）&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;fd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;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="k"&gt;if&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="mi"&gt;1&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="k"&gt;break&lt;/span&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;&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="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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;一次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&#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;实际使用非阻塞 fd 时，accept() 返回 -1 且 errno 为 EAGAIN/EWOULDBLOCK，表示本轮队列已经取空。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="九syn-队列如何匹配第三次握手"&gt;九、SYN 队列如何匹配第三次握手&#10;&lt;/h2&gt;&lt;p&gt;收到第三次握手报文后，内核需要找到它属于哪一条正在建立的连接。&lt;/p&gt;&#10;&lt;p&gt;IP 头和 TCP 头能够提供：&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;源IP&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;目的IP&#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;协议类型TCP&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;内核结合五元组、序号和连接状态查找对应的半连接状态，验证 ACK 后，将连接推进到已建立状态并放入已完成连接队列。&lt;/p&gt;&#10;&lt;p&gt;连接状态的生命周期不是从 accept() 才开始。服务端收到 SYN 后，内核已经需要保存握手状态；accept() 只是把完成后的连接交给应用程序。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十syn-flood"&gt;十、SYN Flood&#10;&lt;/h2&gt;&lt;p&gt;SYN Flood 的基本过程：&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;攻击者发送大量SYN&#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;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;SYN cookies。&lt;/li&gt;&#10;&lt;li&gt;调整 SYN 队列和重试参数。&lt;/li&gt;&#10;&lt;li&gt;防火墙、负载均衡器和云安全组。&lt;/li&gt;&#10;&lt;li&gt;限速和异常源识别。&lt;/li&gt;&#10;&lt;li&gt;DDoS 清洗服务。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;listen() 的 backlog 主要用于已完成连接队列，不能单独解决 SYN Flood。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十一send-和-recv-到底做了什么"&gt;十一、send() 和 recv() 到底做了什么&#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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;发送方application&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ send/write&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;发送方kernel TCP协议栈&#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;接收方kernel TCP协议栈&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ recv/read&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;接收方application&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="111-send"&gt;11.1 send()&#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="n"&gt;send&lt;/span&gt;&lt;span class="err"&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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;send&lt;/span&gt;&lt;span class="err"&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="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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;send&lt;/span&gt;&lt;span class="err"&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="mi"&gt;400&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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;send() 返回正数，表示本次有多少字节被内核 socket 发送路径接受。它不表示对端应用已经读取这些数据。&lt;/p&gt;&#10;&lt;p&gt;后续什么时候组成 TCP 段、什么时候交给 IP 层、何时重传，由内核协议栈处理。&lt;/p&gt;&#10;&lt;p&gt;TCP 是字节流，不保留应用层每次 send() 的边界。上面三次调用总共交给 TCP 700 字节，接收端不一定按照 100、200、400 三次收到。&lt;/p&gt;&#10;&lt;h3 id="112-recv"&gt;11.2 recv()&#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="n"&gt;recv&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&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;recv&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&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;recv&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="mi"&gt;250&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&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;recv&lt;/span&gt;&lt;span class="err"&gt;（&lt;/span&gt;&lt;span class="n"&gt;fd&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="n"&gt;buffer&lt;/span&gt;&lt;span class="err"&gt;，&lt;/span&gt;&lt;span class="mi"&gt;150&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;接收端可以按照 50 + 250 + 250 + 150 取出相同的 700 字节。&lt;/p&gt;&#10;&lt;p&gt;recv() 把已经到达 socket 接收缓冲区的数据复制到应用层 buffer。一次返回多少字节取决于：&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;应用提供的buffer有多大&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;socket是否阻塞&#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;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;发送端三次send&#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&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这也是 TCP 半包、粘包问题的来源。应用层协议需要自己定义消息边界，例如固定长度、长度字段或分隔符。&lt;/p&gt;&#10;&lt;h3 id="113-一次接收-1000-与十次接收-100"&gt;11.3 一次接收 1000 与十次接收 100&#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="n"&gt;recv&lt;/span&gt;&lt;span class="err"&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="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;recv&lt;/span&gt;&lt;span class="err"&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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;// 循环10次&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;复制总数据量可以相同，但十次 recv() 会产生更多系统调用、循环判断和状态处理开销，因此性能并非完全没有差异。&lt;/p&gt;&#10;&lt;p&gt;实际代码应以协议完整性和程序结构为主，选择合理缓冲区大小，不必把每次读取切得非常小。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十二mtumss-与-tcp-分段"&gt;十二、MTU、MSS 与 TCP 分段&#10;&lt;/h2&gt;&lt;p&gt;MTU 是链路能够承载的最大 IP 包大小。常见以太网 MTU 是 1500 字节，但它包含 IP 头，不等于 TCP 应用数据长度。&lt;/p&gt;&#10;&lt;p&gt;TCP 常用 MSS 表示一个 TCP 段最多携带的 payload 大小：&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;MSS ≈ MTU - IP头 - TCP头&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;常见 IPv4、无额外选项时：&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;1500 - 20 - 20 = 1460字节&#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-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;send&lt;/span&gt;&lt;span class="err"&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="mi"&gt;1700&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&gt;）&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;应用一次交给内核 1700 字节，不代表网络上一定只有一个包。协议栈会根据 MSS、网卡卸载、路径 MTU 等条件分段。&lt;/p&gt;&#10;&lt;p&gt;接收端 recv() 的边界与网络分段也没有一一对应关系。网络上传输多个 TCP 段，接收端可能一次取出合并后的数据，也可能分多次取出。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十三tcp-如何可靠并尽量快速地传输"&gt;十三、TCP 如何可靠并尽量快速地传输&#10;&lt;/h2&gt;&lt;p&gt;这些机制主要运行在两端内核 TCP 协议栈之间，不由应用程序逐包控制。&lt;/p&gt;&#10;&lt;h3 id="131-序号与确认号"&gt;13.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;SEQ → 当前报文携带的数据从哪个字节序号开始&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ACK → 下一次期望收到哪个字节序号&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果 ACK number = 1235，可以理解为序号 1235 之前的数据已经连续收到，下一次希望从 1235 开始。&lt;/p&gt;&#10;&lt;h3 id="132-接收窗口与滑动窗口"&gt;13.2 接收窗口与滑动窗口&#10;&lt;/h3&gt;&lt;p&gt;接收方通过窗口告诉发送方：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;当前接收缓冲区还允许接收多少数据。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;发送方可以在没有逐段等待 ACK 的情况下发送窗口范围内的多段数据。ACK 推进后，允许发送的范围继续向前滑动。&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;既要连续发送提高吞吐&#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;h3 id="133-慢启动"&gt;13.3 慢启动&#10;&lt;/h3&gt;&lt;p&gt;连接开始时，发送方不知道网络能承受多少在途数据，因此从较小的拥塞窗口开始。在慢启动阶段，拥塞窗口按每个 RTT 观察通常接近指数增长。&lt;/p&gt;&#10;&lt;h3 id="134-拥塞避免"&gt;13.4 拥塞避免&#10;&lt;/h3&gt;&lt;p&gt;拥塞窗口达到阈值后，增长转为更平缓的方式。检测到丢包或拥塞信号后，具体如何降低窗口取决于拥塞控制算法和丢包检测方式，不能把所有情况都理解为固定“减半重新开始”。&lt;/p&gt;&#10;&lt;h3 id="135-延迟确认"&gt;13.5 延迟确认&#10;&lt;/h3&gt;&lt;p&gt;接收方可以短暂等待，把 ACK 与反向数据一起发送，或者用一个 ACK 确认更多数据，以减少纯 ACK 包数量。&lt;/p&gt;&#10;&lt;p&gt;延迟时间由协议实现和当前状态决定，不是所有系统都固定延迟 200 ms。&lt;/p&gt;&#10;&lt;h3 id="136-超时重传与快速重传"&gt;13.6 超时重传与快速重传&#10;&lt;/h3&gt;&lt;p&gt;发送方在规定时间内没有收到确认，会认为数据可能丢失并触发重传。连续收到重复 ACK 时，也可能在超时前触发快速重传。&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;可靠传输&#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;hr&gt;&#10;&lt;h2 id="十四close-与四次挥手"&gt;十四、close() 与四次挥手&#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;close&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;close() 首先是通用的 fd 关闭接口。对 TCP socket 来说，当最后一个引用被关闭时，内核会根据 socket 状态和配置推进 TCP 关闭过程。&lt;/p&gt;&#10;&lt;p&gt;TCP 关闭使用 FIN 标志，不是 final。&lt;/p&gt;&#10;&lt;h3 id="141-为什么通常是四次"&gt;14.1 为什么通常是四次&#10;&lt;/h3&gt;&lt;p&gt;TCP 是全双工连接，两个方向需要分别关闭：&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;FIN&#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; ACK&#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; FIN&#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;ACK&#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;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;1. 主动方发送FIN：我没有更多数据要发送&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 被动方返回ACK：我知道你不再发送&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 被动方处理完自己的发送后，也发送FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 主动方返回ACK：确认对方也不再发送&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;第二步和第三步有时会合并在同一个报文中，所以抓包时不一定总能机械地看到四个独立数据包。&lt;/p&gt;&#10;&lt;h3 id="142-主动关闭方状态"&gt;14.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;ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ close()，发送FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FIN_WAIT_1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到对方对FIN的ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FIN_WAIT_2&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到对方FIN，返回ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TIME_WAIT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 等待2MSL&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="143-被动关闭方状态"&gt;14.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;ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到FIN，返回ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CLOSE_WAIT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 本地应用调用close()，发送FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;LAST_ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到对方ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="144-为什么-recv-返回-0"&gt;14.4 为什么 recv() 返回 0&#10;&lt;/h3&gt;&lt;p&gt;被动方收到 FIN 后，如果此前还有已到达的数据，应用应先读出这些数据。全部读完以后，再次调用：&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;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;size&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;/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;0&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它表示对方正常关闭了发送方向，后面不会再有数据。非阻塞 socket 暂时没有数据返回的是 -1/EAGAIN，不是 0。&lt;/p&gt;&#10;&lt;h3 id="145-数据与-fin"&gt;14.5 数据与 FIN&#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="n"&gt;send&lt;/span&gt;&lt;span class="err"&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="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="err"&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-c" data-lang="c"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nf"&gt;close&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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;TCP 会保持字节流顺序：内核已经接受的待发送数据排在 FIN 之前。接收端先读到数据，数据全部读完后才观察到 EOF，即 recv() == 0。&lt;/p&gt;&#10;&lt;h3 id="146-shutdown"&gt;14.6 shutdown()&#10;&lt;/h3&gt;&lt;p&gt;shutdown() 可以只关闭 TCP 的一个方向：&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;SHUT_RD → 关闭读方向&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SHUT_WR → 关闭写方向，向对端发送FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;SHUT_RDWR → 关闭读写方向&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;它适合“我已经发送完，但还要继续接收对方响应”的半关闭协议。普通请求响应程序只使用 close() 也很常见，但 shutdown() 并不是没有用途。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十五time_waitclosing-与同时关闭"&gt;十五、TIME_WAIT、CLOSING 与同时关闭&#10;&lt;/h2&gt;&lt;h3 id="151-time_wait"&gt;15.1 TIME_WAIT&#10;&lt;/h3&gt;&lt;p&gt;通常由最终发送 ACK 的主动关闭方进入 TIME_WAIT。作用：&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;如果最后一个ACK丢失，可以重新确认对方重发的FIN&#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;服务端出现大量 TIME_WAIT，通常说明服务端经常主动关闭连接，不等于双方一定同时调用了 close()。&lt;/p&gt;&#10;&lt;h3 id="152-同时关闭"&gt;15.2 同时关闭&#10;&lt;/h3&gt;&lt;p&gt;如果双方几乎同时调用 close()：&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;双方都发送FIN&#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;双方都可能在FIN_WAIT_1时收到对方FIN&#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;进入CLOSING&#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;收到自己FIN对应的ACK&#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;进入TIME_WAIT&#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;ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 本地发送FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;FIN_WAIT_1&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 先收到对端FIN&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CLOSING&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;TIME_WAIT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果主动方先收到 ACK，再收到 FIN，则通常经过：&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;FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;TCP 状态机用于处理这些不同的报文到达顺序。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十六同时打开与-tcp-p2p"&gt;十六、同时打开与 TCP P2P&#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-text" data-lang="text"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;客户端调用connect()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;服务端调用listen()/accept()&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;但 TCP 建立后，双方都是全双工通信端点。客户端和服务端主要描述谁主动连接、谁被动监听，以及应用协议中的角色。&lt;/p&gt;&#10;&lt;p&gt;TCP 还定义了“同时打开”：双方都主动发送 SYN，并在 SYN_SENT 状态收到对方 SYN。&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;双方CLOSED&#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;双方SYN_SENT&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到对方SYN并返回SYN+ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;双方SYN_RECEIVED&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ↓ 收到ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;双方ESTABLISHED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;课堂中的 P2P 方向：&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;fd&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;socket&lt;/span&gt;&lt;span class="err"&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;bind&lt;/span&gt;&lt;span class="err"&gt;（）&lt;/span&gt;&lt;span class="c1"&gt;//optional&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;connect&lt;/span&gt;&lt;span class="err"&gt;（）；&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;真正跨公网实现 P2P 还需要考虑 NAT、端口映射、防火墙和连接协调。单纯同时调用 connect() 不保证一定能穿透网络设备。&lt;/p&gt;&#10;&lt;p&gt;本节需要理解：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;TCP 建立后没有“只能客户端发、只能服务端收”的限制，双方都可以 send 和 recv。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;hr&gt;&#10;&lt;h2 id="十七把-api-与-tcp-状态对应起来"&gt;十七、把 API 与 TCP 状态对应起来&#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;内核中的主要含义&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;socket()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建 fd 和 socket 相关内核对象&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;bind()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;设置本地 IP 和端口&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;listen()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;进入 LISTEN，准备接收连接&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;connect()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&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;收到 SYN&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;创建或查找握手状态，进入 SYN_RECEIVED&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;连接进入 ESTABLISHED，并等待 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;accept()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;从已完成连接队列取出连接，返回 clientfd&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;send()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把应用字节交给 socket 发送路径&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;recv()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;把 socket 接收数据复制到应用缓冲区&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;收到 FIN&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;对端关闭发送方向，最终让 recv 返回 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;close()&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;释放 fd 引用，并推动 TCP 关闭过程&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;socket()&#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;bind()&#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;listen() / connect()&#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()得到clientfd&#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()/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;序号、确认、窗口、拥塞控制、重传&#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;close()&#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;FIN/ACK与四次挥手&#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;TIME_WAIT或CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;hr&gt;&#10;&lt;h2 id="十八本节课需要真正掌握的内容"&gt;十八、本节课需要真正掌握的内容&#10;&lt;/h2&gt;&lt;h3 id="重点一api-是入口协议栈完成真正的-tcp-工作"&gt;重点一：API 是入口，协议栈完成真正的 TCP 工作&#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;connect()不是应用自己发送三个包&#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;send()不等于对端应用已经收到&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;recv()不对应某一次send()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;close()会推动TCP关闭状态机&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="重点二监听-fd-和客户端-fd-对应不同状态"&gt;重点二：监听 fd 和客户端 fd 对应不同状态&#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;listenfd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → LISTEN状态&#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;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;clientfd&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 对应某条已建立TCP连接&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; → 用于send和recv&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="重点三tcp-是可靠字节流"&gt;重点三：TCP 是可靠字节流&#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;没有应用消息边界&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;使用序号和ACK保证顺序与可靠性&#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;h3 id="重点四建立和关闭都是状态机"&gt;重点四：建立和关闭都是状态机&#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;三次握手：&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;CLOSED → SYN_SENT/SYN_RECEIVED → ESTABLISHED&#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;ESTABLISHED → FIN_WAIT_1 → FIN_WAIT_2 → TIME_WAIT&#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;ESTABLISHED → CLOSE_WAIT → LAST_ACK → CLOSED&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h3 id="课后实践"&gt;课后实践&#10;&lt;/h3&gt;&lt;p&gt;准备两台虚拟机，观察 TCP P2P：&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;1. 双方创建TCP socket&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;2. 根据实验设计绑定本地地址&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;3. 协调双方连接时机&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;4. 抓包观察SYN、SYN+ACK、ACK&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;5. 建立后让双方都执行send和recv&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;6. 同时或分别close，观察TCP状态&#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-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;ss -ant&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;tcpdump -nn -i any tcp&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;</description></item><item><title>网络 I/O 与 I/O 多路复用</title><link>https://jasperstonnne.github.io/MyBlog/p/network-io-and-io-multiplexing/</link><pubDate>Thu, 06 Aug 2026 00:00:00 +0800</pubDate><guid>https://jasperstonnne.github.io/MyBlog/p/network-io-and-io-multiplexing/</guid><description>&lt;img src="https://jasperstonnne.github.io/MyBlog/p/network-io-and-io-multiplexing/cover.svg" alt="Featured image of post 网络 I/O 与 I/O 多路复用" /&gt;&lt;h2 id="一网络-io-的使用场景"&gt;一、网络 I/O 的使用场景&#10;&lt;/h2&gt;&lt;p&gt;网络是后端服务开发中的重要环节。生活中大量功能都依赖客户端与服务端之间的网络通信。&lt;/p&gt;&#10;&lt;h3 id="1-微信发送文字语音和视频"&gt;1. 微信发送文字、语音和视频&#10;&lt;/h3&gt;&lt;p&gt;当用户在微信发送消息时，大致过程是：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;发送方微信&#10; ↓&#10;通过网络将数据发送给微信服务器&#10; ↓&#10;服务器找到接收方&#10; ↓&#10;将数据转发给接收方微信&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;文字、语音、图片、视频在网络上传输时，最终都会被转换成字节数据。&lt;/p&gt;&#10;&lt;p&gt;网络 I/O 主要关心：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;数据从哪里读取&#10;数据向哪里发送&#10;读取了多少字节&#10;发送了多少字节&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;网络 I/O 本身并不关心这些字节具体表示文字、图片还是视频。&lt;/p&gt;&#10;&lt;h3 id="2-刷抖音"&gt;2. 刷抖音&#10;&lt;/h3&gt;&lt;p&gt;刷视频时：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;抖音 App 请求视频&#10; ↓&#10;服务器返回视频信息和资源地址&#10; ↓&#10;App 通过网络下载视频数据&#10; ↓&#10;客户端解码并播放&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通常视频会被切分成多个数据块，App 可以一边下载、一边播放。&lt;/p&gt;&#10;&lt;h3 id="3-使用-git-clone"&gt;3. 使用 &lt;code&gt;git clone&lt;/code&gt;&#10;&lt;/h3&gt;&lt;p&gt;执行：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;git clone 仓库地址&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;大致过程是：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;本地 Git 客户端连接 GitHub&#10; ↓&#10;向 GitHub 请求仓库数据&#10; ↓&#10;GitHub 通过网络发送 Git 对象&#10; ↓&#10;本地接收、校验和解压&#10; ↓&#10;形成本地仓库&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;代码能够到达本地，本质上也是客户端和 GitHub 服务端之间进行了网络 I/O。&lt;/p&gt;&#10;&lt;p&gt;这些场景都可以概括成：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;客户端发起请求 → 服务端处理请求 → 服务端返回响应&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h2 id="二什么是网络-io"&gt;二、什么是网络 I/O&#10;&lt;/h2&gt;&lt;p&gt;网络 I/O 是建立在客户端和服务端之间的数据传输过程，类似二者之间连接的网络管道。例如十个客户端分别连接服务器，服务端通常会获得十个连接 fd：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;客户端 A ←→ fd 4&#10;客户端 B ←→ fd 5&#10;客户端 C ←→ fd 6&#10;...&#10;客户端 J ←→ fd 13&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;随着客户端数量增加，fd 数量也会增加，于是就产生了一个重要问题：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;服务端如何同时管理大量 fd？&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;这正是后面需要学习 &lt;code&gt;select&lt;/code&gt;、&lt;code&gt;poll&lt;/code&gt; 和 &lt;code&gt;epoll&lt;/code&gt; 的原因。&lt;/p&gt;&#10;&lt;h2 id="三socket-与-fd"&gt;三、socket 与 fd&#10;&lt;/h2&gt;&lt;p&gt;在 Linux 中，Linux 通过 socket API 通信。创建 socket：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;int sockfd = socket(AF_INET, SOCK_STREAM, 0);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;参数的含义：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;AF_INET&lt;/code&gt;：IPv4&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;SOCK_STREAM&lt;/code&gt;：面向字节流，对应 TCP&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：使用该组合默认的 TCP 协议&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;fd 本质是进程中的非负整数（此处了解，记忆也没啥用）：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;0：标准输入&#10;1：标准输出&#10;2：标准错误&#10;3：监听 socket&#10;4：客户端连接 A&#10;5：客户端连接 B&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;在当前服务端进程中，一个已连接 socket 的 fd 通常对应一条 TCP 连接。（存在 ACCEPT 的情况）&lt;/p&gt;&#10;&lt;h2 id="四tcp-服务端的简便理解"&gt;四、TCP 服务端的简便理解&#10;&lt;/h2&gt;&lt;p&gt;可以把 TCP 服务端理解成酒店：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;socket()&lt;/code&gt;：聘请迎宾员&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;bind()&lt;/code&gt;：安排迎宾员在哪个门口工作&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;listen()&lt;/code&gt;：正式开始迎宾&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;accept()&lt;/code&gt;：接待一位到达的客人&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;recv()&lt;/code&gt;：听客人提出需求&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;send()&lt;/code&gt;：将处理结果交给客人&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;close()&lt;/code&gt;：结束服务&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;说说两类重要 fd：&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;1. 监听 fd&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;定义完 sockfd 经过 bind、listen 后，sockfd 就成为监听 fd。主要职责是监听新连接，不负责收发数据。&lt;/p&gt;&#10;&lt;p&gt;&lt;strong&gt;2. 连接 fd&lt;/strong&gt;&lt;/p&gt;&#10;&lt;p&gt;参考代码中的 &lt;code&gt;int clientfd = accept(sockfd, ...);&lt;/code&gt;。&lt;code&gt;accept()&lt;/code&gt; 成功后会返回一个新的 fd：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;sockfd：负责监听新客户端&#10;clientfd：负责和某个客户端通信&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关系如下：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt; ┌→ clientfd 4 ←→ 客户端 A&#10;listenfd → accept() ─┼→ clientfd 5 ←→ 客户端 B&#10; └→ clientfd 6 ←→ 客户端 C&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;监听 fd 通常只有一个，但每建立一个客户端连接，服务端就会获得一个新的连接 fd。&lt;/p&gt;&#10;&lt;h2 id="五服务端代码执行流程"&gt;五、服务端代码执行流程&#10;&lt;/h2&gt;&lt;h3 id="1-创建-socket"&gt;1. 创建 socket&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;int sockfd = socket(AF_INET, SOCK_STREAM, 0);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;作用：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;在内核中创建 socket 对象。&lt;/li&gt;&#10;&lt;li&gt;返回一个 fd。&lt;/li&gt;&#10;&lt;li&gt;当前还没有绑定 IP 和端口。&lt;/li&gt;&#10;&lt;li&gt;当前也没有开始监听。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;更准确的变量名可以理解为：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;int listenfd;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因为它后面会被设置为监听 socket。&lt;/p&gt;&#10;&lt;h3 id="2-准备服务器地址"&gt;2. 准备服务器地址&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;struct sockaddr_in servaddr;&#10;&#10;servaddr.sin_family = AF_INET;&#10;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);&#10;servaddr.sin_port = htons(2000);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;h4 id="sin_family"&gt;&lt;code&gt;sin_family&lt;/code&gt;&#10;&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;servaddr.sin_family = AF_INET;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表示使用 IPv4。&lt;/p&gt;&#10;&lt;h4 id="inaddr_any"&gt;&lt;code&gt;INADDR_ANY&lt;/code&gt;&#10;&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;servaddr.sin_addr.s_addr = htonl(INADDR_ANY);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;INADDR_ANY&lt;/code&gt; 表示监听本机所有 IPv4 网络接口，对应：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;0.0.0.0&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;假设服务器有多个 IP：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;127.0.0.1&#10;192.168.1.10&#10;10.0.0.10&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;绑定 &lt;code&gt;0.0.0.0&lt;/code&gt;，表示这些本机地址上的 2000 端口都可以接收连接。&lt;/p&gt;&#10;&lt;h4 id="htons"&gt;&lt;code&gt;htons()&lt;/code&gt;&#10;&lt;/h4&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;servaddr.sin_port = htons(2000);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;htons&lt;/code&gt; 表示：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;host to network short&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;它把主机字节序的 16 位端口号转换成网络字节序。&lt;/p&gt;&#10;&lt;p&gt;同理：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;htonl&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;表示把 32 位数据从主机字节序转换成网络字节序。&lt;/p&gt;&#10;&lt;p&gt;端口范围是：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;0～65535&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;通常：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;code&gt;0&lt;/code&gt;：让操作系统自动分配端口。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;1～1023&lt;/code&gt;：传统意义上的特权端口。&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;1024～65535&lt;/code&gt;：普通程序通常可以使用。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;但 1024 以上的端口也可能已经被其他程序占用。&lt;/p&gt;&#10;&lt;h3 id="3-绑定-ip-和端口"&gt;3. 绑定 IP 和端口&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;bind(sockfd,&#10; (struct sockaddr *)&amp;amp;servaddr,&#10; sizeof(struct sockaddr));&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;bind()&lt;/code&gt; 将 socket 与本地 IP、端口建立联系：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;sockfd ←→ 0.0.0.0:2000&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;可以理解为：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;给迎宾人员安排具体的工作地点。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;如果端口已经被其他服务绑定，可能出现：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;Address already in use&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;你的代码通过下面的方式查看错误：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;printf(&amp;#34;bind error:%s\n&amp;#34;, strerror(errno));&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;不过如果 &lt;code&gt;bind()&lt;/code&gt; 失败，程序理论上不应该继续执行 &lt;code&gt;listen()&lt;/code&gt;。&lt;/p&gt;&#10;&lt;hr&gt;&#10;&lt;h3 id="4-进入监听状态"&gt;4. 进入监听状态&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;listen(sockfd, 10);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行成功后，socket 从普通 socket 变成监听 socket。&lt;/p&gt;&#10;&lt;p&gt;此时可以使用：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;ss -lntp | grep &amp;#39;:2000&amp;#39;&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;查看监听状态。&lt;/p&gt;&#10;&lt;p&gt;可能看到：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;LISTEN 0 10 0.0.0.0:2000&#10;&lt;/code&gt;&lt;/pre&gt;&lt;hr&gt;&#10;&lt;h3 id="5-接收客户端连接"&gt;5. 接收客户端连接&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;int clientfd = accept(&#10; sockfd,&#10; (struct sockaddr *)&amp;amp;clientaddr,&#10; &amp;amp;len&#10;);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果当前没有已经建立完成的客户端连接，阻塞模式下的 &lt;code&gt;accept()&lt;/code&gt; 会暂停程序：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;程序阻塞在 accept()&#10; ↓&#10;等待客户端连接&#10; ↓&#10;客户端完成 TCP 三次握手&#10; ↓&#10;accept() 返回 clientfd&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;accept()&lt;/code&gt; 不会把原来的 &lt;code&gt;sockfd&lt;/code&gt; 变成客户端连接。&lt;/p&gt;&#10;&lt;p&gt;它会返回一个全新的 &lt;code&gt;clientfd&lt;/code&gt;。&lt;/p&gt;&#10;&lt;h2 id="六代码中的三个服务端版本代码文末附"&gt;六、代码中的三个服务端版本（代码文末附）&#10;&lt;/h2&gt;&lt;h3 id="第一阶段只处理一个客户端的一次数据"&gt;第一阶段：只处理一个客户端的一次数据&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;#if 0&#10; int clientfd = accept(...);&#10;&#10; char buffer[1024] = {0};&#10; int count = recv(clientfd, buffer, 1024, 0);&#10;&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10;&#10; count = send(clientfd, buffer, count, 0);&#10;#endif&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;执行过程：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;accept 一个客户端&#10; ↓&#10;recv 一次&#10; ↓&#10;send 一次&#10; ↓&#10;后面不再 accept&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这个版本只能：&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;接收一个客户端。&lt;/li&gt;&#10;&lt;li&gt;读取一次数据。&lt;/li&gt;&#10;&lt;li&gt;返回一次数据。&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;p&gt;如果客户端想再次发送，服务端没有循环继续读取。&lt;/p&gt;&#10;&lt;h3 id="第二阶段循环接收客户端但串行处理"&gt;第二阶段：循环接收客户端，但串行处理&#10;&lt;/h3&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;#elif 0&#10;while (1) {&#10; int clientfd = accept(...);&#10;&#10; char buffer[1024] = {0};&#10; int count = recv(clientfd, buffer, 1024, 0);&#10;&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10;&#10; count = send(clientfd, buffer, count, 0);&#10;}&#10;#endif&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这里增加了：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;while (1)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;因此服务端可以不断接收新客户端。&lt;/p&gt;&#10;&lt;p&gt;但是它仍然是串行处理：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;accept 客户端 A&#10; ↓&#10;recv 等待 A 发送数据&#10; ↓&#10;send 返回给 A&#10; ↓&#10;重新 accept 客户端 B&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;关键问题在这里：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;recv(clientfd, buffer, 1024, 0);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;默认情况下，&lt;code&gt;recv()&lt;/code&gt; 是阻塞的。&lt;/p&gt;&#10;&lt;p&gt;假设客户端 A 已经连接，但一直没有发送数据：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;服务端阻塞在 A 的 recv()&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;此时即使客户端 B 已经发起连接，应用程序也暂时无法回到 &lt;code&gt;accept()&lt;/code&gt; 处理 B。&lt;/p&gt;&#10;&lt;h3 id="第三阶段一个客户端对应一个线程"&gt;第三阶段：一个客户端对应一个线程&#10;&lt;/h3&gt;&lt;p&gt;当前实际编译的是 &lt;code&gt;#else&lt;/code&gt;：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;#else&#10;while (1) {&#10; int clientfd = accept(...);&#10;&#10; pthread_t thid;&#10; pthread_create(&#10; &amp;amp;thid,&#10; NULL,&#10; client_thread,&#10; &amp;amp;clientfd&#10; );&#10;}&#10;#endif&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;每接收一个客户端，创建一个线程。&lt;/p&gt;&#10;&lt;p&gt;线程函数：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;void *client_thread(void *arg)&#10;{&#10; int clientfd = *(int *)arg;&#10;&#10; while (1) {&#10; char buffer[1024] = {0};&#10;&#10; int count = recv(&#10; clientfd,&#10; buffer,&#10; 1024,&#10; 0&#10; );&#10;&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10;&#10; count = send(&#10; clientfd,&#10; buffer,&#10; count,&#10; 0&#10; );&#10; }&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;运行关系变成：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;主线程&#10; │&#10; ├─ accept 客户端 A&#10; │ └─ 创建线程 1 → recv(A)&#10; │&#10; ├─ accept 客户端 B&#10; │ └─ 创建线程 2 → recv(B)&#10; │&#10; └─ accept 客户端 C&#10; └─ 创建线程 3 → recv(C)&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;主线程只负责：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;不断执行 accept()&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;工作线程负责：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;recv() → 处理数据 → send()&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;这样即使线程 1 阻塞在客户端 A 的 &lt;code&gt;recv()&lt;/code&gt;，主线程仍然可以继续 &lt;code&gt;accept()&lt;/code&gt; 客户端 B。&lt;/p&gt;&#10;&lt;p&gt;准确来说，这是：&lt;/p&gt;&#10;&#10; &lt;blockquote&gt;&#10; &lt;p&gt;一个连接对应一个线程。&lt;/p&gt;&#10;&#10; &lt;/blockquote&gt;&#10;&lt;p&gt;不是严格的”一次请求对应一个线程”，因为一个连接线程内部可以通过 &lt;code&gt;while&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="1-线程需要内存"&gt;1. 线程需要内存&#10;&lt;/h3&gt;&lt;p&gt;每创建一个线程，都需要线程栈及相关管理资源。连接越多，线程越多，百万并发的时候&amp;hellip;&lt;/p&gt;&#10;&lt;h3 id="2-上下文切换成本"&gt;2. 上下文切换成本&#10;&lt;/h3&gt;&lt;p&gt;CPU 核心数量有限。&lt;/p&gt;&#10;&lt;p&gt;假设服务器只有 8 个 CPU 核心，却创建了 5000 个线程，操作系统必须不停地：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;保存线程 A 的状态&#10;切换到线程 B&#10;保存线程 B 的状态&#10;切换到线程 C&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;线程太多时，大量 CPU 时间可能消耗在线程调度上。&lt;/p&gt;&#10;&lt;h3 id="3-大部分线程可能只是在等待"&gt;3. 大部分线程可能只是在等待&#10;&lt;/h3&gt;&lt;p&gt;很多网络连接并不是一直都有数据。&lt;/p&gt;&#10;&lt;p&gt;例如即时通信中的长连接：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;客户端保持在线&#10;但大部分时间没有发送消息&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;对应线程会长期阻塞在：&lt;/p&gt;&#10;&lt;pre tabindex="0"&gt;&lt;code&gt;recv(clientfd, ...);&#10;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;如果一个空闲连接占用一个线程，就会浪费大量线程资源。&lt;/p&gt;&#10;&lt;h2 id="代码"&gt;代码&#10;&lt;/h2&gt;&lt;pre tabindex="0"&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt; // printf&#10;#include &amp;lt;string.h&amp;gt; // memset&#10;#include &amp;lt;sys/socket.h&amp;gt;&#10;#include &amp;lt;arpa/inet.h&amp;gt; // htonl, htons&#10;#include &amp;lt;errno.h&amp;gt; // errno&#10;#include &amp;lt;netinet/in.h&amp;gt; // struct sockaddr_in&#10;#include &amp;lt;unistd.h&amp;gt; // close&#10;#include &amp;lt;pthread.h&amp;gt;&#10;&#10;void *client_thread(void *arg) {&#10; int clientfd = *(int *)arg;&#10; while (1) {&#10; char buffer[1024] = {0};&#10; int count = recv(clientfd, buffer, 1024, 0); // 全部阻塞在 recv 上&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10; count = send(clientfd, buffer, count, 0);&#10; printf(&amp;#34;SEND:%s\n&amp;#34;, count);&#10; }&#10;}&#10;&#10;int main() {&#10; int sockfd = socket(AF_INET, SOCK_STREAM, 0); // create socket 聘请迎宾者&#10; // 绑定本地端口&#10; struct sockaddr_in servaddr;&#10; servaddr.sin_family = AF_INET;&#10; servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 0.0.0.0&#10; servaddr.sin_port = htons(2000); // 0-1023 系统默认，1024 之后都可以用，不能与其他的冲突&#10; // 在哪个门迎宾，安排的位置&#10; if (-1 == bind(sockfd, (struct sockaddr *)&amp;amp;servaddr, sizeof(struct sockaddr))) {&#10; printf(&amp;#34;bind error:%s\n&amp;#34;, strerror(errno));&#10; }&#10; // 登记绑定&#10; listen(sockfd, 10); // 正式上榜&#10; printf(&amp;#34;listen finished\n&amp;#34;);&#10; struct sockaddr_in clientaddr;&#10; socklen_t len = sizeof(clientaddr);&#10;#if 0&#10; printf(&amp;#34;accept\n&amp;#34;);&#10; int clientfd = accept(sockfd, (struct sockaddr *)&amp;amp;clientaddr, &amp;amp;len); // 建立链接&#10; printf(&amp;#34;accept finished\n&amp;#34;);&#10; char buffer[1024] = {0};&#10; int count = recv(clientfd, buffer, 1024, 0);&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10; count = send(clientfd, buffer, count, 0);&#10; printf(&amp;#34;SEND:%s\n&amp;#34;, count);&#10;#elif 0&#10; while (1) {&#10; printf(&amp;#34;accept\n&amp;#34;);&#10; int clientfd = accept(sockfd, (struct sockaddr *)&amp;amp;clientaddr, &amp;amp;len); // 建立链接&#10; printf(&amp;#34;accept finished\n&amp;#34;);&#10; char buffer[1024] = {0};&#10; int count = recv(clientfd, buffer, 1024, 0); // 全部阻塞在 recv 上&#10; printf(&amp;#34;RECV:%s\n&amp;#34;, buffer);&#10; count = send(clientfd, buffer, count, 0);&#10; printf(&amp;#34;SEND:%s\n&amp;#34;, count);&#10; } // 多个 io 如何接收数据&#10;#else&#10; while (1) {&#10; printf(&amp;#34;accept\n&amp;#34;);&#10; int clientfd = accept(sockfd, (struct sockaddr *)&amp;amp;clientaddr, &amp;amp;len); // 建立链接&#10; printf(&amp;#34;accept finished\n&amp;#34;);&#10; pthread_t thid;&#10; pthread_create(&amp;amp;thid, NULL, client_thread, &amp;amp;clientfd);&#10; }&#10;#endif&#10; getchar();&#10; printf(&amp;#34;exit\n&amp;#34;);&#10; return 0;&#10;}&#10;&lt;/code&gt;&lt;/pre&gt;</description></item></channel></rss>