Не удается поймать сигнал SIGINT при использовании pthreads
Я сделал чат-сервер, который использует многопоточность для работы с несколькими клиентами. У меня есть цикл 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;
}
1 ответ
Захват сигнала с помощью флага, установленного обработчиком прерываний, не подходит для случаев, когда сигнал должен надежно прервать системный вызов блокировки (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;
}
...
}
Обратите внимание, что в случае многопоточной программы сигнал должен быть явно заблокирован для всех потоков, кроме основного. В противном случае закрытие файла в одном потоке, в то время как другой поток считывает, прерывать чтение потока не требуется.
Кроме того, в случае многопоточной программы следует учитывать, что если какой-то другой поток создает (открывает) файловый дескриптор, он может повторно использовать один, закрытый в обработчике сигналов, непосредственно перед его использованием в системном вызове.