Swift转C#:TCP连接StreamReader无法持续读取服务端消息
解决C# TCP流仅能读取首条回复的问题
我来帮你拆解下问题核心:你的Swift代码能持续捕获TCP流的所有消息,但C#用StreamReader的方法只能拿到第一条,本质是TCP流处理逻辑不匹配,以及StreamReader/StreamWriter的默认行为不符合你的服务端推送场景。
问题根源分析
- 发送命令时的额外字符:Swift代码是直接发送
*99*1##的原始UTF-8字节,但C#的sw.WriteLine(command)会自动在命令末尾添加\r\n换行符。虽然服务端回复了第一条消息,但这种额外字符可能干扰服务端后续的消息推送逻辑(至少不符合协议的精确要求)。 - StreamReader方法不适合无边界流:TCP是无消息边界的字节流,
ReadToEnd()会一直阻塞到流关闭(服务端断开连接),而你的服务端是持续发送消息不关闭连接。你第一次调用ReadToEnd()时刚好读到了第一条消息,但后续调用时,要么因为服务端未发新消息而无限阻塞,要么被误判为流已结束。 - 缺乏逐缓冲的循环读取逻辑:Swift代码是循环读取固定大小的缓冲,每次读到数据就处理,这才是TCP流持续读取的正确姿势;而C#代码依赖
ReadToEnd()这种“一次性读到底”的方法,完全不匹配服务端的持续推送模式。
解决方案:模仿Swift的原始字节读取逻辑
最可靠的方式是直接使用NetworkStream读取字节,和Swift的代码逻辑完全对齐,避免StreamReader的缓冲和格式干扰。
完整代码示例
using System; using System.IO; using System.Net.Sockets; using System.Text; class TcpClientDemo { static void Main() { string address = "192.168.0.10"; int port = 20000; // 使用using语句自动管理资源,避免连接/流泄漏 using (TcpClient client = new TcpClient(address, port)) using (NetworkStream stream = client.GetStream()) { // 发送命令:和Swift一致,直接发送原始字节,不添加任何额外字符 string command = "*99*1##"; byte[] commandBytes = Encoding.UTF8.GetBytes(command); Console.WriteLine("Client->" + command); stream.Write(commandBytes, 0, commandBytes.Length); // 设置接收超时(和服务端30分钟超时保持一致) client.ReceiveTimeout = 30 * 60 * 1000; // 和Swift使用相同大小的缓冲(可根据你的Swift代码调整buffer大小) byte[] buffer = new byte[1024]; int readBytesCount; while (true) { try { // 循环读取流中的字节 readBytesCount = stream.Read(buffer, 0, buffer.Length); if (readBytesCount > 0) { // 将读取到的字节转为字符串 string receivedMsg = Encoding.UTF8.GetString(buffer, 0, readBytesCount); Console.WriteLine("Server->" + receivedMsg); // 写入日志文件(追加模式) File.AppendAllText("server_response_log.txt", receivedMsg + Environment.NewLine); } else { // 流已关闭,服务端断开连接 Console.WriteLine("Server closed connection."); break; } } catch (IOException ex) { // 处理超时或其他IO异常 Console.WriteLine("读取异常: " + ex.Message); break; } } } } }
关键调整点说明
- 发送命令用
Write而非WriteLine:确保发送的字节和Swift完全一致,没有额外的换行符,严格符合服务端的协议要求。 - 使用
NetworkStream直接读取字节:避免StreamReader的内部缓冲和字符边界处理,和Swift的读取逻辑完全对齐,能持续捕获服务端推送的所有消息。 - 设置合理的接收超时:和服务端的30分钟超时保持一致,避免无限等待。
- 循环读取缓冲:每次读取固定大小的字节,读取到数据就处理,这是TCP流持续读取的标准模式。
- 资源自动管理:用
using语句包裹TcpClient和NetworkStream,确保连接和流被正确释放。
备选方案:如果一定要用StreamReader
如果你坚持使用StreamReader,需要禁用不必要的缓冲,并用循环读取字符的方式处理,同时避免ReadToEnd()/ReadLine()这类依赖边界的方法:
using System; using System.IO; using System.Net.Sockets; using System.Text; class TcpClientWithStreamReader { static void Main() { string address = "192.168.0.10"; int port = 20000; using (TcpClient client = new TcpClient(address, port)) using (NetworkStream stream = client.GetStream()) // 禁用自动编码检测,设置缓冲大小,leaveOpen=true避免提前关闭流 using (StreamReader sr = new StreamReader(stream, Encoding.UTF8, false, 1024, true)) using (StreamWriter sw = new StreamWriter(stream, Encoding.UTF8, 1024, true)) { string command = "*99*1##"; Console.WriteLine("Client->" + command); sw.Write(command); // 不要用WriteLine sw.Flush(); char[] charBuffer = new char[1024]; int readCharsCount; while (true) { try { readCharsCount = sr.Read(charBuffer, 0, charBuffer.Length); if (readCharsCount > 0) { string receivedMsg = new string(charBuffer, 0, readCharsCount); Console.WriteLine("Server->" + receivedMsg); File.AppendAllText("server_response_log.txt", receivedMsg + Environment.NewLine); } else { Console.WriteLine("Server closed connection."); break; } } catch (IOException ex) { Console.WriteLine("读取异常: " + ex.Message); break; } } } } }
这个方案的核心是用Read()方法循环读取字符,而不是依赖行或流结束符,同时确保StreamWriter发送的命令和Swift一致。
内容的提问来源于stack exchange,提问作者Annah
相关产品推荐
相关产品推荐

