【问题标题】:pthread_join fails in forking TCP server when handling multiple clientspthread_join 在处理多个客户端时无法分叉 TCP 服务器
【发布时间】:2020-05-30 05:30:25
【问题描述】:

我必须创建一个多线程 TCP/IP 服务器,其中包含一个变量来计算连接的客户端数量(以及断开连接的客户端数量),并在客户端连接到服务器时打印连接的客户端数量。

这是我的 client.c 文件:

#define PORT 4444

int main ()
{
    int clientSocket;
    struct sockaddr_in serverAddress;
    char buffer[1024];
    ssize_t nread;

    clientSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (clientSocket == -1) {
        perror("[-]Errore durante la creazione della socket\n");
        exit(-1);
    }
    printf("[+]Client socket has been created\n");


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

    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(PORT);
    serverAddress.sin_addr.s_addr = INADDR_ANY;

    if (connect(clientSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress)) == -1) {
        perror("Errore con la connessione\n");
        exit(-1);
    }

    while (1) {

        printf("> ");
        fflush(stdin);
        scanf("%s", buffer);

        if (nread != -1)
            buffer[nread] = '\0';*/

        if (send(clientSocket, buffer, strlen(buffer), 0) == -1) {
            perror("Errore con l'invio");
            exit(1);
        }

        if(strcmp(buffer, ":exit") == 0) {
            close(clientSocket);
            printf("[-]Disconnected from Server\n");
            exit(0);
        }

        if ( (nread=recv(clientSocket, buffer, sizeof buffer - 1, 0)) <= 0) {
            perror("[-]Error in receiving data from server\n");
        }
        else {
            buffer[nread] = '\0';
            printf("Server received: %s\n", buffer);
        }

    }

    close(clientSocket);

    return 0;
}

这是我的 server.c 文件:

#define PORT 4444
#define MAX_CONNESSIONI 100

typedef struct myStruct {
    int clientCollegati;
    int clientCheSiSonoScollegati;
    pthread_mutex_t mutex; // Creazione del mutex per sincronizzare la struttura
} myStruct;

myStruct *test; 

myStruct *initStruct();
void *incrementa(void*);

int main ()
{
    int serverSocket, bindStatus;
    struct sockaddr_in serverAddress;
    int clientSocket;
    struct sockaddr_in newAddress;
    char buffer[1024];
    pid_t child;
    socklen_t addrSize;
    ssize_t nread;
    pthread_t tid;

    test = initStruct(); 

    if (pthread_create(&tid, NULL, incrementa, NULL) != 0) {
        perror("Errore nella creazione del thread t1\n");
        exit(1);
    }
    serverSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (serverSocket == -1) {
        perror("[-]Errore durante la creazione della socket\n");
        exit(-1);
    }


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

    serverAddress.sin_family = AF_INET;
    serverAddress.sin_port = htons(PORT);
    serverAddress.sin_addr.s_addr = INADDR_ANY;
    bindStatus = bind(serverSocket, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
    if (bindStatus == -1) {
        perror("[-]Errore durante il binding\n");
        exit(1);
    }
    printf("[+]Bind to port %d\n", PORT);


    if (listen(serverSocket, MAX_CONNESSIONI) != -1) {
        printf("Listening . . .\n\n");
    }
    else {
        perror("[-]Error during listening\n");
        exit(1);
    }


    while (1) {

        clientSocket = accept(serverSocket, (struct sockaddr*)&newAddress, &addrSize);
        if (clientSocket == -1) {
            exit(-1);
        }
        printf("%s:%d joined\n", inet_ntoa(newAddress.sin_addr), ntohs(newAddress.sin_port));

        if (pthread_join(tid, NULL)) { // returns 3
            perror("pthread_join error\n");
            exit(1);
        }

        printf("There is/are %d client(s) connected\n", test->clientCollegati);

        child = fork();
        if (child == 0) {

            close(serverSocket);

            while (1) {

                if ( (nread=recv(clientSocket, buffer, sizeof buffer - 1, 0)) <= 0) {
                    perror("[-]Error in receiving data from server\n");
                }
                else {
                    buffer[nread] = '\0';
                }


                if (strcmp(buffer, ":exit") == 0) {
                    printf("%s:%d left\n", inet_ntoa(newAddress.sin_addr), ntohs(newAddress.sin_port));
                    break;
                }
                else {
                    printf("%s:%d wrote: %s\n", inet_ntoa(newAddress.sin_addr), ntohs(newAddress.sin_port), buffer);
                    send(clientSocket, buffer, strlen(buffer), 0);
                    bzero(buffer, sizeof(buffer));
                }

            }

        }

    }

    close(clientSocket);

    return 0;
}

myStruct *initStruct()
{
    struct myStruct *ptr = malloc(sizeof(myStruct));

    ptr->clientCollegati = 0;
    ptr->clientCheSiSonoScollegati = 0;

    pthread_mutex_init(&ptr->mutex, NULL); // inizializzazione dinamica del mutex

    return ptr;

}

void *incrementa(void *ptr)
{

    pthread_mutex_lock(&test->mutex); 


    test->clientCollegati++;


    pthread_mutex_unlock(&test->mutex);

    pthread_exit(0);

}

不幸的是,这似乎无法正常工作,您可以看到server output,这里是client1,这里是client2 output

  • 哪里出错了?我想问题是当 tid 完成并且第二次调用 pthread_join 时调用一个不存在的线程(因为只有一个线程)

【问题讨论】:

  • pthread_join 返回的错误码是什么?
  • 你知道线程做什么吗?
  • 返回 3...
  • 什么是strerror(3)?
  • 我已编辑您的问题标题以使其更清晰。

标签: c tcp client-server


【解决方案1】:

您的代码逻辑错误。你正在做一个pthread_create(),然后是多个pthread_join()。如果您想为每个连接的客户端增加值,您必须在每个成功的accept() 之后执行一个pthread_create(),然后是一个pthread_join()。注意:所有这些都必须发生在调用fork() 之前。

哪里出错了?我想问题是当tid 完成并且对pthread_join 的第二次调用被调用到一个不存在的线程(因为只有一个线程)

这正是发生的事情。在您当前的程序中,第一个 pthread_join() 成功,并且任何后续调用都失败并显示 errno 3(没有这样的进程),因为线程不再存在。所以你已经想通了。明知道不对,为什么要创建一个线程却多次加入?

您的程序的编写方式(增量后分叉)也意味着根本没有理由使用线程来执行增量,而且您甚至不需要互斥锁,因为只有一个线程可以访问任何给定时间的值。换句话说,您的服务器根本不是多线程的。如果您只想将其作为实验进行,那没关系,但这没有多大意义。

您可能想要做的(拥有一个多线程服务器)是每个客户端有一个线程,而不是每次都使用线程函数中的客户端代码(在这种情况下有一个互斥锁是有道理的)。请注意,尽管这不适用于大量客户端(通常,经典的fork() 方法更好,在这种情况下您真的不需要任何线程)。

【讨论】:

  • fork() 方法并不是最好的,因为创建进程(在 PID 意义上)对系统资源(尤其是内存)的要求更高,而不会忘记从父进程复制数据的成本, ... 多线程方法仍然更轻,因此更高效。选择基于分叉的方法应该通过在多线程模式下无法满足的约束来证明
  • @Landstalker 在现实中几乎不使用多线程方法,因为不提供与服务器进程的隔离,因为线程的调度方式与进程不同,最重要的是因为系统限制,例如打开文件的数量每个进程的描述符很容易被超过。 fork()s 紧跟在accept() 之后并执行不同(更轻)客户端进程的分叉服务器是实现多客户端 TCP 服务器的事实上的标准方式。这就是我想要表达的观点。当然,多线程可能仍然有用,具体取决于场景。
  • 如果您正在寻找一个简单的解决方案,fork 是最好的方法,因为默认情况下内存空间是分开的(不同的堆,不同的堆栈,...)但是,如果您正在寻找性能,系统资源的优化,...多线程更适合(但管理更复杂)。你只需要比较一个FORK的时间与create_thread相比,加上独占资源的数量......使用fork必须根据需要合理。在选择分叉之前,你必须问自己一个问题,多线程允许我这样做吗?
猜你喜欢
  • 1970-01-01
  • 2019-07-25
  • 1970-01-01
  • 1970-01-01
  • 2021-04-09
  • 1970-01-01
  • 1970-01-01
  • 2017-02-24
  • 2018-04-28
相关资源
最近更新 更多