c - Linux TCP 套接字 : client has sent data but server still blocks on read()

标签 c linux sockets tcp

我有一个在 Linux 上使用 TCP 套接字的简单客户端-服务器示例。服务器监听环回地址。客户端连接到服务器并发送一些整数和一个“END”字符串来标记数据的结束。服务器读取数字,将它们全部相加并返回总和。但是,即使客户端已成功发送所有数据,我的服务器有时也会阻塞 read()

代码如下:

服务器.c:

#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define BACKLOG 5

int main(int argc, char *argv[]) {
    struct sockaddr_in addr;
    int down_flag = 0;
    int result = 0;
    int ret = 0;

    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd < 0) {
        perror("Create server socket error: %s\n");
        return 0;
    }

    /* Bind socket to loopback address */
    memset((void *) &addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    if (bind(sfd, (struct sockaddr *) &addr, sizeof(struct sockaddr_in)) == -1) {
        perror("Bind server socket failed");
        goto _exit;
    }

    if (listen(sfd, BACKLOG) == -1) {
        perror("Listen failed");
        goto _exit;
    }

    ssize_t num_rd = 0;
    char buf[100] = {0};
    for (;;)
    {
        printf("Waiting to accept a connection...\n");
        int cfd = accept(sfd, NULL, NULL);
        printf("Accepted socket fd = %d\n", cfd);
        result = 0;
        while ((num_rd = read(cfd, buf, sizeof(buf))) > 0) {
            /* Ensure the buffer is 0-terminated */
            buf[sizeof(buf) - 1] = 0;
            printf("Read data: %s\n", buf);

            /* Handle commands */
            if (!strncmp(buf, "DOWN", sizeof(buf))) {
                down_flag = 1;
                break;
            }
            if (!strncmp(buf, "END", sizeof(buf))) {
                break;
            }
            /* Add received summand */
            result += atoi(buf);
        }
        if (-1 == num_rd) {
            perror("Read error");
        }

        /* Send result */
        sprintf(buf, "%d", result);
        ret = write(cfd, buf, sizeof(buf));
        if (-1 == ret) {
            perror("Write error\n");
            goto _exit;
        }
        close(cfd);
        /* Quit on DOWN command */
        if (down_flag) {
            break;
        }
    }
_exit:
    close(sfd);
    return 0;
}

客户端.c:

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <unistd.h>
#include <netinet/in.h>
#include <arpa/inet.h>

int main(int argc, char *argv[]) {
    struct sockaddr_in addr;
    int ret;
    int data_socket;
    char buf[100] = {0};
    int i = 0;

    data_socket = socket(AF_INET, SOCK_STREAM, 0);
    if (-1 == data_socket) {
        perror("Create client socket error");
        exit(EXIT_FAILURE);
    }

    /* Connect to server socket */
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
    ret = connect(data_socket, (const struct sockaddr *) &addr, sizeof(addr));
    if (-1 == ret) {
        perror("Connect error");
        exit(EXIT_FAILURE);
    }

    /* Send arguments */
    for (i = 1; i < argc; i++) {
        ret = write(data_socket, argv[i], strlen(argv[i]) + 1);
        if (-1 == ret) {
            perror("Write error");
            break;
        }
    }
    strcpy(buf, "END");
    ret = write(data_socket, buf, strlen(buf) + 1);
    printf("write %s to socket, ret = %d\n", buf, ret);
    if (-1 == ret) {
        perror("Write to socket error");
        exit(EXIT_FAILURE);
    }
    /* Read the result */
    memset(buf, 0, sizeof(buf));
    ret = read(data_socket, buf, sizeof(buf));
    if (-1 == ret) {
        perror("Read from client socket error");
        exit(EXIT_FAILURE);
    }
    buf[sizeof(buf) - 1] = 0;
    printf("Result = %s\n", buf);
    close(data_socket);
    exit(EXIT_SUCCESS);
}

运行客户端几次,服务器将在某些时候阻塞 read() 调用:

$ for i in {1..100}; do ./client 3 4 5 6; done
write END to socket, ret = 4
Result = 18
write END to socket, ret = 4

服务器输出:

$ ./server
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3
Read data: 4
Read data: 5
Read data: 6
Read data: END
Waiting to accept a connection...
Accepted socket fd = 4
Read data: 3

服务器阻塞在 while ((num_rd = read(cfd, buf, sizeof(buf))) > 0) 行。

编辑:我的问题是为什么 read() 会阻塞。 AFAIK,read() 将阻塞,直到从套接字中读取至少 1 个字节的数据。在这种情况下,客户端发送的数据多于服务器读取的数据,因此我认为可以从套接字读取可用数据。那么为什么 read() 仍然阻塞?

最佳答案

问题的核心是代码测试缓冲区中的第一条消息并忽略同一缓冲区可能包含多条消息、部分消息或任何其他组合的可能性(参见编辑) .由于这个原因,消息 END 有时会被忽略,并且 read 循环永远不会终止。

该代码假定单个 read 将准确接收到单个 write 调用发送的内容。

这是非常不准确的,很少是真的,而且可能只有在客户端和服务器都在同一台机器上的时候才有效。

单个 read 可能同时读取 2 个 write 调用,或者它可能读取一半的 write 调用然后再读取 1.5 个 write 稍后调用...

TCP/IP(与 UDP 不同)是一个 streaming protocol并且不知道消息边界。


编辑:

为了澄清(如评论中所要求的),假设调用 read 收集以下数据 "1234\0EN"(下一个 read 将收集 "D\0")... 程序做什么?

另一种可能的情况是,所有的writes都被一次性读取了。即,buf 包含字符串 "3\04\05\06\0END\0"

此时循环内发生了什么?

在此示例场景中,if 语句 (strncmp(buf, "END", sizeof(buf)) 始终为 false(并且不安全),导致服务器永远不会从 while(read) 循环中中断。

由于 while 循环继续,服务器将在没有可用数据时尝试另一次读取,导致服务器阻塞,直到客户端发送额外的数据。

关于c - Linux TCP 套接字 : client has sent data but server still blocks on read(),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/64726403/

相关文章:

c - 字符串中的垃圾字符

c - PIC18F46J50 EEPROM 读/写

c - 当 MPI_Bcast 中的 root 是随机的时会发生什么

C 中嵌套 for 循环中的条件不起作用

linux - linux 下的 shebang 不拆分参数

php - 自动 uploader

java - 套接字编程中int数组的读写,java

javascript - 如何部署从命令行运行的 Javascript 应用程序?

javascript - 如何在 NodeJs 中断开与 tcp 套接字的连接

C 切割 char 数组并从 socket 中保存二进制数据