開源網絡庫boost.asio,libevent,mongoose學習記錄以及多線程模式的實現

IO操做

IO操做包括兩個部分:html

      等待數據準備好:對於一個套接口上的操做,這一步驟關係到數據從網絡到達,並將其複製到內核的某個緩衝區。linux

      將數據從內核緩衝區複製到進程緩衝區。nginx

同步IO和異步IO

同步IO致使請求進程阻塞,直到IO操做完成;c++

異步IO不致使請求進程阻塞。git

IO多路複用(select,poll,epoll)

 使用c++進行網絡開發,socket幾乎是一切技術的基石。最簡單的socket套接字用法就是listen後調用accept阻塞等待客戶端的鏈接,每當有一個鏈接到來的時候,建立子套接字對鏈接進行處理,由於網絡傳輸中最影響性能的是IO的讀寫,這樣的話若是短期內有多個鏈接請求,socket只能一個一個的去處理。前一個IO進行讀寫的時候,由於進程阻塞,accept是無法準備接收下一個鏈接的。web

               這種狀況有個簡單的解決方式,就是accept返回後,在新的線程中進行數據收發,這樣主線程裏面的accept能夠繼續接收下一個客戶端鏈接請求。這種方式能夠同時處理多個IO,可是會產生建立銷燬進程的開銷,特別是在短任務的狀況下開銷會更大。服務器

              IO多路複用能夠在單個線程內監聽多個socket鏈接請求,此模型用到select和poll函數,這兩個函數也會使進程阻塞,select先阻塞,有活動套接字才返回,可是和阻塞I/O不一樣的是,這兩個函數能夠同時阻塞多個I/O操做,並且能夠同時對多個讀操做,多個寫操做的I/O函數進行檢測,直到有數據可讀或可寫(就是監聽多個socket)。select被調用後,進程會被阻塞,內核監視全部select負責的socket,當有任何一個socket的數據準備好了,select就會返回套接字可讀,咱們就能夠調用recvfrom處理數據正由於阻塞I/O只能阻塞一個I/O操做,而I/O複用模型可以阻塞多個I/O操做,因此才叫作多路複用。網絡

              select,poll,epoll都是IO多路複用的機制。I/O多路複用就經過一種機制,能夠監視多個描述符,一旦某個描述符就緒(通常是讀就緒或者寫就緒),可以通知程序進行相應的讀寫操做。但select,poll,epoll本質上都是同步I/O由於他們都須要在讀寫事件就緒後本身負責進行讀寫,也就是說這個讀寫過程是阻塞的。

多線程

select

select系統調用的目的是:在一段指定時間內,監聽用戶感興趣的文件描述符上的可讀、可寫和異常事件。poll和select應該被歸類爲這樣的系統 調用:它們能夠阻塞地同時探測一組鏈接請求,直至某一個設備觸發了事件或者超過了指定的等待時間——也就是說它們的職責不是作IO,而是幫助調用者尋找當前就緒的設備。 併發

IO多路複用模型是創建在內核提供的多路分離函數select基礎之上的,使用select函數能夠避免同步非阻塞IO模型中輪詢等待的問題。

select的優勢:能夠在一個線程上同時監聽多個鏈接請求。

select的幾大缺點:

(1)每次調用select,都須要把fd集合(文件描述符)從用戶態拷貝到內核態,這個開銷在fd不少時會很大

(2)同時每次調用select都須要在內核遍歷傳遞進來的全部fd,這個開銷在fd不少時也很大

(3)select支持的文件描述符數量過小了,默認是1024

poll:
  poll的實現和select很是類似,只是描述fd集合的方式不一樣,poll使用pollfd結構而不是select的fd_set結構,poll支持的文件描述符數量沒有限制,其餘的都差很少。

epoll:
  epoll既然是對select和poll的改進,就應該能避免上述的三個缺點。那epoll都是怎麼解決的呢?在此以前,咱們先看一下epoll和select和poll的調用接口上的不一樣,select和poll都只提供了一個函數——select或者poll函數。而epoll提供了三個函數,epoll_create,epoll_ctl和epoll_wait,epoll_create是建立一個epoll句柄;epoll_ctl是註冊要監聽的事件類型;epoll_wait則是等待事件的產生。

  對於第一個缺點,epoll的解決方案在epoll_ctl函數中。每次註冊新的事件到epoll句柄中時(在epoll_ctl中指定EPOLL_CTL_ADD),會把全部的fd拷貝進內核,而不是在epoll_wait的時候重複拷貝。epoll保證了每一個fd在整個過程當中只會拷貝一次。

  對於第二個缺點,epoll的解決方案不像select或poll同樣每次都把current輪流加入fd對應的設備等待隊列中,而只在epoll_ctl時把current掛一遍(這一遍必不可少)併爲每一個fd指定一個回調函數,當設備就緒,喚醒等待隊列上的等待者時,就會調用這個回調函數,而這個回調函數會把就緒的fd加入一個就緒鏈表)。epoll_wait的工做實際上就是在這個就緒鏈表中查看有沒有就緒的fd(利用schedule_timeout()實現睡一會,判斷一會的效果,和select中的實現是相似的)。

  對於第三個缺點,epoll沒有這個限制,它所支持的FD上限是最大能夠打開文件的數目,這個數字通常遠大於2048,舉個例子,在1GB內存的機器上大約是10萬左右,具體數目在linux上能夠cat /proc/sys/fs/file-max察看,通常來講這個數目和系統內存關係很大。

select,poll實現須要本身不斷輪詢全部fd集合,直到設備就緒,期間可能要睡眠和喚醒屢次交替。而epoll其實也須要調用epoll_wait不斷輪詢就緒鏈表,期間也可能屢次睡眠和喚醒交替,可是它是設備就緒時,調用回調函數,把就緒fd放入就緒鏈表中,並喚醒在epoll_wait中進入睡眠的進程。雖然都要睡眠和交替,可是select和poll在「醒着」的時候要遍歷整個fd集合,而epoll在「醒着」的時候只要判斷一下就緒鏈表是否爲空就好了,這節省了大量的CPU時間。這就是回調機制帶來的性能提高。

select,poll每次調用都要把fd集合從用戶態往內核態拷貝一次,而且要把current往設備等待隊列中掛一次,而epoll只要一次拷貝,並且把current往等待隊列上掛也只掛一次(在epoll_wait的開始,注意這裏的等待隊列並非設備等待隊列,只是一個epoll內部定義的等待隊列)。這也能節省很多的開銷。

 

異步IO(iocp,epoll)
        epoll屬於IO多路複用,它只是模擬實現了異步IO的功能。  「真正」的異步IO須要操做系統更強的支持。在IO多路複用模型中,事件循環將文件句柄的狀態事件通知給用戶線程,由用戶線程自行讀取數據、處理數據。而在異步IO模型中,當用戶線程收到通知時,數據已經被內核讀取完畢,並放在了用戶線程指定的緩衝區內,內核在IO完成後通知用戶線程直接使用便可。

          IOCP全稱 IO完成端口。它是一種WIN32的網絡I/O模型,既包括了網絡鏈接部分,也負責了部分的I/O操做功能,用於方便咱們控制有併發性的網絡I/O操做。它有以下特色:
1:它是一個WIN32內核對象,因此沒法運行於linux。
2:它本身負責維護了工做線程池,同時也負責了I/O通道的內存池。
3:它本身實現了線程的管理以及I/O請求通知,最小化的作到了線程的上下文切換。
4:它本身實現了線程的優化調度,提升了CPU和內存緩衝的使用率。

           真正意義上的異步IO嚴格的來講只有IOCP,可是epoll也模擬實現了異步IO的功能。

        epoll 由於採用 mmap的機制, 使得 內核socket buffer和 用戶空間的 buffer共享, 從而省去了 socket data copy, 這也意味着, 當epoll 回調上層的 callback函數來處理 socket 數據時, 數據已經從內核層 "自動" 到了用戶空間, 雖然和 用poll 同樣, 用戶層的代碼還必需要調用 read/write, 但這個函數內部實現所觸發的深度不一樣了。

        poll 時, poll通知用戶空間的Appliation時, 數據還在內核空間, 因此Appliation調用 read API 時, 內部會作 copy socket data from kenel space to user space。

       而用 epoll 時, epoll 通知用戶空間的Appliation時, 數據已經在用戶空間, 因此 Appliation調用 read API 時, 只是讀取用戶空間的 buffer, 沒有 kernal space和 user space的switch了。

IOCP和Epoll之間的異同。
異:
1:IOCP是WINDOWS系統下使用。Epoll是Linux系統下使用。
2:IOCP是IO操做完畢以後,經過Get函數得到一個完成的事件通知。
Epoll是當你但願進行一個IO操做時,向Epoll查詢是否可讀或者可寫,若處於可讀或可寫狀態後,Epoll會經過epoll_wait進行通知。
3:IOCP封裝了異步的消息事件的通知機制,同時封裝了部分IO操做。但Epoll僅僅封裝了一個異步事件的通知機制,並不負責IO讀寫操做,可是由於mmap機制,epoll其實已經省去了IO操做的第二部分(將數據從內核緩衝區複製到進程緩衝區)。
4: 基於上面的描述,咱們能夠知道Epoll不負責IO操做,因此它只告訴你當前可讀可寫了,而且將協議讀寫緩衝填充,由用戶去讀寫控制,此時咱們能夠作出額 外的許多操做。IOCP則直接將IO通道里的讀寫操做都作完了才通知用戶,當IO通道里發生了堵塞等情況咱們是沒法控制的。

同:
1:它們都是異步的事件驅動的網絡模型。
2:它們均可以向底層進行指針數據傳遞,當返回事件時,除可通知事件類型外,還能夠通知事件相關數據(通知到來時IO已經徹底完成)。

還有一個概念,邊緣觸發和水平觸發,可瞭解也可不瞭解。

開源網絡庫boost.asio,libevent,mongoose學習記錄以及多線程模式的實現

 

Libevent
libevent是一個輕量級的基於事件驅動的高性能的開源網絡庫,而且支持多個平臺,對多個平臺的I/O複用技術進行了封裝。在linux下面集成了poll,epoll;在window下面集成了select,舊版本沒有集成IOCP,因此在window上面 libevent的性能並不優秀,新版本的libevent也集成了IOCP,可是隻做爲網絡開發庫的話,libevent的綜合評價仍是不如boost.asio。

對於網絡通訊Libevent和boost.asio功能相近,可是asio綜合性能更好,並且集成到了boost裏面,只須要引入頭文件便可使用。因此須要開發高性能web服務的時候,推薦使用asio,在這裏就再也不臃述libevent。

(如對libevent有興趣可參考https://www.cnblogs.com/nearmeng/p/4043548.html)

Boost.asio

Boost.Asio是利用當代C++的先進方法,跨平臺,異步I/O模型的C++網絡庫,Windows下使用IOCP,Linux下使用epoll。下面是一個asio使用多線程異步IO的網絡服務器demo。

 

通常來講,高性能web服務器的io和業務處理都是分離的,服務器開銷主要在io上。由於asio已經實現了異步io,因此若是隻是做爲轉發服務器,只使用一個線程處理便可(多線程有線程切換的開銷)。好比nginx就是使用單線程異步io的服務器。

可是若是io和業務處理沒有分離,好比上例中處理post的時候 sleep了5秒(假設業務處理佔用了5秒的時間),若是使用單線程那就會阻塞客戶端新的請求(其實請求不會阻塞,只是asio的事件回調函數被阻塞了)。在這種狀況下就須要使用如上例的多線程處理。

關於boost.asio介紹不錯的兩個連接:

https://blog.csdn.net/somestill/article/details/52159948

https://mmoaay.gitbooks.io/boost-asio-cpp-network-programming-chinese/content/Chapter1.html

相關文章
相關標籤/搜索