Как я могу перехватить сигнал (`SIGPIPE`) для сокета, который закрывается? - PullRequest
7 голосов
/ 12 января 2012

Я написал сервер, который принимает подключение к сокету через вторичный порт для потоковой отладочной информации, которая обычно переходит на stderr.Этот второй порт - порт обслуживания ошибок - предназначен только для одного соединения за раз, что удобно, поскольку позволяет перенаправить stderr с помощью вызова dup2(2).(См. Можно ли перенаправить stderr родительского процесса в дескриптор файла сокета на разветвленном процессе? ).

Следующий код почти удовлетворителен во всех отношениях.Когда клиент входит в порт, поток stderr направляется в сокет.Когда другой клиент входит в систему, поток снова перенаправляется, и первый клиент перестает получать: вполне удовлетворительно.

В проекте не хватает места, когда клиент закрывает соединение, сервер падает, потому что он пытаетсяна write() на сокет, который закрыт.

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

Как я могу перехватить сигнал (в родительском элементе), что соединение на ERR_PORT_NUM закрыто и обработчик сигнала повторно открывает (или dup) stderr обратно к /dev/null для следующего ожиданияошибка клиента?

Кроме того, что я должен делать с исходным соединением клиента ошибки при подключении второго?В настоящее время первый клиент остался болтаться.Допустимо даже незаметное отключение первого соединения.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/socket.h>
#include <fcntl.h>
#include <errno.h>
#include <pwd.h>
#include <signal.h>
#include <netinet/in.h>
#include <sys/mman.h>

#define PORT_NUM 12345
#define ERR_PORT_NUM 54321

static void child_handler(int signum)
{
    switch (signum) {
        case SIGALRM:
            exit(EXIT_FAILURE);
            break;
        case SIGUSR1:
            exit(EXIT_SUCCESS);
            break;
        case SIGCHLD:
            exit(EXIT_FAILURE);
            break;
    }
}

static void daemonize(void)
{
    /* Trap signals that we expect to recieve */
    signal(SIGUSR1, child_handler);
    signal(SIGALRM, child_handler);

    signal(SIGCHLD, SIG_IGN);   /* A child process dies */
    signal(SIGTSTP, SIG_IGN);   /* Various TTY signals */
    signal(SIGTTOU, SIG_IGN);
    signal(SIGTTIN, SIG_IGN);
    signal(SIGHUP, SIG_IGN);    /* Ignore hangup signal */
    signal(SIGTERM, SIG_DFL);   /* Die on SIGTERM */

    freopen("/dev/null", "r", stdin);
    freopen("/dev/null", "w", stdout);
    freopen("/dev/null", "w", stderr);
}

static void server_work(void)
{
    int sockfd, err_sockfd;
    socklen_t clilen;
    struct sockaddr_in serv_addr, cli_addr, err_serv_addr, err_cli_addr;
    struct timeval tv = { 0 };
    int new_stderr;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    err_sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0 || err_sockfd < 0)
        return;

    memset((char *) &serv_addr, '\0', sizeof(serv_addr));
    memset((char *) &err_serv_addr, '\0', sizeof(serv_addr));

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(PORT_NUM);

    err_serv_addr.sin_family = AF_INET;
    err_serv_addr.sin_addr.s_addr = INADDR_ANY;
    err_serv_addr.sin_port = htons(ERR_PORT_NUM);

    if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr))
        < 0)
        return;
    if (bind
        (err_sockfd, (struct sockaddr *) &err_serv_addr,
         sizeof(err_serv_addr)) < 0)
        return;

    listen(sockfd, 5);
    listen(err_sockfd, 5);

    clilen = sizeof(cli_addr);

    while (1) {
        int maxfd;
        fd_set read_sockets_set;

        FD_ZERO(&read_sockets_set);
        FD_SET(sockfd, &read_sockets_set);
        FD_SET(err_sockfd, &read_sockets_set);

        maxfd = (err_sockfd > sockfd) ? err_sockfd : sockfd;

        if (select(maxfd + 1, &read_sockets_set, NULL, NULL, NULL) < 0) {
            break;
        }
        if (FD_ISSET(sockfd, &read_sockets_set)) {
            /* Typical process fork(2) and such ... not gremaine to the question. */
        }
        if (FD_ISSET(err_sockfd, &read_sockets_set)) {
            new_stderr =
                accept(err_sockfd, (struct sockaddr *) &err_cli_addr,
                       &clilen);
            dup2(new_stderr, STDERR_FILENO);
        }
    }
    close(sockfd);
    close(err_sockfd);
    return;
}

int main(int argc, char *argv[])
{
    daemonize();                /* greatly abbreviated for question */

    server_work();
    return 0;
}

1 Ответ

13 голосов
/ 12 января 2012

Вы можете просто игнорировать SIGPIPE.Это бесполезный, раздражающий сигнал.

signal(SIGPIPE, SIG_IGN);

Если вы проигнорируете его, ваша программа вместо этого получит код ошибки EPIPE от неудачного вызова write ().Это позволяет вам обрабатывать ошибку ввода-вывода в разумном месте в вашем коде, а не в каком-то глобальном обработчике сигналов.труба или розетка, конец чтения которой закрыт.Когда это происходит, процесс записи также получит сигнал SIGPIPE.(Таким образом, возвращаемое значение записи отображается только в том случае, если программа перехватывает, блокирует или игнорирует этот сигнал.)

...