0

I have an implementation of a TCP client and server in C where the client continuously sends data from a file and the server is continuously reading the data. I successfully connect the two together and can send successfully using the following code snippets:

/* File: client.c */

while (fread(buffer, 1, TCP_BUFFER_SIZE, in_file) > 0)
{
    send_socket_data(socket_desc, buffer, TCP_BUFFER_SIZE);
}

where TCP_BUFFER_SIZE = 2 << 20 /* approx 2MB */ and send_socket_data is defined:

/* File: client.c */

void send_socket_data(int socket_desc, void *buffer, int buffer_size)
{
    /* Send data to server */
    if (send(socket_desc, buffer, buffer_size, 0) < 0)
    {
        fprintf(stderr, "Send failed\n");
        exit(EXIT_FAILURE);
    }
}

(... and in the server I do the following)

/* File: server.c */
while ((read_size = recv(new_socket, buffer, TCP_BUFFER_SIZE, 0)) > 0)
{
    /* Write to binary output file */
    fwrite(buffer, TCP_BUFFER_SIZE, 1, out_file);
}

I do checking of read error or client disconnection etc as well in the file.

However, my problem is during the duration of the program, the recv() is being called multiple times when only one send() has been called and after using clock, I could see that the receiving side runs much faster than the sending. Therefore, if I'm sending a file of 322MB it winds up being stored as a 1GB file on the server end.

How can I resolve this problem? Or is my implementation completely wrong?

I've seen people talking about implementing an application protocol on top of TCP kind of like what HTTP does etc. Can anyone please prescribe for me a path I must go down. Thanks.

hyde
  • 60,639
  • 21
  • 115
  • 176
Tommy Wolfheart
  • 440
  • 4
  • 16

2 Answers2

3

TCP is a byte stream, it has no concept of message boundaries, like UDP does. As such, there is no 1:1 relationship between sends and reads in TCP. It may take multiple send()s to send a block of data, and it may take multiple recv()s to read that same block data. You have to handle this in the data itself that is being transmitted, either by:

  • sending the data's length before sending the data itself, so that the receiver knows how many bytes to expect up front and can stop reading once that many bytes has been read.

  • sending a unique delimiter after the data, so that the receiver can keep reading until the delimiter is read.

Try something more like this instead:

Client

void send_socket_data(int socket_desc, void *buffer, int buffer_size)
{
    unsigned char *pbuf = (unsigned char*) buffer;
    int sent_size;

    while (buffer_size > 0)
    {
        if ((sent_size = send(socket_desc, pbuf, buffer_size, 0)) < 0)
        {
            perror("Send failed");
            exit(EXIT_FAILURE);
        }
        pbuf += sent_size;
        buffer_size -= sent_size;
    }
}

void send_file_data(int socket_desc, FILE* in_file)
{
    if (fseek(in_file, 0, SEEK_END) != 0)
    {
        perror("Seek failed");
        exit(EXIT_FAILURE);
    }

    long in_size = ftell(in_file);
    if (pos < 0)
    {
        perror("Tell failed");
        exit(EXIT_FAILURE);
    }

    rewind(in_file);

    // see: https://stackoverflow.com/questions/3022552/
    uint64_t tmp_size = htonll(in_size);
    send_socket_data(socket_desc, &tmp_size, sizeof(tmp_size));

    size_t read_size;
    while (in_size > 0)
    {
        if ((read_size = fread(buffer, 1, min(in_size, TCP_BUFFER_SIZE), in_file)) < 1)
        {
            perror("Read failed");
            exit(EXIT_FAILURE);
        }

        send_socket_data(socket_desc, buffer, read_size);
        in_size -= read_size;
    }
}

void send_file(int socket_desc, const char *filename)
{
    FILE *in_file = fopen(filename, "rb");
    if (!in_file)
    {
        perror("Open failed");
        exit(EXIT_FAILURE);
    }

    send_file_data(socket_desc, in_file);
    fclose(in_file);
}

Server

int read_socket_data(int socket_desc, void *buffer, int buffer_size)
{
    unsigned char *pbuf = (unsigned char*) buffer;
    int read_size;

    while (buffer_size > 0)
    {
        if ((read_size = recv(socket_desc, pbuf, buffer_size, 0)) <= 0)
        {
            if (read_size < 0)
                perror("Recv failed");
            else
                fprintf(stderr, "Client disconnected prematurely\n");
            return read_size;
        }
        pbuf += read_size;
        buffer_size -= read_size;
    }

    return 1;
}

int read_file_data(int socket_desc, FILE* out_file)
{
    uint64_t in_size;
    int read_size = read_socket_data(socket_desc, &in_size, sizeof(in_size));
    if (read_size < 1)
        return read_size;

    // see: https://stackoverflow.com/questions/809902/
    in_size = ntohll(in_size);

    while (in_size > 0)
    {
        if ((read_size = read_socket_data(socket_desc, buffer, min(in_size, TCP_BUFFER_SIZE))) < 1)
            return read_size;

        if (fwrite(buffer, read_size, 1, out_file) < 1)
        {
            perror("Write failed");
            return -1;
        }

        in_size -= read_size;
    }

    return 1;
}

int read_file(int socket_desc, const char *filename)
{
    FILE *out_file = fopen(filename, "wb");
    if (!on_file)
    {
        perror("Open failed");
        return -1;
    }

    int read_size = read_file_data(socket_desc, out_file);
    fclose(in_file);

    return read_size;
}
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • Thanks so much for the response! Will give this a try as soon as I can and report back when it works. – Tommy Wolfheart Jan 25 '22 at 18:49
  • So given that send() will not always send the exact amount of data that I want to send, how do I prevent recv() from reading too much data before send had a chance to send it all? I don't suppose there is a way that I can send over the variable ```sent_size``` along with the data. Or I need some way to send a flag that tells the server that "Sending is done. You may start ```recv()```" . – Tommy Wolfheart Jan 26 '22 at 08:37
  • Or will read_size be however many bytes are in the TCP buffer i.e. if send() sends only 1 byte and I try to read 100 bytes, the value of my read_size variable will be 1 (byte). Will test it out but would love a second opinion. – Tommy Wolfheart Jan 26 '22 at 09:13
  • @TommyWolheart as I said, there is no 1:1 between sends and reads in TCP. If you have X bytes to send, call `send()` as many times as it takes to send X bytes, and call `recv()` as many times as it takes to read X bytes. Period. If you ask `recv()` for X bytes and only Y bytes are available, where Y < X, then `recv()` will either wait (blocking mode), or will exit with an `EAGAIN`/`EWOULDBLOCK` error (non-blocking mode), depending on your socket's mode (the default is blocking). `recv()` doesn't know how many bytes `send()` is actually sending. That is up to your protocol to dictate... – Remy Lebeau Jan 26 '22 at 09:45
  • @TommyWolheart ... so, in my example, the protocol is that `send_file()` sends the file's size (as an 8-byte integer) before sending the file's data, then `read_file()` reads the (8-byte) size first and then reads as many bytes as the size says. – Remy Lebeau Jan 26 '22 at 09:49
  • I have now adapted your version of the sending and but on the receive side, I have used what Slava's answer recommends. I found ```recv``` consumes the values in the buffer therefore I might not need to add the ```read_size``` to the pbuf? Anyway, my application is working very well now. Thanks so much for the assistance. – Tommy Wolfheart Jan 26 '22 at 11:58
2

However, my problem is during the duration of the program, the recv() is being called multiple times when only one send() has been called

This is how TCP works, so you have to change code so it will handle this situation. It is slightly more complicated though, for example there is no guarantee that send() and fwrite() would handle the whole buffer. For fwrite() though that would be an error condition, for socket send is normal and expected and your code must handle that.

Or is my implementation completely wrong?

Not completely, you just need small change:

while ((read_size = recv(new_socket, buffer, TCP_BUFFER_SIZE, 0)) > 0)
{
    /* Write to binary output file */
    fwrite(buffer, read_size, 1, out_file);
}

Remember TCP socket is stream oriented - it guarantees that you will receive data without duplicate and in order, it does not guarantee that you will receive it the same packets as you send. There are actually no packets in TCP, just stream.

Note: you fread code may have similar issue:

while (fread(buffer, 1, TCP_BUFFER_SIZE, in_file) > 0)
{
    send_socket_data(socket_desc, buffer, TCP_BUFFER_SIZE);
}

may not read the whole buffer, for example if you reach the end of file (unless you file is always precisely size that devided by TCP_BUFFER_SIZE without reminder). So you need to keep size of read and use it to send data.

Slava
  • 43,454
  • 1
  • 47
  • 90
  • 1
    Apart from that: `send` is not guaranteed to send all given data either, so one need to actually check the return code of `send` to see how much data were actually send and then retry with the remaining data. – Steffen Ullrich Jan 25 '22 at 17:52
  • @SteffenUllrich good point, updated my answer, thanks. – Slava Jan 25 '22 at 17:57
  • Absolutely amazing. Tried this out and after days of banging my head against the wall, it worked! Really grateful. I'll implement the second part tomorrow @SteffenUllrich . Thanks so much. – Tommy Wolfheart Jan 25 '22 at 18:03
  • So given that send() will not always send the exact amount of data that I want to send, how do I prevent recv() from reading too much data before send had a chance to send it all? I don't suppose there is a way that I can send over the variable sent_size along with the data. Or I need some way to send a flag that tells the server that "Sending is done. You may start recv()" . @SteffenUllrich and Slava. – Tommy Wolfheart Jan 26 '22 at 08:38
  • Or will read_size be however many bytes are in the TCP buffer i.e. if send() sends only 1 byte and I try to read 100 bytes, the value of my read_size variable will be 1 (byte). Will test it out but would love a second opinion. – Tommy Wolfheart Jan 26 '22 at 09:13
  • @TommyWolheart: TCP has no concept of messages. The number of bytes `send` does not need to match a single `recv` call - its only a byte stream. If you need some message semantic you have to add in on top of the byte stream, like with a length prefix, with a end-of-message signal delimiter or similar. – Steffen Ullrich Jan 26 '22 at 11:32
  • "So given that send() will not always send the exact amount of data that I want to send, how do I prevent recv() from reading too much data before send had a chance to send it all?" You don't. `recv` will not read more data than available, so you call read in a loop until you get all data or stream is closed. It is possible that stream would be closed before all data was sent, you may need to handle that or you may not, that depends on your protocol. – Slava Jan 26 '22 at 16:56
  • Great. Thank you very much for the assistance. My application is working very well now. – Tommy Wolfheart Jan 27 '22 at 09:45