Marcel Marcel - 14 days ago 9
C Question

Sending image over socket and saving it on the server

I'm facing currently some problems with my project and I hope that you are able to identify my problem as I'm not capable to see it by myself.

I'm trying to send a picture from a C# Client (Windows) to my C Server that is running on a linux system. I'm transmitting the image binary data via a TCP Socket and that works just fine, the problem is when I'm writing the received buffer on the linux system with fwrite, it seems that some information that is present in the buffer, is not written or written with a corrupted value to the file.

E.g. I'm trying to send this picture here:

enter image description here

And that one I get on the server:

enter image description here

The client:

public static void sendPicture(Image image)
{
Byte[] imageBytes;

using (MemoryStream s = new MemoryStream())
{
image.Save(s, ImageFormat.Jpeg);
imageBytes = s.ToArray();

s.Close();
}

if (imageBytes.Length <= 5242880)
{
try
{
NetworkStream stream = client.GetStream();

File.WriteAllBytes("before.jpg", imageBytes);

//Send image Size
Byte[] imgSize = BitConverter.GetBytes((UInt32)imageBytes.Length);

stream.Write(imgSize, 0, imgSize.Length);

//Get answer from server if filesize is ok
Byte[] data = new Byte[1];
// recv only looks if we have a partial read,
// works like stream.Read(data, 0, data.Length)
Int32 count = recv(stream, data, data.Length);

if (count != 1 || data[0] != 0x4)
return;

stream.Write(imageBytes, 0, imageBytes.Length);

...
}


The server:

...
// INFO_BUFFER_SIZE (1)
buffer = malloc (INFO_BUFFER_SIZE);

// does allow parital reads
if (read_from_client (connfd, buffer, INFO_BUFFER_SIZE) == NULL)
{
error_out (buffer,
"Error during receiv of client data @ read_from_client");
close (connfd);
continue;
}

// reconstruct the image_size
uint32_t image_size =
buffer[3] << (0x8 * 3) | buffer[2] << (0x8 *
2) | buffer[1] << 0x8 |
buffer[0];

fprintf(stderr, "img size: %u\n", image_size);

// Check if file size is ok
if (check_control (image_size, 1, response) > 0)
{
inform_and_close (connfd, response);
continue;
}

// Inform that the size is ok and we're ready to receive the image now
(void) send (connfd, response, CONT_BUFFER_SIZE, 0);


free (buffer);
buffer = malloc (image_size);


if (read_from_client (connfd, buffer, image_size) == NULL)
{
error_out (buffer,
"Error during receiv of client data @ read_from_client");
close (connfd);
continue;
}


FILE *f;

// Generate a GUID for the name
uuid_t guid;
uuid_generate_random (guid);

char filename[37];
uuid_unparse (guid, filename);

if ((f = fopen (filename, "wb")) == NULL)
{
inform_and_close (connfd, 0x0);
error_out (buffer,
"Error while trying to open a file to save the data");
continue;
}

if (fwrite (buffer, sizeof (buffer[0]), image_size, f) != image_size)
{
inform_and_close (connfd, 0x0);
error_out (buffer, "Error while writing to file");
continue;
}

char output[100];
(void) snprintf (output, sizeof (output), "mv %s uploads/%s.jpg",
filename, filename);
system (output);

free (buffer);
close (connfd);

...


If I receive the image and send it directly back to the client and the client then writes the received buffer to a file, everything is fine, there is not difference between the file that has been sent and the one it received. Because of that I’m quite sure that the transmission works as expected.

If you need anything more, let me know!

Answer

fopen creates a buffered I/O stream. But you aren't flushing the buffer and closing the file. Cutting out the error checking, you're doing:

f = fopen(filename, "wb");
fwrite(buffer, sizeof (buffer[0]), image_size, f);

You should add these at the end:

fflush(f);
fclose(f);

fclose will actually flush for you but it's best to do the flush separately so that you can check for errors prior to closing.

What's happening here (somewhat oversimplified) is:

  1. fopen creates the file on disk (by using open(2) [or creat(2)] system call) and also allocates an internal buffer
  2. fwrite repeatedly fills the internal buffer. Each time the buffer fills to some boundary (determined by BUFSIZ -- see setbuf(3) man page), fwrite flushes it to disk (i.e. it does a write(2) system call).

However, when you're finished, the end of the file is still sitting in the internal buffer -- because the library can't know that you're done writing to the file and you didn't happen to be on a BUFSIZ boundary. Either fflush or fclose will tell the library to flush out that last partial buffer, writing it to disk.. Then with fclose the underlying OS file descriptor will be closed (which you should always do anyway, or your server will have a "file descriptor leak").

Comments