net.pipe 创建的是无超时、无地址、非网络的同步内存管道,仅适用于测试流逻辑;它不支持 deadline、tls 或 http 服务端,无法替代 net.listen + net.dial。

net.Pipe 创建的是阻塞式、全双工的内存管道,不是真正的网络连接,不走 TCP/IP 协议栈,也没有地址、端口或超时概念——它只是两个 io.ReadWriteCloser 的配对实现,适合测试、模拟流式通信逻辑。
为什么 net.Pipe 不能替代 net.Listen + net.Dial
常见误解是把它当“本地回环 TCP”的轻量替代。实际它:
- 不支持
SetReadDeadline/SetWriteDeadline(调用会 panic) - 没有
LocalAddr或RemoteAddr方法(返回nil) - 读写完全同步:一端
Write后,另一端Read才能返回,无缓冲区(底层是chan []byte,容量为 1) - 无法被
http.Transport或tls.Conn包装(缺少net.Conn的完整接口契约)
正确初始化和基础用法
直接调用 net.Pipe() 得到一对连接,注意必须成对使用,且任一端关闭都会导致另一端 Read 返回 io.EOF:
conn1, conn2 := net.Pipe()
// 注意:conn1 和 conn2 是对称的,但通常按角色命名
go func() {
defer conn1.Close()
conn1.Write([]byte("hello"))
}()
buf := make([]byte, 10)
n, _ := conn2.Read(buf)
fmt.Println(string(buf[:n])) // 输出 "hello"
- 不要在同一个 goroutine 中对同一连接反复
Read/Write,容易死锁 - 务必显式
Close(),否则 goroutine 可能泄漏(net.Pipe内部用 channel 阻塞等待) - 若需模拟粘包或分片,手动切分
[]byte写入,net.Pipe不做任何帧处理
在 HTTP 测试中安全使用 net.Pipe
不能直接传给 http.Serve(它要求实现 net.Listener),但可包装成 net.Conn 并用于 http.Client 的自定义传输:
conn1, conn2 := net.Pipe()
client := &http.Client{
Transport: &http.Transport{
DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
return conn1, nil
},
},
}
// 启动服务端逻辑(需另起 goroutine 模拟 handler 行为)
go func() {
defer conn2.Close()
// 手动解析 HTTP 请求头/体,写响应
io.WriteString(conn2, "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello")
}()
resp, _ := client.Get("http://unused/")
- 这种用法绕过了标准 server stack,适合验证 client 端行为(如 header 处理、重试逻辑)
- 不要试图用
httptest.Server替换——它内部用真实 TCP,和net.Pipe无关 - 若要测 server 端逻辑,改用
httptest.NewRecorder更简单直接
真正要注意的是:一旦你开始需要超时、地址信息、TLS、Keep-Alive 或多路复用,net.Pipe 就该让位给 net.Listen("tcp", "127.0.0.1:0") 配合 net.Dial——内存管道只适合最简流控验证,别让它承担协议层职责。











