EPIPE блокирует сервер - PullRequest
1 голос
/ 29 марта 2010

Я написал однопоточный асинхронный сервер на C, работающий в Linux: сокет неблокируемый, и для опроса я использую epoll. Тесты показывают, что сервер работает нормально, и, согласно Valgrind, нет утечек памяти или других проблем.

Единственная проблема заключается в том, что при прерывании команды write () (так как клиент закрыл соединение) сервер встречает EPIPE. Я делаю прерванное искусственно, запустив утилиту сравнения "siege" с параметром -b. Это делает много запросов подряд, которые все работают отлично. Теперь я нажимаю CTRL-C и возобновляю «осаду». Иногда мне везет, и серверу не удается отправить полный ответ, потому что fd клиента недействителен. Как и ожидалось, errno установлен в EPIPE. Я справляюсь с этой ситуацией, выполняю close () на fd и затем освобождаю память, связанную с соединением. Теперь проблема в том, что сервер блокирует и больше не отвечает должным образом. Вот результат вывода:

epoll_wait(4, {{EPOLLIN, {u32=0, u64=0}}}, 128, -1) = 1
accept(3, {sa_family=AF_INET, sin_port=htons(55328), sin_addr=inet_addr("127.0.0.1")}, [16]) = 5
fcntl64(5, F_GETFL)                     = 0x2 (flags O_RDWR)
fcntl64(5, F_SETFL, O_RDWR|O_NONBLOCK)  = 0
epoll_ctl(4, EPOLL_CTL_ADD, 5, {EPOLLIN|EPOLLERR|EPOLLHUP|EPOLLET, {u32=144039912, u64=144039912}}) = 0
epoll_wait(4, {{EPOLLIN, {u32=144039912, u64=144039912}}}, 128, -1) = 1
read(5, "GET /user/register HTTP/1.1\r\nHos"..., 4096) = 161
send(5, "HTTP/1.1 200 OK\r\nContent-Type: t"..., 106, MSG_NOSIGNAL) = 106 <<<<
send(5, "00001000\r\n", 10, MSG_NOSIGNAL) = -1 EPIPE (Broken pipe)        <<<< Why did the previous send() work?
close(5)                                = 0
epoll_wait(4, {{EPOLLIN, {u32=0, u64=0}}}, 128, -1) = 1
accept(3, {sa_family=AF_INET, sin_port=htons(55329), sin_addr=inet_addr("127.0.0.1")}, [16]) = 5
...

(я удалил printf () из журнала, если вам интересно)

Как видите, клиент устанавливает новое соединение, которое в результате принимается. Затем он добавляется в очередь EPOLL. epoll_wait () сигнализирует, что клиент отправил данные (EPOLLIN). Запрос анализируется и составляется ответ. Отправка заголовков работает нормально, но когда дело доходит до тела, write () приводит к EPIPE. Это не ошибка в «осаде», поскольку она блокирует любые входящие соединения, независимо от того, с какого клиента.

#include "Connection.h"

static ExceptionManager *exc;

void Connection0(ExceptionManager *e) {
    exc = e;
}

void Connection_Init(Connection *this) {
    Socket_Init(&this->server);
    Socket_SetReusableFlag(&this->server);
    Socket_SetCloexecFlag(&this->server, true);
    Socket_SetBlockingFlag(&this->server, true);
    Socket_ListenTCP(&this->server, 8080, SOMAXCONN);

    // Add the server socket to epoll
    Poll_Init(&this->poll, SOMAXCONN, (void *) &Connection_OnEvent, this);
    Poll_AddEvent(&this->poll, NULL, this->server.fd, EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR);

    this->activeConn = 0;
}

void Connection_Destroy(Connection *this) {
    Poll_Destroy(&this->poll);
    Socket_Destroy(&this->server);
}

void Connection_Process(Connection *this) {
    Poll_Process(&this->poll, -1);
}

void Connection_AcceptClient(Connection *this) {
    Client *client;

    SocketConnection conn = Socket_Accept(&this->server);
    SocketConnection_SetBlockingFlag(&conn, true);

    client = New(Client);

    client->req = NULL;

    client->conn = New(SocketConnection);
    client->conn->remote = conn.remote;
    client->conn->fd = conn.fd;

    this->activeConn++;

    Poll_AddEvent(&this->poll, client, conn.fd, EPOLLIN | EPOLLET | EPOLLHUP | EPOLLERR);
}

void Connection_DestroyClient(Connection *this, Client *client) {
    // Poll_DeleteEvent(&this->poll, client->conn->fd);
    SocketConnection_Close(client->conn);

    if (client->req != NULL) {
        Request_Destroy(client->req);
        Memory_Free(client->req);
    }

    if (client->conn != NULL) {
        Memory_Free(client->conn);
    }

    Memory_Free(client);

    this->activeConn--;
}

void Connection_OnEvent(Connection *this, int events, Client *client) {
    /* error or connection hung up */
    if (client != NULL && (events & (EPOLLHUP | EPOLLERR))) {
        String_Print(String("EPOLLHUP | EPOLLERR received\n"));
        Connection_DestroyClient(this, client);
        return;
    }

    /* incoming connection */
    if (client == NULL && (events & EPOLLIN)) {
        if (this->activeConn > SOMAXCONN - 1) { /* TODO */
            String_Print(String("Too many connections...\n"));
            return;
        }

        Connection_AcceptClient(this);
        return;
    }

    /* receiving data from client */
    if (client != NULL && (events & EPOLLIN)) {
        if (client->req == NULL) {
            client->req = New(Request);
            Request_Init(client->req, client->conn);
        }

        bool keepOpen = false;

        try (exc) {
            keepOpen = Request_Parse(client->req);
        } catch(&SocketConnection_PipeException, e) {
            printf("Caught PipeException on fd=%d\n", client->conn->fd); fflush(stdout);
        } catch(&SocketConnection_ConnectionResetException, e) {
            printf("Caught ConnectionResetException on fd=%d\n", client->conn->fd); fflush(stdout);
        } finally {
            if (!keepOpen) {
                printf("Will close...\n"); fflush(stdout);
                Connection_DestroyClient(this, client);
            }
        } tryEnd;
    }
}

Ответы [ 2 ]

6 голосов
/ 29 марта 2010

Используйте sigaction(), чтобы установить для действия SIGPIPE значение SIG_IGN. Тогда вы просто получите код возврата -1 с errno, установленным на EPIPE, без сигнала.

В Linux альтернативой является использование send() с флагом MSG_NOSIGNAL вместо write(). Это позволяет подавить сигнал для этой записи, не затрагивая другие. Альтернативой в системах BSD является опция сокета SO_NOSIGPIPE, которая подавляет SIGPIPE для всех записей в этом сокете.

В любое время реализация TCP ядра может получить TCP RST от однорангового узла. Следующая запись в сокет после этой точки приведет к ошибке EPIPE и сигналу SIGPIPE, если она не была подавлена. Таким образом, даже при двух последовательных записях первая может быть успешной, а следующая может завершиться ошибкой с EPIPE и SIGPIPE.

Обновление: Хотя это не является частью кода, который был опубликован, я вижу в вашем выводе strace, что вы звоните fcntl64(5, F_SETFL, O_RDONLY|O_NONBLOCK). Поскольку O_RDONLY равно 0, возможно, ваш код просто устанавливает флаги на O_NONBLOCK. Он должен получить текущие флаги и затем установить его на OldFlags | O_NONBLOCK, чтобы установить неблокирующий режим. Установка сокета в режим только для чтения, вероятно, вызывает проблемы при записи. Или, может быть, вы случайно использовали F_GETFD вместо F_GETFL, чтобы получить старые флаги.

1 голос
/ 29 марта 2010

Я не могу сказать из вашего кода (возможно, потому что я не посмотрел в нужном месте), корректируете ли вы структуру epoll после того, как получите SIGPIPE от одного из дескрипторов. После того, как вы получите SIGPIPE (или EPIPE) из файлового дескриптора, вы получите повтор при любом последующем использовании того же файлового дескриптора. Вам нужно закрыть файловый дескриптор, а затем соответствующим образом настроить внутренние структуры вашего сервера.

...