【问题标题】:XPS Files Received Through TcpClient are Corrupt通过 TcpClient 接收的 XPS 文件已损坏
【发布时间】:2014-12-11 07:57:17
【问题描述】:

我正在尝试在 C# 中创建一个“虚拟打印机”应用程序,它通过网络接收打印作业,解析原始打印数据以获取某些信息,然后将文档保存到数据库中。以下类的修改版本适用于 postscript 打印作业(它将传入数据保存到有效的 .prn 文件中,就像打印机设置为打印到“文件:”端口一样。)当我尝试捕获 .但是,来自 Microsoft XPS Document Writer 的 XPS 文档无法打开。如果扩展名被重命名,有效的 XPS 文件也应该是有效的 ZIP 文件,这也不起作用。当我将相同的文档打印到 FILE: 端口,然后打印到我的应用程序,并在 Notepad++ 中比较结果时,数据长度存在 5 个字符的差异,但看起来相同(它不是纯文本,所以它是很难看,但前几个字符和最后几个字符似乎相同)。以“正常”方式保存的文件工作正常,但我的代码生成的文件却不行。

更一般地说,我试图通过 TCP 端口接收任意数据并将其写入文件。我的解决方案是“关闭”但不起作用。我不知道 XPS 使用什么样的编码,但我在 postscript 中使用了 ASCII,并且我已经为这个 XPS 版本尝试了 ASCII 和 UTF8。

非常感谢任何帮助!这是我的代码的相关部分:

class XPSListener
    {
        private TcpListener tcpListener;
        private Thread listenThread;
        private string instanceName = "";
        private string fileShare = (Settings.Default.SharedPath.Substring(Settings.Default.SharedPath.Length - 1) == @"\") ? Settings.Default.SharedPath : Settings.Default.SharedPath + @"\"; // use SharedPath setting value - append backslash if it isn't already there.

        public XPSListener(string initInstanceName, Int32 initPort)
        {
            this.instanceName = initInstanceName;
            this.tcpListener = new TcpListener(IPAddress.Any, initPort);
            this.listenThread = new Thread(new ThreadStart(ListenForClients));
            this.listenThread.Start();
        }

        private void ListenForClients()
        {
            try
            {
                this.tcpListener.Start();
            }
            catch (Exception e)
            {
                MessageBox.Show("Socket Error 1 - " + e.StackTrace);
            }

            while (true)
            {
                //blocks until a client has connected to the server
                TcpClient client = this.tcpListener.AcceptTcpClient();

                //create a thread to handle communication with connected client
                Thread clientThread = new Thread(new ParameterizedThreadStart(AcceptXPSData));
                clientThread.Start(client);
            }
        }

        private void AcceptXPSData(object client)
        {
            TcpClient tcpClient = (TcpClient)client;
            NetworkStream clientStream = tcpClient.GetStream();
            string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";

            byte[] message = new byte[65536];
            int bytesRead;
            string input;

            while (true)
            {
                bytesRead = 0;

                try
                {
                    //blocks until a client sends a message
                    bytesRead = clientStream.Read(message, 0, 65536);
                    Debug.WriteLine("Bytes read: " + bytesRead.ToString());  
                }
                catch
                {
                    //a socket error has occured
                    break;
                }

                if (bytesRead == 0)
                {
                    //the client has disconnected from the server
                    break;
                }

                //message has successfully been received
                if (instanceName != "DontPrint")
                {
                    Debug.WriteLine(instanceName + " Receiving Data");
                    //ASCIIEncoding encoder = new ASCIIEncoding();
                    UTF8Encoding encoder = new UTF8Encoding();

                    using (FileStream fs = new FileStream(tempFilePath, FileMode.Append, FileAccess.Write))
                    {
                        using (StreamWriter sw = new StreamWriter(fs))
                        {
                            input = encoder.GetString(message, 0, bytesRead);
                            sw.Write(input);
                            // first capture this input and write it to an xps file.  This file can be converted to PDF at a later time by Ghostscript
                            // but we will still have access to the temp file for parsing purposes.
                        }
                    }
                }
                
               
            }

            tcpClient.Close();

            // processXPS();
        }

【问题讨论】:

  • @PeterDuniho - 我正在使用 Microsoft XPS Document Writer 打印驱动程序,只是尝试使用不同的端口将结果发送到我的应用程序,而不是提示用户保存到硬盘驱动器。这样做的原因是自动化文件的命名和存储方式。如果每次用户命名错误时文件都会丢失,这将是一场噩梦。
  • 嗯,您可以使用 Visual Studio 中的二进制编辑器来查看文件之间的 精确 区别是什么。我仍然不明白TcpClient 是如何参与其中的,但它不会破坏你的数据,我可以告诉你这么多。您没有正确使用它——没有理由相信在一次调用Read() 时一定会收到整个文件的数据价值。重新编码:如果您只是将数据写入文件,则根本不要解释它......只需将字节直接写入文件。 XML(以及 XPS)使用 UTF8,但这似乎并不重要。
  • 我不是在责怪 TCPClient,我只是有一个错误。 Read() 处于循环中 - 它填充缓冲区,然后附加到文件,直到没有更多数据进入。代码读取的调试结果“读取字节:65536 字节读取:65536 字节读取:45888 字节读取:0 " - 缓冲区填充两次,然后读取剩余的 45888 字节。总共 172.8 kb,这是我“以正常方式打印到文件”时得到的文件的确切大小,但有趣的是,我的程序生成的文件是 310kb,而不是调试显示的 173kb。二进制编辑器显示“相似的数据”,但我的数据中有很多额外的垃圾。

标签: c# printing tcpclient xps


【解决方案1】:

您的代码中至少有两个问题,其中一个几乎可以肯定是您编写的文件不正确的原因:

  1. 您不断重新打开正在写入的文件,而不是只打开一次。
  2. 您将收到的字节解释为文本,然后对其重新编码。

第一个问题更多的是效率/文件锁定问题,而不是正确性问题。但第二个是个大问题。

您似乎知道,XPS 文件基本上是一个 .zip 文件。这意味着虽然底层数据是 XML(即 UTF8),但文件本身是压缩的二进制文件。您不能以任何有意义的方式将其解释为文本。

您应该直接将读取的字节写入文件。更好的代码版本如下所示:

private void AcceptXPSData(object client)
{
    string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";

    using (TcpClient tcpClient = (TcpClient)client)
    using (NetworkStream clientStream = tcpClient.GetStream())
    using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
    {
        clientStream.CopyTo(fs);
    }

    // processXPS();
}

如果您确实想在 I/O 发生时对其进行监控,您可以显式处理它,但仍然比您的代码简单得多:

private void AcceptXPSData(object client)
{
    string tempFilePath = fileShare + "XPStemp_" + instanceName + ".oxps";

    using (TcpClient tcpClient = (TcpClient)client)
    using (NetworkStream clientStream = tcpClient.GetStream())
    using (FileStream fs = new FileStream(tempFilePath, FileMode.Create, FileAccess.Write))
    {
        byte[] message = new byte[65536];
        int bytesRead;

        while ((bytesRead = clientStream.Read(message, 0, message.Length)) > 0)
        {
            fs.Write(message, 0, bytesRead);

            // Add logging or whatever here
        }
    }

    // processXPS();
}

请注意,如果您想处理异常,您只需要处理那些您特别期望可能发生的情况,并且您有合理的方法来处理这些情况。在这样的代码中应避免使用裸露的catch 子句或宽泛的catch (Exception)

【讨论】:

  • 太好了,谢谢!第一个解决方案正是我所需要的,而且效果很好——一旦编写了临时文件,我就可以担心任何其他要求。感谢您的快速回复!
猜你喜欢
  • 2014-08-16
  • 1970-01-01
  • 1970-01-01
  • 2023-03-07
  • 2017-05-09
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多