日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux epoll 开发指南-【ffrpc源码解析】

發布時間:2023/12/9 linux 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux epoll 开发指南-【ffrpc源码解析】 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

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

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

主要討論服務器程序中epoll的使用,主要涉及tcp socket的相關api。
Tcp socket 為異步模式,包括socket的異步讀寫,以及監聽的異步操作。
本文不會過多討論API的細節,而是專注流程與設計。
Epoll 的io模型
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模型構建的類關系為:

//! 文件描述符相關接口
typedef int socket_fd_t;
class fd_i
{
public:
virtual ~fd_i(){}

virtual socket_fd_t socket() = 0; virtual int handle_epoll_read() = 0; virtual int handle_epoll_write() = 0; virtual int handle_epoll_del() = 0;virtual void close() = 0;

};
int epoll_impl_t::event_loop()
{
int i = 0, nfds = 0;
struct epoll_event ev_set[EPOLL_EVENTS_SIZE];

do {nfds = ::epoll_wait(m_efd, ev_set, EPOLL_EVENTS_SIZE, EPOLL_WAIT_TIME);if (nfds < 0 && EINTR == errno){nfds = 0;continue;}for (i = 0; i < nfds; ++i){epoll_event& cur_ev = ev_set[i];fd_i* fd_ptr = (fd_i*)cur_ev.data.ptr;if (cur_ev.data.ptr == this)//! iterupte event{if (false == m_running){return 0;}//! 刪除那些已經出現error的socket 對象fd_del_callback();continue;}if (cur_ev.events & (EPOLLIN | EPOLLPRI)){fd_ptr->handle_epoll_read();}if(cur_ev.events & EPOLLOUT){fd_ptr->handle_epoll_write();}if (cur_ev.events & (EPOLLERR | EPOLLHUP)){fd_ptr->close();}}}while(nfds >= 0);return 0;

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

LT模式的特點是:
若數據可讀,epoll返回可讀事件
若開發者沒有把數據完全讀完,epoll會不斷通知數據可讀,直到數據全部被讀取。
若socket可寫,epoll返回可寫事件,而且是只要socket發送緩沖區未滿,就一直通知可寫事件。
優點是對于read操作比較簡單,只要有read事件就讀,讀多讀少都可以。
缺點是write相關操作較復雜,由于socket在空閑狀態發送緩沖區一定是不滿的,故若socket一直在epoll wait列表中,則epoll會一直通知write事件,所以必須保證沒有數據要發送的時候,要把socket的write事件從epoll wait列表中刪除。而在需要的時候在加入回去,這就是LT模式的最復雜部分。
ET模式的特點是:
若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完全狀態機。

先來看epoll中read 的狀態圖:

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

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

int socket_impl_t:: handle_epoll_read ()
{
if (is_open())
{
int nread = 0;
char recv_buffer[RECV_BUFFER_SIZE];
do
{
nread = ::read(m_fd, recv_buffer, sizeof(recv_buffer) - 1);
if (nread > 0)
{
recv_buffer[nread] = ‘\0’;
m_sc->handle_read(this, recv_buffer, size_t(nread));
if (nread < int(sizeof(recv_buffer) - 1))
{
break;//! equal EWOULDBLOCK
}
}
else if (0 == nread) //! eof
{
this->close();
return -1;
}
else
{
if (errno == EINTR)
{
continue;
}
else if (errno == EWOULDBLOCK)
{
break;
}
else
{
this->close();
return -1;
}
}
} while(1);
}
return 0;
}
再來看write 的狀態機:

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

當發送數據時,若應用層待發送列表有數據,則將要發送的數據追加到待發送列表中。否則直接調用write系統調用。
Write系統調用發送數據時,檢測write返回值,若返回數值>0且小于傳入的buffer參數大小,或返回EWouldBlock錯誤碼,表示,發送緩沖區已滿,將未發送的數據追加到待發送列表
Epoll返回write事件后,檢測待發送列表是否有數據,若有數據,依次嘗試發送指導數據全部發送完畢或者發送緩沖區被填滿。
示例代碼:

void socket_impl_t::send_impl(const string& src_buff_)
{
string buff_ = src_buff_;

if (false == is_open() || m_sc->check_pre_send(this, buff_)) {return; } //! socket buff is full, cache the data if (false == m_send_buffer.empty()) {m_send_buffer.push_back(buff_);return; }string left_buff; int ret = do_send(buff_, left_buff);if (ret < 0) {this ->close(); } else if (ret > 0) {m_send_buffer.push_back(left_buff); } else {//! send okm_sc->handle_write_completed(this); }

}
int socket_impl_t:: handle_epoll_write ()
{
int ret = 0;
string left_buff;

if (false == is_open() || true == m_send_buffer.empty()) {return 0; }do {const string& msg = m_send_buffer.front();ret = do_send(msg, left_buff);if (ret < 0){this ->close();return -1;}else if (ret > 0){m_send_buffer.pop_front();m_send_buffer.push_front(left_buff);return 0;}else{m_send_buffer.pop_front();} } while (false == m_send_buffer.empty());m_sc->handle_write_completed(this); return 0;

}
總結
  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事件,有些錯誤是可以接受的錯誤,比如文件描述符用光的錯誤
示例代碼:

int acceptor_impl_t::handle_epoll_read()
{
struct sockaddr_storage addr;
socklen_t addrlen = sizeof(addr);

int new_fd = -1; do {if ((new_fd = ::accept(m_listen_fd, (struct sockaddr *)&addr, &addrlen)) == -1){if (errno == EWOULDBLOCK){return 0;}else if (errno == EINTR || errno == EMFILE || errno == ECONNABORTED || errno == ENFILE ||errno == EPERM || errno == ENOBUFS || errno == ENOMEM){perror("accept");//! if too many open files occur, need to restart epoll eventm_epoll->mod_fd(this);return 0;}perror("accept");return -1;}socket_i* socket = create_socket(new_fd);socket->open(); } while (true); return 0;

}
GitHub :https://github.com/fanchy/FFRPC

ffrpc 介紹: http://www.cnblogs.com/zhiranok/p/ffrpc_summary.html

總結

以上是生活随笔為你收集整理的linux epoll 开发指南-【ffrpc源码解析】的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。