当 send() 在 TCP C 套接字中仅发送一次时,recv() 读取多次。我可以同步套接字通信吗?

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

我在 C 中实现了 TCP 客户端和服务器,其中客户端不断从文件发送数据,而服务器不断读取数据。我成功地将两者连接在一起并可以使用以下代码片段成功发送:

/* File: client.c */

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

其中 TCP_BUFFER_SIZE = 2 << 20 /* approx 2MB */send_socket_data 定义为:

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

(...并在服务器中执行以下操作)

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

我也在文件中检查读取错误或客户端断开连接等。

但是,我的问题是在程序运行期间,recv() 被多次调用,而只有一个 send() 被调用,在使用 clock 之后,我可以看到接收端比发送端运行得快得多。因此,如果我发送一个 322MB 的文件,它最终会在服务器端存储为一个 1GB 的文件。

我该如何解决这个问题?还是我的实现完全错误?

我看到有人在谈论在 TCP 之上实现一个应用协议,就像 HTTP 所做的那样。谁能给我开一条我必须走的路。谢谢。

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

这就是 TCP 的工作原理,因此您必须更改代码才能处理这种情况。不过它稍微复杂一些,例如,不能保证 send()fwrite() 会处理整个缓冲区。对于 fwrite() 虽然这将是一个错误条件,但套接字发送是正常的并且是预期的,您的代码必须处理它。

Or is my implementation completely wrong?

不完全,你只需要一点零钱:

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

请记住 TCP 套接字是面向流的 - 它保证您将收到数据而不会重复且按顺序,但它不保证您将收到与发送的相同的数据包。 TCP里面其实没有包,只有stream。

注意:你害怕代码可能有类似的问题:

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

可能不会读取整个缓冲区,例如,如果您到达文件末尾(除非您的文件总是精确地被 TCP_BUFFER_SIZE 分割而没有提醒)。所以你需要保持读取的大小并使用它来发送数据。

TCP 是一个字节流,它没有消息边界的概念,就像 UDP 一样。因此,在 TCP 中发送和读取之间没有 1:1 关系。发送一个数据块可能需要多个 send()s,读取同一个块数据可能需要多个 recv()s。您必须在正在传输的数据本身中处理此问题,方法是:

  • 在发送数据本身之前发送数据的长度,这样接收方就知道预先需要多少字节,一旦读取了那么多字节就可以停止读取。

  • 在数据后发送一个唯一的分隔符,以便接收方可以继续读取,直到读取到分隔符。

尝试更像这样的东西:

客户

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

服务器

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