TCP:何时生成EPOLLHUP?

时间:2018-10-24 18:56:44

标签: linux tcp linux-kernel epoll epollet

也请参见this question,目前尚未答复。

即使在EPOLLHUP和Kernel文档中,对man也有很多困惑。人们似乎认为,当轮询在本地关闭以进行写入的描述符(即shutdown(SHUT_WR),即在同位体上导致EPOLLRDHUP 的同一调用时,它会返回。 / em>。但这是不正确的,在我的实验中,EPOLLOUT之后我得到EPOLLHUP,而没有shutdown(SHUT_WR)(是的,写作可写是违反直觉的,一半是封闭的,但这不是问题的重点。

man很差,因为它说EPOLLHUP是在关联文件描述符上发生挂断时出现的,EPOLLHUP却没有说“挂断”是什么意思-同行吗?发送了什么数据包? This other article只是使事情更加混乱,对我来说似乎是完全错误的。

我的实验表明,shutdown(SHUT_WR)是在双方交换EOF(FIN数据包)后即到达双方SHUT_RD后到达的。它与close无关,我从未称呼它。也与EPOLLHUP无关。在数据包方面,我怀疑TcpSocket::createListener是在主机发送的FIN的确认信号上引发的,即终止发起方在4向关机握手的第3步中引发此事件,而对等方在步骤4(请参阅here)。如果得到确认,那就太好了,因为它填补了我一直在寻找的空白,即如何在不使用LINGER的情况下轮询非阻塞套接字以获得最终的确认。 这正确吗?

(注意:我正在使用ET,但我认为与此无关)

示例代码和输出。

代码在框架中,我提取了其中的内容,但TcpSocket::connectTcpSocket::acceptvoid 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?

输出:

<?php

$input = file_get_contents("input.json");

$json = json_decode($input, true);

$required_income = $json['required_income'];
$sms = array();
$index = 0;
$suma = 0;

function imoka($suma, $json, &$index, &$sms) {

    for($i = 3; $i >= 0; $i--){
        if($suma + $json['sms_list'][$i]['income'] <= $json['required_income']) {
            $sms[$index] = $json['sms_list'][$i]['income'];
            $index = $index + 1;
            return imoka($suma + $json['sms_list'][$i]['income'], $json, $index, $sms);
        }
    }
}

imoka($suma, $json, $index, $sms);

for($i = 0; $i < $index; $i++){
    echo $sms[$i] . '<br>';
}

?>

除了 EPOLLHUP是什么意思以外,没有更好的方法来重述该问题。我认为documentation很差,而其他地方(例如herehere)的信息是错误的或无用的。

注意:要考虑回答的Q,我想确认EPOLLHUP在两个方向的最终FIN-ACK上都升高。

1 个答案:

答案 0 :(得分:6)

对于此类问题,use the source!除其他有趣的评论外,还有以下文字:

  

EPOLLHUP UNMASKABLE 事件(...)。这意味着在我们收到EOF之后,poll总是立即返回,使得poll()上处于状态write()的{​​{1}}不可能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

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

关于epoll

  1. 如果套接字上标有SEND_SHUTDOWNRCV_SHUTDOWN,则poll将返回EPOLLHUP
  2. 如果套接字上标有RCV_SHUTDOWN,则poll将返回EPOLLRDHUP

因此HUP事件可以理解为:

  1. EPOLLRDHUP:您已收到FIN或已致电shutdown(SHUT_RD)。在任何情况下,您的读取半插槽都处于挂起状态,也就是说,您将不再读取任何数据。
  2. EPOLLHUP:两个插座都挂了。阅读半插口就像上一点,对于发送半插口,您做了类似shutdown(SHUT_WR)的事情。

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

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

PS :关于您的评论:

  

由于写作部分是封闭的,因此变得可写是违反直觉的

如果您理解epoll的输出不是 ready 而是不会阻塞,那么实际上是可以预期的。也就是说,如果您获得EPOLLOUT,则可以保证不会阻塞调用write()。当然,在shutdown(SHUT_WR)之后,write()将立即返回。