【发布时间】:2021-01-16 06:19:14
【问题描述】:
最终编辑:我选择的答案说明了问题的解决方案。代表性示例代码见diffhere
编辑:帖子底部的完整可编译代码。
我有这个简单的多线程服务器,它只接受一个连接,并且应该将文件描述符传递给一个线程,以允许该线程直接处理它,直到客户端断开连接。
出于某种原因,即使在服务器内部使用以下代码流,一些客户端也会“掉进裂缝”并陷入困境。 (他们永远不会被服务器处理,所以他们只是在接受连接后挂起)
以下块是我的服务器主运行循环:
while(g_serv.b_running)
{
//printf("Awaiting connection.\n");
client_fd = accept(g_serv.serv_listener_fd,
(struct sockaddr*)&cli_addr,
&clilen);
if (0 > client_fd)
{
fprintf(stderr,
"Error accepting connection. [%s]\n",
strerror(errno));
continue;
}
err = sem_trywait(&(g_serv.client_count_sem));
if (0 > err)
{
fprintf(stderr,
"Max connections reached. [%s]\n",
strerror(errno));
notify_client_max_connections(client_fd);
close(client_fd);
client_fd = 0;
continue;
}
printf("A client has connected.\n");
char byte[2] = "0";
err = send(client_fd, byte, 1, 0);
// Set up client FD in global position and wake up a thread to grab it
//
pthread_mutex_lock(&(g_serv.new_connection_fd_lock));
g_serv.new_connection_fd = client_fd;
if (0 != g_serv.new_connection_fd)
{
pthread_cond_signal(&(g_serv.new_connection));
}
pthread_mutex_unlock(&(g_serv.new_connection_fd_lock));
}
这个块是线程处理函数:
void* thread_handler(void* args)
{
serv_t* p_serv = (serv_t*)args;
bool thread_client_connected;
int thread_client_fd;
while(p_serv->b_running)
{
pthread_mutex_lock(&(p_serv->new_connection_fd_lock));
while (0 == p_serv->new_connection_fd && p_serv->b_running)
{
pthread_cond_wait(&(p_serv->new_connection),
&(p_serv->new_connection_fd_lock));
}
thread_client_fd = p_serv->new_connection_fd;
p_serv->new_connection_fd = 0;
pthread_mutex_unlock(&(p_serv->new_connection_fd_lock));
// In the case of a pthread cond broadcast for exiting the server.
//
if (0 == thread_client_fd)
{
continue;
}
thread_client_connected = true;
while (thread_client_connected)
{
thread_client_connected = handle_client(thread_client_fd);
}
close(thread_client_fd);
thread_client_fd = 0;
sem_post(&(p_serv->client_count_sem));
}
return NULL;
} /* thread_handler */
我的 serv_t 结构仅供参考:
typedef struct serv_t {
bool b_running;
int max_connections;
int serv_listener_fd;
sem_t client_count_sem;
pthread_mutex_t new_connection_fd_lock;
pthread_cond_t new_connection;
int new_connection_fd;
pthread_t* p_thread_ids;
} serv_t;
基本上,如果我运行 netcat 或客户端程序,我通过 bash 命令使用多个实例来“后台”应用程序,其中一些实例会卡住。我让它将输出重定向到一个文件,但发生的事情是客户端/netcat 的特定实例在接受调用后卡住了。
更具体地说,如果我用两个线程运行我的程序,一个程序的一个实例会卡住,而后续副本不会卡住,即使在服务器上运行 6500 个实例。
如果我用 10 个线程运行它,多达 8 或 9 个实例会卡住,但线程在服务器中仍然正常运行。
编辑:
我指的客户端代码,从服务端开始,让客户端知道服务端已经准备好接收数据了:
char buff[2] = { 0 };
err = recv(client_socket_fd, buff, 1, 0);
if ('0' != buff[0] && 1 != err)
{
fprintf(stderr,
"Server handshake error. [%s]\n",
strerror(errno));
close(client_socket_fd);
return EXIT_FAILURE;
}
if (NULL != p_infix_string)
{
if (MAX_BUFFER_SIZE < strlen(p_infix_string))
{
fprintf(stderr,
"Infix string is over 100 characters long.\n");
return EXIT_FAILURE;
}
errno = 0;
char* p_postfix = infix_to_postfix(p_infix_string);
if (EINVAL == errno || NULL == p_postfix)
{
fprintf(stderr, "Error converting provided string.\n");
}
bool success = send_postfix(p_postfix, client_socket_fd);
free(p_postfix);
if (false == success)
{
fprintf(stderr,
"An error occured while sending the equation to the server.\n");
close(client_socket_fd);
return EXIT_FAILURE;
}
}
客户端卡在此处的接收呼叫中:
bool send_postfix(char* p_postfix, int client_socket_fd)
{
if (NULL == p_postfix)
{
fprintf(stderr, "No postfix string provided to send to server.\n");
return false;
}
printf("Sending postfix to server\n");
int err = send(client_socket_fd,
p_postfix,
strnlen(p_postfix, MAX_BUFFER_SIZE),
0);
if(strnlen(p_postfix, MAX_BUFFER_SIZE) > err)
{
fprintf(stderr,
"Unable to send message to server. [%s]\n",
strerror(errno));
return false;
}
char response[MAX_BUFFER_SIZE] = { 0 };
printf("Waiting for receive\n");
err = recv(client_socket_fd, &response, MAX_BUFFER_SIZE, 0);
if (0 == err)
{
fprintf(stderr,
"Connection to server lost. [%s]\n",
strerror(errno));
return false;
}
else if (0 > err)
{
fprintf(stderr,
"Unable to receive message on socket. [%s]\n",
strerror(errno));
return false;
}
printf("Server responded with: \n%s\n", response);
return true;
} /* send_postfix */
编辑:https://github.com/TheStaplergun/Problem-Code
我将代码上传到这个 repo 并删除了对我使用的无关文件的需求,并用占位符填充它们。
您可以在服务器上使用命令./postfix_server -p 8888 -n 2 重新创建此问题,并在另一个终端中使用for i in {1..4}; do ./postfix_client -i 127.0.0.1 -p 8888 -e "3 + $i" &> $i.txt & done 重新创建客户端问题
由于客户端顶部的setbuf,每个客户端的输出都会被强制刷新。运行它,看看是否有任何程序挂起,如果没有再次运行该命令。只需键入 PS 并查看其中一个是否挂起,然后查看生成的文本文件。你会看到它卡在接听电话上。
如果您对服务器 (CTRL + C) 进行签名,则被卡住的客户端将关闭并收到来自服务器的 Connection reset by peer 响应,因此服务器仍然将该文件描述符锁定在某个地方。
我相信某种竞争条件正在以某种方式发生,因为它只是随机发生的。
奇怪的是,每个服务器实例只发生一次。
如果我杀死那个挂起的实例并继续执行 10000 次,它永远不会再次挂起,直到服务器重置。
【问题讨论】:
-
这可能是真的。如果
p_serv->b_running为假,while 循环将退出,线程将处理。我的关闭函数中有一个pthread_cond_broadcast来唤醒所有等待的线程。 -
是的。我拥有的客户端应该连接,然后等待
"0",这意味着服务器实际上已经接受了连接,而不是连接卡在侦听器的积压中。客户端成功地处理了它,并到达了客户端将数据发送到服务器的地步。服务器应该用一条消息来回报这一点,以返回“你提供了错误的数据”或“这是你的答案”。客户端卡住了,我认为这与主线程和工作线程之间的竞争条件有关。 -
如果我不清楚,我很抱歉。我正在试图弄清楚如何传达这一点。如果您需要更多信息,我可以发布它。如您所见,我对在 SO 上发帖还比较陌生。
-
请创建一个minimal reproducible example,即允许我们编译和运行它以重现您的问题的最小程序。显示带有示例输入以及预期和实际输出的 netcat 命令。你调试过你的程序吗?在我看来,您似乎有一个用于将文件描述符传递给线程的单一数据结构。是否保证工作线程在主线程填充新FD之前拾取FD?请不要使用 cmets 来回答澄清请求。请edit您的问题并在那里添加所有信息。
-
运行带有命令的代码以重新创建添加到帖子的问题。