- r - 以节省内存的方式增长 data.frame
- ruby-on-rails - ruby/ruby on rails 内存泄漏检测
- android - 无法解析导入android.support.v7.app
- UNIX 域套接字与共享内存(映射文件)
另见 this question ,截至目前尚未答复。
关于 EPOLLHUP
存在很多混淆,甚至在 man
和内核文档中也是如此。人们似乎相信它在轮询描述符时返回 本地关闭写入,即 shutdown(SHUT_WR)
,即导致 EPOLLRDHUP
在同行。但这不是真的,在我的实验中,在
shutdown(SHUT_WR)
之后,我得到了 EPOLLOUT
,没有 EPOLLHUP
(是的,这是违反直觉的可写,因为写的那一半是封闭的,但这不是问题的重点)。
man很差,因为它说
EPOLLHUP
当 挂断发生在关联的文件描述符上 时,没有说明“挂断”是什么意思——对方做了什么?发送了什么数据包? This other article只是进一步混淆了事情,对我来说似乎是完全错误的。
我的实验表明,一旦 EOF(FIN 数据包)双向交换,即一旦双方发出
shutdown(SHUT_WR)
,EPOLLHUP
就会到达。它与 SHUT_RD
无关,我从不调用它。也与 close
无关。在数据包方面,我怀疑 EPOLLHUP
是在主机发送的 FIN 的 ack 上引发的,即终止发起者在 4 次关闭握手的第 3 步引发此事件,并且peer,在第 4 步中(参见 here )。如果得到证实,那就太好了,因为它填补了我一直在寻找的空白,即如何在没有 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 是什么意思之外,没有更好的方式来改写这个问题?我提出了 documentation很差,其他地方的信息(例如 here 和 here )是错误的或无用的。
注意:为了考虑问题的回答,我想确认在两个方向的最终 FIN-ACK 上都引发了 EPOLLHUP。
最佳答案
此类问题,use the source !在其他有趣的评论中,有这段文字:
EPOLLHUP
is UNMASKABLE event (...). It means that after we received EOF
, poll
always returns immediately, making impossible poll()
on write()
in state CLOSE_WAIT
. One solution is evident --- to set EPOLLHUP
if and only if shutdown
has been made in both directions.
然后是设置EPOLLHUP
的唯一代码:
if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
mask |= EPOLLHUP;
SHUTDOWN_MASK
等于 RCV_SHUTDOWN |SEND_SHUTDOWN
。
TL;博士;没错,这个标志只有在读和写都关闭时才会发送(我认为对等关闭写等于我关闭读)。当然,或者当连接关闭时。
更新:通过更详细地阅读源代码,这些是我的结论。
关于关闭
:
- 执行
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:关于您的评论:
it's counterintuitive to get writable, as the writing half is closed
如果您理解 epoll
的输出不是就绪,而是不会阻塞,这实际上是可以预期的。也就是说,如果您得到 EPOLLOUT
,您可以保证调用 write()
不会阻塞。当然,在 shutdown(SHUT_WR)
之后,write()
将立即返回。
关于linux - TCP:EPOLLHUP 是什么时候产生的?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52976152/
你们能给我提供一个很好的示例代码,使用 EPOLLHUP 处理死节点吗?我知道这是检测用户断开连接的信号,但不确定如何在代码中使用它。提前致谢。 最佳答案 您使用 EPOLLRDHUP 来检测对等关闭
我有几个线程,正在使用 epoll_wait() 等待同一个 epoll_fd。 epoll集中的描述符是这样设置的: struct epoll_event event; event.events =
我有一些代码在虚拟机上运行时由于某种原因出现异常。 初始化的顺序是: s_listen = socket(...) bind(s_listen, ...) epoll_ctl(epfd, EPOLL_
另见 this question ,截至目前尚未答复。 关于 EPOLLHUP 存在很多混淆,甚至在 man 和内核文档中也是如此。人们似乎相信它在轮询描述符时返回 本地关闭写入,即 shutdo
我正在阅读 redis 源代码,在 ae_epoll.c 中我发现了以下代码: static int aeApiPoll(aeEventLoop *eventLoop, struct timeval
第一次 epoll() 用户。我正在使用 epoll 编写一个简单的 HTTP 客户端,以连接到我控制的主机上的 nginx Web 服务器。 这是我正在查看的事件序列 我关闭了(服务器)主机 我启动
我是一名优秀的程序员,十分优秀!