C# Named Pipelines编码异常及连接关闭问题排查求助
问题描述
首次用Named Pipelines开发兼具客户端、服务端功能的独立exe,服务端拥有高权限。目前碰到两个问题:
- 服务端返回客户端的消息,除首次响应外都以'?'开头,虽使用同一
writeClient函数且编码一致仍未解决; - 需要确认连接关闭的实现是否规范。
客户端输出
已连接。 已安装版本: 1.2.8 ?最新版本: 1.2.85 ?需要更新 ?不带'-Check'参数重新运行以执行静默安装。 ?关闭 连接已关闭。
主程序代码
using System; using System.Diagnostics; using System.Xml.Linq; using System.Runtime.Versioning; using System.IO.Pipes; using System.Text; using System.Net; using System.ServiceProcess; using System.Security.Authentication.ExtendedProtection; [SupportedOSPlatform("windows")] class DAQUpdate { static void Main(string[] args) { if (args.Length > 0) { //检查是否有同程序以服务运行,有的话用服务端,因为它有高权限 try { using (NamedPipeClientStream pipeClient = new NamedPipeClientStream(".", "DAQUpdater", PipeDirection.InOut)) { //1秒超时连接服务端,超时会返回错误 pipeClient.Connect(1); //再次确认连接状态 if(pipeClient.IsConnected) { Console.WriteLine("Connected."); // 发送参数给服务端 using (StreamWriter writer = new StreamWriter(pipeClient,Encoding.UTF8,-1,true)) { //用;分隔参数,因为没法直接传对象 writer.WriteLine(string.Join(';', args)); writer.Flush(); } //读取服务端所有响应,最后一条是Close string response = ""; using (StreamReader reader = new StreamReader(pipeClient, Encoding.UTF8)) { while(!response.Contains("Close")) { response = reader.ReadLine(); Console.WriteLine(response); } pipeClient.Close(); } Console.WriteLine("Connection closed."); } } } catch (Exception ex) { Console.WriteLine(ex.Message); Console.WriteLine("No Connection."); } } else { NamedPipeServer.runServer(); } } public static void update(string[] args) { NamedPipeServer.writeClient("hello"); NamedPipeServer.writeClient(args[0]); NamedPipeServer.writeClient("Testing"); NamedPipeServer.writeClient("Close"); } }
服务端函数代码
// NamedPipeServer.cs using System.IO.Pipes; using System.Text; public class NamedPipeServer { private static NamedPipeServerStream pipeServer; public static void runServer() { try { while(true) { //改成全局变量,方便其他函数调用writeClient给客户端发消息 pipeServer = new NamedPipeServerStream("DAQUpdater", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); { Console.WriteLine("Waiting for connection..."); pipeServer.WaitForConnection(); Console.WriteLine("Client connected."); // 读取并处理客户端发来的命令 using (StreamReader reader = new StreamReader(pipeServer, Encoding.UTF8,true,-1,true )) { string command = reader.ReadLine(); string[] commandargs = command.Split(';'); Console.WriteLine($"Received command: {commandargs[0]}"); //运行更新检查程序,同时会给客户端发消息 DAQUpdate.update(commandargs); // 给客户端返回响应(如果需要) using (StreamWriter writer = new StreamWriter(pipeServer, Encoding.UTF8, -1 , true)) { writer.WriteLine("Close"); writer.Flush(); } } pipeServer.Close(); Console.WriteLine("Connection closed."); } } } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } public static void writeClient(string messageTxt) { //允许其他函数给客户端发送更新消息 if(pipeServer != null) { if(pipeServer.IsConnected) { using (StreamWriter writer = new StreamWriter(pipeServer, Encoding.UTF8, -1 , true)) { Console.WriteLine(writer.Encoding); writer.WriteLine(messageTxt); writer.Flush(); } } } } }
问题解决方案
1. 消息开头出现'?'的问题
问题根源是重复创建流包装器导致的编码不同步:
服务端中,你先创建StreamReader包裹管道流,之后又在writeClient里多次创建StreamWriter包裹同一个管道流。每个流包装器都有自己的内部缓冲区,多实例操作同一底层流会导致字节读写错位,出现乱码(表现为开头的'?')。
修复方案:
连接建立后一次性创建StreamReader和StreamWriter,复用这两个实例直到连接关闭,避免重复包装。
修改后的核心代码示例:
// 服务端runServer方法修改 public static void runServer() { try { while(true) { using (NamedPipeServerStream pipeServer = new NamedPipeServerStream("DAQUpdater", PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous)) { Console.WriteLine("Waiting for connection..."); pipeServer.WaitForConnection(); Console.WriteLine("Client connected."); // 一次性创建读写流并复用 using (StreamReader reader = new StreamReader(pipeServer, Encoding.UTF8, true, -1, true)) using (StreamWriter writer = new StreamWriter(pipeServer, Encoding.UTF8, -1, true)) { string command = reader.ReadLine(); string[] commandargs = command.Split(';'); Console.WriteLine($"Received command: {commandargs[0]}"); // 传入复用的writer给update方法 DAQUpdate.update(commandargs, writer); writer.WriteLine("Close"); writer.Flush(); } Console.WriteLine("Connection closed."); } } } catch (Exception ex) { Console.WriteLine($"Error: {ex.Message}"); } } // 修改writeClient方法,直接传入已创建的writer public static void writeClient(StreamWriter writer, string messageTxt) { if(writer != null && !writer.BaseStream.IsClosed) { Console.WriteLine(writer.Encoding); writer.WriteLine(messageTxt); writer.Flush(); } } // 主程序update方法修改,接收writer参数 public static void update(string[] args, StreamWriter writer) { NamedPipeServer.writeClient(writer, "hello"); NamedPipeServer.writeClient(writer, args[0]); NamedPipeServer.writeClient(writer, "Testing"); NamedPipeServer.writeClient(writer, "Close"); }
2. 连接关闭的规范实现
当前代码的关闭逻辑存在两个问题:
- 客户端在
using块内手动调用pipeClient.Close(),但using语句本身会自动释放资源,重复关闭可能引发异常; - 服务端中
pipeServer的using块使用不完整,存在多余的大括号,没有用using正确包裹管道流实例。
规范实现方式:
- 优先使用
using语句自动管理管道流和读写流的资源,using块结束时会自动调用Dispose(),该方法会自动关闭流; - 除非需要提前关闭且明确后续不再使用,否则不要手动调用
Close()。
修改后的客户端读取部分:
using (StreamReader reader = new StreamReader(pipeClient, Encoding.UTF8)) { string response; while((response = reader.ReadLine()) != null) { Console.WriteLine(response); if(response.Contains("Close")) break; } } // 移除pipeClient.Close(),外层using(pipeClient)会自动处理关闭
修改后的服务端runServer方法已在上面的修复示例中体现,核心是用using完整包裹pipeServer实例,确保资源自动释放。
内容的提问来源于stack exchange,提问作者Furbs
相关产品推荐
相关产品推荐

