【问题标题】:C# TCP NetworkStream missing a few bytes of dataC# TCP NetworkStream 缺少几个字节的数据
【发布时间】:2014-06-21 03:22:24
【问题描述】:

我遇到了两个问题,在尝试了一些我在 stackoverflow 上阅读过的技术后,问题仍然存在。我正在尝试使用下面的代码将文件从服务器发送到客户端,但问题是文件总是几个字节短,导致文件损坏。第二个问题是尽管实现了流并没有关闭最后一个零长度的数据包,表示传输完成而没有关闭连接。

服务器代码sn-p:

 /*
 * Received request from client for file, sending file to client.
 */

  //open file to send to client
  FileStream fs = new FileStream(fileLocation, FileMode.Open, FileAccess.Read);
  byte[] data = new byte[1024];
  long fileSize = fs.Length;
  long sent = 0;
  int count = 0;
  while (sent < fileSize)
  {
        count = fs.Read(data, 0, data.Length);
        netStream.Write(data, 0, count);
        sent  += count;
  }
  netStream.Write(new byte[1024], 0, 0); //send zero length byte stream to indicate end of file.
  fs.Flush();
  netStream.Flush();

客户端代码sn-p:

TcpClient client;
NetworkStream serverStream;


/*
*   [...] client connect
*/

//send request to server for file
byte[] dataToSend = SerializeObject(obj);
serverStream.Write(dataToSend, 0, dataToSend.Length);

//create filestream to save file
FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.Write);

//handle response from server
byte[] response = new byte[client.ReceiveBufferSize];
byte[] bufferSize = new byte[1024];
int bytesRead;
while ((bytesRead = serverStream.Read(bufferSize, 0, bufferSize.Length)) > 0 && client.ReceiveBufferSize > 0)
{
    Debug.WriteLine("Bytes read: " + bytesRead);
    fs.Write(response, 0, bytesRead);
}
fs.Close();

【问题讨论】:

  • 你从哪里得到发送零字节应该神奇地关闭流?
  • 我不认为它会“神奇地关闭流”,但我不完全确定之后如何检查零长度流。我想也许是 client.ReceivedBufferSize 但这显然是错误的。这个答案在这里提到了stackoverflow.com/a/13524750/1364673我对文件传输真的很陌生,我花了过去 8 个小时来尝试构建一个基本的客户端/服务器应用程序来传输大小文件。
  • 请记住,使用 TCP,仅仅因为您调用 netStream.Write() 并不意味着您提交的内容要么 (a) 立即发送,要么 (b) 将发送作为一个单独的数据包。底层套接字提供程序决定如何将所有内容分成数据包以及何时/如何发送它们。具体来说,它可以将多个写入捆绑到一个数据包中,以减少“喋喋不休”。我不确定 TCP 应该如何处理 0 字节写入,但一个非常合理的选择是完全忽略它。
  • 我认为 Jon Skeet 指的是在块之前写出块的大小和最后的大小为零。
  • 不管整个发送一个零长度字节数组来表示传输结束,为什么这个流在字节大小的最后会变短?我的意思是它会传输 99% 的文件,但它总是只是缺少完全传输的文件,这会阻止它在另一端打开。例如,我从服务器向客户端发送了一个 MP3,但文件不完整且无法播放。

标签: c# filestream tcpclient file-transfer networkstream


【解决方案1】:

使用 UDP,您可以传输一个有效的空数据包,但 TCP 不允许您这样做。在应用层,TCP 协议是一个字节流,所有的数据包级别的东西都被抽象掉了。发送零字节不会导致客户端的流级别发生任何事情。

发送文件传输结束的信号可以简单到让服务器在发送最后一个数据块后关闭连接。客户端将收到最终的数据包,然后注意到套接字已关闭,这表明数据已完全传递。这种方法的缺陷在于,TCP 连接可能会因其他原因而关闭,从而使客户端处于一种认为自己拥有所有数据的状态,即使连接因其他原因而断开也是如此。

因此,即使您打算使用“完成时关闭”方法来表示传输结束,您也需要有一种机制允许客户端识别文件实际上是完整的。

最常见的形式是在传输开始时发送一个标头块,告诉您有关正在传输的数据的一些信息。这可能与 4 字节长度值一样简单,也可能是一个可变长度的描述符结构,其中包含有关文件的各种元数据,例如文件的长度、名称、创建/修改时间以及您可以使用的校验和或哈希验证收到的内容。客户端先读取头部,然后将流中的其余数据作为内容处理。

让我们采用最简单的情况,在流的开头发送一个 4 字节长度指示符。

服务器代码:

public void SendStream(Socket client, Stream data)
{
    // Send length of stream as first 4 bytes
    byte[] lenBytes = BitConverter.GetBytes((int)data.Length);
    client.Send(lenBytes);

    // Send stream data
    byte[] buffer = new byte[1024];
    int rc;
    data.Position = 0;
    while ((rc = data.Read(buffer, 0, 1024)) > 0)
        client.Send(buffer, rc, SocketFlags.None);
}

客户代码:

public bool ReceiveStream(Socket server, Stream outdata)
{
    // Get length of data in stream from first 4 bytes 
    byte[] lenBytes = new byte[4];
    if (server.Receive(lenBytes) < 4)
        return false;
    long len = (long)BitConverter.ToInt32(lenBytes, 0);

    // Receive remainder of stream data
    byte[] buffer = new byte[1024];
    int rc;
    while ((rc = server.Receive(buffer)) > 0)
        outdata.Write(buffer, 0, rc);

    // Check that we received the expected amount of data
    return len == outdata.Position;
}

在错误检查等方面并没有太多的方式,并在各个方向阻塞代码,但你明白了。

【讨论】:

    【解决方案2】:

    没有在流中发送“零字节”这样的事情。一旦流看到您尝试发送零字节,它就可以立即返回,并且会按照您的要求完成。

    由于您使用的是 TCP,因此您可以在客户端和服务器之间使用商定的协议。例如:

    • 服务器可以在发送完所有数据后关闭连接。客户端会将此视为“读取”,完成后返回零字节。

    • 服务器可以发送一个固定大小(可能是 4 个字节)的标头,其中包括即将到来的数据的长度。然后客户端可以读取这 4 个字节,然后知道还要等待多少字节。

    最后,您可能需要在上面的服务器代码中使用“netStream.Flush()”(如果您打算保持连接打开)。

    【讨论】:

    • 如何发送带有文件大小的 4 字节标头?我想在之后保持连接打开。
    • 看起来@Corey 为您提供了一个完整的示例。这几乎正​​是我的想法,所以我不会在这里重复。 :-)
    猜你喜欢
    • 2012-01-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-12-22
    • 2018-06-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多