gpt4 book ai didi

linux - TCP:EPOLLHUP 是什么时候产生的?

转载 作者:IT王子 更新时间:2023-10-29 00:50:54 26 4
gpt4 key购买 nike

另见 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::createListenerTcpSocket::connectTcpSocket: :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很差,其他地方的信息(例如 herehere )是错误的或无用的。

注意:为了考虑问题的回答,我想确认在两个方向的最终 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;博士;没错,这个标志只有在读和写都关闭时才会发送(我认为对等关闭写等于我关闭读)。当然,或者当连接关闭时。

更新:通过更详细地阅读源代码,这些是我的结论。

关于关闭:

  1. 执行shutdown(SHUT_WR) 发送FIN 并用SEND_SHUTDOWN 标记套接字。
  2. 执行 shutdown(SHUT_RD) 不发送任何内容并使用 RCV_SHUTDOWN 标记套接字。
  3. 接收到 FIN 会用 RCV_SHUTDOWN 标记套接字。

关于epoll:

  1. 如果套接字标有SEND_SHUTDOWNRCV_SHUTDOWNpoll将返回EPOLLHUP
  2. 如果套接字标有RCV_SHUTDOWNpoll 将返回EPOLLRDHUP

所以 HUP 事件可以理解为:

  1. EPOLLRDHUP:您已收到 FIN 或已调用 shutdown(SHUT_RD)。在任何情况下,您的读取半套接字都会挂起,也就是说,您不会再读取任何数据。
  2. EPOLLHUP:您的两个半套接字都已挂起。读取半套接字就像上一点一样,对于发送半套接字,你做了类似shutdown(SHUT_WR)的事情。

要完成正常关机,我会这样做:

  1. 执行shutdown(SHUT_WR)发送一个FIN并标记发送数据结束。
  2. 等待对方通过轮询执行相同操作,直到您获得 EPOLLRDHUP
  3. 现在您可以优雅地关闭套接字了。

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/

26 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com