C TCP Socket 使用 select() 从同一客户端获取多个输入

C TCP Socket using select() to get multiple inputs from same client

我是套接字新手,我正在尝试向服务器发送消息,如果服务器在 5 秒内没有收到来自客户端的另一条消息,则向客户端发送警告,否则合并两条消息并发回给客户。

我正在使用 select,一旦 select() 被调用,服务器就无法接收第二条消息,它总是超时。

我做错了什么??

服务器

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

char *concat(const char *s1, const char *s2);

int main(int argc, char const *argv[]) {
    struct sockaddr_in server, client;
    struct timeval tv;
    int sock, readSize, fd = 0;
    char buf[BUF_SIZE], stringA[BUF_SIZE], stringB[BUF_SIZE];
    socklen_t addressSize;
    fd_set readfds;

    bzero(&server, sizeof(server));
    server.sin_family = PF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(6000);

    // TCP check
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        printf("socket: %s\n", strerror(errno));
        return 1;
    }

    // Handle binding error
    if (bind(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
        printf("bind: %s\n", strerror(errno));
        return 1;
    }

    // Handle connection error
    if (listen(sock, 5) == -1) {
        printf("listen: %s\n", strerror(errno));
        return 1;
    }

    // Handle client acceptance error
    addressSize = sizeof(client);
    sock = accept(sock, (struct sockaddr *)&client, &addressSize);

    while ((readSize = recv(sock, stringA, sizeof(stringA), 0))) {
        stringA[readSize] = '[=11=]';
        printf("Read Message A: %s", stringA);

        FD_ZERO(&readfds);
        FD_SET(sock, &readfds);
        FD_SET(0, &readfds);

        tv.tv_sec = 5;
        tv.tv_usec = 0;

        if (select(sock + 1, &readfds, NULL, NULL, &tv) < 0) {
            printf("ERROR in select");
        }

        char *result;

        // If more messgae receve within 5 seconds, but the program never reached this part
        if (FD_ISSET(sock, &readfds)) {
            // Get string B
            readSize = recv(sock, stringB, sizeof(stringB), 0);
            stringB[readSize] = '[=11=]';
            result = concat(stringA, stringB);
            printf("Some more input received\n");
        } else {
            printf("Time out\n");
        }

        send(sock, &result, sizeof(result), 0);
    }

    printf("Client has closed the connection.\n");
    close(sock);

    return 0;
}

char *concat(const char *s1, const char *s2) {
    char *result = malloc(strlen(s1) + strlen(s2) + 1); 
    strcpy(result, s1);
    strcat(result, s2);
    return result;
}

客户

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

int main(int argc, char const *argv[]) {
    struct sockaddr_in server;
    struct timeval tv;
    int sock, readSize, addressSize;
    char buf[BUF_SIZE];

    bzero(&server, sizeof(server));
    server.sin_family = PF_INET;
    server.sin_addr.s_addr = inet_addr("127.0.0.1");
    server.sin_port = htons(6000);
    sock = socket(PF_INET, SOCK_STREAM, 0);
    addressSize = sizeof(server);

    // TCP check
    sock = socket(PF_INET, SOCK_STREAM, 0);
    if (sock == -1) {
        printf("socket: %s\n", strerror(errno));
        return 1;
    }

    // Handle connection error
    if (connect(sock, (struct sockaddr *)&server, sizeof(server)) == -1) {
        printf("connect: %s\n", strerror(errno));
        return 1;
    }

    while (1) {
        fgets(buf, 256, stdin);
        if (feof(stdin)) break;
        send(sock, &buf, sizeof(buf), 0);

        readSize = recv(sock, buf, sizeof(buf), 0);
        buf[readSize] = '[=12=]';
        printf("%s\n", buf);
    }

    close(sock);
    return 0;
}

您询问的问题似乎是客户端在每次 send() 之后尝试从服务器接收响应。这将阻塞,直到它可以读取至少一个字节或服务器关闭连接。同时,服务端希望客户端一个接一个发送消息,奇数条消息不响应。

这是设计问题的症状。这个...

I am trying to send a message to the server, and if the server does not receive another message from client within 5 seconds, then send a warning to client, otherwise combine two messages and send back to client.

... 听上去很简单,似乎也很有道理,但实际上既不简单也不理智。假设您的服务有一个善意的客户。它知道该服务需要一条接一条的两条消息,它将以两条消息的串联响应。为什么这样的客户端无法发送预期的第二条消息?最可能的解释是

  • 它不能,因为它被挂起,因为网络 link 宕机,因为它的主机宕机,或类似的。
  • 它没有,因为它在它来之前就被杀死了。
  • 它没有,因为它是越野车。

None 由服务器发送警告消息拯救。 (但是如果客户端在传递第二条消息之前被杀死,那么服务器可能会在套接字上看到 EOF 并发出读取准备就绪的信号。)

此外,假设客户端挂起,服务器发出警告,然后客户端恢复。当它尝试从服务器读取预期的响应时,它会收到警告消息,或者很可能是警告与预期响应相关联。客户端应该如何区分警告消息和正常响应?

我建议完全放弃警告。服务器可以只断开无响应的客户端。

如果您想保留警告并继续只使用一个套接字,那么您需要扩充协议以使警告消息与正常响应可区分和分离。例如,服务器的响应可能分为两部分——标识消息类型的响应代码,后跟响应正文。

或者,您可以为两个不同的消息流使用单独的套接字,但我认为这会比您现在想要处理的更复杂。


不过,您的代码还有其他问题。主要有

  • 您似乎假设 send/writerecv/read 调用将配对,以便从一侧发送的数据一个电话接收到的信息正是另一方的一个电话接收到的信息。这根本不是一个安全的假设。您正在使用面向流的套接字,这种套接字的特点之一是数据流没有任何内置的消息边界。如果要将数据划分为逻辑上独立的消息,则需要将其分层在流之上。

  • 您没有考虑到 send/writerecv/read 可能(成功)传输的字节数比您少要求。一般来说,你需要注意这些函数的return值,并准备使用多次调用来传输给定传输的所有字节。