You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C#实现TCP/IP监听接收XML流写入文件方案合理性咨询

原实现存在的问题

你当前的代码在简单测试场景下可能偶尔跑通,但放到生产环境大概率会出现数据丢失、程序卡死的问题,核心缺陷如下:

  • 单线程阻塞模型:整个循环同一时间只能处理1个客户端连接,执行当前连接的读取、写文件逻辑时,新的连接请求会直接被拒绝。如果有多台透析机同时上报、或者某台设备连接后传输速度慢,后续所有上报都会失败。
  • ReadToEnd() 逻辑错误:TCP是面向流的协议,没有天然的消息边界,StreamReader.ReadToEnd() 只有在TCP连接被主动关闭时才会返回结果。如果透析机推送完XML后没有主动断开连接,代码会永久卡在读取步骤,既不会存储文件也不会接收新连接;如果出现网络粘包,还会把多次推送的XML拼成一个文件,完全不符合需求。
  • 文件名生成逻辑有bug:默认的DateTime.Now.ToString()输出包含:等Windows文件名非法字符,仅替换空格无法解决问题,会直接触发文件写入异常;如果同一时间点收到多个上报,还会出现文件名重复、覆盖已有文件的问题。
  • 资源泄漏风险:TcpClient、NetworkStream、StreamReader都没有做显式释放,一旦中间抛出异常(比如磁盘满、路径不存在、网络中断),连接和文件句柄会一直被占用,运行时间久了会导致程序崩溃、端口无法正常监听。
  • 无容错机制:整个代码没有异常捕获,任意一步报错都会直接导致整个监听程序退出,无人值守的情况下会持续丢失数据。
  • 无数据校验:传输过程中如果出现网络丢包、内容截断,会直接把损坏的内容存为XML文件,后续数据处理环节会出问题。
更优实现方案

针对透析机每60秒主动推送XML的场景,优化方向如下:

  • 采用异步监听模型,主循环仅负责接收连接,每个客户端连接的读取、存文件逻辑放到独立任务中处理,避免阻塞新连接接入。
  • 基于XML的格式特征做消息边界判断:边读取流内容边拼接缓冲区,检测到完整的XML根节点闭合标签后,立刻取出内容存储文件,不需要等待连接断开。
  • 修正文件名生成规则:使用明确的无非法字符的时间格式,追加随机后缀避免重名覆盖。
  • 所有实现了IDisposable接口的对象用using块包裹,确保异常场景下资源也能被正常回收。
  • 增加分层异常捕获:单个连接处理失败仅记录日志、释放当前连接,不影响整个监听服务运行。
  • 增加基础XML完整性校验,损坏的内容单独归档,不写入正常文件目录。

参考实现代码:

using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Xml.Linq;

// 初始化监听器,开启地址重用避免重启时端口占用
TcpListener server = new TcpListener(IPAddress.Any, port);
server.Server.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true);
server.Start();
NiproEventsLog.WriteLog("监听服务启动成功,等待设备连接...");

// 主循环持续接收连接
while (true)
{
    try
    {
        TcpClient client = await server.AcceptTcpClientAsync();
        // 接收到连接后交给独立任务处理,主循环立刻返回接收下一个连接
        _ = ProcessClientAsync(client);
    }
    catch (Exception ex)
    {
        NiproEventsLog.WriteLog($"接收连接出错:{ex.Message}");
        // 短暂等待避免异常循环占满CPU
        await Task.Delay(100);
    }
}

// 单个客户端连接处理逻辑
async Task ProcessClientAsync(TcpClient client)
{
    try
    {
        using (client)
        using (NetworkStream stream = client.GetStream())
        {
            // 设置读取超时,避免异常连接永久占用资源
            client.ReceiveTimeout = 10000;
            StringBuilder buffer = new StringBuilder();
            byte[] recvBuffer = new byte[1024];
            int recvLen;

            NiproEventsLog.WriteLog($"设备连接成功,客户端地址:{client.Client.RemoteEndPoint}");

            // 循环读取直到拿到完整XML或者连接断开
            while ((recvLen = await stream.ReadAsync(recvBuffer, 0, recvBuffer.Length)) > 0)
            {
                buffer.Append(Encoding.UTF8.GetString(recvBuffer, 0, recvLen));
                string currentContent = buffer.ToString();
                
                // 判断XML是否接收完整:匹配XML头和根节点闭合标签,请替换为实际业务的根节点名
                if (currentContent.TrimStart().StartsWith("<?xml") && currentContent.TrimEnd().EndsWith("</NiproMI>"))
                {
                    // 校验XML格式合法性
                    try
                    {
                        XDocument.Parse(currentContent);
                    }
                    catch
                    {
                        NiproEventsLog.WriteLog("接收到非法XML内容,跳过存储");
                        buffer.Clear();
                        continue;
                    }

                    // 生成合法文件名:时间精确到毫秒+GUID片段避免重名
                    string fileName = $"NiproMI{DateTime.Now.ToString("yyyyMMddHHmmssfff")}_{Guid.NewGuid().ToString("N").Substring(0,6)}.xml";
                    string savePath = Path.Combine(GetAppConfigValues.GetAppConfigValue("XmlFilePath"), fileName);
                    
                    // 自动创建不存在的存储目录
                    Directory.CreateDirectory(Path.GetDirectoryName(savePath));
                    await File.WriteAllTextAsync(savePath, currentContent);
                    NiproEventsLog.WriteLog($"文件存储成功:{fileName}");
                    
                    buffer.Clear();
                    // 若设备为单次连接推完即断,可直接break关闭连接
                    break;
                }
            }
        }
    }
    catch (Exception ex)
    {
        NiproEventsLog.WriteLog($"处理客户端连接出错:{ex.Message}");
    }
    finally
    {
        NiproEventsLog.WriteLog("设备连接断开");
    }
}

注意:代码里的XML根节点判断标记</NiproMI>需要替换为你实际业务中XML的真实根节点闭合标签,如果设备协议明确规定推完数据后会主动断开连接,也可以去掉节点判断逻辑,配合读取超时机制使用即可。


内容的提问来源于stack exchange,提问作者Ravi Kumar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 01:27:32