Linux網絡編程之IO模型

本文基於IO訪問中存在的兩個階段詳細介紹了Linux產生的五種IO模型。
上篇文章回顧: 小米開源監控Open-Falcon收錄汽車之家貢獻的Win版Agent

同步與異步

同步是指一個任務的完成須要依賴另一個任務時,只有等待被依賴的任務完成後,依賴的任務才能算完成。緩存

異步是指不須要等待被依賴的任務完成,只是通知被依賴的任務要完成什麼工做,依賴的任務也當即執行,只要本身完成了整個任務就算完成了,異步通常使用狀態、通知和回調。bash

阻塞與非阻塞

阻塞是指調用結果返回以前,當前線程會被掛起,一直處於等待消息通知,不可以執行其餘業務。運維

非阻塞是指在不能馬上獲得結果以前,該函數不會阻塞當前線程,而會馬上返回。異步

五種IO模型

對於一次IO訪問,數據會先被拷貝到內核的緩衝區中,而後纔會從內核的緩衝區拷貝到應用程序的地址空間。須要經歷兩個階段:socket

1)準備數據函數

2)將數據從內核緩衝區拷貝到進程地址空間ui

因爲存在這兩個階段,Linux產生了下面五種IO模型。spa

阻塞IO

當用戶進程調用了recvfrom調用時,內核進入IO的第一個階段:準備數據(內核須要等待足夠的數據再拷貝),這個過程須要等待,用戶進程會被阻塞,等內核將數據準備好,而後拷貝到用戶地址空間,內核返回結果,用戶進程才從阻塞態進入就緒態。
Linux中,默認狀況下全部的socket都是阻塞的。線程

非阻塞IO

當用戶進程發出read操做時,若是kernel中的數據尚未準備好,那麼它並不會block用戶進程,而是馬上返回一個error。用戶進程判斷結果是一個error時,它就知道數據尚未準備好,因而它能夠再次發送read操做。一旦kernel中的數據準備好了,而且又再次收到了用戶進程的system call,那麼它立刻就將數據拷貝到了用戶內存,而後返回。3d

非阻塞IO模式下用戶進程須要不斷地詢問內核的數據準備好了沒有。

Linux下能夠經過設置socket使其變爲non-blocking。

IO多路複用

經過一種機制,一個進程能夠監視多個文件描述符(套接字描述符),一旦某個文件描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。這樣就不須要每一個用戶進程不斷的詢問內核數據準備好了沒有。

經常使用的IO多路複用方式有select、poll和epoll。

select

kernel會「監視」全部select負責的socket,當任何一個socket中的數據準備好了,select就會返回。這個時候用戶進程再調用read操做,將數據從kernel拷貝到用戶進程。

int select (int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);複製代碼

select 函數監視的文件描述符分3類,分別是writefds、readfds、和exceptfds。調用後select函數會阻塞,直到有描述副就緒(有數據可讀、可寫、或者有except),或者超時(timeout指定等待時間,若是當即返回設爲null便可),函數返回。當select函數返回後,能夠經過遍歷fdset,來找到就緒的描述符。

select的一個缺點在於單個進程可以監視的文件描述符的數量存在最大限制,在Linux上通常爲1024。

poll

poll使用一個 pollfd的指針實現。

int poll (struct pollfd *fds, unsigned int nfds, int timeout);複製代碼

pollfd結構包含了要監視的event和發生的event

struct pollfd { int fd; /* file descriptor */short events; /* requested events to watch */short revents; /* returned events witnessed */ };複製代碼

和select函數同樣,poll返回後,須要遍歷pollfd來獲取就緒的描述符。poll沒有監聽最大數量限制。

epoll

epoll使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,採用監聽回調的機制,這樣在用戶空間和內核空間的copy只需一次,避免再次遍歷就緒的文件描述符列表。

epoll的操做過程須要三個接口:

int epoll_create(int size); int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);複製代碼

int epoll_create(int size):

建立一個epoll的句柄,size用來告訴內核這個監聽的數目一共有多大。


int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event):

對指定描述符fd執行op操做。

- epfd:是epoll_create()的返回值。

- op:表示op操做,用三個宏來表示:添加EPOLL_CTL_ADD,刪除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分別添加、刪除和修改對fd的監聽事件。

- fd:是須要監聽的fd(文件描述符)

- epoll_event:是告訴內核須要監聽什麼事,struct epoll_event結構以下:

struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ }; //events能夠是如下幾個宏的集合:EPOLLIN :表示對應的文件描述符能夠讀(包括對端SOCKET正常關閉); EPOLLOUT:表示對應的文件描述符能夠寫; EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這裏應該表示有帶外數據到來); EPOLLERR:表示對應的文件描述符發生錯誤; EPOLLHUP:表示對應的文件描述符被掛斷; EPOLLET: 將EPOLL設爲邊緣觸發(Edge Triggered)模式,這是相對於水平觸發(Level Triggered)來講的。 EPOLLONESHOT:只監聽一次事件,當監聽完此次事件以後,若是還須要繼續監聽這個socket的話,須要再次把這個socket加入到EPOLL隊列裏複製代碼


int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout):

等待epfd上的io事件,最多返回maxevents個事件。

參數events用來從內核獲得事件的集合,maxevents告以內核這個events有多大,這個maxevents的值不能大於建立epoll_create()時的size,參數timeout是超時時間(毫秒,0會當即返回,-1將不肯定,也有說法說是永久阻塞)。該函數返回須要處理的事件數目,如返回0表示已超時。

epoll的兩種工做模式

LT(level trigger,水平觸發)模式:當epoll_wait檢測到描述符就緒,將此事件通知應用程序,應用程序能夠不當即處理該事件。下次調用epoll_wait時,會再次響應應用程序並通知此事件。LT模式是默認的工做模式。

LT模式同時支持阻塞和非阻塞socket。

ET(edge trigger,邊緣觸發)模式:當epoll_wait檢測到描述符就緒,將此事件通知應用程序,應用程序必須當即處理該事件。若是不處理,下次調用epoll_wait時,不會再次響應應用程序並通知此事件。

ET是高速工做方式,只支持非阻塞socket。ET模式減小了epoll事件被重複觸發的次數,所以效率要比LT模式高。

異步IO

用戶進程發起read操做以後,馬上就能夠開始去作其它的事。內核收到一個異步IO read以後,會馬上返回,不會阻塞用戶進程。內核會等待數據準備完成,而後將數據拷貝到用戶內存,當這一切都完成以後,內核會給用戶進程發送一個signal,告訴它read操做完成了。

信號驅動IO

內核文件描述符就緒後,經過信號通知用戶進程,用戶進程再經過系統調用讀取數據。此方式屬於同步IO,由於實際讀取數據到用戶進程緩存的工做仍然是由用戶進程本身負責的。


本文首發於公衆號「小米運維」,點擊查看原文

相關文章
相關標籤/搜索