linux - TCP:EPOLLHUP 何时生成?
问题描述
另请参阅this question,截至目前尚未得到解答。
EPOLLHUP
即使在man
和 Kernel 文档中,也有很多混淆。人们似乎相信它是在对本地关闭写入的描述符进行轮询时返回的,即在对等方shutdown(SHUT_WR)
引起 an 的相同调用。但这不是真的,在我的实验中,我得到了,而不是,之后(是的,得到writable是违反直觉的,因为写作的一半已经结束,但这不是问题的重点)。EPOLLRDHUP
EPOLLOUT
EPOLLHUP
shutdown(SHUT_WR)
这个人很可怜,因为它说在关联文件描述符上发生挂断时就EPOLLHUP
来了,而没有说明“挂断”是什么意思——对等方做了什么?发送了哪些数据包?另一篇文章只是进一步混淆了事情,对我来说似乎完全错误。
我的实验表明EPOLLHUP
,一旦 EOF(FIN 数据包)双向交换,即双方发出shutdown(SHUT_WR)
. 它与 无关SHUT_RD
,我从不打电话。也无关close
。就数据包而言,我怀疑EPOLLHUP
主机发送的 FIN 的 ack 引发了这个问题,即终止发起者在 4 次关闭握手的第 3 步中引发了这个事件,而对等方在第 4 步引发了这个事件(见这里)。如果得到确认,那就太好了,因为它填补了我一直在寻找的空白,即如何在没有 LINGER 的情况下轮询非阻塞套接字以获取最终 ack。它是否正确?
(注意:我正在使用 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 上引发。
解决方案
对于这类问题,请使用来源!在其他有趣的评论中,有这样一段文字:
EPOLLHUP
是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; 博士;你是对的,这个标志只在读写同时关闭时发送(我认为对等端关闭写入等于我关闭读取)。或者当连接关闭时,当然。
更新:通过更详细地阅读源代码,这些是我的结论。
关于shutdown
:
- Doing
shutdown(SHUT_WR)
发送一个FIN
并用 . 标记套接字SEND_SHUTDOWN
。 - Doing
shutdown(SHUT_RD)
什么都不发送,并用RCV_SHUTDOWN
. - 收到 a
FIN
标记套接字RCV_SHUTDOWN
。
关于epoll
:
- 如果套接字标有
SEND_SHUTDOWN
andRCV_SHUTDOWN
,poll
将返回EPOLLHUP
。 - 如果套接字被标记
RCV_SHUTDOWN
,poll
将返回EPOLLRDHUP
。
所以HUP
事件可以被解读为:
EPOLLRDHUP
: 你已经收到FIN
或者你已经打电话了shutdown(SHUT_RD)
。无论如何,您的读取半套接字被挂起,也就是说,您将不再读取数据。EPOLLHUP
: 你挂了两个半插座。读取半套接字就像上一点一样,对于发送半套接字你做了类似的事情shutdown(SHUT_WR)
。
要完成正常关机,我会这样做:
shutdown(SHUT_WR)
发送一个FIN
并标记发送数据的结束。- 等待对等方通过轮询来做同样的事情,直到你得到一个
EPOLLRDHUP
. - 现在您可以优雅地关闭套接字了。
PS:关于您的评论:
可写是违反直觉的,因为写作的一半是封闭的
如果您了解epoll
not as ready但 as will not block的输出,实际上是可以预期的。也就是说,如果你得到EPOLLOUT
你有保证调用write()
不会阻塞。当然,之后shutdown(SHUT_WR)
,write()
将立即返回。
推荐阅读
- javascript - 使用代理请求
- html - HTML 中的文件输入接受 .pdf 格式
- angular - Mat-DatePicker - 显示格式 dd/mm/yyyy with useUtc: true
- node.js - FCM 检查属性是否为零(node.js)
- dart - 如何在 AppBar 下方制作 2 条颜色线?
- python - 具有奇数但相等数量的 x 和 y 条目的 pcolormesh 的行为不像相等、相等数量的 x 和 y 条目
- java - 单击管理员按钮时显示自动售货机库存
- node.js - K8s pod 内存高于进程要求
- python - 从目录复制到 USB
- html - 如何水平和对角切割div