TCP: Когда генерируется EPOLLHUP?
Также посмотрите этот вопрос, без ответа на данный момент.
Существует много путаницы по поводу EPOLLHUP
даже в man
и документы ядра. Люди, кажется, считают, что он возвращается при опросе дескриптора, локально закрытого для записи, т.е. shutdown(SHUT_WR)
то есть тот же вызов, который вызывает EPOLLRDHUP
на пэра. Но это неправда, в моих экспериментах я получаю EPOLLOUT
, и нет EPOLLHUP
, после shutdown(SHUT_WR)
(да, нелогично получать доступ к записи, поскольку половина письма закрыта, но это не главное в этом вопросе).
Человек беден, потому что говорит 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 ответ
Для подобных вопросов используйте источник! Среди других интересных комментариев есть этот текст:
EPOLLHUP
НЕИЗМЕРЕННОЕ событие (...). Это означает, что после того, как мы получили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
:
- дела
shutdown(SHUT_WR)
отправляетFIN
и помечает розеткуSEND_SHUTDOWN
, - дела
shutdown(SHUT_RD)
ничего не отправляет и помечает сокетRCV_SHUTDOWN
, - Получение
FIN
помечает розеткуRCV_SHUTDOWN
,
И о epoll
:
- Если розетка помечена
SEND_SHUTDOWN
а такжеRCV_SHUTDOWN
,poll
вернусьEPOLLHUP
, - Если розетка помечена
RCV_SHUTDOWN
,poll
вернусьEPOLLRDHUP
,
Итак HUP
события могут быть прочитаны как:
EPOLLRDHUP
: ты получилаFIN
или вы позвонилиshutdown(SHUT_RD)
, В любом случае ваша половина сокета чтения зависла, то есть вы больше не будете читать данные.EPOLLHUP
: у вас обе половинки подвешены. Считывающая половина сокета аналогична предыдущему пункту. Для отправляющей половины сокета вы сделали что-то вродеshutdown(SHUT_WR)
,
Чтобы завершить корректное отключение, я бы сделал:
- Делать
shutdown(SHUT_WR)
отправитьFIN
и отметьте конец отправки данных. - Подождите, пока пэр сделает то же самое, опросив, пока не получите
EPOLLRDHUP
, - Теперь вы можете закрыть розетку с изяществом.
PS: о вашем комментарии:
нелогично получать доступ к записи, так как половина записи закрыта
Это на самом деле ожидается, если вы понимаете вывод epoll
не так, как готов, но как не будет блокировать. То есть, если вы получите EPOLLOUT
у вас есть гарантия, что звонит write()
не будет блокировать. И, конечно же, после shutdown(SHUT_WR)
, write()
вернется немедленно.