Nano Hash - криптовалюты, майнинг, программирование

Невозможно поймать сигнал 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;
    }
    ...
}

Обратите внимание, что в случае многопоточной программы сигнал должен быть явно заблокирован для всех потоков, кроме основного. В противном случае закрытие файла в одном потоке, в то время как другой поток читает его, не требуется прерывать поток чтения.

Также в случае многопоточной программы следует учитывать, что если какой-то другой поток создает(открывает) файловый дескриптор, он может повторно использовать один, закрытый в обработчике сигнала, непосредственно перед тем, как он будет использован в системный вызов.

31.12.2015
  • Вызов close() в обработчике сигнала, подобного этому, вводит гонку: самое следующее распределение файлового дескриптора будет использовать только что закрытый номер файлового дескриптора. Ваш accept() может вызываться для чужого сокета, канала, файла, дескриптора очереди сообщений и т. д. 02.01.2016
  • @pilcrow: Хорошее замечание о возможном повторном использовании дескриптора закрытого файла, но в данном случае это вряд ли может быть проблемой. Во-первых, в данном примере поток фактически не создает файловый дескриптор. Во-вторых, даже если это так, он должен создать сокет прослушивания на основе соединения для accept(), который может причинить какой-либо вред: для любого другого типа файлового дескриптора accept() ничего не делает и возвращает ошибку (EINVAL, ENOSOCK или EOPNOTSUPP) . 02.01.2016
  • Для полного исключения распределения файловых дескрипторов между close и accept вместо close(s1) обработчик сигнала может использовать dup2(oldfd, s1), где oldfd - любой уже открытый файловый дескриптор, но не подходящий для accept() сокет. Таким образом, s1 будет атомарно повторно связан с тем же объектом, что и oldfd, а accept(s1) вернет соответствующую ошибку без возможности причинения вреда. 02.01.2016
  • Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

    Понимание расстояния Вассерштейна: мощная метрика в машинном обучении
    В обширной области машинного обучения часто возникает необходимость сравнивать и измерять различия между распределениями вероятностей. Традиционные метрики расстояния, такие как евклидово..

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..