【问题标题】:TCP/IP Stream dumping text in arbitrary clumps (C Socket Programming)TCP/IP 流转储任意簇中的文本(C 套接字编程)
【发布时间】:2021-05-18 15:50:39
【问题描述】:

我正在编写一个聊天应用程序,包括服务器端和客户端。为长代码道歉。大概是我误解了 TCP/IP 的整体,但我不知道从哪里开始。问题是,当多个客户端连接到服务器时,如果向服务器发送了许多消息,则不会立即将消息传递给客户端。我可以收集到许多消息被附加到服务器端套接字连接上的缓冲区,并且在任意数量的消息之后,该缓冲区被转储到其中一个客户端。此转储在客户端按 Enter 后立即发生,因此我认为多线程中也存在问题。

我的想法:

  • 也许有一种替代方法可以在服务器端跟踪客户端连接,而不是使用文件。全局文件指针可能会更好!这看起来不错?
  • TCP/IP 是纯数据流而不是数据包传输(逐个消息),所以我不应该这样对待。
  • 学习 pthread,用途更广泛。

(另外,我在使用 GDB 附加功能进行调试时遇到了问题,因此我无法调试我的程序:/ 我正在运行 Ubuntu 20.04,我也将不胜感激)

客户:

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

int main(int argc, char *argv[]) {

    int cli_sock, port;
    struct sockaddr_in serv_addr;
    char buffer[1024];

    port = atoi(argv[1]);

    if((cli_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket failed.\n");
        exit(1);
    }

    printf("Client socket initialised.\n");

    memset(&serv_addr, '\0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(connect(cli_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        perror("Error in connection.\n");
        exit(1);
    }

    while(1) {
        printf("Client: ");
        fgets(buffer, 1024, stdin);
        send(cli_sock, buffer, strlen(buffer), 0);
        if(strcmp(buffer, "/leave") == 0) {
            close(cli_sock);
            printf("Disconnected from server.\n");
            exit(1);
        }

        if(recv(cli_sock, buffer, 1024, 0) < 0) {
            printf("Error in receiving data.\n");
        }
        else {
            if(sizeof(*buffer) == 1) { // prints data for all but sender.
                printf("%s", buffer);
            }
        }

    }

    return 0;
}

服务器:

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


int fileLineCount(FILE *);
void deleteSockFromFile(FILE *, int);

int main(int argc, char *argv[]) {

    int* senderID = calloc(1, sizeof(int));

    int serv_sock, port;
    struct sockaddr_in serv_addr;

    int new_sock;
    struct sockaddr_in new_addr;

    socklen_t addrlen;

    char buffer[1024];

    int roomSize;

    port = atoi(argv[1]);
    roomSize = atoi(argv[2]);

    if((serv_sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Server socket failed.\n");
        exit(1);
    }

    memset(&serv_addr, '\0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    if(bind(serv_sock, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) {
        printf("Bind unsuccessful.\n");
        exit(1);
    }

    if(listen(serv_sock, roomSize) != 0) {
        printf("Listening...\n");
    }
    else {
        perror("Listening failed.\n");
        exit(1);
    }

    memset(buffer, '\0', sizeof(buffer));

    FILE *clientsFile = NULL;
    if(!(clientsFile = fopen("clients.txt", "w+"))) { // file for tracking active client connections (socket fds)
        perror("Failed to open file.\n");
        exit(1);
    }

    while(1) {
        new_sock = accept(serv_sock, (struct sockaddr *) &new_addr, &addrlen);
        if(new_sock < 0) { //handling failed socket creation.
            perror("Socket failed.\n");
            exit(1);
        }

                printf("Connection accepted from %s:%d. Sock fd = %d\n", inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port), new_sock);

        printf("lineCount: %d\n", fileLineCount(clientsFile));

        fseek(clientsFile, 0, SEEK_END);
        fprintf(clientsFile, "%d\n", new_sock);

        printf("lineCount: %d\n", fileLineCount(clientsFile));

        if(fork() == 0) {
            close(serv_sock);

            while(1) {
                memset(buffer, '\0', sizeof(buffer));
                recv(new_sock, buffer, 1024, 0);
                if(strcmp(buffer, "/leave") == 0) {
                    printf("Disconnected from %s:%d.\n", inet_ntoa(new_addr.sin_addr), ntohs(new_addr.sin_port));
                    deleteSockFromFile(clientsFile, new_sock);
                    break;
                }
                else {
                    printf("Client: %s", buffer);

                    int temp_sock;
                    size_t length = 10;
                    char *line = calloc(length, sizeof(char));

                    rewind(clientsFile);

                    while(getline(&line, &length, clientsFile) >= 0) { // reads file with client sock fd
                        line[strcspn(line, "\n")] = 0;
                        temp_sock = atoi(line); // set temp_sock to a client sock fd
                        if(temp_sock != new_sock) { // compares thread value of new_sock with all client socks, so the sender isn't sent the message, but everyone else is.
                            send(temp_sock, buffer, strlen(buffer), 0);
                            printf("SHOULD SEND TO %d\n", temp_sock); //debugging line #1/2
                        }
                        else {
                            send(temp_sock, senderID, sizeof(senderID), 0);
                            printf("Should not send, cause sender.\n"); //debugging line #2/2
                        }
                    }
                }
            }
            break;
        }

    }
    fclose(clientsFile);
    remove("clients.txt");

    close(new_sock);

}

int fileLineCount(FILE *ptr) {

    int ch, lineCount = 0;

    fseek(ptr, 0, SEEK_SET);

    do {
            ch = fgetc(ptr);

            if(ch == '\n')
                lineCount++;

    }

    while (ch != EOF);

    return lineCount;
}

void deleteSockFromFile(FILE *ptr, int sock2delete) {

        size_t length = 10;

    char *line = calloc(length, sizeof(char));
    char *tempLine = calloc(length, sizeof(char));

    FILE *tempPtr = NULL;
    if((tempPtr = fopen("temp.txt", "w")) == 0) {
        perror("Unable to open file.\n");
        exit(1);
    }

    rewind(ptr); // must reset file cursor before switching from writing to reading mode (w+)

    while(getline(&line, &length, ptr) >= 0) {
        strcpy(tempLine, line);
        tempLine[strcspn(tempLine, "\n")] = 0; //remove newline char
        if(atoi(tempLine) != sock2delete) {
            fputs(line, tempPtr);
        }
    }

    remove("clients.txt");
    rename("temp.txt", "clients.txt");
    fclose(tempPtr);

    free(line);
    free(tempLine);

}

这里是上下文的测试输出,测试并不顺利,但你可以看到只有一小部分消息通过(看@左上角终端,“c2”是从客户端 2 收到的)@987654321 @

(也许这个问题应该重命名为 GDB attach 导致笔记本电脑在使用 fork() 在多线程程序上使用 sudo 运行时冻结,感谢您的想法!)

【问题讨论】:

  • if(sizeof(*buffer) == 1) 始终为真。
  • 从服务器接收的缓冲区是char指针或int指针,“buffer”是一个不清楚的名字我很抱歉,但是当“buffer”是一个int指针时,大小是4,不是1.
  • buffer[] 是一个字符数组,所以buffer[0](等于*buffer)是一个字符,与已收到(未收到)的内容无关。
  • if(strcmp(buffer, "/leave") == 0) {永远为真(fgets() 保留\n)
  • 您还没有阅读服务器端代码。所有这些行都按预期工作。

标签: c multithreading sockets


【解决方案1】:

问题是,当多个客户端连接到服务器时,如果向服务器发送的消息很多,则消息不会即时传递给客户端。

您的客户端在 fgets() 上阻塞、等待来自标准输入的输入和在 recv() 上阻塞、等待来自服务器的输入之间交替进行,因此它们只有在两个方向之间严格交替时才能工作。在 TCP 套接字上一次调用 recv() 可以为您提供多次调用 send() 写入的数据,因为 TCP 确实是一个字节流,因此您可以一次获取多条消息(尽管您无法区分它们) .但是fgets() 一次只给你一行,如果客户端正在等待从服务器接收一些东西,它就不能同时从标准输入读取更多的输入。

也许有一种替代方法可以在服务器端跟踪客户端连接,而不是使用文件。

在文件中存储文件描述符编号似乎根本没有任何意义。 fd 数只是小整数,对于不同的进程,它们的含义是不同的。它们不是全球唯一标识符!

您正在为每个传入连接创建一个新进程。每个子进程都将获得一份服务器已打开的文件描述符的副本当时。因此,第一个孩子将获得第一个客户端连接的 fd,第二个孩子将获得前两个客户端连接的 fd,依此类推。但是没有一个孩子将获得稍后出现的客户端的 fd,所以你应该每次尝试从文件中写入 fd 时都会出现许多错误。

只有当客户端不需要直接交互时,才能为每个连接分叉一个子节点。此外,如果您的子进程突破了内部while(1),那么他们似乎会退出整个main 函数,并且第一个这样做的人将删除clients.txt 文件。更重要的是,您似乎没有注意recv() 调用的返回值,因此如果远程关闭连接,您将永远在recv() 上旋转,它返回0 标记“文件结束”。


相反,您需要将所有客户端 fd 保存在单个服务器进程中,然后该服务器进程可以读取和写入所有这些文件。将它们的数字存储在数组等中。在这里,您与客户端处于相同的情况,您需要能够同时监视多个 fd 的输入,而不是一次阻塞一个。为此,您需要使用select()(或poll(),或epoll())。客户端也是如此。在 GNU libc 文档中有一个使用 select() 的示例:https://www.gnu.org/software/libc/manual/html_node/Server-Example.html

TCP/IP 是纯数据流而不是包传输(message-by-message),所以我不应该这样对待。

是的,确实如此。如果遥控器发送两条线路,每条线路有一个write()/send() 呼叫,例如hello!\nhow are you?\n,则另一端原则上可以分三部分接收:hello!\nhow are you?\n .没有任何保证,但尤其是在负载轻到无负载且消息短的本地网络上,这样的分裂是不太可能的。但是操作系统会一次性给你完整的hello!\nhow are you?\n,如果它在进程开始阅读之前全部收到的话。应用程序的工作是找出何时收到完整的行,以及之后是否有任何内容。

【讨论】:

  • 这非常有用。谢谢先生。
猜你喜欢
  • 1970-01-01
  • 2013-05-27
  • 1970-01-01
  • 2012-10-07
  • 1970-01-01
  • 2014-08-03
  • 2017-08-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多