![](/img/trans.png)
[英]epoll: What is appropriate behavior, when I get EPOLLOUT and EPOLLHUP in the same event?
[英]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::connect
, TcpSocket::connect
和TcpSocket::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上引發的。
對於這些問題,請使用來源 ! 在其他有趣的評論中,有這樣的文字:
EPOLLHUP
是UNMASKABLE事件(...)。 這意味着在我們收到EOF
,poll
總是立即返回,在狀態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
:
shutdown(SHUT_WR)
發送FIN
並使用SEND_SHUTDOWN
標記套接字。 shutdown(SHUT_RD)
不會發送任何內容,並使用RCV_SHUTDOWN
標記套接字。 FIN
使用RCV_SHUTDOWN
標記套接字。 關於epoll
:
SEND_SHUTDOWN
和RCV_SHUTDOWN
,則poll
將返回EPOLLHUP
。 RCV_SHUTDOWN
,則poll
將返回EPOLLRDHUP
。 所以HUP
事件可以讀作:
EPOLLRDHUP
:您已收到FIN
或已調用shutdown(SHUT_RD)
。 在任何情況下,您的讀取半插槽都會掛起,也就是說,您將不再讀取數據。 EPOLLHUP
:你有兩個半插座。 讀取半插槽就像前一點一樣,對於發送半插槽,你做了類似shutdown(SHUT_WR)
。 為了完成一個優雅的關閉我會做:
shutdown(SHUT_WR)
以發送FIN
並標記發送數據的結束。 EPOLLRDHUP
。 PS :關於你的評論:
寫作的一半是關閉的,因此可寫入是違反直覺的
實際上,如果你理解epoll
的輸出不是准備好但是不會阻止它 。 也就是說,如果你得到EPOLLOUT
你就可以保證調用write()
不會阻塞。 當然,在shutdown(SHUT_WR)
, write()
會立即返回。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.