namedpipeserverstream 创建时必须指定 pipedirection,推荐按实际流向设为 pipedirection.in(服务端只收)或 pipedirection.out(服务端只发);若用 pipedirection.inout,须两端均调用 flushasync() 并检查 isconnected 状态。

NamedPipeServerStream 创建时必须指定 PipeDirection
不指定方向或设为 PipeDirection.InOut 看似通用,但客户端连接后若只写不读(或只读不写),服务端可能卡在 WaitForConnectionAsync() 或后续 I/O 上。真实场景中,多数本地 IPC 是“请求-响应”模式,推荐显式拆分为两个单向管道,或至少按实际流向设为 PipeDirection.In(服务端只收)或 PipeDirection.Out(服务端只发)。
常见错误现象:WaitForConnectionAsync() 永远不返回,且无异常;Wireshark 看不到命名管道流量(因为根本没建立逻辑连接)。
- 服务端接收请求:用
PipeDirection.In+ReadAsync() - 服务端返回结果:用
PipeDirection.Out+WriteAsync() - 若坚持单管道双向通信,务必在两端都调用
FlushAsync(),并注意IsConnected状态检查
客户端连接 NamedPipeClientStream 要处理“管道不存在”和“拒绝访问”两种超时
NamedPipeClientStream 构造时不连管道,真正连接发生在 ConnectAsync()。此时可能遇到两类典型失败:
-
IOException带消息 “The system cannot find the file specified”:服务端尚未启动,或管道名拼错(注意大小写敏感、前缀是否漏了\.pipe) -
UnauthorizedAccessException:Windows 权限限制,默认只有 LocalSystem 和 Administrators 可访问命名管道;普通用户进程需显式设置PipeSecurity
实操建议:
- 连接前先用
WaitForPipeDrain()无意义,应改用带重试的ConnectAsync(TimeSpan) - 权限配置示例:
var sec = new PipeSecurity(); sec.AddAccessRule(new PipeAccessRule("Everyone", PipeAccessRights.ReadWrite, AccessControlType.Allow));,传入NamedPipeServerStream构造函数第 5 个参数
序列化数据必须约定长度头,不能直接 Read/Write 字符串
命名管道是字节流,没有消息边界。直接 StreamWriter.WriteLine() 写字符串,再用 StreamReader.ReadLine() 读,看似可行,但一旦网络延迟波动或缓冲区满,ReadLine() 会阻塞到超时甚至死锁——因为对方可能还没写完换行符,或已关闭连接但缓冲区残留半条消息。
正确做法是:发送方先写 4 字节消息长度(BinaryWriter.Write(int)),再写内容;接收方先读 4 字节,再按该长度读取完整 payload。
示例关键片段:
await writer.WriteAsync(BitConverter.GetBytes(data.Length)); await writer.WriteAsync(data, 0, data.Length);
对应接收端必须严格按此顺序读取,否则后续所有通信都会错位。
Dispose 时机决定管道是否立即释放,影响快速重启
服务端调用 server.Dispose() 后,操作系统才真正释放 \.pipeMyApp 这个名字。如果没等 Dispose 完就立刻新起一个同名服务端,会抛 IOException:“All pipe instances are busy”。这不是 bug,是 Windows 命名管道的语义。
容易被忽略的点:
-
using块结束即触发Dispose,但异步方法中若没await完WriteAsync()就退出using,可能导致部分数据未发出就被强制断开 - 服务端优雅退出需:先停止接受新连接 → 等待已有会话完成 I/O → 再
Dispose所有NamedPipeServerStream - 客户端不必主动
Dispose管道对象来“释放名字”,名字只由服务端绑定
快速重启失败,八成是因为上一个服务端的 Dispose 还没跑完,而不是代码逻辑问题。











