此C多线程tcp服务器正确吗?

时间:2018-11-23 10:08:49

标签: c multithreading sockets semaphore tcpserver

我创建了一个多线程C TCP服务器。似乎可行(作为客户端,我输入一条消息,然后该消息发送到服务器,服务器打印出线程中客户端发送的内容(并发回客户端ID)。

我是否尊重C多线程TCP服务器的“最佳实践”? 也许我应该使用信号量来访问/使用client_counter变量?

#include <stdlib.h> 
#include <stdio.h> 
#include <string.h>

#include <unistd.h> // disable close() warning
#include <sys/socket.h> 
#include <sys/types.h> 
#include <netinet/in.h> 

#include <pthread.h>

#define MAX_CONNECTIONS 5

static int client_counter = 0;

void* serverWorker(void* context)
{
    char client_response[256];

    int sock = *(int*)context;
    char message[256]  = "\n Hello dear client, you are the client number \n";
    char numero[12];
    sprintf(numero, "%d", client_counter); // SHOULD I USE A SEMAPHORE HERE FOR client_counter ?

    while(1)
    {
        memset(client_response, 0, sizeof(client_response)); // clean string
        recv(sock, &client_response, sizeof(client_response), 0);
        printf("client number %s sent: '%s' \n", numero, client_response);
        if (send(sock, numero , strlen(numero) , 0) < 0)
        {
            printf("ERROR while sending response to client from worker \n");
        }
    }
    return NULL;
}



int main() 
{ 

    printf("Waiting for incoming connections ...\n");

    // socket creation 
    int server_socket;
    server_socket = socket(AF_INET, SOCK_STREAM, 0);

    // dserver address
    struct sockaddr_in server_address;
    server_address.sin_family = AF_INET;
    server_address.sin_port = htons(9002);
    server_address.sin_addr.s_addr = INADDR_ANY;

    // bind the socket to IP and port
    bind(server_socket, (struct sockaddr*) &server_address, sizeof(server_address));

    listen(server_socket, MAX_CONNECTIONS);

    int client_socket;
    while((client_socket = accept(server_socket, NULL ,NULL)))
    {
        client_counter++; 
        pthread_t thread_id;
        pthread_create(&thread_id, NULL, serverWorker, (void*)&client_socket);
        printf("new client ! \n");
    }

    close(server_socket);

    return 0; 
} 

2 个答案:

答案 0 :(得分:0)

您的代码中存在几个问题...您在传入连接上创建了一个线程,并将所有创建的线程的引用(相同的引用)传递给存储套接字描述符的变量。这将使所有线程共享同一个变量,以存储您将从通配符获得的所有套接字描述符。也许您认为好吧,我只是在线程启动时制作了一个副本,所以这不会发生,但是您认为两个几乎同时进入的连接,线程main()运行并处理这两个连接。然后第一个和第二个线程进行调度,并且两者都获得相同的描述符存储(第二个),并且第一个连接泄漏。

另一件事是,尽管此变量是main变量的局部变量,但一旦main()返回,它就将不复存在(如果线程要存活到{{1}之后,则不是程序的结尾) }的main()),但是由于您处于无休止的循环中(您可能不知道,但是return给出错误的唯一方法是销毁(server_socket ),或者将其连接的接口删除。)这可能会导致close()陷阱。

您可以自由地将强制转换为SIGSEGV的{​​{1}}值传递给线程,因为线程主体函数会在使用前将其转换回int,从而完全减少了无操作时间,因为指针类型的大小通常比(void *)大(或相等,但不小于)。无论如何,这严格是未定义的行为,但是可能会起作用(因为旧版软件中充斥着这种转换,因此所有编译器通常都会尝试遵守此行为)正确的方法是声明int信息将在启动时从线程传递并从线程返回。然后,您可以在其上存储任何内容,但是请注意,由于要动态添加线程,因此需要动态分配结构。

关于int变量的使用,唯一涉及该变量的线程是运行struct代码的线程。除了上面提到的风险之外,这没有什么大问题,快速的两次更新可以使两个线程在main进行两次更新后使两个线程在main中更新值。

另一个问题是,您需要将其声明为client_counter,因为线程代码将不会假定仅在访问之间对其进行更改,并且可能会将其缓存为寄存器变量。

main()与要获取的不同线程之间传递的消息可以通过两种方式实现。这是例程在输入时获得volatile并在返回时返回main()的原因:

  • 第一个使用动态void *本地数据(void *编辑,从struct传递到线程,并在终止时返回(当您将线程加入到main)。这样一来,您可以从main中的线程收集结果信息,然后必须malloc()在main中的结构。该结构用作线程和main例程之间双向的通信消息,并且您可以在其中存储需要传递或返回的任何信息。线程完成后,您可以main()将结构保留在main中(不要在线程中使用它,因为它必须生存下来)

  • 第二个不再涉及与free(3)的通信,一旦完成,线程必须取消分配该结构。这更简单,并且更适合您的示例。这样,您可以销毁线程或main中的结构,但前提是您已经加入了线程并确保该结构不会使用该结构。

答案 1 :(得分:0)

一个常见的错误是您不检查sendrecv调用的返回值。这些调用发送和接收的缓冲区可能少于整个缓冲区,因此必须处理此类情况以及断开连接。这也将消除对接收到的数据使用memsetstrlen的需要。

通常,将线程专用于每个客户端被认为是不可扩展的。您可能想读著名的The C10K problem,以很好地对待处理许多客户的I / O策略。这篇文章很旧,但是建议却是永恒的。