Epoll的EPOLLOUT事件的一些疑問?

Linux下的I/O復用epoll,我一直對這個EPOLLOUT事件有一些疑問
當我們調用epoll_wait函數返回時,我們應該可以獲得一個就緒事件的列表,然後我們可以遍歷這個列表,然後查看每一個就緒的文件描述符上發生的事件,然後針對不同的時間進行操作。
對於常用的EPOLLIN,EPOLLRDHUP我都理解,例如
在發生EPOLLIN的時候,如果是監聽socket上,那麼我們調用accept來接受新連接
如果是普通socket,那麼我們調用recv來接受新數據。
可是對於EPOLLOUT事件,其表示數據可寫。但是在伺服器一端來說,一般時我們接受客戶端的請求,然後處理請求,最後直接將處理結果調用send函數發送給客戶端,我實在沒有看出在這一步有任何需要在調用epoll_wait函數等待EPOLLOUT事件的必要,
或者說,我不是很了解在什麼情況之下調用epoll_wait函數後會觸發EPOLLOUT事件,以及當我們檢測到這個事件發生的時候我們應該如何處理這個事件。
等待高人解答呀


假設一個這樣的場景:
你需要將一個10G大小的文件返回給用戶,那麼你簡單send這個文件是不會成功的。
這個場景下,你send 10G的數據,send返回值不會是10G,而是大約256k,表示你只成功寫入了256k的數據。接著調用send,send就會返回EAGAIN,告訴你socket的緩衝區已經滿了,此時無法繼續send。
此時非同步程序的正確處理流程是調用epoll_wait,當socket緩衝區中的數據被對方接收之後,緩衝區就會有空閑空間可以繼續接收數據,此時epoll_wait就會返回這個socket的EPOLLOUT事件,獲得這個事件時,你就可以繼續往socket中寫出數據。

這裡有個簡單的例子,裡面包含了EPOLLOUT處理的詳細過程
handy/epoll.cc at master · yedf/handy · GitHub
還有epoll的ET模式下的EPOLLOUT處理過程
https://github.com/yedf/handy/blob/master/raw-examples/epoll-et.cc


處理EPOLLIN的時候,就可以往sockfd里寫了(如果需要的話),只不過這時候,套接字如果是非阻塞的,緩衝區寫滿了,返回EAGAIN , 然而判斷send(write/sendfile)返回值發現數據並沒有發完,想要接著把數據發出去,這時候就需要暫時記錄一下現場,包括fd,當前發送到buffer的哪個位元組了,什麼的(可以用一個結構記錄下來). 然後註冊EPOLLOUT事件,等待下一次觸發寫事件.這時要先刪除這個fd註冊的寫事件,之後找到fd對應的buffer(這個可以用map保存起來,以fd做鍵去找對應的結構),從原來的沒有發完的偏移處繼續發數據,發完了就刪掉這個map或者將struct里置空. 沒有發完就再註冊寫事件,記錄這次的位置到哪了.等待下一次觸發寫.


首先要理解send之後,數據只是去了緩衝區,而緩衝區滿了會觸發EAGAIN
EPOLLOUT說明緩衝區可寫了


EPOLLOUT事件表示fd的發送緩衝區可寫,在一次發送大量數據(超過發送緩衝區大小)的情況下很有用。要理解該事件的意義首先要清楚一下幾個知識:
1、多路分離器。多路分離器存在的意義在於可以同時監測多個fd的事件,便於單線程處理多個fd,epoll是眾多多路分離器的一種,類似的還有select、poll等。伺服器程序通常需要具備較高處理用戶並發的能力,使用多路分離器意味著可以用一個線程同時處理多個用戶並發請求。

2、非阻塞套接字。
2.1 阻塞。
在了解非阻塞之前先了解一下阻塞,阻塞指的是用戶態程序調用系統api進入內核態後,如果條件不滿足則被加入到對應的等待隊列中,直到條件滿足。比如:sleep 2s。在此期間線程得不到CPU調度,自然也就不會往下執行,表現的現象為線程卡在系統api不返回。
2.2 非阻塞。
非阻塞則相反,不論條件是否滿足都會立即返回到用戶態,線程的CPU資源不會被剝奪,也就意味著程序可以繼續往下執行。
2.3、高性能。
在一次發送大量數據(超過發送緩衝區大小)的情況下,如果使用阻塞方式,程序一直阻塞,直到所有的數據都寫入到緩衝區中。例如,要發送M位元組數據,套接字發送緩衝區大小為B位元組,只有當對端向本機返回ack表明其接收到大於等於M-B位元組時,才意味著所有的數據都寫入到緩衝區中。很明顯,如果一次發送的數據量非常大,比如M=10GB、B=64KB,則:1)一次發送過程中本機線程會在一個fd上阻塞相當長一段時間,其他fd得不到及時處理;2)如果出現發送失敗,無從得知到底有多少數據發送成功,應用程序只能選擇重新發送這10G數據,結合考慮網路的穩定性,只能呵呵;
總之,上述兩點都是無法接受的。因此,對性能有要求的伺服器一般不採用阻塞而採用非阻塞。

3、使用非阻塞套接字時的處理流程。
採用非阻塞套接字一次發送大量數據的流程:1)使勁往發送緩衝區中寫數據,直到返回不可寫;2)等待下一次緩衝區可寫;3)要發送的數據寫完;
其中2)可以有兩種方式:a)查詢式,程序不停地查詢是否可寫;b)程序去干其他的事情(多路分離器的優勢所在),等出現可寫事件後再接著寫;很明顯方式b)更加優雅。

4、EPOLLOUT事件的用途。
EPOLLOUT事件就是以事件的方式通知用戶程序,可以繼續往緩衝區寫數據了。


最後直接將處理結果調用send函數發送給客戶端

以 HTTP 伺服器為例,用戶請求了一個 4.7G 的 .mkv 文件……


當可以發送數據時,再發,效率比較高,不會被block或Eagain。


EPOLLOUT主要用於傳送大量數據時,如果一次無法將數據全部發送出去就需要將剩下的數據緩存起來,然後等待內核發送緩衝區可寫時再繼續發送。內核發送緩衝區可寫,就是通過觸發EPOLLOUT事件來告知用戶的。


那是你的機器太快啦,換個發包慢的機器,分分鐘讓你write也eagain


如果只發小數據的話,不用處理EPOLLOUT。把socket設成非阻塞的,收到EWOULDBLOCK / EAGAIN之類的,代表它對應的系統緩衝滿了,可以認為這個客戶端連接出問題了,把它關掉就好了。


推薦閱讀:

運維工程師必須掌握的基礎技能有哪些?
Linux 如何才能吸引軟體廠商為之開發軟體?
請問在kali linux 虛擬機安裝vm tools的具體步驟是怎樣? 謝謝。?

TAG:Linux | C編程語言 | epoll | Socket |