TCP:何时生成EPOLLHUP? [英] TCP: When is EPOLLHUP generated?
问题描述
另请参阅此问题,未作答现在.
Also see this question, unanswered as of now.
即使在man
和内核文档中,对EPOLLHUP
也有很多困惑.人们似乎认为,当轮询在本地关闭以进行写入的描述符即shutdown(SHUT_WR)
时,即返回该值,即在对等端导致EPOLLRDHUP
的同一调用.但这是不正确的,在我的实验中,在shutdown(SHUT_WR)
之后我得到EPOLLOUT
而没有EPOLLHUP
(是的,得到可写是违反直觉的,因为写作的一半是封闭的,但这不是问题的重点).
There is a lot of confusion about EPOLLHUP
, even in the man
and Kernel docs. People seem to believe it is returned when polling on a descriptor locally closed for writing, i.e. shutdown(SHUT_WR)
, i.e. the same call that causes an EPOLLRDHUP
at the peer. But this is not true, in my experiments I get EPOLLOUT
, and no EPOLLHUP
, after shutdown(SHUT_WR)
(yes, it's counterintuitive to get writable, as the writing half is closed, but this is not the main point of the question).
man 很差,因为它说
The man is poor, because it says EPOLLHUP
comes when Hang up happened on the associated file descriptor, without saying what "hang up" means - what did the peer do? what packets were sent? This other article just confuses things further and seems outright wrong to me.
我的实验显示,一旦双方交换了EOF(FIN数据包),即双方都发出了shutdown(SHUT_WR)
,则EPOLLHUP
就会到达.它与SHUT_RD
无关,我从不曾称呼它.同样与close
无关.在数据包方面,我怀疑在主机发送的FIN的确认信号上引发了EPOLLHUP
,即,终止发起方在4向关机握手的步骤3中引发了该事件,而对等方在步骤4中引发了该事件. (请参见此处).如果得到确认,那就太好了,因为它填补了我一直在寻找的空白,即如何在不使用LINGER的情况下轮询非阻塞套接字以获取最终的ack. 这正确吗?
My experiments show EPOLLHUP
arrives once EOF (FIN packets) are exchanged both ways, i.e. once both sides issue shutdown(SHUT_WR)
. It has nothing to do with SHUT_RD
, which I never call. Also nothing to do with close
. In terms of packets, I have the suspicion that EPOLLHUP
is raised on the ack of the hosts' sent FIN, i.e. the termination initiator raises this event in step 3 of the 4-way shutdown handshake, and the peer, in step 4 (see here). If confirmed, this is great, because it fills a gap that I've been looking for, namely how to poll non-blocking sockets for the final ack, without LINGER. Is this correct?
(注意:我正在使用ET,但我认为与此无关)
(note: I'm using ET, but I don't think it's relevant for this)
代码在框架中,我提取了其中的内容,但TcpSocket::createListener
,TcpSocket::connect
和TcpSocket::accept
例外,它们可以满足您的期望(此处未显示).
The code being in a framework, I extracted the meat of it, with the exception of TcpSocket::createListener
, TcpSocket::connect
and TcpSocket::accept
, which do what you'd expect (not shown here).
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是什么意思以外,没有更好的方法来重述该问题.我认为文档很差,而其他信息位置(例如此处和
There is not better way to rephrase the question, other than, what does EPOLLHUP mean? I made the case that documentation is poor, and information in other places (e.g. here and here) is wrong or useless.
注意:要考虑回答的Q,我想确认EPOLLHUP在两个方向的最终FIN-ACK上都升高.
推荐答案
For these kind of questions, use the source! Among other interesting comments, there is this text:
EPOLLHUP
是 UNMASKABLE 事件(...).这意味着在我们收到EOF
之后,poll
总是立即返回,使得在状态CLOSE_WAIT
上的write()
上的poll()
不可能.很明显的一种解决方案---仅当在两个方向上都已制作shutdown
时,才设置EPOLLHUP
.
EPOLLHUP
is UNMASKABLE event (...). It means that after we receivedEOF
,poll
always returns immediately, making impossiblepoll()
onwrite()
in stateCLOSE_WAIT
. One solution is evident --- to setEPOLLHUP
if and only ifshutdown
has been made in both directions.
然后是设置EPOLLHUP
的唯一代码:
And then the only code that sets EPOLLHUP
:
if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
mask |= EPOLLHUP;
SHUTDOWN_MASK
等于RCV_SHUTDOWN |SEND_SHUTDOWN
.
TL; DR;没错,仅当读写同时关闭时才发送此标志(我认为对等的关闭写入等同于我关闭读取).当然,或者当连接关闭时.
TL; DR; You are right, this flag is only sent when the shutdown has been both for read and write (I reckon that the peer shutdowning the write equals to my shutdowning the read). Or when the connection is closed, of course.
更新:通过更详细地阅读源代码,这些是我的结论.
UPDATE: From reading the source code with more detail, these are my conclusions.
关于shutdown
:
- 执行
shutdown(SHUT_WR)
会发送FIN
并用SEND_SHUTDOWN
标记套接字. - 执行
shutdown(SHUT_RD)
不会发送任何内容,并用RCV_SHUTDOWN
标记套接字. - 接收
FIN
会将套接字标记为RCV_SHUTDOWN
.
- Doing
shutdown(SHUT_WR)
sends aFIN
and marks the socket withSEND_SHUTDOWN
. - Doing
shutdown(SHUT_RD)
sends nothing and marks the socket withRCV_SHUTDOWN
. - Receiving a
FIN
marks the socket withRCV_SHUTDOWN
.
关于epoll
:
- 如果套接字上标有
SEND_SHUTDOWN
和RCV_SHUTDOWN
,则poll
将返回EPOLLHUP
. - 如果套接字标记有
RCV_SHUTDOWN
,则poll
将返回EPOLLRDHUP
.
- If the socket is marked with
SEND_SHUTDOWN
andRCV_SHUTDOWN
,poll
will returnEPOLLHUP
. - If the socket is marked with
RCV_SHUTDOWN
,poll
will returnEPOLLRDHUP
.
因此,HUP
事件可以理解为:
So the HUP
events can be read as:
-
EPOLLRDHUP
:您已收到FIN
或已致电shutdown(SHUT_RD)
.在任何情况下,您的读取半插槽都处于挂起状态,也就是说,您将不再读取任何数据. -
EPOLLHUP
:两个半插座都挂了.阅读半插口就像上一点,对于发送半插口,您做了类似shutdown(SHUT_WR)
的操作.
EPOLLRDHUP
: you have receivedFIN
or you have calledshutdown(SHUT_RD)
. In any case your reading half-socket is hung, that is, you will read no more data.EPOLLHUP
: you have both half-sockets hung. The reading half-socket is just like the previous point, For the sending half-socket you did something likeshutdown(SHUT_WR)
.
要完成正常关机,我会这样做:
To complete a a graceful shutdown I would do:
- 执行
shutdown(SHUT_WR)
以发送FIN
并标记发送数据的结束. - 等待对等方进行轮询,直到获得
EPOLLRDHUP
. - 现在您可以优雅地关闭套接字了.
- Do
shutdown(SHUT_WR)
to send aFIN
and mark the end of sending data. - Wait for the peer to do the same by polling until you get a
EPOLLRDHUP
. - Now you can close the socket with grace.
PS :关于您的评论:
由于写作部分是封闭的,因此变得可写是违反直觉的
it's counterintuitive to get writable, as the writing half is closed
如果您理解epoll
的输出不是 ready 而是不会阻塞,则实际上是可以预期的.也就是说,如果得到EPOLLOUT
,则可以保证调用write()
不会阻塞.当然,在shutdown(SHUT_WR)
之后,write()
将立即返回.
It is actually expected if you understand the output of epoll
not as ready but as will not block. That is, if you get EPOLLOUT
you have the guarantee that calling write()
will not block. And certainly, after shutdown(SHUT_WR)
, write()
will return immediately.
这篇关于TCP:何时生成EPOLLHUP?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!