使用TcpClient的网络流写入大数据的最佳方法

3
我们需要上传大型固件文件到打印机来升级设备的固件。打印机设备与我的服务器在同一网络中,我们试图上传的固件大小约为 200-500 MB。我们选择的方法是将固件(.bin文件)加载到内存流中,并使用 TcpClient 按块通过网络进行写入。
根据网络流的响应,我们向客户端显示固件升级的状态。以下是我们用于固件升级的代码片段。我想知道是否这是最佳方法,因为错误的方法可能会损坏设备。 编辑:
class MyClass
{
    int port = 9100;
    string _deviceip;
    byte[] m_ReadBuffer = null;
    TcpClient _tcpclient;
    NetworkStream m_NetworkStream;
    static string CRLF = "\r\n";

    public event EventHandler<DeviceStatus> onReceiveUpdate;

    public async Task<bool> UploadFirmware(Stream _stream)
    {
        bool success = false;
        try
        {
            _tcpclient = new TcpClient();
            _tcpclient.Connect(_deviceip, port);

            _stream.Seek(0, SeekOrigin.Begin);
            m_NetworkStream = _tcpclient.GetStream();
            byte[] buffer = new byte[1024];
            m_ReadBuffer = new byte[1024];
            int readcount = 0;
            m_NetworkStream.BeginRead(m_ReadBuffer, 0, m_ReadBuffer.Length,
                                     new AsyncCallback(EndReceive), null);
            await Task.Run(() =>
            {
                while ((readcount = _stream.Read(buffer, 0, buffer.Length)) > 0)
                {
                    m_NetworkStream.Write(buffer, 0, readcount);
                    m_NetworkStream.Flush();
                }
            });
            success = true;
        }
        catch (Exception ex)
        {
            upgradeStatus = false;
        }
        return success;
    }

     private void EndReceive(IAsyncResult ar)
     {
         try
         {
             int nBytes;
             nBytes = m_NetworkStream.EndRead(ar);
             if (nBytes > 0)
             {
                 string res = Encoding.UTF8.GetString(m_ReadBuffer, 0, nBytes);
                 DeviceStatus status = new DeviceStatus();

                 string[] readlines = res.Split(new string[] { CRLF }, 
                              StringSplitOptions.RemoveEmptyEntries);
                 foreach (string readline in readlines)
                 {
                     if (readline.StartsWith("CODE"))
                     {
                         //read readline string here
                         break;
                     }
                 }
             }

            if (m_NetworkStream.CanRead)
            {
                do
                {
                    m_NetworkStream.BeginRead(m_ReadBuffer, 0, m_ReadBuffer.Length, new 
                                           AsyncCallback(EndReceive), null);
                } while (m_NetworkStream.DataAvailable);
            }
         }
         catch (ObjectDisposedException ods)
         {
             return;
         }
         catch (System.IO.IOException ex)
         {
         }
     }
}

任何帮助都将不胜感激。
2个回答

2

你的代码基本上还可以,但有几个问题:

  1. m_NetworkStream.Flush(); 据我所知,这个方法什么都没做。如果它真的做了什么,那么会损害吞吐量。因此请删除此语句。
  2. _stream.Seek(0, SeekOrigin.Begin); 定位的责任应该由调用方来负责,请删除此语句。这是一种层次结构侵犯。
  3. 使用更大的缓冲区。通过实验确定正确的大小。我通常从64KB开始进行大容量传输。这可以减少IO交互。
  4. 打开Nagle算法可帮助进行大容量传输,因为它可以避免产生杂散的小数据包。
  5. 你可以使用 Stream.Copy 代替整个读写循环。
  6. 你向调用方报告异常的方式隐藏了很多信息。只需让异常抛出即可,不要返回bool值。
  7. 使用 using 确保所有资源在错误情况下都被清理。
  8. nBytes = m_NetworkStream.EndRead(ar); 在这里,你假设单次读取将返回全部数据。但是你可能只收到第一个字节。最好使用StreamReader.ReadLine,并在循环中使用它,直到你确定完成为止。
  9. catch (System.IO.IOException ex) { } 这是怎么回事?如果固件更新是如此重要的事情,那么抑制错误似乎非常危险。否则,你怎么能发现错误?
  10. 我会将读取代码转换为使用await

1
非常感谢您提供这样详细的分析。然而,我对第八点的实现感到困惑。您是指我应该在我的EndReceive方法中使用循环吗?如果您能详细解释一下这一点,我将不胜感激。 - Saket Kumar
1
我认为更应该这样做:Task.Run(() => { while(true) var line = myStreamReader.ReadLine(); if (IsEnd(line)) break; else ProcessLine(line); });。这就是想法,请看看您是否可以为自己工作。您可以删除所有的Begin/End垃圾。APM模式已经过时且难以处理。 - usr
1
如果我理解你的意图正确,那么IsEnd可以是return line == null || line.StartsWith("CODE") - usr
谢谢你的建议。我会尝试将你提到的逻辑融入我的代码,并验证它是否真正有效。 - Saket Kumar

-2

由于TcpPacket的最大长度为65535(2^16-1),如果发送任何超过此长度的数据包,它将被截断。如果我是你,我认为发送大型数据包的最佳方法是设置每个数据包的标头并对其进行枚举。例如:
C->S; [P1] <content>
然后相同的结构,只需加1 [P2] <content>
为此,只需使用几个子字符串来截断数据并发送它们。

干杯!


我并不是一次性发送它。也许你没有注意到,我正在使用 while 循环以数据块的形式发送数据。 - Saket Kumar
TCP不暴露数据包语义。它是一连串的字节流,应用程序不能通过发送某些数据包来做错任何事情,因为应用程序根本没有发送数据包。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接