我创建了一个多线程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;
}
答案 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)
一个常见的错误是您不检查send
和recv
调用的返回值。这些调用发送和接收的缓冲区可能少于整个缓冲区,因此必须处理此类情况以及断开连接。这也将消除对接收到的数据使用memset
和strlen
的需要。
通常,将线程专用于每个客户端被认为是不可扩展的。您可能想读著名的The C10K problem,以很好地对待处理许多客户的I / O策略。这篇文章很旧,但是建议却是永恒的。