网络栈:从原始套接字到系统监控 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. 可带走的¶
- echo 协议的命门:发完必须
shutdown(.send),否则双向死等。 parseIp4只认点分字面量、不查 DNS;HostName.init + connect是一行答案;lookup队列拿完整 DNS 结果。- 0.16 接口模式:
var impl = x.reader(io,&buf); const r = &impl.interface;——接口指针住命名变量里。 - TCP 与 UDP 的取舍是需求驱动的,不是谁更好;UDP 每个数据报独立寻址,服务器
recvfrom白得发送者地址。 - 端口 0 = 让 OS 分配临时端口;
receive无限阻塞,真实应用要设超时。 - 容器化 zig:静态链接(
-target native-native-musl -static -lc)+ 两阶段构建,镜像只装二进制。 - HTTP 服务器三行骨架:listen → accept → 包成 std.http.Server;路由就是 path 字符串分支。
- HTTP 客户端四函数:parseAddress / connect / sendRequest / readResponse;
Connection: close让交互一锤定音。 - JSON 边界解析用匿名结构体 +
ignore_unknown_fields,服务器加字段客户端不坏。 - 线上协议的终极形态:结构体即规格,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 回核。
-
出处:「Chapter 6: Programming the Network Stack」(
text/70-ch06-chapter-6-programming-the-network-stack.txt,搜「Port numbers」)。 ↩ -
出处:「Testing the Echo server」第 221 段(
text/71-fm-testing-the-echo-server.txt:221,搜「half-close」)。 ↩ -
出处:「The service client」第 889 段(
text/78-fm-the-service-client.txt:889,搜「repeat themselves endlessly」)。 ↩ -
出处:「Testing the Echo server」第 131 段(
text/71-fm-testing-the-echo-server.txt:131,搜「strictly a parser」)。 ↩ -
出处:「Testing the Echo server」第 229 段(
text/71-fm-testing-the-echo-server.txt:229,搜「block indefinitely」)。 ↩ -
出处:「Testing the Echo server」第 243 段(
text/71-fm-testing-the-echo-server.txt:243,搜「catch 0」)。 ↩ -
出处:「Testing the Echo server」第 205 段(
text/71-fm-testing-the-echo-server.txt:205,搜「must remain stable」)。 ↩ -
出处:「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」)。 ↩ -
出处:「Performing low-level hostname resolution」第 251 段(
text/72-fm-performing-low-level-hostname-resolution.txt:251,搜「142.251.141.100」)。 ↩ -
出处:「Improving the TCP Echo client」第 73 段(
text/73-fm-improving-the-tcp-echo-client.txt:73,搜「no network activity yet」)。 ↩ -
出处:「Improving the TCP Echo client」第 216 段(
text/73-fm-improving-the-tcp-echo-client.txt:216,搜「Choose」)。 ↩ -
出处:「Improving the TCP Echo client」第 210 段(
text/73-fm-improving-the-tcp-echo-client.txt:210,搜「tradeoff」)。 ↩ -
出处:「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」)。 ↩↩↩ -
出处:「Improving the TCP Echo client」第 339 段(
text/73-fm-improving-the-tcp-echo-client.txt:339,搜「u8」)。 ↩ -
出处:「Improving the TCP Echo client」第 625 段(
text/73-fm-improving-the-tcp-echo-client.txt:625,搜「ephemeral port」)。 ↩ -
出处:「Improving the TCP Echo client」第 680 段(
text/73-fm-improving-the-tcp-echo-client.txt:680,搜「blocks indefinitely」)。 ↩ -
出处:「Improving the TCP Echo client」第 901 段(
text/73-fm-improving-the-tcp-echo-client.txt:901,搜「wrongly defined mapping」)。 ↩ -
出处:「Improving the TCP Echo client」第 895 段(
text/73-fm-improving-the-tcp-echo-client.txt:895,搜「single server per Docker image」)。 ↩ -
出处:「Developing a minimal web server」第 55 段(
text/74-fm-developing-a-minimal-web-server.txt:55,搜「synchronous」)。 ↩↩ -
出处:「Developing a minimal web server」第 132 段(
text/74-fm-developing-a-minimal-web-server.txt:132,搜「Connection refused」)。 ↩ -
出处:「Developing a minimal web server」第 145 段(
text/74-fm-developing-a-minimal-web-server.txt:145,搜「left intact」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 157 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:157,搜「0.023%」)。 ↩↩ -
出处:「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」)。 ↩↩ -
出处:「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」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 521 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:521,搜「ResolutionFailed」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 695 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:695,搜「single clean」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 769 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:769,搜「irregular chunks」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 844 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:844,搜「516022」)。 ↩↩ -
出处:「Reading memory information」第 82 段(
text/76-fm-reading-memory-information.txt:82,搜「/proc/meminfo」)。 ↩ -
出处:「Reading memory information」第 188 段(
text/76-fm-reading-memory-information.txt:188,搜「active_pages」)。 ↩↩ -
出处:「Reading memory information」第 196 段(
text/76-fm-reading-memory-information.txt:196,搜「Unsupported OS」)。 ↩ -
出处:「The service client」第 121 段(
text/78-fm-the-service-client.txt:121,搜「ignore_unknown_fields」)。 ↩ -
出处:「The service client」第 260 段(
text/78-fm-the-service-client.txt:260,搜「18771/65536」)。 ↩ -
出处:「The service client」第 292 段(
text/78-fm-the-service-client.txt:292,搜「undetected until runtime」)。 ↩↩ -
出处:「The service client」第 394 段(
text/78-fm-the-service-client.txt:394,搜「buffer sizing」)与第 607 段(text/78-fm-the-service-client.txt:607,搜「wireInt」)。 ↩ -
出处:「The service client」第 687 段(
text/78-fm-the-service-client.txt:687,搜「02 00 00 00」)。 ↩ -
出处:「The service client」第 438 段(
text/78-fm-the-service-client.txt:438,搜「single source of truth」)。 ↩ -
出处:「Developing a minimal web server」第 239 段(
text/74-fm-developing-a-minimal-web-server.txt:239,搜「dual-stack」)。 ↩ -
出处:「Developing a minimal web server」第 259 段(
text/74-fm-developing-a-minimal-web-server.txt:259,搜「identical for both protocol families」)。 ↩ -
出处:「Improving the TCP Echo client」第 575 段(
text/73-fm-improving-the-tcp-echo-client.txt:575,搜「streams results」)。 ↩ -
出处:「Improving the TCP Echo client」第 549 段(
text/73-fm-improving-the-tcp-echo-client.txt:549,搜「UnknownHostName」)。 ↩ -
出处:「Creating a web service that generates random numbers」第 592 段(
text/75-fm-creating-a-web-service-that-generates-random-num.txt:592,搜「straightforward choice」)。 ↩ -
出处:「Being interested in the first result only」第 923 段(
text/109-fm-being-interested-in-the-first-result-only.txt:923,搜「keeps the backend trivial」)。 ↩ -
出处:「Being interested in the first result only」第 374 段(
text/109-fm-being-interested-in-the-first-result-only.txt:374,搜「production proxy」)。 ↩