關於epoll的問題很早就像寫文章講講自己的看法,但是由於ffrpc一直沒有完工,所以也就拖下來了。Epoll主要在服務器編程中使用,本文主要探討服務器程序中epoll的使用技巧。Epoll一般和異步io結合使用,故本文討論基於以下應用場合:
Epoll是為異步io操作而設計的,epoll中IO事件被分為read事件和write事件,如果大家對於linux的驅動模塊或者linux io 模型有接觸的話,就會理解起來更容易。Linux中IO操作被抽象為read、write、close、ctrl幾個操作,所以epoll只提供read、write、error事件,是和linux的io模型是統一的。
為什麼要了解epoll的io模型呢,本文認為,某些情況下epoll操作的代碼的復雜性是由於代碼中的模型(或者類設計)與epoll io模型不匹配造成的。換句話說,如果我們的編碼模型和epoll io模型匹配,那麼非阻塞socket的編碼就會很簡單、清晰。
按照epoll模型構建的類關系為:
typedef ~ socket_fd_t socket() = handle_epoll_read() = handle_epoll_write() = handle_epoll_del() = close() = i = , nfds = = (nfds < && EINTR === (i = ; i < nfds; ++& cur_ev =* fd_ptr = (fd_i* (cur_ev.data.ptr == ) ( == (cur_ev.events & (EPOLLIN |->(cur_ev.events &-> (cur_ev.events & (EPOLLERR |->(nfds >=
先簡單比較一下level trigger 和 edge trigger 模式的不同。
讓我們換一個角度來理解ET模式,事實上,epoll的ET模式其實就是socket io完全狀態機。
當socket由不可讀變成可讀時,epoll的ET模式返回read 事件。對於read 事件,開發者需要保證把讀取緩沖區數據全部讀出,man epoll可知:
示例代碼
nread = = ::read(m_fd, recv_buffer, (recv_buffer) - (nread > = ->handle_read( (nread < ((recv_buffer) - ; ( == nread) -> - (errno == (errno ==-> -(
需要讀者注意的是,socket模式是可寫的,因為發送緩沖區初始時空的。故應用層有數據要發送時,直接調用write系統調用發送數據,若write系統調用返回EWouldBlock則表示socket變為不可寫,或者write系統調用返回的數值小於傳入的buffer參數的大小,這時需要把未發送的數據暫存在應用層待發送列表中,等待epoll返回write事件,再繼續發送應用層待發送列表中的數據,同樣若應用層待發送列表中的數據沒有一次性發完,那麼繼續等待epoll返回write事件,如此循環往復。所以可以反推得到如下結論,若應用層待發送列表有數據,則該socket一定是不可寫狀態,那麼這時候要發送新數據直接追加到待發送列表中。若待發送列表為空,則表示socket為可寫狀態,則可以直接調用write系統調用發送數據。總結如下:
示例代碼:
socket_impl_t::send_impl( & buff_ = ( == is_open() || m_sc->check_pre_send( ( == ret = (ret < -> (ret > m_sc->handle_write_completed( ret = ( == is_open() || == & msg == (ret < -> - (ret > ( ==->handle_write_completed(
LT模式主要是讀操作比較簡單,但是對於ET模式並沒有優勢,因為將讀取緩沖區數據全部讀出並不是難事。而write操作,ET模式則流程非常的清晰,按照完全狀態機來理解和實現就變得非常容易。而LT模式的write操作則復雜多了,要頻繁的維護epoll的wail列表。
在代碼編寫時,把epoll ET當成狀態機,當socket被創建完成(accept和connect系統調用返回的socket)時加入到epoll列表,之後就不用在從中刪除了。為什麼呢?man epoll中的FAQ告訴我們,當socket被close掉後,其自動從epoll中刪除。對於監聽socket簡單說幾點注意事項:
示例代碼:
= new_fd = - ((new_fd = ::accept(m_listen_fd, ( sockaddr *)&addr, &addrlen)) == - (errno == (errno == EINTR || errno == EMFILE || errno == ECONNABORTED || errno == ENFILE ||== EPERM || errno == ENOBUFS || errno ==); m_epoll->mod_fd( -* socket =-> (
GitHub :https://github.com/fanchy/FFRPC
ffrpc 介紹: http://www.cnblogs.com/zhiranok/p/ffrpc_summary.html