简体   繁体   中英

recv() reading several times when send() sends only once in TCP C sockets. Can I synchronize the socket communication?

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.

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.

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM