复用NamedPipeClientStream重复创建StreamWriter致多余BOM问题
命名管道重复创建StreamWriter导致多余BOM问题分析
问题背景
我有一个保持打开状态的NamedPipeServerStream和一个保持打开状态的NamedPipeClientStream。NamedPipeServerStream关联着一个持续打开的UTF-8编码StreamReader;NamedPipeClientStream则每次打开UTF-8编码的StreamWriter、写入消息、关闭后再重新打开以写入下一条消息。StreamReader和StreamWriter均设置leaveOpen为true,因此StreamWriter关闭时不会关闭NamedPipeClientStream。
从第二次打开StreamWriter发送消息开始,StreamReader会读取到多余的BOM。以下是.NET 6环境下的复现代码:
using System.IO.Pipes; namespace NamedPipeTest { internal class EncodingTest { private static NamedPipeServerStream namedPipeServer = new NamedPipeServerStream("mypipe", PipeDirection.InOut, NamedPipeServerStream.MaxAllowedServerInstances, PipeTransmissionMode.Byte, PipeOptions.Asynchronous); private static NamedPipeClientStream namedPipeClient = new NamedPipeClientStream(".", "mypipe", PipeDirection.InOut, PipeOptions.Asynchronous); static void Main(string[] args) { Console.WriteLine("Hello! You are the client. Press any key to send a request."); Task[] tasks = new Task[] { Task.Run(() => Server()), Task.Run(() => Client()) }; Task.WaitAll(tasks); } static async Task Server() { if (!namedPipeServer.IsConnected) { namedPipeServer.WaitForConnection(); } using (var reader = new StreamReader(namedPipeServer, System.Text.Encoding.UTF8, false, 1024, true)) { while (true) { string? line = await reader.ReadLineAsync(); Console.WriteLine($"Server: Received request: {line}"); if (!string.IsNullOrEmpty(line)) { var lineBytes = System.Text.Encoding.UTF8.GetBytes(line); var possibleBOMString = System.Text.Encoding.UTF8.GetString(new byte[] { lineBytes[0], lineBytes[1], lineBytes[2] }); if (possibleBOMString.Equals("\uFEFF")) { Console.WriteLine("Server: Whoa, what's up with the extra BOM?"); } } } } } static async Task Client() { if (!namedPipeClient.IsConnected) { namedPipeClient.Connect(6000); } while (true) { using (var writer = new StreamWriter(namedPipeClient, System.Text.Encoding.UTF8, 1024, true)) { Console.ReadKey(true); Console.WriteLine("Client: Received key press, sending request."); string? line = "Hello!"; await writer.WriteLineAsync(line); await writer.FlushAsync(); Console.WriteLine($"Client: Sent request: {line}"); } } } } }
程序输出:
Hello! You are the client. Press any key to send a request. Client: Received key press, sending request. Server: Received request: Hello! Client: Sent request: Hello! Client: Received key press, sending request. Server: Received request: ?Hello! Client: Sent request: Hello! Server: Whoa, what's up with the extra BOM? Client: Received key press, sending request. Server: Received request: ?Hello! Client: Sent request: Hello! Server: Whoa, what's up with the extra BOM?
原因分析
问题核心在于每次创建StreamWriter时,默认会自动写入UTF-8的BOM(字节顺序标记,三个字节:0xEF, 0xBB, 0xBF)。
- 第一次创建
StreamWriter时,写入的BOM会被服务器端的StreamReader在初始化阶段自动识别并跳过,因此第一次读取结果正常。 - 后续每次重新创建
StreamWriter时,都会再次向管道写入BOM,但服务器端的StreamReader仅在第一次读取时执行BOM检测逻辑,后续的BOM字节会被当作普通字符读取,也就是输出中看到的?。 - 虽然
leaveOpen=true保证了管道流不被关闭,但StreamWriter的Dispose方法会重置内部编码状态,下次新建实例时仍会从头写入BOM。
解决方案
1. 复用同一个StreamWriter
既然管道流保持打开状态,无需每次创建新的StreamWriter,复用一个实例即可避免重复写入BOM:
static async Task Client() { if (!namedPipeClient.IsConnected) { namedPipeClient.Connect(6000); } // 仅创建一次StreamWriter并复用 using (var writer = new StreamWriter(namedPipeClient, System.Text.Encoding.UTF8, 1024, true)) { while (true) { Console.ReadKey(true); Console.WriteLine("Client: Received key press, sending request."); string? line = "Hello!"; await writer.WriteLineAsync(line); await writer.FlushAsync(); Console.WriteLine($"Client: Sent request: {line}"); } } }
2. 使用不带BOM的UTF-8编码
创建StreamWriter时,通过new UTF8Encoding(false)指定不生成BOM的UTF-8编码,确保每次创建实例都不会写入BOM:
// 客户端修改StreamWriter的编码参数 using (var writer = new StreamWriter(namedPipeClient, new System.Text.UTF8Encoding(false), 1024, true))
同时,服务器端的StreamReader也可以明确使用不带BOM的UTF-8编码,消除不必要的BOM检测逻辑:
// 服务器端修改StreamReader的编码参数 using (var reader = new StreamReader(namedPipeServer, new System.Text.UTF8Encoding(false), false, 1024, true))
内容的提问来源于stack exchange,提问作者Helena
相关产品推荐
相关产品推荐

