TCP:何时生成EPOLLHUP? [英] TCP: When is EPOLLHUP generated?

查看:104
本文介绍了TCP:何时生成EPOLLHUP?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

另请参阅此问题,未作答现在.

Also see this question, unanswered as of now.

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

There is a lot of confusion about EPOLLHUP, even in the man and Kernel docs. People seem to believe it is returned when polling on a descriptor locally closed for writing, i.e. shutdown(SHUT_WR), i.e. the same call that causes an EPOLLRDHUP at the peer. But this is not true, in my experiments I get EPOLLOUT, and no EPOLLHUP, after shutdown(SHUT_WR) (yes, it's counterintuitive to get writable, as the writing half is closed, but this is not the main point of the question).

man 很差,因为它说是在关联文件描述符上发生挂断时出现的,而没有说挂断"是什么意思-对方做了什么?发送了什么数据包? 这另一篇文章只是使事情更加混乱,对我来说似乎是完全错误的.

The man is poor, because it says EPOLLHUP comes when Hang up happened on the associated file descriptor, without saying what "hang up" means - what did the peer do? what packets were sent? This other article just confuses things further and seems outright wrong to me.

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

My experiments show EPOLLHUP arrives once EOF (FIN packets) are exchanged both ways, i.e. once both sides issue shutdown(SHUT_WR). It has nothing to do with SHUT_RD, which I never call. Also nothing to do with close. In terms of packets, I have the suspicion that EPOLLHUP is raised on the ack of the hosts' sent FIN, i.e. the termination initiator raises this event in step 3 of the 4-way shutdown handshake, and the peer, in step 4 (see here). If confirmed, this is great, because it fills a gap that I've been looking for, namely how to poll non-blocking sockets for the final ack, without LINGER. Is this correct?

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

(note: I'm using ET, but I don't think it's relevant for this)

代码在框架中,我提取了其中的内容,但TcpSocket::createListenerTcpSocket::connectTcpSocket::accept例外,它们可以满足您的期望(此处未显示).

The code being in a framework, I extracted the meat of it, with the exception of TcpSocket::createListener, TcpSocket::connect and TcpSocket::accept, which do what you'd expect (not shown here).

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是什么意思以外,没有更好的方法来重述该问题.我认为文档很差,而其他信息位置(例如此处

There is not better way to rephrase the question, other than, what does EPOLLHUP mean? I made the case that documentation is poor, and information in other places (e.g. here and here) is wrong or useless.

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

推荐答案

对于此类问题,

For these kind of questions, use the source! Among other interesting comments, there is this text:

EPOLLHUP UNMASKABLE 事件(...).这意味着在我们收到EOF之后,poll总是立即返回,使得在状态CLOSE_WAIT上的write()上的poll()不可能.很明显的一种解决方案---仅当在两个方向上都已制作shutdown时,才设置EPOLLHUP.

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的唯一代码:

And then the only code that sets EPOLLHUP:

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

SHUTDOWN_MASK等于RCV_SHUTDOWN |SEND_SHUTDOWN.

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

TL; DR; You are right, this flag is only sent when the shutdown has been both for read and write (I reckon that the peer shutdowning the write equals to my shutdowning the read). Or when the connection is closed, of course.

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

UPDATE: From reading the source code with more detail, these are my conclusions.

关于shutdown:

  1. 执行shutdown(SHUT_WR)会发送FIN并用SEND_SHUTDOWN标记套接字.
  2. 执行shutdown(SHUT_RD)不会发送任何内容,并用RCV_SHUTDOWN标记套接字.
  3. 接收FIN会将套接字标记为RCV_SHUTDOWN.
  1. Doing shutdown(SHUT_WR) sends a FIN and marks the socket with SEND_SHUTDOWN.
  2. Doing shutdown(SHUT_RD) sends nothing and marks the socket with RCV_SHUTDOWN.
  3. Receiving a FIN marks the socket with RCV_SHUTDOWN.

关于epoll:

  1. 如果套接字上标有SEND_SHUTDOWNRCV_SHUTDOWN,则poll将返回EPOLLHUP.
  2. 如果套接字标记有RCV_SHUTDOWN,则poll将返回EPOLLRDHUP.
  1. If the socket is marked with SEND_SHUTDOWN and RCV_SHUTDOWN, poll will return EPOLLHUP.
  2. If the socket is marked with RCV_SHUTDOWN, poll will return EPOLLRDHUP.

因此,HUP事件可以理解为:

So the HUP events can be read as:

  1. EPOLLRDHUP:您已收到FIN或已致电shutdown(SHUT_RD).在任何情况下,您的读取半插槽都处于挂起状态,也就是说,您将不再读取任何数据.
  2. EPOLLHUP:两个半插座都挂了.阅读半插口就像上一点,对于发送半插口,您做了类似shutdown(SHUT_WR)的操作.
  1. EPOLLRDHUP: you have received FIN or you have called shutdown(SHUT_RD). In any case your reading half-socket is hung, that is, you will read no more data.
  2. EPOLLHUP: you have both half-sockets hung. The reading half-socket is just like the previous point, For the sending half-socket you did something like shutdown(SHUT_WR).

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

To complete a a graceful shutdown I would do:

  1. 执行shutdown(SHUT_WR)以发送FIN并标记发送数据的结束.
  2. 等待对等方进行轮询,直到获得EPOLLRDHUP.
  3. 现在您可以优雅地关闭套接字了.
  1. Do shutdown(SHUT_WR) to send a FIN and mark the end of sending data.
  2. Wait for the peer to do the same by polling until you get a EPOLLRDHUP.
  3. Now you can close the socket with grace.

PS :关于您的评论:

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

it's counterintuitive to get writable, as the writing half is closed

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

It is actually expected if you understand the output of epoll not as ready but as will not block. That is, if you get EPOLLOUT you have the guarantee that calling write() will not block. And certainly, after shutdown(SHUT_WR), write() will return immediately.

这篇关于TCP:何时生成EPOLLHUP?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆