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

C# TcpListener接收XML流偶发拆分存储为多文件问题求助

问题根因

你遇到的XML拆分存储问题是两个核心逻辑缺陷共同导致的:

  • 文件名生成时机错误:当前每次执行写入操作时都会重新读取DateTime.Now生成文件名,而TCP是流传输协议,你设置的接收缓冲区仅256字节,单份4KB的XML需要十几次Read操作才能读完。只要相邻两次读取操作刚好跨过时间格式的最小精度边界(默认DateTime.Now.ToString()精度到秒),两次写入拿到的文件名就会不同,直接生成第二个文件拆分数据。
  • 未做应用层报文完整性校验:TCP不保证单次Read返回完整的业务报文,你当前逻辑没有判断XML是否接收完成,只要接收过程触发文件名变更就会直接拆分存储,没有任何报文聚合逻辑。

除此之外代码还存在几个隐性问题:

  • 接收数据时用ASCII解码、写文件时用UTF8编码,非ASCII字符会乱码
  • TcpClient、NetworkStream、文件流均未手动释放,长期运行会出现句柄泄漏
  • 空catch块直接吞掉Socket异常,网络故障时无日志可排查
  • 先判断文件存在再打开的逻辑存在竞态条件,极端场景会触发文件占用错误
  • 用WriteLine写入会在每个接收分片后追加换行符,可能破坏XML结构
修复方案

调整逻辑:新连接建立后累计接收字节流,检测到XML起始标记时生成唯一文件名持续写入,直到检测到对应XML的完整闭合标记后,结束当前文件写入,等待下一份XML。所有流资源用语法糖自动释放,统一编码格式,补全异常日志。
修复后可直接运行的代码如下:

public static void GetXMLStream()
{
    TcpListener server = null;
    try
    {
        Int32 port = Int32.Parse(GetAppConfigValues.GetAppConfigValue("Port"));
        IPAddress localAddr = IPAddress.Parse(GetAppConfigValues.GetAppConfigValue("IPAddress"));
        server = new TcpListener(localAddr, port);
        server.Start();
        // 调大缓冲区到8KB适配单份XML大小,可根据实际报文大小调整
        Byte[] bytes = new Byte[8192];
        while (true)
        {
            // 自动释放客户端连接和流资源,避免句柄泄漏
            using TcpClient client = server.AcceptTcpClient();
            using NetworkStream stream = client.GetStream();
            StringBuilder xmlBuffer = new StringBuilder();
            string currentFileName = null;
            FileStream currentFs = null;
            StreamWriter currentSw = null;
            int i;
            // 逐块读取流数据
            while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
            {
                // 统一用UTF8编解码,和写文件编码保持一致
                string segment = System.Text.Encoding.UTF8.GetString(bytes, 0, i);
                xmlBuffer.Append(segment);
                string currentContent = xmlBuffer.ToString();
                
                // 检测到新XML开头,仅生成一次文件名
                if (currentFileName == null && currentContent.TrimStart().StartsWith("<?xml"))
                {
                    // 文件名加毫秒级精度,避免同秒多文件重名
                    string fileName = "NiproMI" + DateTime.Now.ToString("yyyyMMddHHmmssfff") + ".xml";
                    string filePath = Path.Combine(System.Configuration.ConfigurationManager.AppSettings["XmlFilePath"].ToString(), fileName);
                    currentFs = new FileStream(filePath, FileMode.Create, FileAccess.Write);
                    currentSw = new StreamWriter(currentFs, System.Text.Encoding.UTF8);
                    currentFileName = fileName;
                }

                // 已有打开的文件就写入当前分片
                if (currentSw != null)
                {
                    currentSw.Write(segment);
                    currentSw.Flush();
                    
                    // 检测XML根节点闭合(请替换成你实际业务的XML根节点闭合标签,比如</NiproMI>)
                    if (currentContent.TrimEnd().EndsWith("</Root>"))
                    {
                        // 写完一份完整XML,释放流,重置状态等待下一份
                        currentSw.Close();
                        currentFs.Close();
                        currentSw = null;
                        currentFs = null;
                        currentFileName = null;
                        xmlBuffer.Clear();
                    }
                }
            }
            // 连接断开时兜底释放未关闭的文件流
            currentSw?.Close();
            currentFs?.Close();
        }
    }
    catch (SocketException e)
    {
        // 补全异常日志,禁止吞异常
        NiproEventsLog.WriteLog(e.ToString());
    }
    finally
    {
        server?.Stop();
    }
}

注意事项:

  • 代码中</Root>请替换为你实际业务XML的根节点闭合标签,比如你的XML根节点是<NiproData>就改成</NiproData>,这是判断单份XML是否接收完成的核心标记
  • 文件名仅在检测到新XML开头时生成一次,从根源上规避跨时间边界拆分文件的问题
  • 写入用Write替代原WriteLine,不会额外追加换行符破坏XML结构
  • 所有连接、流资源都确保释放,长期运行不会出现句柄泄漏
  • 异常全部写入日志,出现网络问题可直接追溯

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 08:57:20