2

I'm having two problems and after trying a few techniques I've read on stackoverflow, the problem persists. I'm trying to send a file from the server to client with the following code below but the problem is that the file is always a few bytes short, causing file corruption.. The second problem is that the stream doesn't close despite implementing a zero length packet at the end to indicate the transfer is finished without closing the connection.

Server code snippet:

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

Client code snippet:

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();
Euthyphro
  • 700
  • 1
  • 9
  • 26
  • Where did you get from that sending zero bytes should magically close the stream? – 500 - Internal Server Error May 04 '14 at 22:10
  • I didn't think it would "magically close the stream" but I'm not entirely sure how to check for zero length stream after. I thought maybe client.ReceivedBufferSize but that is wrong obviously. This answer mentions it here http://stackoverflow.com/a/13524750/1364673 I'm really new to file transfer, I spent the last 8 hours working with it trying to build a basic client/server app to transfer small and large files. – Euthyphro May 04 '14 at 22:12
  • Remember that with TCP, just because you call `netStream.Write()` doesn't mean that what you've submitted is either (a) going to be sent immediately, or (b) is going to be sent as a single packet. The underlying socket provider determines how to divide everything up into packets and when/how to send them. Specifically, it can bundle together multiple writes into a single packet, to decrease "chattiness". I'm not sure how TCP is supposed to handle a 0-byte write, but one very reasonable option is to ignore it completely. – Ken Smith May 04 '14 at 22:17
  • I think what Jon Skeet is referring to there is writing out the size of the block before the block and the a size of zero at the end. – 500 - Internal Server Error May 04 '14 at 22:17
  • Regardless of the whole sending a zero length byte array to signal end of transfer, why then is this stream coming up short at the end in byte size? I mean it will transfer 99% of the file but it is always just short of the file being transferred completely which prevents it from opening on the other end. For example, I send an MP3 from the server to the client but the file is not fully complete and unplayable. – Euthyphro May 05 '14 at 00:21

2 Answers2

2

With UDP you can transmit an effectively empty packet, but TCP won't allow you to do that. At the application layer the TCP protocol is a stream of bytes, with all of the packet-level stuff abstracted away. Sending zero bytes will not result in anything happening at the stream level on the client side.

Signalling the end of a file transfer can be as simple as having the server close the connection after sending the last block of data. The client will receive the final data packet then note that the socket has been closed, which indicates that the data has been completely delivered. The flaw in this method is that the TCP connection can be closed for other reasons, leaving a client in a state where it believes that it has all the data even though the connection was dropped for another reason.

So even if you are going to use the 'close on complete' method to signal end of transfer, you need to have a mechanism that allows the client to identify that the file is actually complete.

The most common form of this is to send a header block at the start of the transfer that tells you something about the data being transferred. This might be as simple as a 4-byte length value, or it could be a variable-length descriptor structure that includes various metadata about the file such as its length, name, create/modify times and a checksum or hash that you can use to verify the received content. The client reads the header first, then processes the rest of the data in the stream as content.

Let's take the simplest case, sending a 4-byte length indicator at the start of the stream.

Server Code:

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

Client Code:

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;
}

Not much in the way of error checking and so on, and blocking code in all directions, but you get the idea.

Corey
  • 15,524
  • 2
  • 35
  • 68
1

There is no such thing as sending "zero bytes" in a stream. As soon as the stream sees you're trying to send zero bytes it can just return immediately and will have done exactly what you asked.

Since you're using TCP, it is up to you to use an agreed-upon protocol between the client and server. For example:

  • The server could close the connection after sending all its data. The client would see this as a "Read" that completes with zero bytes returned.

  • The server could send a header of a fixed size (maybe 4 bytes) that includes the length of the upcoming data. The client could then read those 4 bytes and would then know how many more bytes to wait for.

Finally, you might need a "netStream.Flush()" in your server code above (if you intended to keep the connection open).

Hawkmooon
  • 508
  • 3
  • 9
  • How would one send a 4 byte header with the file size? I'd like to keep the connection open after. – Euthyphro May 04 '14 at 22:23
  • It looks like @Corey included a full example for you. It's pretty much exactly what I was thinking so I won't repeat it here. :-) – Hawkmooon May 05 '14 at 01:47