另请参阅this question,到目前为止尚未答复。

即使在EPOLLHUP和Kernel文档中,也有很多关于man的困惑。人们似乎相信在轮询本地关闭以进行写入的描述符时会返回该值,即shutdown(SHUT_WR),即在对等方导致EPOLLRDHUP的同一调用。但这是不正确的,在我的实验中,我在EPOLLOUT之后得到EPOLLHUP,而没有shutdown(SHUT_WR)(是的,由于写作部分是封闭的,所以获得可写是违反直觉的,但这不是问题的重点)。

man很差,因为它说EPOLLHUP是在关联文件描述符上发生挂断时出现的,而没有说“挂断”是什么意思-对方做了什么?发送了什么数据包? This other article进一步使事情变得困惑,对我来说似乎是完全错误的。

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

(注意:我正在使用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)是错误的或无用的。

注意:为了考虑所回答的Q,我想确认是否在两个方向的最终FIN-ACK上都产生了EPOLLHUP。

最佳答案

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



然后是设置EPOLLHUP的唯一代码:

if (sk->sk_shutdown == SHUTDOWN_MASK || state == TCP_CLOSE)
    mask |= EPOLLHUP;

等于SHUTDOWN_MASK等于RCV_SHUTDOWN |SEND_SHUTDOWN

TL; DR;没错,仅当读写同时关闭时才发送此标志(我认为对等的关闭写入等同于我关闭读取)。当然,或者当连接关闭时。

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

关于shutdown:
  • 进行shutdown(SHUT_WR)发送FIN并用SEND_SHUTDOWN标记套接字。
  • 进行shutdown(SHUT_RD)不会发送任何内容,并使用RCV_SHUTDOWN标记套接字。
  • 接收FIN将套接字标记为RCV_SHUTDOWN

  • 关于epoll:
  • 如果套接字上标有SEND_SHUTDOWNRCV_SHUTDOWN,则poll将返回EPOLLHUP
  • 如果套接字标记有RCV_SHUTDOWN,则poll将返回EPOLLRDHUP

  • 因此,HUP事件可以读取为:
  • EPOLLRDHUP:您已收到FIN或已致电shutdown(SHUT_RD)。无论如何,您的读取半插槽已挂起,也就是说,您将不再读取任何数据。
  • EPOLLHUP:两个 socket 都挂了。阅读半插口就像上一点,对于发送半插口,您做了类似shutdown(SHUT_WR)的操作。

  • 要完成正常关机,我将执行以下操作:
  • 执行shutdown(SHUT_WR)以发送FIN并标记发送数据的结束。
  • 等待对等方通过轮询进行相同操作,直到获得EPOLLRDHUP为止。
  • 现在,您可以使用宽限期关闭套接字。

  • PS :关于您的评论:



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

    10-07 21:45