使用 pthreads 时无法捕获 SIGINT 信号
Not able to catch a SIGINT signal while using pthreads
我制作了一个使用多线程处理多个客户端的聊天服务器。我有一个无限运行并等待新客户的 while 循环。我想在我按下ctrl+c之后就出来了。所以,我正在尝试捕捉 SIGINT 信号,正如已经提到的那样 here。但是我无法退出该程序。我在 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] = '[=10=]';
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;
}
通过中断处理程序设置的标志捕获信号不适用于信号需要可靠地中断阻塞系统调用(在您的情况下为accept
)的情况。问题是信号可能恰好在进入阻塞系统之前到达:在检查标志之后但在信号中断给定系统调用的状态之前。因此,即使设置了标志,系统调用也会阻止程序的执行。
此外,当多个线程允许信号时,只有一个线程捕获信号,并且未指定是哪个线程。在你的情况下,信号可能没有被主线程捕获,所以 accept
根本没有被中断。
虽然第二个问题(与多线程程序有关)可以通过阻塞除主线程之外的所有线程中的信号轻松解决,但第一个问题需要特殊的方法。可能的:
signalfd() 结合 poll() / select()
最复杂的方法,但几乎适用于所有情况。 Signal 是 "transformed" 进入文件描述符,它与系统调用等待的文件描述符结合在一起。结果文件描述符集用于 轮询:
// 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, ...);
...
}
注意,所有线程的信号都被阻塞,包括主线程。
在信号处理程序中完成并退出
最简单的方法,但用途有限。如果所有完成操作都是 signal-safe(请参阅 man singnal(7) 以获取在信号处理程序中允许调用的函数的完整列表),则该操作可以由信号执行处理程序本身,然后从程序中退出:
void handler(int signal)
{
close(s1);
close(s2);
_exit(0); // This function is thread-safe, unlike to *exit*.
}
但是对于多线程程序,这种方法通常不适用,因为函数 thread_join
不是 signal-safe。
在信号处理程序中更改系统调用参数的状态
所以系统调用会立即return,不会阻塞。最简单的状态改变是关闭系统调用工作的文件描述符:
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;
}
...
}
注意,在多线程程序的情况下,除了主线程外,所有线程的信号都应该被显式阻塞。否则,在一个线程中关闭文件而其他线程读取它不需要中断读取线程。
此外,在多线程程序的情况下,应该考虑到,如果一些 其他 线程创建(打开)文件描述符,它可以重用一个,在信号处理程序中关闭,就在系统调用中使用它之前。
我制作了一个使用多线程处理多个客户端的聊天服务器。我有一个无限运行并等待新客户的 while 循环。我想在我按下ctrl+c之后就出来了。所以,我正在尝试捕捉 SIGINT 信号,正如已经提到的那样 here。但是我无法退出该程序。我在 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] = '[=10=]';
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;
}
通过中断处理程序设置的标志捕获信号不适用于信号需要可靠地中断阻塞系统调用(在您的情况下为accept
)的情况。问题是信号可能恰好在进入阻塞系统之前到达:在检查标志之后但在信号中断给定系统调用的状态之前。因此,即使设置了标志,系统调用也会阻止程序的执行。
此外,当多个线程允许信号时,只有一个线程捕获信号,并且未指定是哪个线程。在你的情况下,信号可能没有被主线程捕获,所以 accept
根本没有被中断。
虽然第二个问题(与多线程程序有关)可以通过阻塞除主线程之外的所有线程中的信号轻松解决,但第一个问题需要特殊的方法。可能的:
signalfd() 结合 poll() / select()
最复杂的方法,但几乎适用于所有情况。 Signal 是 "transformed" 进入文件描述符,它与系统调用等待的文件描述符结合在一起。结果文件描述符集用于 轮询:
// 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, ...);
...
}
注意,所有线程的信号都被阻塞,包括主线程。
在信号处理程序中完成并退出
最简单的方法,但用途有限。如果所有完成操作都是 signal-safe(请参阅 man singnal(7) 以获取在信号处理程序中允许调用的函数的完整列表),则该操作可以由信号执行处理程序本身,然后从程序中退出:
void handler(int signal)
{
close(s1);
close(s2);
_exit(0); // This function is thread-safe, unlike to *exit*.
}
但是对于多线程程序,这种方法通常不适用,因为函数 thread_join
不是 signal-safe。
在信号处理程序中更改系统调用参数的状态
所以系统调用会立即return,不会阻塞。最简单的状态改变是关闭系统调用工作的文件描述符:
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;
}
...
}
注意,在多线程程序的情况下,除了主线程外,所有线程的信号都应该被显式阻塞。否则,在一个线程中关闭文件而其他线程读取它不需要中断读取线程。
此外,在多线程程序的情况下,应该考虑到,如果一些 其他 线程创建(打开)文件描述符,它可以重用一个,在信号处理程序中关闭,就在系统调用中使用它之前。