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

标签 linux tcp linux-kernel epoll epollet

另见 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/

相关文章:

linux - do_fast_gettimeoffset() 在 Linux 中的重要性

linux - 从 Linux 到 Mac 执行脚本

linux - 除了使用正则表达式之外,如何在 shell 中忽略或排除备份文件 [以 ~ 结尾的文件]?

python - TCP服务器无法在Python中正确接收数据

networking - tcp_probe 模块没有输出

linux - put_user() linux 内核

Linux 内核 - 等待队列

linux - 在较旧的 Linux Svrs (Redhat EL 5.7) 上使用最新的 GCC(4.8.1)

linux - id_rsa.pub 文件 SSH 错误 : invalid format

c++ - 如何在通过套接字发送之前正确分隔多个图像