在使用pthread时无法捕获SIGINT信号

3
我已经制作了一个聊天服务器,使用多线程处理多个客户端。我有一个无限循环的while循环,等待新的客户端。我想在按下ctrl + c后退出它。因此,我正在尝试捕获SIGINT信号,如此处所述。但是我无法退出程序。我在Linux终端上工作。
server.c
//for running type ./a.out anyportnumber
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <pthread.h>
#include <signal.h>
int s2;
int arr[100];
int tc = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
volatile sig_atomic_t flag = 1;
void handler(int signal)
{
    flag = 0;
}
void sendtoall(char *msg,int s1)
{
    int i;
    pthread_mutex_lock(&mutex);
    for(i = 0; i < tc; i++) {
        if(arr[i] != s1) 
            write(arr[i],msg,strlen(msg));
    }
    pthread_mutex_unlock(&mutex);
}
void *function(void *s)
{
    int s1;
    int n;
    char rmsg[500];
    s1 = *(int *)s;
    while((n = read(s1,rmsg,500)) > 0) {
        rmsg[n] = '\0';
        sendtoall(rmsg,s1);
        bzero(rmsg,500);
    }
    pthread_exit(NULL);
}
int main(int arrc,char *argv[])
{
    struct sockaddr_in server,client;
    int s1,len;
    int n;
    int port;
    pthread_t t1;
    char message[500];
    port = atoi(argv[1]);
    bzero((char *)&server,sizeof(server));
    server.sin_port = htons(port);
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_family = AF_INET;
    s1 = socket(AF_INET,SOCK_STREAM,0);
    if(s1 == -1) {
        perror("socket not created\n");
        exit(1);
    }
    if(bind(s1,(struct sockaddr *)&server,sizeof(struct sockaddr)) == -1) {
        perror("socket not binded\n");
        exit(1);
    }
    if(listen(s1,5) == -1) {
        perror("unable to listen");
        exit(1);
    }
    len = sizeof(struct sockaddr_in);
    signal(SIGINT, handler);
    while(flag) {
        s2 = accept(s1,(struct sockaddr *)&client,&len);
        pthread_create(&t1,NULL,function,(void *)&s2);
        arr[tc] = s2;
        tc++;
    }
    close(s1);
    close(s2);
    return 0;

}

我正在使用Linux系统,在终端上使用Ctrl+C。 - Shivam Mitra
您的代码运行良好。我正在使用 Linux 版本 3.19.0-33-generic。 - Shivam Mitra
我在与线程相关的函数中有一个while循环。 - Shivam Mitra
调试器也可能有所帮助(例如 gdb)。 - Jason
1
可能是修复发送信号以中断系统调用时的竞态条件的重复问题。 - pilcrow
1个回答

4
通过由中断处理程序设置的标志来捕获信号,不适用于在信号需要可靠地中断阻塞系统调用的情况下(例如您的情况中的accept)。问题在于信号可能会在进入阻塞系统之前就到达:在检查标志但在信号中断给定系统调用的状态之前。因此,即使设置了标志,系统调用也会阻塞程序的执行。
此外,当多个线程允许信号时,只有一个线程会捕获信号,而且不确定是哪个线程。在您的情况下,可能没有主线程捕获信号,因此accept根本没有被中断。
虽然第二个问题(与多线程程序相关)可以通过在除主线程之外的所有线程中阻止信号来轻松解决,但第一个问题需要特殊的方法。可能的方法:
signalfd() 与 poll()/select() 结合使用
最复杂的方法,但几乎在所有情况下都有效。信号被“转换”为文件描述符,然后与等待系统调用的文件描述符结合在一起。结果文件描述符集用于轮询。
// Preparations
sigset_t s;
sigemptyset(&s);
sigaddset(&s, SIGINT);
sigprocmask(SIGBLOCK, &s, NULL); // For multithreaded program *pthread_sigmask* should be used.
int fd_int = signalfd(0, &s, 0); // When signal arises, this file becomes readable

// Usage
fd_set fds;
FD_ZERO(&fds);
FD_SET(fd_int, &fds);
FD_SET(s1, &fds);
int nfds = MAX(fd_int, s1) + 1;
select(nfds, &fds, NULL, NULL, NULL);
if(FD_ISSET(fd_int, &fds)) {
    // Signal is arrived
    ...
}
else {
    // New connection request is received
    accept(s1, ...);
    ...
}

注意,该信号对于所有线程都被阻止,包括主线程。
在信号处理程序和退出中的终止
最简单的方法但使用范围有限。如果所有终止操作都是“信号安全”的(请参见man singnal(7),了解允许在信号处理程序中调用的完整函数列表),则可以由信号处理程序本身执行这些操作,然后退出程序:
void handler(int signal)
{
    close(s1);
    close(s2);
    _exit(0); // This function is thread-safe, unlike to *exit*.
}

但是对于多线程程序来说,这种方法通常不适用,因为函数thread_join不是信号安全的。

在信号处理程序中更改系统调用参数的状态

因此,系统调用将立即返回,而不会阻塞。最简单的状态更改是关闭系统调用所使用的文件描述符:

void handler(int signal)
{
    flag = 0;
    close(s1); // Close file descriptor which is used by the system call.
}

while(flag)
{
    s2 = accept(s1, ...);
    if(s2 == -1 && !flag) {
        // Signal is catched
        break;
    }
    ...
}

请注意,在多线程程序中,除主线程外,所有线程都应明确地阻止信号。否则,在一个线程关闭文件而另一个线程正在读取它时,不需要中断读取线程。
此外,在多线程程序中,应考虑到如果某个其他线程创建(打开)文件描述符,则可以在系统调用之前重用在信号处理程序中关闭的文件描述符。

在信号处理程序中调用close()会引入竞争条件:下一个文件描述符的分配将使用刚关闭的文件描述符编号。您的accept()可能会在其他人的套接字、管道、文件、消息队列描述符等上被调用。 - pilcrow
@pilcrow:关于可能重复使用已关闭的文件描述符的好注意事项,但在给定的情况下它几乎不可能成为问题。首先,在给定的示例中,线程实际上并没有创建任何文件描述符。其次,即使它这样做了,它也应该创建基于连接的监听套接字,以便accept()不会造成任何损害:对于任何其他类型的文件描述符,accept()什么也不做并返回错误(EINVAL、ENOSOCK或EOPNOTSUPP)。 - Tsyvarev
1
为了完全消除closeaccept之间的竞争文件描述符分配,信号处理程序可以使用dup2(oldfd, s1),而不是close(s1)。其中oldfd是任何已打开的文件描述符,但不是适用于accept()的套接字。因此,s1原子地“重新链接”到与oldfd相同的对象,并且accept(s1)将返回适当的错误,没有可能造成损害。 - Tsyvarev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接