天天看點

linux epoll 開發指南-【ffrpc源碼解析】

關于epoll的問題很早就像寫文章講講自己的看法,但是由于ffrpc一直沒有完工,是以也就拖下來了。Epoll主要在伺服器程式設計中使用,本文主要探讨伺服器程式中epoll的使用技巧。Epoll一般和異步io結合使用,故本文讨論基于以下應用場合:

  主要讨論伺服器程式中epoll的使用,主要涉及tcp socket的相關api。

  Tcp socket 為異步模式,包括socket的異步讀寫,以及監聽的異步操作。

  本文不會過多讨論API的細節,而是專注流程與設計。

Epoll是為異步io操作而設計的,epoll中IO事件被分為read事件和write事件,如果大家對于linux的驅動子產品或者linux io 模型有接觸的話,就會了解起來更容易。Linux中IO操作被抽象為read、write、close、ctrl幾個操作,是以epoll隻提供read、write、error事件,是和linux的io模型是統一的。

  當epoll通知read事件時,可以調用io系統調用read讀取資料

  當epoll通知write事件時,可以調用io系統調用write發送資料

  當error事件時,可以close回收資源

  Ctrl相關的接口則用來設定socket的非阻塞選項等。

為什麼要了解epoll的io模型呢,本文認為,某些情況下epoll操作的代碼的複雜性是由于代碼中的模型(或者類設計)與epoll io模型不比對造成的。換句話說,如果我們的編碼模型和epoll io模型比對,那麼非阻塞socket的編碼就會很簡單、清晰。

按照epoll模型建構的類關系為:

         先簡單比較一下level trigger 和 edge trigger 模式的不同。

  若資料可讀,epoll傳回可讀事件

  若開發者沒有把資料完全讀完,epoll會不斷通知資料可讀,直到資料全部被讀取。

  若socket可寫,epoll傳回可寫事件,而且是隻要socket發送緩沖區未滿,就一直通知可寫事件。

  優點是對于read操作比較簡單,隻要有read事件就讀,讀多讀少都可以。

  缺點是write相關操作較複雜,由于socket在空閑狀态發送緩沖區一定是不滿的,故若socket一直在epoll wait清單中,則epoll會一直通知write事件,是以必須保證沒有資料要發送的時候,要把socket的write事件從epoll wait清單中删除。而在需要的時候在加入回去,這就是LT模式的最複雜部分。

  若socket可讀,傳回可讀事件

  若開發者沒有把所有資料讀取完畢,epoll不會再次通知epoll read事件,也就是說存在一種隐患,如果開發者在讀到可讀事件時,如果沒有全部讀取所有資料,那麼可能導緻epoll在也不會通知該socket的read事件。(其實這個問題并沒有聽上去難,參見下文)。

  若發送緩沖區未滿,epoll通知write事件,直到開發者填滿發送緩沖區,epoll才會在下次發送緩沖區由滿變成未滿時通知write事件。

  ET模式下,隻有socket的狀态發生變化時才會通知,也就是讀取緩沖區由無資料到有資料時通知read事件,發送緩沖區由滿變成未滿通知write事件。

  缺點是epoll read事件觸發時,必須保證socket的讀取緩沖區資料全部讀完(事實上這個要求很容易達到)

  優點:對于write事件,發送緩沖區由滿到未滿時才會通知,若無資料可寫,忽略該事件,若有資料可寫,直接寫。Socket的write事件可以一直發在epoll的wait清單。Man epoll中我們知道,當向socket寫資料,傳回的值小于傳入的buffer大小或者write系統調用傳回EWouldBlock時,表示發送緩沖區已滿。

讓我們換一個角度來了解ET模式,事實上,epoll的ET模式其實就是socket io完全狀态機。

linux epoll 開發指南-【ffrpc源碼解析】

當socket由不可讀變成可讀時,epoll的ET模式傳回read 事件。對于read 事件,開發者需要保證把讀取緩沖區資料全部讀出,man epoll可知:

  Read系統調用傳回EwouldBlock,表示讀取緩沖區資料全部讀出

  Read系統調用傳回的數值小于傳入的buffer參數,表示讀取緩沖區全部讀出。

示例代碼

linux epoll 開發指南-【ffrpc源碼解析】

需要讀者注意的是,socket模式是可寫的,因為發送緩沖區初始時空的。故應用層有資料要發送時,直接調用write系統調用發送資料,若write系統調用傳回EWouldBlock則表示socket變為不可寫,或者write系統調用傳回的數值小于傳入的buffer參數的大小,這時需要把未發送的資料暫存在應用層待發送清單中,等待epoll傳回write事件,再繼續發送應用層待發送清單中的資料,同樣若應用層待發送清單中的資料沒有一次性發完,那麼繼續等待epoll傳回write事件,如此循環往複。是以可以反推得到如下結論,若應用層待發送清單有資料,則該socket一定是不可寫狀态,那麼這時候要發送新資料直接追加到待發送清單中。若待發送清單為空,則表示socket為可寫狀态,則可以直接調用write系統調用發送資料。總結如下:

  當發送資料時,若應用層待發送清單有資料,則将要發送的資料追加到待發送清單中。否則直接調用write系統調用。

  Write系統調用發送資料時,檢測write傳回值,若傳回數值>0且小于傳入的buffer參數大小,或傳回EWouldBlock錯誤碼,表示,發送緩沖區已滿,将未發送的資料追加到待發送清單

  Epoll傳回write事件後,檢測待發送清單是否有資料,若有資料,依次嘗試發送指導資料全部發送完畢或者發送緩沖區被填滿。

示例代碼:

  LT模式主要是讀操作比較簡單,但是對于ET模式并沒有優勢,因為将讀取緩沖區資料全部讀出并不是難事。而write操作,ET模式則流程非常的清晰,按照完全狀态機來了解和實作就變得非常容易。而LT模式的write操作則複雜多了,要頻繁的維護epoll的wail清單。

      在代碼編寫時,把epoll ET當成狀态機,當socket被建立完成(accept和connect系統調用傳回的socket)時加入到epoll清單,之後就不用在從中删除了。為什麼呢?man epoll中的FAQ告訴我們,當socket被close掉後,其自動從epoll中删除。對于監聽socket簡單說幾點注意事項:

  監聽socket的write事件忽略

  監聽socket的read事件表示有新連接配接,調用accept接受連接配接,直到傳回EWouldBlock。

  對于Error事件,有些錯誤是可以接受的錯誤,比如檔案描述符用光的錯誤

示例代碼:

繼續閱讀