簡體   English   中英

TCP:EPOLLHUP何時生成?

[英]TCP: When is EPOLLHUP generated?

還看到這個問題 ,截至目前尚未得到答復。

關於EPOLLHUP存在很多困惑,即使在man和Kernel docs中也是如此。 人們似乎相信在輪詢本地為寫入的描述符時返回它,即shutdown(SHUT_WR) ,即在對等端引起EPOLLRDHUP的同一調用。 但是,這是不正確的,在我的實驗,我得到EPOLLOUT ,並沒有EPOLLHUP ,后shutdown(SHUT_WR)是的,這是違反直覺的獲得可寫 ,因為寫一半被關閉,但這不是問題的要點)。

這個很窮,因為它說EPOLLHUP是在相關文件描述符掛起時發生的 ,而不是說“掛斷”意味着什么 - 對等者做了什么? 發送了什么數據包? 這篇文章只是讓事情進一步混淆,對我來說似乎完全錯了。

我的實驗表明EPOLLHUP到達一次EOF(FIN數據包)是一次雙方問題交換了兩種方式,即shutdown(SHUT_WR) 它與SHUT_RD ,我從不稱呼它。 也與close無關。 就數據包而言,我懷疑EPOLLHUP是在主機發送的FIN的ack上引發的,即終止發起者在步驟4中在4路關閉握手的步驟3和對等體中引發此事件(參見步驟4) 在這里 )。 如果確認,這很好,因為它填補了我一直在尋找的空白,即如何在沒有LINGER的情況下輪詢最終確認的非阻塞套接字。 它是否正確?

(注意:我正在使用ET,但我不認為這與此相關)

示例代碼和輸出。

代碼在框架中,我提取了它的內容,但TcpSocket::createListener TcpSocket::connectTcpSocket::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是什么意思之外,還沒有更好的方法來重新解釋這個問題? 我認為文檔很差,其他地方(例如這里這里 )的信息是錯誤或無用的。

注意:要考慮Q回答,我想確認EPOLLHUP是在兩個方向的最終FIN-ACK上引發的。

對於這些問題,請使用來源 在其他有趣的評論中,有這樣的文字:

EPOLLHUPUNMASKABLE事件(...)。 這意味着在我們收到EOFpoll總是立即返回,在狀態CLOSE_WAIT write()上進行不可能的poll() 一個明顯的解決方案---當且僅當雙向shutdown時才設置EPOLLHUP

然后是設置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的輸出不是准備好但是不會阻止它 也就是說,如果你得到EPOLLOUT你就可以保證調用write()不會阻塞。 當然,在shutdown(SHUT_WR)write()會立即返回。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM