跳转至

网络栈:从原始套接字到系统监控 API

这一章讲什么: 一个端口一个端口地搭网络程序。TCP echo 先把「连接」这件事演清楚, 然后是主机名解析(为什么 localhost 直接连会报错)、UDP 的无连接世界、把服务器塞进 Docker, 再往上是 HTTP:一个最小服务器、一个手写客户端,最后合成一个「系统内存监控 API」。 收尾是全书最漂亮的 comptime 演示之一:ZEMP 协议编解码器从结构体定义自动生成。

主线例子:① echo 客户端为什么必须 shutdown 才能收到回声;② ZEMP 一个 40 字节帧的编解码全程, 每个字节都能对账。

1. 先补三个常识词

TCP/IP 是互联网的协议套件:IP 管寻址和路由(把数据送到正确的机器),TCP 在 IP 之上保证可靠、有序的送达;端口号再指明这台机器的哪个进程该处理这份数据——IP 地址找机器,端口号找进程1。

回显(echo)服务器是网络教学的经典靶子:客户端发什么,服务器原样退回。它小,但把「建立连接 → 发 → 收 → 关」的全生命周期都逼出来了。

TCP 半关闭是一个容易漏掉的动作:TCP 连接是全双工的(两个方向独立),shutdown(SHUT_WR) 只关「我→你」这个方向,告诉对方「我不再发了」,同时保留「你→我」方向的接收能力2。

2. 顶层全景:一章的攀升路线

  层 0  原始字节   TCP echo 服务器/客户端        ← 连接、半关闭
  层 1  名字       getaddrinfo → HostName.lookup  ← localhost 为什么不能直接连
  层 2  无连接     UDP echo(server 用 C 套接字)    ← 每个数据报独立寻址
  层 3  打包       Docker 双阶段构建、静态链接     ← zig 的 -target native-native-musl -static
  层 4  协议       最小 HTTP 服务器 + 手写 HTTP 客户端(四函数流水线)
  层 5  服务       内存监控:OS 取值 → JSON → TCP → 客户端解析
  层 6  铁律       ZEMP:comptime 从结构体生成编解码器

  图说:每上一层不是替换下一层,而是把下一层包起来——HTTP 还是 TCP,echo 还是 socket。
  章末的原话:listen、accept、read、write、close 这五个动作在一切网络软件里 endlessly 重复,
  亲手实现过一遍,你就懂了每个高层库底下在干什么。[^3]

3. 核心原理

3.1 TCP echo 客户端:一个 shutdown 引发的等待

服务器端极简:监听端口,接受连接,把读到的字节原样写回。客户端第一次写出来,书故意让它在真实场景里栽一次:连 localhost 报 error: ParseFailed——因为地址构造用的是 parseIp4(),它只是个解析器,只认 127.0.0.1 这种点分十进制字面量,不做 DNS 查询,连 localhost 都拒4。

假设已经连上(用 IP 直连),主线例子的完整一次通话:

第 1 步  writer.writeAll("Hello from Zig client!\n")  ← 只是搬进写缓冲
第 2 步  writer.flush()                               ← 才真正发出去
第 3 步  stream.shutdown(io, .send)                   ← 半关闭:我这头说完了
第 4 步  reader.readSliceShort(&rbuf)                 ← 等回声
第 5 步  打印 Received: Hello from Zig client!

图说:第 3 步是全章第一个真知识点。echo 协议的规矩是「服务器看到你写完,才把收到的一切弹回来」;没有 shutdown,服务器无法区分「客户端还在打字」和「客户端说完了」,会一直阻塞在读上,客户端也会一直阻塞在等回声——双向死等5。catch 0 把读错误(比如服务器直接断连)按零字节处理,和「正常收完」走同一条出口6。

一个 0.16 特有的语法模式这里定型:var reader_impl = stream.reader(io, &rbuf); const reader = &reader_impl.interface;——reader() 返回的是具体实现结构体(拥有你给的栈缓冲),通用接口指针住在它的 .interface 字段里;接口指针必须指向命名变量的稳定地址,所以不能拿临时值的地址7。

3.2 主机名解析:先徒手,再封装

解析主机名(把 www.example.com 变成 IP 地址,而且常常不止一个)先用 C 的原语手搓一遍:getaddrinfo() 填一个 hints 结构(地址族 AF_UNSPEC 表示 v4 v6 都要,套接字类型 SOCK_STREAM 表示 TCP),返回的是一条 addrinfo 链表——一个主机名可以对应多个地址,谷歌就同时有 v6 和 v48。书里实测:www.mtsoukalos.eu 只解析出 IPv4: 65.21.59.44,www.google.com 解析出一串,前两个是 IPv6: 2a00:1450:4001:807::2004 和 IPv4: 142.251.141.1009。

然后才换成 0.16 的原生 API:std.Io.net.HostName.init(host) 建一个轻量包装(此时不做网络活动),.connect(io, port, .{ .mode = .stream }) 一行完成「解析 + 连接」——这是被删掉的旧 std.net.tcpConnectToHost 的接班人10。更细的版本是 hn.lookup(io, &queue, .{ .port = port }):解析结果流式灌进一个调用方提供的定长队列,每条是 .address 或 .canonical_name,抽干为止——书给的取舍是:只想要一个能连的地址用 resolve();要完整结果或想「先到先得、先试先连」用 lookup()11。

3.3 UDP:每个数据报自带地址

UDP(用户数据报协议)和 TCP 的取舍一句话:要保证送达和顺序用 TCP;要低延迟、简单、广播语义用 UDP——DNS、视频流、游戏状态更新都选 UDP,可靠性和排序的负担交给应用层12。

两个代码层面的差异。服务器端:Zig 0.16 的 std.Io.net 还没暴露 UDP 套接字创建,书直接用 @cImport 的 C 套接字——socket(AF_INET, SOCK_DGRAM)、bind()、然后循环 recvfrom() 阻塞等数据报,返回时同时填好发送者地址;因为是无连接的,每次 sendto() 都要显式把 other_addr 再递回去,回应包才知道往哪飞13。一个可移植性细节:sa_family_t 在 macOS 是 u8、在 Linux 是 u16,所以代码用具体 C 类型而不是定宽整数14。

客户端走原生 API:lookup 队列解析出目标地址后,IpAddress.parseIp4("0.0.0.0", 0) 绑定本地——端口 0 是让操作系统自动分配临时端口;sock.send(io, &dest, message) 每次调用都显式带目标地址(无连接:每个数据报都是独立的一封,和远端没有持久关联)15。书自己贴的安全提醒值得记住:sock.receive() 若服务器不应答会无限阻塞,真实应用要设接收超时(SO_RCVTIMEO)16。

3.4 Docker:两阶段、静态链接

把两个 echo 服务器打进一个镜像的 Dockerfile 只有两层关键决策。构建阶段 FROM alpine 装 zig,用 zig build-exe … -O ReleaseSafe -target native-native-musl -static -lc 编译——静态链接出的二进制不依赖容器里的任何库;运行阶段再 FROM alpine,只把两个二进制 copy 过来,EXPOSE 1234 1235 开端口。compose 文件里 1234:1234 和 1235:1235/udp 的映射把内部端口钉到外部——内外端口映射写错,服务对外就是不可见的17。书也承认这不是标准做法(通常一容器一服务),这里一容器两个是为了教学紧凑18。

3.5 最小 HTTP 服务器与一次真实的握手

HTTP 服务器本体只有三行循环:address.listen() 把套接字置于监听;server.accept() 阻塞到一次 TCP 握手完成;handleConnection() 用 std.http.Server.init(&reader_impl.interface, &writer_impl.interface) 把字节流包成 HTTP 状态机,receiveHead() 解析请求头,req.respond("Hello world\n", .{}) 回响应。这是同步模型:一个连接没处理完,服务器顾不上接受下一个19。

用 curl -v 看一次真实交易,比任何示意图都清楚20:

* Trying [::1]:8080...        ← 先试 IPv6,被拒(服务器只听 127.0.0.1)
* Trying 127.0.0.1:8080...    ← 回退 IPv4,连上
> GET / HTTP/1.1              ← 客户端发:方法 路径 协议版本
> Host: localhost:8080        ← 三个头:Host / User-Agent / Accept
< HTTP/1.1 200 OK             ← 服务器回:状态行
< content-length: 12          ← 一个头
<                             ← 空行之后是 12 字节正文 Hello world

图说:HTTP/1.1 默认不断连,除非服务器发 Connection: close21。多路由的服务器(/rand 返回 0–100 万的随机数)只是在这个骨架上加了一个 if/else if/else 路由;书还坦白 % 1_000_001 引入约 0.023% 的取余偏差,演示可以、密码学场景必须上拒绝采样——第 1 章那个知识点在这里回声了一次22。

3.6 手写 HTTP 客户端:四函数流水线

fetch() 把一次请求拆成四个职责单一的函数,书的评价值得划重点:解析地址、建立连接、构造请求、读取响应各归各——网络应用变复杂后,这份分离就是可测、可维护、可扩展的全部来源23。

  • parseAddress():按有没有冒号分两条路,无冒号默认端口 80;先试着按 IPv4 字面量解析,成功就把二进制地址存下跳过 DNS;失败就当主机名。配了三个单元测试(主机:端口拆分、字面量识别、默认端口),全过24。
  • connectToAddress():已是 IP 直接连;是主机名先 resolve() 再连;解析失败包装成 ResolutionFailed,不向上抛底层的 OS 错误——错误面要干净25。
  • sendRequest():手工拼 HTTP/1.1 报文——GET {url} HTTP/1.1\r\nHost: …\r\nConnection: close\r\n\r\n。端口是 80 时 Host 头不带端口语从惯例;Connection: close 让服务器发完就关,整个交互是「一锤子交易」而不是挂着的持久连接26。
  • readResponse():循环 readSliceShort()(有多少读多少、不等到填满——正适合网络这种「数据不成块到达」的场景),读进 ArrayListUnmanaged 攒着,返回 0 就是服务器关连收工,toOwnedSlice() 把整块内存的所有权交出去27。

实测:webClient localhost:8080 /rand 拉回完整响应,状态行、三个头、一行正文 Random number: 51602228。

3.7 系统监控服务:一个跨平台取数的活教材

服务要回答「现在用了多少内存」,难的不是网络,是两个操作系统给数的方式完全不同29:

Linux macOS
总内存 /proc/meminfo 里找 MemTotal: sysctl -n hw.memsize
可用内存 同行文件找 MemAvailable: 没有等价物
已用 总 − 可用 (active + wired + compressor 页) × hw.pagesize,数字从跑 vm_stat 子进程的输出里抠

图说:macOS 那条路要起两次 sysctl 子进程、一次 vm_stat 子进程(std.process.run 抓 stdout),再按行匹配「Pages active:」「Pages wired down:」「Pages occupied by compressor:」三个计数器,乘页大小得字节数30。分支用 switch (builtin.os.tag) 在编译期定型,else 是 @compileError——不支持的 OS 构建直接拒31。

服务器把 MemStats{os, used_mb, total_mb, timestamp} 用 std.json.fmt() 序列化成 JSON 文本,一根 TCP 连接发一份,一次只服务一个客户端;客户端这边有个漂亮的边界模式:JSON 反序列化直接用匿名结构体——在 std.json.parseFromSlice() 调用点当场声明「我关心的形状」,.ignore_unknown_fields = true 让服务器将来加字段时客户端默认前向兼容;百分比先乘后除保精度,不用浮点32。实测三行:[1772818962] macos Memory: 18771/65536 MB (28%)33。

3.8 ZEMP:让结构体定义自己生成协议

收尾是全书最锋利的一段,先讲它防的三种事故:手写编解码器时,结构体加字段忘了同步更新编码器、解码器、尺寸常量和文档,漏任何一样,解析器就把错值读进错字段,直到线上真来一条消息才爆;有人往线上格式里塞 f64 或指针,各平台的字节表示都不一样;缓冲区写死 [256]u8,结构体一长就悄悄不够34。

ZEMP(Zig Exchange Memory Protocol)的答案:帧 = 8 字节定长头(opcode、flags、序列号,小端)+ 载荷;载荷用 extern struct 钉死布局。然后编解码器不写死,而是从 @typeInfo(Payload).@"struct".fields 在编译期生成:encode() 里 inline for 展开每个字段,std.mem.asBytes 零拷贝写字节,wireInt() 按字段类型做大小端转换——整数通过、整数支撑的枚举通过、其他一切类型(f64、指针、切片)在编译期 @compileError,报错直接点名是哪个字段引入了不支持的类型;frameSize(Payload) 返回 comptime_int,于是 var buf: [frameSize(MemQueryResp)]u8 = undefined 永远恰好正确,不可能过时、不可能太小、不需要堆35。

主线例子,一个 40 字节帧的全程对账(书上真实输出)36:

编码输入: MemQueryResp{ total_mb=16384, used_mb=4200, available_mb=12184, timestamp=1700000000 }

字节流(8 字节头 + 32 字节载荷):
02 00 00 00 2A 00 00 00   ← opcode 0x0002、flags 00 00、seq 42(0x2A)
00 40 00 00 00 00 00 00   ← total_mb  = 16384 = 0x4000
68 10 00 00 00 00 00 00   ← used_mb   = 4200  = 0x1068
98 2F 00 00 00 00 00 00   ← available = 12184 = 0x2F98
00 F1 53 65 00 00 00 00   ← timestamp = 1700000000 = 0x6553F100

解码回读: 四个字段逐一相等 → Round-trip OK
comptime 帧尺寸: Req=16、Resp=40、Ping=16 字节 —— 编译期常量,零运行时代价

图说:每个字段都是小端,每个十六进制数都能和输入值对上;结构体定义成了线上格式的唯一事实来源,而 comptime 让这个事实在编译期不可能被反驳37。

4. 加厚:五个补完拼图的小块

4.1 IPv6 的 "::" 不只是「所有接口」

把监听地址换成 parseIp6("::", 8080),在多数操作系统上得到的不是一个纯 IPv6 套接字,而是双栈套接字——它同时收 IPv4 连接,IPv4 客户端以 ::ffff:127.0.0.1 这种映射形式到达38。只想回环就用 "::1"。拿到 IpAddress 值之后,listen/accept/读写对两个协议族完全同一套调用39。

4.2 UDP 客户端的查表队列长什么样

0.16 解析主机名的细活版不是「给一个地址」,而是流式交付:栈上开一块能装 16 个结果的缓冲当队列,hn.lookup(io, &queue, .{ .port = port }) 发起解析,然后循环 queue.getOne(io) 收结果——每条要么是 .address(可直接用的地址)要么是 .canonical_name(规范名),收到 error.Closed 表示结果全部到齐40。客户端只取第一个 .address;全没收到就 error.UnknownHostName41。

4.3 resolve() 与 lookup() 的分工

书里把两条解析 API 的分工说得很干净:IpAddress.resolve() 返回单个地址,「你只需要一个能连的」时选它;HostName.lookup() 把完整 DNS 回答(多条 A/AAAA 记录加规范名)灌进调用方的队列,想「先到先得、先试先连」、或想看全部候选时选它。两者都收 io 句柄,阻塞与否不由它们自己决定,而由运行时后端决定42。

4.4 双模式基准的另一半:echo 后端

proxyBench 的 --server 模式是个带 --latency N 旋钮的回显后端:读 4 字节大端长度、读足、原样弹回。延迟是它故意拧大的信号——没有这 50 ms,串行与异步代理的墙钟差缩小到看不清;有了它,等待被放大成可测的形状43。

4.5 两个代理的错误哲学

同步版与异步版都把「连接断开」当常态:转发循环里的错误静默丢弃,注释里写明生产代码至少该记 @errorName。书给的理由一致——这个简化把焦点保持在异步协调模式上,而不是生产服务的运维关切(分类日志、瞬时故障重试、统计上报)44。

5. 作者的判断与证据

  • 判断: 「listen、accept、read、write、close 这五个动作亲手实现过一遍,你就懂了每个高层库底下在干什么」3。这是本章的存在理由,也是证据标准——每层的代码都给了可跑的实测输出。
  • 判断: 手写 HTTP 客户端的四函数分离是网络应用的正确骨架23。证据:三个针对解析器的单元测试(All 3 tests passed)。
  • 判断: JSON 文本协议「留一整类 bug 到运行时才现形」,comptime 编解码把那类 bug 变成编译错误34。证据:40 字节帧的完整 round-trip 对账 + 三个场景的机制推演。这是论证最实的一节。
  • 坦白的缺口: std.Io.net 没有 UDP 套接字创建,只能 @cImport——书把这当「系统编程偶尔需要降到标准库之下」的正面教材,而不是避讳13。

5. 边界与局限

  • 所有服务器都是同步、单连接的:一个客户端占线,别人排队;并发服务器要到第 7、8、9 章才出现19。
  • 最小 HTTP 服务器不读请求体、不支持 POST、没有路由库;手写客户端不解析响应头(整块当文本返回)28。
  • UDP 服务器无限循环没有退出路径,kqueue 版 fd 管理的老问题在这里同样存在13。
  • 内存监控的 macOS 口径(active+wired+compressed)和 Linux 口径(total−available)不是同一把尺,跨平台对比数字会偏30。
  • /rand 端点的取余偏差约 0.023%,书明说了演示可接受、密码学不行22。

6. 可带走的

  1. echo 协议的命门:发完必须 shutdown(.send),否则双向死等。
  2. parseIp4 只认点分字面量、不查 DNS;HostName.init + connect 是一行答案;lookup 队列拿完整 DNS 结果。
  3. 0.16 接口模式:var impl = x.reader(io,&buf); const r = &impl.interface;——接口指针住命名变量里。
  4. TCP 与 UDP 的取舍是需求驱动的,不是谁更好;UDP 每个数据报独立寻址,服务器 recvfrom 白得发送者地址。
  5. 端口 0 = 让 OS 分配临时端口;receive 无限阻塞,真实应用要设超时。
  6. 容器化 zig:静态链接(-target native-native-musl -static -lc)+ 两阶段构建,镜像只装二进制。
  7. HTTP 服务器三行骨架:listen → accept → 包成 std.http.Server;路由就是 path 字符串分支。
  8. HTTP 客户端四函数:parseAddress / connect / sendRequest / readResponse;Connection: close 让交互一锤定音。
  9. JSON 边界解析用匿名结构体 + ignore_unknown_fields,服务器加字段客户端不坏。
  10. 线上协议的终极形态:结构体即规格,comptime 生成编解码,不支持的类型编译期点名枪毙,缓冲尺寸由 frameSize() 永不过期。

7. 原文地图

主题 原书章 原文位置
TCP/IP 与端口 Chapter 6: Programming the Network Stack text/70-ch06-chapter-6-programming-the-network-stack.txt:39(搜「identifies a host」)
半关闭与死等 Testing the Echo server text/71-fm-testing-the-echo-server.txt:229(搜「block indefinitely」)
parseIp4 不查 DNS Testing the Echo server text/71-fm-testing-the-echo-server.txt:131(搜「strictly a parser」)
getaddrinfo 与地址链表 Performing low-level hostname resolution text/72-fm-performing-low-level-hostname-resolution.txt:40(搜「getaddrinfo」)
谷歌双栈实测 Performing low-level hostname resolution text/72-fm-performing-low-level-hostname-resolution.txt:251(搜「142.251.141.100」)
HostName.init 惰性 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:73(搜「no network activity yet」)
resolve 与 lookup 的取舍 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:216(搜「Choose」)
TCP/UDP 取舍 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:210(搜「tradeoff」)
UDP 无连接、sendto 带地址 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:429(搜「connectionless」)
sa_family_t 跨平台 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:339(搜「u8」)
std.Io.net 无 UDP Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:238(搜「does not yet expose」)
Docker 两阶段构建 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:813(搜「two-stage」)
端口映射写错=不可见 Improving the TCP Echo client text/73-fm-improving-the-tcp-echo-client.txt:901(搜「wrongly defined mapping」)
同步服务器模型 Developing a minimal web server text/74-fm-developing-a-minimal-web-server.txt:55(搜「synchronous」)
curl -v 握手实录 Developing a minimal web server text/74-fm-developing-a-minimal-web-server.txt:132(搜「Connection refused」)
HTTP/1.1 默认不断连 Developing a minimal web server text/74-fm-developing-a-minimal-web-server.txt:145(搜「left intact」)
/rand 的 0.023% 偏差 Creating a web service that generates random numbers text/75-fm-creating-a-web-service-that-generates-random-num.txt:157(搜「0.023%」)
四函数分离的价值 Creating a web service that generates random numbers text/75-fm-creating-a-web-service-that-generates-random-num.txt:314(搜「separation of concerns」)
Connection: close 一锤交易 Creating a web service that generates random numbers text/75-fm-creating-a-web-service-that-generates-random-num.txt:695(搜「single clean」)
readSliceShort 适合网络 Creating a web service that generates random numbers text/75-fm-creating-a-web-service-that-generates-random-num.txt:769(搜「irregular chunks」)
内存取值两平台差异 Reading memory information text/76-fm-reading-memory-information.txt:82(搜「/proc/meminfo」)
macOS 三页计数器口径 Reading memory information text/76-fm-reading-memory-information.txt:188(搜「active_pages」)
单连接服务器 The server process text/77-fm-the-server-process.txt:83(搜「single-client-at-a-time」)
匿名结构体前向兼容 The service client text/78-fm-the-service-client.txt:121(搜「ignore_unknown_fields」)
JSON 协议的运行时盲区 The service client text/78-fm-the-service-client.txt:292(搜「undetected until runtime」)
comptime 三场景 The service client text/78-fm-the-service-client.txt:394(搜「buffer sizing」)
wireInt 的类型枪毙 The service client text/78-fm-the-service-client.txt:607(搜「wireInt」)
40 字节帧实录 The service client text/78-fm-the-service-client.txt:687(搜「02 00 00 00」)
结构体=唯一事实来源 The service client text/78-fm-the-service-client.txt:438(搜「single source of truth」)

本章拆解依据原书第 6 章(含各小节)。每条出处可用 node scripts/book-verify.mjs systems-programming-with-zig 回核。


  1. 出处:「Chapter 6: Programming the Network Stack」(text/70-ch06-chapter-6-programming-the-network-stack.txt,搜「Port numbers」)。 ↩

  2. 出处:「Testing the Echo server」第 221 段(text/71-fm-testing-the-echo-server.txt:221,搜「half-close」)。 ↩

  3. 出处:「The service client」第 889 段(text/78-fm-the-service-client.txt:889,搜「repeat themselves endlessly」)。 ↩

  4. 出处:「Testing the Echo server」第 131 段(text/71-fm-testing-the-echo-server.txt:131,搜「strictly a parser」)。 ↩

  5. 出处:「Testing the Echo server」第 229 段(text/71-fm-testing-the-echo-server.txt:229,搜「block indefinitely」)。 ↩

  6. 出处:「Testing the Echo server」第 243 段(text/71-fm-testing-the-echo-server.txt:243,搜「catch 0」)。 ↩

  7. 出处:「Testing the Echo server」第 205 段(text/71-fm-testing-the-echo-server.txt:205,搜「must remain stable」)。 ↩

  8. 出处:「Performing low-level hostname resolution」第 40 段(text/72-fm-performing-low-level-hostname-resolution.txt:40,搜「getaddrinfo」)与第 181 段(text/72-fm-performing-low-level-hostname-resolution.txt:181,搜「linked list」)。 ↩

  9. 出处:「Performing low-level hostname resolution」第 251 段(text/72-fm-performing-low-level-hostname-resolution.txt:251,搜「142.251.141.100」)。 ↩

  10. 出处:「Improving the TCP Echo client」第 73 段(text/73-fm-improving-the-tcp-echo-client.txt:73,搜「no network activity yet」)。 ↩

  11. 出处:「Improving the TCP Echo client」第 216 段(text/73-fm-improving-the-tcp-echo-client.txt:216,搜「Choose」)。 ↩

  12. 出处:「Improving the TCP Echo client」第 210 段(text/73-fm-improving-the-tcp-echo-client.txt:210,搜「tradeoff」)。 ↩

  13. 出处:「Improving the TCP Echo client」第 238 段(text/73-fm-improving-the-tcp-echo-client.txt:238,搜「does not yet expose」)与第 429 段(text/73-fm-improving-the-tcp-echo-client.txt:429,搜「connectionless」)。 ↩↩↩

  14. 出处:「Improving the TCP Echo client」第 339 段(text/73-fm-improving-the-tcp-echo-client.txt:339,搜「u8」)。 ↩

  15. 出处:「Improving the TCP Echo client」第 625 段(text/73-fm-improving-the-tcp-echo-client.txt:625,搜「ephemeral port」)。 ↩

  16. 出处:「Improving the TCP Echo client」第 680 段(text/73-fm-improving-the-tcp-echo-client.txt:680,搜「blocks indefinitely」)。 ↩

  17. 出处:「Improving the TCP Echo client」第 901 段(text/73-fm-improving-the-tcp-echo-client.txt:901,搜「wrongly defined mapping」)。 ↩

  18. 出处:「Improving the TCP Echo client」第 895 段(text/73-fm-improving-the-tcp-echo-client.txt:895,搜「single server per Docker image」)。 ↩

  19. 出处:「Developing a minimal web server」第 55 段(text/74-fm-developing-a-minimal-web-server.txt:55,搜「synchronous」)。 ↩↩

  20. 出处:「Developing a minimal web server」第 132 段(text/74-fm-developing-a-minimal-web-server.txt:132,搜「Connection refused」)。 ↩

  21. 出处:「Developing a minimal web server」第 145 段(text/74-fm-developing-a-minimal-web-server.txt:145,搜「left intact」)。 ↩

  22. 出处:「Creating a web service that generates random numbers」第 157 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:157,搜「0.023%」)。 ↩↩

  23. 出处:「Creating a web service that generates random numbers」第 314 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:314,搜「separation of concerns」)。 ↩↩

  24. 出处:「Creating a web service that generates random numbers」第 832 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:832,搜「All 3 tests passed」)。 ↩

  25. 出处:「Creating a web service that generates random numbers」第 521 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:521,搜「ResolutionFailed」)。 ↩

  26. 出处:「Creating a web service that generates random numbers」第 695 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:695,搜「single clean」)。 ↩

  27. 出处:「Creating a web service that generates random numbers」第 769 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:769,搜「irregular chunks」)。 ↩

  28. 出处:「Creating a web service that generates random numbers」第 844 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:844,搜「516022」)。 ↩↩

  29. 出处:「Reading memory information」第 82 段(text/76-fm-reading-memory-information.txt:82,搜「/proc/meminfo」)。 ↩

  30. 出处:「Reading memory information」第 188 段(text/76-fm-reading-memory-information.txt:188,搜「active_pages」)。 ↩↩

  31. 出处:「Reading memory information」第 196 段(text/76-fm-reading-memory-information.txt:196,搜「Unsupported OS」)。 ↩

  32. 出处:「The service client」第 121 段(text/78-fm-the-service-client.txt:121,搜「ignore_unknown_fields」)。 ↩

  33. 出处:「The service client」第 260 段(text/78-fm-the-service-client.txt:260,搜「18771/65536」)。 ↩

  34. 出处:「The service client」第 292 段(text/78-fm-the-service-client.txt:292,搜「undetected until runtime」)。 ↩↩

  35. 出处:「The service client」第 394 段(text/78-fm-the-service-client.txt:394,搜「buffer sizing」)与第 607 段(text/78-fm-the-service-client.txt:607,搜「wireInt」)。 ↩

  36. 出处:「The service client」第 687 段(text/78-fm-the-service-client.txt:687,搜「02 00 00 00」)。 ↩

  37. 出处:「The service client」第 438 段(text/78-fm-the-service-client.txt:438,搜「single source of truth」)。 ↩

  38. 出处:「Developing a minimal web server」第 239 段(text/74-fm-developing-a-minimal-web-server.txt:239,搜「dual-stack」)。 ↩

  39. 出处:「Developing a minimal web server」第 259 段(text/74-fm-developing-a-minimal-web-server.txt:259,搜「identical for both protocol families」)。 ↩

  40. 出处:「Improving the TCP Echo client」第 575 段(text/73-fm-improving-the-tcp-echo-client.txt:575,搜「streams results」)。 ↩

  41. 出处:「Improving the TCP Echo client」第 549 段(text/73-fm-improving-the-tcp-echo-client.txt:549,搜「UnknownHostName」)。 ↩

  42. 出处:「Creating a web service that generates random numbers」第 592 段(text/75-fm-creating-a-web-service-that-generates-random-num.txt:592,搜「straightforward choice」)。 ↩

  43. 出处:「Being interested in the first result only」第 923 段(text/109-fm-being-interested-in-the-first-result-only.txt:923,搜「keeps the backend trivial」)。 ↩

  44. 出处:「Being interested in the first result only」第 374 段(text/109-fm-being-interested-in-the-first-result-only.txt:374,搜「production proxy」)。 ↩