C# 文件系统命名管道 C#如何使用Named Pipes进行高效的进程间通信

来源:这里教程网 时间:2026-02-21 17:43:11 作者:

NamedPipeServerStream 创建时必须指定管道名和方向

命名管道不是“开箱即用”的通信通道,

NamedPipeServerStream
构造函数里漏掉
pipeName
或搞错
PipeDirection
,服务端直接抛
ArgumentException
。Windows 要求管道名全局唯一且符合
\.pipe<em>name</em>
格式,但 C# 封装层允许只传
"mypipe"
—— 它会自动补前缀,前提是不能含反斜杠或控制字符。

常见错误现象:

IOException: "The pipe name is invalid"
,往往是因为传了
"\.pipemypipe"
(重复前缀)或空字符串;还有人误用
PipeDirection.InOut
却在客户端用
NamedPipeClientStream
指定
PipeDirection.In
,握手失败。

服务端必须用
new NamedPipeServerStream("log_collector", PipeDirection.InOut)
,名字别带路径分隔符
客户端对应用
new NamedPipeClientStream(".", "log_collector", PipeDirection.InOut)
,第一个参数是服务器机器名,本地用
"."
即可
方向不匹配时,
ConnectAsync()
会超时而非立刻报错,调试时容易误判为网络问题

读写必须配对使用 ReadAsync/WriteAsync 且注意缓冲区大小

命名管道底层基于字节流,没有消息边界。你

WriteAsync
发 1024 字节,对方
ReadAsync
可能一次只收到 512 字节——这不是 bug,是 Windows 管道的正常行为。硬编码固定长度读取(比如总期待 1024 字节)会导致阻塞或数据错位。

典型场景:日志转发服务中,客户端每条日志加 4 字节长度头,服务端必须先读够 4 字节再按长度读正文。若直接

await stream.ReadAsync(buffer, 0, buffer.Length)
,buffer 剩余部分就永远等不到数据。

务必用
await stream.ReadExactlyAsync(buffer, cancellationToken)
(.NET 5+),它会循环读直到填满 buffer
如果用旧版 .NET,自己封装循环读逻辑,别依赖单次
ReadAsync
返回值等于 buffer 长度
WriteAsync
后建议调用
FlushAsync()
,尤其跨进程时,否则对方可能迟迟收不到数据

客户端 ConnectAsync 超时需主动处理,不能只靠 try-catch

ConnectAsync
默认无限等待,服务端没启动时,客户端线程就卡死了。这不是设计缺陷,而是 Windows 管道语义:客户端要“等到管道可用”。但实际业务中,你得控制等待时间,否则 UI 冻结或健康检查失败。

错误做法:把

ConnectAsync
包进
Task.Run(() => stream.Connect())
再用
Wait(timeout)
—— 这会浪费线程且无法取消底层 IO。

正确方式:用
await stream.ConnectAsync(timeoutMs, cancellationToken)
(.NET 6+),timeoutMs 设为 3000 比较稳妥
.NET 5 及以下只能用
Task.WhenAny(ConnectAsync(), Task.Delay(3000))
,但要注意
ConnectAsync
未完成时手动
Dispose
流对象,避免句柄泄漏
超时后不要重试
ConnectAsync
在同一个
NamedPipeClientStream
实例上,必须新建实例

多客户端并发时,每个连接必须独立线程/任务处理

NamedPipeServerStream
不是线程安全的,也不能复用。一个实例只能服务一个客户端连接。常见误区是启一个 server stream 后反复
WaitForConnectionAsync
,却把后续读写丢给同一个 stream 对象 —— 第二个客户端一连,第一个就断了。

真实场景:监控代理要同时接收 10 个采集器的数据,必须为每个连接分配独立的

NamedPipeServerStream
和处理任务。

标准模式:server 启动后循环
await server.WaitForConnectionAsync()
,每次成功就
Task.Run(() => HandleClientAsync(server))
HandleClientAsync 内部要立即创建新 buffer、读写逻辑封闭,别共享任何 stream 级变量 别忘了在 finally 块里
server.Dispose()
,否则句柄累积到上限(默认 65536)后整个进程无法创建新管道

最易被忽略的是异常后资源清理 —— 某个客户端连接突然断开,

ReadAsync
IOException
,如果没在 catch 里 dispose stream,这个句柄就泄露了。Windows 下看不见内存增长,但句柄数爆满时所有新管道都创建失败,排查起来极难定位。

相关推荐

热文推荐