TCP: Когда генерируется EPOLLHUP? - PullRequest
0 голосов
/ 24 октября 2018

Также см. этот вопрос , без ответа на данный момент.

Существует большая путаница в EPOLLHUP, даже в документах man и Kernel.Люди, кажется, полагают, что он возвращается при опросе дескриптора , локально закрытого для записи , то есть shutdown(SHUT_WR), то есть того же вызова, который вызывает EPOLLRDHUP в узле .Но это неправда, в моих экспериментах я получаю EPOLLOUT, и нет EPOLLHUP после shutdown(SHUT_WR) (да, нелогично получать доступный для записи , так как половина записи закрыта, но этоне основной вопрос).

man плохой, потому что он говорит, что EPOLLHUP происходит, когда Зависание произошло на связанном файловом дескрипторе , безсказать, что значит «повесить трубку» - что сделал пэр?какие пакеты были отправлены? Эта другая статья просто еще больше сбивает с толку и кажется мне совершенно неправильной.

Мои эксперименты показывают, что EPOLLHUP приходит, когда EOF (пакеты FIN) обмениваются в обе стороны, то есть, когда обе стороны выдают shutdown(SHUT_WR).Это не имеет ничего общего с SHUT_RD, который я никогда не называю.Также не имеет ничего общего с close.С точки зрения пакетов, у меня есть подозрение, что EPOLLHUP возникает на подтверждении отправленного FIN хостов, то есть инициатор завершения вызывает это событие на шаге 3 при четырехстороннем установлении соединения, а партнер на шаге 4(см. здесь ).Если подтвердится, это здорово, потому что он заполняет пробел, который я искал, а именно, как опрашивать неблокирующие сокеты для окончательного подтверждения без LINGER. Это правильно?

(примечание: я использую ET, но я не думаю, что это актуально для этого)

Пример кода и вывод.

Код, находящийся в фреймворке, я извлек из него суть, за исключением TcpSocket::createListener, TcpSocket::connect и TcpSocket::accept, которые делают то, что вы ожидаете (не показано здесь).

void registerFd(int pollFd, int fd, const char* description)
{
    epoll_event ev = {
        EPOLLIN | EPOLLOUT | EPOLLRDHUP | EPOLLET,
        const_cast<char*>(description) // union aggregate initialisation, initialises first member (void* ptr)
    };
    epoll_ctl(pollFd, EPOLL_CTL_ADD, fd, &ev);
}

struct EventPrinter
{
    friend std::ostream& operator<<(std::ostream& stream, const EventPrinter& obj)
    {
        return stream << "0x" << std::hex << obj.events_ << " = "
            << ((obj.events_& EPOLLIN) ? "EPOLLIN " : " ")
            << ((obj.events_& EPOLLOUT) ? "EPOLLOUT " : " ")
            << ((obj.events_& EPOLLERR) ? "EPOLLERR " : " ")
            << ((obj.events_& EPOLLRDHUP) ? "EPOLLRDHUP " : " ")
            << ((obj.events_& EPOLLHUP) ? "EPOLLHUP " : " ");
    }

    const uint32_t events_;
};

void processEvents(int pollFd)
{
    static int iterationCount = 0;
    ++iterationCount;

    std::array<epoll_event, 25> events;
    int eventCount;
    if (-1 ==
        (eventCount = epoll_wait(pollFd, events.data(), events.size(), 1)))
    {
        throw Exception("fatal: epoll_wait failed");
    }

    for (int i = 0; i < eventCount; ++i)
    {
        std::cout << "iteration #" << iterationCount << ": events on [" << static_cast<const char*>(events[i].data.ptr) << "]: [" << EventPrinter{events[i].events} << "]" << std::endl;
    }
}

TEST(EpollhupExample, SmokeTest)
{
    int pollFd_;
    if (-1 ==
        (pollFd_ = epoll_create1(0)))
    {
        throw Exception("fatal: could not create epoll socket");
    }

    const TcpSocket listener_ = TcpSocket::createListener(13500);
    if (!listener_.setFileStatusFlag(O_NONBLOCK, true))
        throw Exception("could not make listener socket non-blocking");
    registerFd(pollFd_, listener_.fd(), "listenerFD");

    const TcpSocket client = TcpSocket::connect("127.0.0.1", AF_INET, 13500);
    if (!client.valid()) throw;
    registerFd(pollFd_, client.fd(), "clientFD");





    //////////////////////////////////////////////
    /// start event processing ///////////////////
    //////////////////////////////////////////////

    processEvents(pollFd_); // iteration 1

    const TcpSocket conn = listener_.accept();
    if (!conn.valid()) throw;
    registerFd(pollFd_, conn.fd(), "serverFD");

    processEvents(pollFd_); // iteration 2

    conn.shutdown(SHUT_WR);

    processEvents(pollFd_); // iteration 3

    client.shutdown(SHUT_WR);

    processEvents(pollFd_); // iteration 4
}

Вывод:

    Info| TCP connection established to [127.0.0.1:13500]
iteration #1: events on [listenerFD]: [1 = EPOLLIN     ]
iteration #1: events on [clientFD]: [4 =  EPOLLOUT    ]
    Info| TCP connection accepted from [127.0.0.1:35160]

iteration #2: events on [serverFD]: [4 =  EPOLLOUT    ]
    // calling serverFD.shutdown(SHUT_WR) here

iteration #3: events on [clientFD]: [2005 = EPOLLIN EPOLLOUT  EPOLLRDHUP  ]           // EPOLLRDHUP arrives, nice.
iteration #3: events on [serverFD]: [4 =  EPOLLOUT    ]                               // serverFD (on which I called SHUT_WR) just reported as writable, not cool... but not the main point of the question
    // calling clientFD.shutdown(SHUT_WR) here

iteration #4: events on [serverFD]: [2015 = EPOLLIN EPOLLOUT  EPOLLRDHUP EPOLLHUP ]   // EPOLLRDHUP arrives, nice. EPOLLHUP too!
iteration #4: events on [clientFD]: [2015 = EPOLLIN EPOLLOUT  EPOLLRDHUP EPOLLHUP ]   // EPOLLHUP on the other side as well. Why? What does EPOLLHUP mean actually?

Нет лучшего способа перефразировать вопрос, кроме что означает EPOLLHUP ?Я доказал, что документация плохая, а информация в других местах (например, здесь и здесь ) неверна или бесполезна.

Примечание. Чтобы рассмотреть вопрос с ответом Q, я хочу получить подтверждение того, что EPOLLHUP поднят на последних FIN-ACK в обоих направлениях.

1 Ответ

0 голосов
/ 24 октября 2018

Для вопросов такого рода используйте источник !Среди других интересных комментариев есть такой текст:

EPOLLHUP is UNMASKABLE событие (...).Это означает, что после того, как мы получили EOF, poll всегда возвращается немедленно, что делает невозможным poll() на write() в состоянии CLOSE_WAIT.Очевидно одно решение - установить EPOLLHUP тогда и только тогда, когда shutdown был сделан в обоих направлениях.

И тогда единственный код, который устанавливает EPOLLHUP:

if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
    mask |= EPOLLHUP;

Быть SHUTDOWN_MASK равно RCV_SHUTDOWN |SEND_SHUTDOWN.

TL;DR;Вы правы, этот флаг отправляется только тогда, когда завершение было как для чтения, так и для записи (я считаю, что одноранговое отключение, завершающее запись, равно моему закрытию чтения).Или, конечно, когда соединение закрыто.

ОБНОВЛЕНИЕ : Из более подробного чтения исходного кода я делаю выводы.

О shutdown:

  1. Выполнение shutdown(SHUT_WR) отправляет FIN и помечает сокет SEND_SHUTDOWN.
  2. Выполнение shutdown(SHUT_RD) ничего не отправляет и помечает сокет RCV_SHUTDOWN.
  3. При получении FIN пометка сокета RCV_SHUTDOWN.

И около epoll:

  1. Если сокет помечен SEND_SHUTDOWN иRCV_SHUTDOWN, poll вернет EPOLLHUP.
  2. Если сокет помечен RCV_SHUTDOWN, poll вернет EPOLLRDHUP.

Так что HUP события могут быть прочитаны как:

  1. EPOLLRDHUP: вы получили FIN или позвонили shutdown(SHUT_RD).В любом случае ваша половина сокета для чтения зависла, то есть вы больше не будете читать данные.
  2. EPOLLHUP: у вас подвешены обе половины сокета.Полуразъем для чтения такой же, как и в предыдущем пункте. Для отправляющего полу-сокета вы сделали что-то вроде shutdown(SHUT_WR).

. Чтобы завершить корректное отключение, я бы сделал:

  1. Выполните shutdown(SHUT_WR), чтобы отправить FIN и отметьте конец отправки данных.
  2. Подождите, пока одноранговый узел сделает то же самое, опросив, пока не получите EPOLLRDHUP.
  3. Теперь вы можете закрыть сокет с изяществом.

PS : По поводу вашего комментария:

Это противоречит интуитивно понятным для записи, как наполовинузакрыто

На самом деле ожидается, что вы понимаете вывод epoll не как ready , а как не будет блокировать .То есть, если вы получите EPOLLOUT, у вас есть гарантия, что вызов write() не заблокирует.И, конечно же, после shutdown(SHUT_WR), write() немедленно вернется.

...