Lee
Lee

Reputation: 3969

C# TCP file transfer - Images semi-transferred

I am developing a TCP file transfer client-server program. At the moment I am able to send text files and other file formats perfectly fine, such as .zip with all contents intact on the server end. However, when I transfer a .gif the end result is a gif with same size as the original but with only part of the image showing as if most of the bytes were lost or not written correctly on the server end.

The client sends a 1KB header packet with the name and size of the file to the server. The server then responds with OK if ready and then creates a fileBuffer as large as the file to be sent is.

Here is some code to demonstrate my problem:

// Serverside method snippet dealing with data being sent
while (true)
{
   // Spin the data in
   if (streams[0].DataAvailable)
   {
      streams[0].Read(fileBuffer, 0, fileBuffer.Length);
      break;
   }
}
// Finished receiving file, write from buffer to created file
FileStream fs = File.Open(LOCAL_FOLDER + fileName, FileMode.CreateNew, FileAccess.Write);
fs.Write(fileBuffer, 0, fileBuffer.Length);
fs.Close();
Print("File successfully received.");
// Clientside method snippet dealing with a file send
while(true)
{
   con.Read(ackBuffer, 0, ackBuffer.Length);
   // Wait for OK response to start sending
   if (Encoding.ASCII.GetString(ackBuffer) == "OK")
   {
      // Convert file to bytes
      FileStream fs = new FileStream(inPath, FileMode.Open, FileAccess.Read);
      fileBuffer = new byte[fs.Length];
      fs.Read(fileBuffer, 0, (int)fs.Length);
      fs.Close();
      con.Write(fileBuffer, 0, fileBuffer.Length);
      con.Flush();
      break;
   }
}

I've tried a binary writer instead of just using the filestream with the same result.

Am I incorrect in believing successful file transfer to be as simple as conversion to bytes, transportation and then conversion back to filename/type?

All help/advice much appreciated.

Upvotes: 2

Views: 4919

Answers (4)

Murhaf Sousli
Murhaf Sousli

Reputation: 13296

Its not about your image .. It's about your code.

  • if your image bytes were lost or not written correctly that's mean your file transfer code is wrong and even the .zip file or any other file would be received .. It's gonna be correpted.
  • It's a huge mistake to set the byte buffer length to the file size. imagine that you're going to send a large a file about 1GB .. then it's gonna take 1GB of RAM .. for an Idle transfering you should loop over the file to send.

    This's a way to send/receive files nicely with no size limitation.

    Send File

    using (FileStream fs = new FileStream(srcPath, FileMode.Open, FileAccess.Read))
    {
            long fileSize = fs.Length;
            long sum = 0;   //sum here is the total of sent bytes.
            int count = 0;
            data = new byte[1024];  //8Kb buffer .. you might use a smaller size also.
            while (sum < fileSize)
            {
                count = fs.Read(data, 0, data.Length);
                network.Write(data, 0, count);
                sum += count;
            }
            network.Flush();
    }
    

    Receive File

    long fileSize = // your file size that you are going to receive it.
    using (FileStream fs = new FileStream(destPath, FileMode.Create, FileAccess.Write))
    {
            int count = 0;
            long sum = 0;   //sum here is the total of received bytes.
            data = new byte[1024 * 8];  //8Kb buffer .. you might use a smaller size also.
            while (sum < fileSize)
            {
                if (network.DataAvailable)
                {
                    {
                        count = network.Read(data, 0, data.Length);
                        fs.Write(data, 0, count);
                        sum += count;
                    }
                }
            }
    }
    

    happy coding :)

    Upvotes: 3

  • Jim Mischel
    Jim Mischel

    Reputation: 134005

    As others have pointed out, the data doesn't necessarily all arrive at once, and your code is overwriting the beginning of the buffer each time through the loop. The more robust way to write your reading loop is to read as many bytes as are available and increment a counter to keep track of how many bytes have been read so far so that you know where to put them in the buffer. Something like this works well:

    int totalBytesRead = 0;
    int bytesRead;
    do
    {
        bytesRead = streams[0].Read(fileBuffer, totalBytesRead, fileBuffer.Length - totalBytesRead);
        totalBytesRead += bytesRead;
    } while (bytesRead != 0);
    

    Stream.Read will return 0 when there's no data left to read.

    Doing things this way will perform better than reading a byte at a time. It also gives you a way to ensure that you read the proper number of bytes. If totalBytesRead is not equal to the number of bytes you expected when the loop is finished, then something bad happened.

    Upvotes: 1

    Lee
    Lee

    Reputation: 3969

    Thanks for your input Tvanfosson. I tinkered around with my code and managed to get it working. The synchronicity between my client and server was off. I took your advice though and replaced read with reading a byte one at a time.

    Upvotes: 0

    Neil Vass
    Neil Vass

    Reputation: 5341

    When you write over TCP, the data can arrive in a number of packets. I think your early tests happened to fit into one packet, but this gif file is arriving in 2 or more. So when you call Read, you'll only get what's arrived so far - you'll need to check repeatedly until you've got as many bytes as the header told you to expect.

    I found Beej's guide to network programming a big help when doing some work with TCP.

    Upvotes: 1

    Related Questions