C#TCP NetworkStream缺少几个字节的数据

时间:2014-05-04 22:00:02

标签: c# filestream tcpclient file-transfer networkstream

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

服务器代码段:

 /*
 * 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();

客户端代码段:

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();

2 个答案:

答案 0 :(得分:2)

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

发送文件传输的结束信号可以像发送最后一个数据块后让服务器关闭连接一样简单。客户端将收到最终数据包,然后注意套接字已关闭,这表示数据已完全传送。此方法的缺陷是TCP连接可能由于其他原因而关闭,使客户端处于认为其拥有所有数据的状态,即使连接因其他原因而被丢弃。

因此,即使您要使用'close on complete'方法来指示传输结束,您也需要一种允许客户端识别该文件实际完成的机制。

最常见的形式是在传输开始时发送一个标题块,告诉您有关正在传输的数据的信息。这可能只是一个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;
}

在错误检查等方面并没有那么多,并且阻止所有方向的代码,但是你明白了。

答案 1 :(得分:1)

没有发送&#34;零字节&#34;在一个小溪里。一旦流看到您尝试发送零字节,它就可以立即返回并完全按照您的要求完成。

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

  • 服务器可以在发送所有数据后关闭连接。客户会将此视为&#34; Read&#34;完成时返回零字节。

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

最后,你可能需要一个&#34; netStream.Flush()&#34;在上面的服务器代码中(如果您打算保持连接打开)。