bigywater.tech
Rust · 137

TCP Listener 与 TcpStream

std::net 的 TCP API 很小,但每个细节都值得验证:端口怎么分配、各种错误长什么样、为什么「一次 write 一次 read」不成立、半关闭是什么、NODELAY 影响多大。这篇用本机的回环地址把它们都跑一遍。

12 字节
发送端 3 次 write 各 4 字节,接收端一次 read 读到的字节数
61 / 48
ConnectionRefused 与 AddrInUse 的 OS 错误码(macOS)
44 → 18 µs
默认与 TCP_NODELAY 的小请求往返时间

listener 与 stream

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    监听地址 127.0.0.1:<端口>(端口由系统分配:true)
      服务端:来自 <客户端地址> 的 5 字节已回显(对端 IP 127.0.0.1,是回环地址: true)
    客户端:发 "hello",收到 "hello"
      服务端:来自 <客户端地址> 的 6 字节已回显(对端 IP 127.0.0.1,是回环地址: true)
    客户端:发 "世界",收到 "世界"

非阻塞与多路复用见非阻塞 IO;异步版本见Tokio TCP Echo Server。

各种错误

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    连接一个没人监听的端口:kind = ConnectionRefused,os_error = Some(61)
    同一个地址 bind 第二次:kind = AddrInUse,os_error = Some(48)
    读超时 100 ms:kind = WouldBlock(实际等了约 100 ms)

字节流

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    发送端分 3 次 write 各 4 字节,接收端一次 read 读到 12 字节:"AAAABBBBCCCC"(消息边界没有保留)
    再 read:0 字节(0 = 对端已关闭,读到 EOF)

HTTP 如何在字节流上分帧见HTTP 请求的底层实现。

半关闭

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    客户端 shutdown(Write) 之后,服务端 read_to_end 返回,回复 "收到 12 字节"
    服务端结果 (12, None)
    客户端不关闭写端:服务端 read_to_end 在 400 ms 超时后失败,返回 (12, Some(WouldBlock))(已经读到的 12 字节仍留在 Vec 里)

NODELAY

实测 · 本机执行输出
  编译:成功
  运行(退出码 0):
    请求分两次 write(2 字节 + 2 字节)再等回复,每轮往返:默认 44 µs,set_nodelay(true) 18 µs

只在本机回环地址上测量,绝对数值和真实网络无关。

分步演示

TCP
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / rustc 1.98.1(brew)/ cargo 1.98.1;单个文件用 rustc 直接编译,需要依赖时用 cargo(经代理从 crates.io 下载);计时用 std::time::Instant,数字每次运行会有小幅抖动。全部使用 127.0.0.1 与系统分配的临时端口
    • 没有验证:跨主机的网络行为、Nagle 与延迟确认的真实停顿、SO_REUSEADDR 与 TIME_WAIT(没有单独验证)、IPv6、连接队列 backlog、keepalive、被 RST 重置时的错误
    京ICP备2026067502号-1京公网安备11010502063487号