Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 99久在线观看,日一日操一操,91高清国产视频

          整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          select、poll和epoll的區別和 IO多路

          select、poll和epoll的區別和 IO多路復用模型講解

          elect、poll和epoll的區別

          在linux沒有實現epoll事件驅動機制之前,我們一般選擇用select或者poll等IO多路復用的方法來實現并發服務程序。在大數據、高并發、集群等一些名詞唱的火熱之年代,select和poll的用武之地越來越有限了,風頭已經被epoll占盡。

          select()和poll() IO多路復用模型

          select的缺點:

          單個進程能夠監視的文件描述符的數量存在最大限制,通常是1024,當然可以更改數量,但由于select采用輪詢的方式掃描文件描述符,文件描述符數量越多,性能越差;

          內核/用戶空間內存拷貝問題,select需要復制大量的句柄數據結構,產生巨大的開銷

          select返回的是含有整個句柄的數組,應用程序需要遍歷整個數組才能發現哪些句柄發生了事件;

          select的觸發方式是水平觸發,應用程序如果沒有完成對一個已經就緒的文件描述符進行IO,那么之后再次select調用還是會將這些文件描述符通知進程。

          相比于select模型,poll使用鏈表保存文件描述符,因此沒有了監視文件數量的限制,但其他三個缺點依然存在。

          拿select模型為例,假設我們的服務器需要支持100萬的并發連接,則在_FD_SETSIZE為1024的情況下,則我們至少需要開辟1k個進程才能實現100萬的并發連接。除了進程間上下文切換的時間消耗外,從內核/用戶空間大量的無腦內存拷貝、數組輪詢等,是系統難以承受的。因此,基于select模型的服務器程序,要達到10萬級別的并發訪問,是一個很難完成的任務。

          epoll IO多路復用模型實現機制

          由于epoll的實現機制與select/poll機制完全不同,上面所說的select的缺點在epoll上不復存在。

          設想一下如下場景:有100萬個客戶端同時與一個服務器進程保持著TCP連接。而每一時刻,通常只有幾百上千個TCP連接是活躍的。如何實現這樣的高并發?

          在select/poll時代,服務器進程每次都把這100萬個連接告訴操作系統(從用戶態復制句柄數據結構到內核態),讓操作系統內核去查詢這些套接字上是否有事件發生,輪詢完后,再將句柄數據復制到用戶態,讓服務器應用程序輪詢處理已發生的網絡事件,這一過程資源消耗較大,因此,select/poll一般只能處理幾千的并發連接。

          epoll的設計和實現select完全不同。epoll通過在linux內核中申請一個簡易的文件系統(文件系統一般用什么數據結構實現?B+樹)。把原先的select/poll調用分成了3個部分:

          1)調用epoll_create()建立一個epoll對象(在epoll文件系統中為這個句柄對象分配資源)

          2)調用epoll_ctl向epoll對象中添加這100萬個連接的套接字

          3)調用epoll_wait收集發生的事件的連接

          如此一來,要實現上面說的場景,只需要在進程啟動時建立一個epoll對象,然后在需要的時候向這個epoll對象中添加或者刪除連接。同時,epoll_wait的效率也非常高,因為調用epoll_wait時,并沒有一股腦的向操作系統復制這100萬個連接的句柄數據,內核也不需要去遍歷全部的連接。

          上面的3個部分非常清晰,首先要調用epoll_create創建一個epoll對象。然后使用epoll_ctl可以操作上面建立的epoll對象,例如,將剛建立的socket加入到epoll中讓其監控,或者把epoll正在監控的某個socket句柄移出epoll,不再監控它等等。

          epoll_wait在調用時,在給定的timeout時間內,當在監控的所有句柄中有事件發生時,就返回用戶態的進程。

          從上面的調用方式就可以看到epoll比select/poll的優越之處:因為后者每次調用時都要傳遞你所要監控的所有socket給select/poll系統調用,這意味著需要將用戶態的socket列表copy到內核態,如果以萬計的句柄會導致每次都要copy幾十幾百KB的內存到內核態,非常低效。而我們調用epoll_wait時就相當于以往調用select/poll,但是這時卻不用傳遞socket句柄給內核,因為內核已經在epoll_ctl中拿到了要監控的句柄列表。

          所以,實際上在你調用epoll_create后,內核就已經在內核態開始準備幫你存儲要監控的句柄了,每次調用epoll_ctl只是在往內核的數據結構里塞入新的socket句柄。

          在內核里,一切皆文件。所以,epoll向內核注冊了一個文件系統,用于存儲上述的被監控socket。當你調用epoll_create時,就會在這個虛擬的epoll文件系統里創建一個file結點。當然這個file不是普通文件,它只服務于epoll。

          epoll在被內核初始化時(操作系統啟動),同時會開辟出epoll自己的內核高速cache區,用于安置每一個我們想監控的socket,這些socket會以紅黑樹的形式保存在內核cache里,以支持快速的查找、插入、刪除。這個內核高速cache區,就是建立連續的物理內存頁,然后在之上建立slab層,簡單的說,就是物理上分配好你想要的size的內存對象,每次使用時都是使用空閑的已分配好的對象。

          epoll的高效就在于,當我們調用epoll_ctl往里塞入百萬個句柄時,epoll_wait仍然可以飛快的返回,并有效的將發生事件的句柄給我們用戶。這是由于我們在調用epoll_create時,內核除了幫我們在epoll文件系統里建了個file結點,在內核cache里建了個紅黑樹用于存儲以后epoll_ctl傳來的socket外,還會再建立一個list鏈表,用于存儲準備就緒的事件,當epoll_wait調用時,僅僅觀察這個list鏈表里有沒有數據即可。有數據就返回,沒有數據就sleep,等到timeout時間到后即使鏈表沒數據也返回。所以,epoll_wait非常高效。

          而且,通常情況下即使我們要監控百萬計的句柄,大多一次也只返回很少量的準備就緒句柄而已,所以,epoll_wait僅需要從內核態copy少量的句柄到用戶態而已,如何能不高效?!

          那么,這個準備就緒list鏈表是怎么維護的呢?當我們執行epoll_ctl時,除了把socket放到epoll文件系統里file對象對應的紅黑樹上之外,還會給內核中斷處理程序注冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表里。所以,當一個socket上有數據到了,內核在把網卡上的數據copy到內核中后就來把socket插入到準備就緒鏈表里了。

          如此,一顆紅黑樹,一張準備就緒句柄鏈表,少量的內核cache,就幫我們解決了大并發下的socket處理問題。執行epoll_create時,創建了紅黑樹和就緒鏈表,執行epoll_ctl時,如果增加socket句柄,則檢查在紅黑樹中是否存在,存在立即返回,不存在則添加到樹干上,然后向內核注冊回調函數,用于當中斷事件來臨時向準備就緒鏈表中插入數據。執行epoll_wait時立刻返回準備就緒鏈表里的數據即可。

          最后看看epoll獨有的兩種模式LT和ET。無論是LT和ET模式,都適用于以上所說的流程。區別是,LT模式下,只要一個句柄上的事件一次沒有處理完,會在以后調用epoll_wait時次次返回這個句柄,而ET模式僅在第一次返回。

          這件事怎么做到的呢?當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,然后清空準備就緒list鏈表,最后,epoll_wait干了件事,就是檢查這些socket,如果不是ET模式(就是LT模式的句柄了),并且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表了。所以,非ET的句柄,只要它上面還有事件,epoll_wait每次都會返回。而ET模式的句柄,除非有新中斷到,即使socket上的事件沒有處理完,也是不會次次從epoll_wait返回的。

          其中涉及到的數據結構:

          epoll用kmem_cache_create(slab分配器)分配內存用來存放structepitem和structeppoll_entry。

          當向系統中添加一個fd時,就創建一個epitem結構體,這是內核管理epoll的基本數據結構:

          structepitem{

          structrb_noderbn;//用于主結構管理的紅黑樹

          structlist_headrdllink;//事件就緒隊列

          structepitem*next;//用于主結構體中的鏈表

          structepoll_filefdffd;//這個結構體對應的被監聽的文件描述符信息

          intnwait;//poll操作中事件的個數

          structlist_headpwqlist;//雙向鏈表,保存著被監視文件的等待隊列,功能類似于select/poll中的poll_table

          structeventpoll*ep;//該項屬于哪個主結構體(多個epitm從屬于一個eventpoll)

          structlist_headfllink;//雙向鏈表,用來鏈接被監視的文件描述符對應的struct file。因為file里有f_ep_link,用來保存所有監視這個文件的epoll節點

          structepoll_eventevent;//注冊的感興趣的事件,也就是用戶空間的epoll_event

          }

          而每個epoll fd(epfd)對應的主要數據結構為:

          structeventpoll {

          spin_lock_tlock;//對本數據結構的訪問

          structmutex mtx;//防止使用時被刪除

          wait_queue_head_t wq;//sys_epoll_wait()使用的等待隊列

          wait_queue_head_tpoll_wait; //file->poll()使用的等待隊列

          structlist_head rdllist;//事件滿足條件的鏈表 /*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/

          structrb_rootrbr;//用于管理所有fd的紅黑樹(樹根)/*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/

          structepitem*ovflist;//將事件到達的fd進行鏈接起來發送至用戶空間

          }

          structeventpoll在epoll_create時創建。

          這樣說來,內核中維護了一棵紅黑樹,大致的結構如下:

          當調用epoll_wait檢查是否有事件發生時,只需要檢查eventpoll對象中的rdlist雙鏈表中是否有epitem元素即可。如果rdlist不為空,則把發生的事件復制到用戶態,同時將事件數量返回給用戶。

          、概覽

          jQuery官網:https://jquery.com/
          jQuery是一個高效、輕量并且功能豐富的js庫。
          核心在于查詢query。
          jQuery是一個優秀的js函數庫,是React/Vue/Angular框架之外中大型項目的首選。
          jQuery的主旨是write less, do more。

          1.1 jQuery的功能

          • html元素的選取
          • 操作html元素
          • css操作
          • html事件處理
          • 實現js動畫效果
          • 能夠鏈式調用
          • 容易擴展插件
          • 封裝了ajax

          1.2 引入jQuery庫

          引入jQuery的方式有2種,一種是項目中直接引入jQuery的min.js文件,一種是使用服務器端jQuery文件(使用cdn)腳本標簽方式引入。

          1.2.1 本地項目引入

          在官網的:https://jquery.com/download/ 鏈接下可以下載到完整的代碼,放到項目文件的js文件夾下。

          <script src="static/js/jquery-3.7.1.min.js"></script>

          1.2.2 cdn方式引入

          在網站:https://www.bootcdn.cn/ 可以獲得穩定、快速、免費的cdn加速服務。

          <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>

          1.2.3 版本兼容

          • 1.x 版本兼容老版本的IE,文件比較大
          • 2.x 版本文件比較小,支持IE8+
          • 3.x 版本引入部分新API,提供多個分包的版本,支持IE9+

          1.2.4 開發的正確姿勢

          開發過程中一般使用非min.js文件方便調試,生產環境部署上線時才使用min.js這種壓縮文件。

          二、jQuery源碼淺析

          2.1 匿名函數調用

          從源碼中可以看出,jQuery的整體邏輯可以用以下簡單的結構進行描述:

          ( function( global, factory ) {
              // 判斷有無window環境的一堆邏輯代碼
          })( typeof window !=="undefined" ? window : this, function( window, noGlobal ) {
              // 構造jQuery的一些邏輯代碼
              return jQuery
          });

          2.2 jQuery是一個函數

          從源碼中可以看出,jQuery被定義為一個函數,函數中返回了一個實例對象(看new關鍵字)。

          繼續跟蹤源碼 new jQuery.fn.init( selector, context),這個函數中調用了makeArray,當然在其他if判斷語句中也有返回偽數組對象(比如,定義了length字段,還有[0]的操作),這里拿makeArray作為演示。

          查看makeArray函數:

          所以這個返回實例對象,是一個偽數組。

          $('#menu-trigger') instanceof Array // false
          $('#menu-trigger') instanceof Object // true

          2.3 jQuery掛載在window上

          從源碼中可以看出,將jQuery函數和window.$ 以及window.jQuery綁定賦值,所以使用jQuery和$ 標識符就可以直接使用jQuery。通常在項目中直接使用$標識符,快捷簡省。

          2.4 jQuery驗證

          所以在引入jQuery的項目中:

          console.log(typeof $); // function
          console.log($===jQuery); // true
          console.log($() instanceof Object); // true

          三、jQuery常見用法

          3.1 函數形式調用

          通常形式為:$(param)

          • param為函數:dom加載完成后,執行該回調函數
          • param為選擇器字符串:查找與該選擇器匹配的所有標簽,并封裝成jQuery對象
          • param為dom對象:將該dom對象封裝成jQuery對象
          • param為標簽字符串:創建標簽對象并封裝成jQuery對象
          $(function() {
              console.log("dom finished and execute this");
          })
          
          $('#btn').click(function () {
              // 這里的this是id為#btn的dom元素
              console.log(this.innerHTML)
              
              console.log($(this).html())
          })
          
          $('<input type="number"></input>').appendTo('div')

          3.2 點語法調用函數

          let list=[1, 2, 3]
          $.each(list, function(i, ele) {
              console.log(i, ele)
          })
          
          $.trim(' hello world ')

          3.3 用法淺析

          • jQuery函數返回的是一個偽數組(Object對象),可以使用length和下標。
          // class中名為btn的dom元素有多少
          $('.btn').length
          
          $('.btn')[0]
          
          $('.btn').get(0)
          
          $('.btn').index()
          
          // 設置名為btn的class對應的dom標簽的文本內容
          $('.btn').text('自定義文本內容')

          通過$(param)傳入的是selector、element、標簽情況下,返回的是包含1個或者多個dom元素對象的偽數組。

          3.4 獲取一組dom元素的常見用法

          // 基礎標簽和class
          // 選擇了所有的div和span標簽
          $('div, span')
          
          // 選擇所有具有某個class的標簽
          $('div.container')
          
          // 層次選擇器
          $('ul span') // ul標簽下的所有span元素
          $('ul>span') // ul標簽下的所有子span元素
          $('.container+li') // class為container的元素后的下一個li元素
          $('ul .item~*') // class為item的元素后面所有兄弟元素
          
          // 過濾選擇器
          $('div:first') // 選擇第一個div
          $('div:last') // 最后一個div
          $('div:not(.container)') // class不為container的所有div
          $('div:lt(3):gt(0)') // 所有div元素中的大于0小于3的div元素,表示1和2索引處的dom元素
          $('div:containers("hello world")') // 內容為hellow world的div元素
          $('div:hidden') // style中display: none的div元素
          $('div[data]') // 有data屬性的div元素, example: <div data=""></div>
          $('div[data="123"]') // 有data屬性且值為123的div元素, example: <div data="123"></div>
          
          // 示例,使table表格的奇數行背景樣式設置
          $('table>tbody>tr:odd')
          
          // form表單中
          $(':text') // 所有單行輸入框
          $(':text:disabled') // 所有disabled的input輸入框
          $(':checkbox') // 所有checkbox
          $(':checkbox:checked') // 所有選中的checkbox
          $('select').val() // select標簽選中的option的value值

          3.5 修改css

          直接修改css屬性(如果其dom標簽存在這個css屬性)

          $('#container').css('background', 'red');
          
          $('#container').css({ 'background' : 'red', 'color': 'blue' }) // 一組屬性

          清空某標簽下的所有dom:

          $('.carousel-inner').empty();

          給某標簽下添加dom標簽:

          $('.carousel-inner').append(domStr);

          移除、添加class:

          $('.carousel-indicators li').removeClass('active');
          $('.carousel-indicators li:first').addClass('active');

          3.6 獲取屬性

          獲取dom標簽上的屬性:

           $('.about-img-1>img').attr('src');

          設置標簽的屬性:

           $('.about-img-1>img').attr('src', (data && data['image']) ? data['image'] : '');

          3.7 一些dom事件

          點擊:

          $('.category-product-page-ul>li').click(function(e) {
              e.preventDefault();
              console.log('this is:', this); // 打印對應的dom標簽
          });

          hover:

          $('#container').hover(  
              function() {  
                  // 當鼠標進入元素時執行的函數
              },
              function() {  
                  // 當鼠標離開元素時執行的函數
              }  
            );

          監聽事件:

          $('.bigImage').on("mousemove", function( e ) {
              // do something
          });

          3.8 發起ajax請求

          const json='/static/js/data/xxx.json';
           $.ajax({
              url: json,  
              dataType: 'json',  
              success: function(data) {
                // do something
              },
              error: function(jqXHR, textStatus, errorThrown) {  
                console.error('Fail to read json:', textStatus, errorThrown, json);
              }  
            });

          post請求:

          /O多路復用

          I/O多路復用(multiplexing)的本質是通過一種機制(系統內核緩沖I/O數據),讓單個進程可以監視多個文件描述符,一旦某個描述符就緒(一般是讀就緒或寫就緒),能夠通知程序進行相應的讀寫操作

          select、poll 和 epoll 都是 Linux API 提供的 IO 復用方式。

          相信大家都了解了Unix五種IO模型,不了解的可以=> 查看這里

          1. blocking IO - 阻塞IO
          2. nonblocking IO - 非阻塞IO
          3. IO multiplexing - IO多路復用
          4. signal driven IO - 信號驅動IO
          5. asynchronous IO - 異步IO

          其中前面4種IO都可以歸類為synchronous IO - 同步IO,而select、poll、epoll本質上也都是同步I/O,因為他們都需要在讀寫事件就緒后自己負責進行讀寫,也就是說這個讀寫過程是阻塞的。

          與多進程和多線程技術相比,I/O多路復用技術的最大優勢是系統開銷小,系統不必創建進程/線程,也不必維護這些進程/線程,從而大大減小了系統的開銷。

          在介紹select、poll、epoll之前,首先介紹一下Linux操作系統中基礎的概念:

          • 用戶空間 / 內核空間現在操作系統都是采用虛擬存儲器,那么對32位操作系統而言,它的尋址空間(虛擬存儲空間)為4G(2的32次方)。操作系統的核心是內核,獨立于普通的應用程序,可以訪問受保護的內存空間,也有訪問底層硬件設備的所有權限。為了保證用戶進程不能直接操作內核(kernel),保證內核的安全,操作系統將虛擬空間劃分為兩部分,一部分為內核空間,一部分為用戶空間。
          • 進程切換為了控制進程的執行,內核必須有能力掛起正在CPU上運行的進程,并恢復以前掛起的某個進程的執行。這種行為被稱為進程切換。因此可以說,任何進程都是在操作系統內核的支持下運行的,是與內核緊密相關的,并且進程切換是非常耗費資源的。
          • 進程阻塞正在執行的進程,由于期待的某些事件未發生,如請求系統資源失敗、等待某種操作的完成、新數據尚未到達或無新工作做等,則由系統自動執行阻塞原語(Block),使自己由運行狀態變為阻塞狀態。可見,進程的阻塞是進程自身的一種主動行為,也因此只有處于運行態的進程(獲得了CPU資源),才可能將其轉為阻塞狀態。當進程進入阻塞狀態,是不占用CPU資源的。
          • 文件描述符文件描述符(File descriptor)是計算機科學中的一個術語,是一個用于表述指向文件的引用的抽象化概念。文件描述符在形式上是一個非負整數。實際上,它是一個索引值,指向內核為每一個進程所維護的該進程打開文件的記錄表。當程序打開一個現有文件或者創建一個新文件時,內核向進程返回一個文件描述符。在程序設計中,一些涉及底層的程序編寫往往會圍繞著文件描述符展開。但是文件描述符這一概念往往只適用于UNIX、Linux這樣的操作系統。
          • 緩存I/O緩存I/O又稱為標準I/O,大多數文件系統的默認I/O操作都是緩存I/O。在Linux的緩存I/O機制中,操作系統會將I/O的數據緩存在文件系統的頁緩存中,即數據會先被拷貝到操作系統內核的緩沖區中,然后才會從操作系統內核的緩沖區拷貝到應用程序的地址空間。

          Select

          我們先分析一下select函數

          int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *exceptset,const struct timeval *timeout);

          【參數說明】int maxfdp1 指定待測試的文件描述字個數,它的值是待測試的最大描述字加1。fd_set *readset , fd_set *writeset , fd_set *exceptsetfd_set可以理解為一個集合,這個集合中存放的是文件描述符(file descriptor),即文件句柄。中間的三個參數指定我們要讓內核測試讀、寫和異常條件的文件描述符集合。如果對某一個的條件不感興趣,就可以把它設為空指針。const struct timeval *timeout timeout告知內核等待所指定文件描述符集合中的任何一個就緒可花多少時間。其timeval結構用于指定這段時間的秒數和微秒數。

          【返回值】int 若有就緒描述符返回其數目,若超時則為0,若出錯則為-1

          select運行機制

          select()的機制中提供一種fd_set的數據結構,實際上是一個long類型的數組,每一個數組元素都能與一打開的文件句柄(不管是Socket句柄,還是其他文件或命名管道或設備句柄)建立聯系,建立聯系的工作由程序員完成,當調用select()時,由內核根據IO狀態修改fd_set的內容,由此來通知執行了select()的進程哪一Socket或文件可讀。

          從流程上來看,使用select函數進行IO請求和同步阻塞模型沒有太大的區別,甚至還多了添加監視socket,以及調用select函數的額外操作,效率更差。但是,使用select以后最大的優勢是用戶可以在一個線程內同時處理多個socket的IO請求。用戶可以注冊多個socket,然后不斷地調用select讀取被激活的socket,即可達到在同一個線程內同時處理多個IO請求的目的。而在同步阻塞模型中,必須通過多線程的方式才能達到這個目的。

          select機制的問題

          1. 每次調用select,都需要把fd_set集合從用戶態拷貝到內核態,如果fd_set集合很大時,那這個開銷也很大
          2. 同時每次調用select都需要在內核遍歷傳遞進來的所有fd_set,如果fd_set集合很大時,那這個開銷也很大
          3. 為了減少數據拷貝帶來的性能損壞,內核對被監控的fd_set集合大小做了限制,并且這個是通過宏控制的,大小不可改變(限制為1024)

          Poll

          poll的機制與select類似,與select在本質上沒有多大差別,管理多個描述符也是進行輪詢,根據描述符的狀態進行處理,但是poll沒有最大文件描述符數量的限制。也就是說,poll只解決了上面的問題3,并沒有解決問題1,2的性能開銷問題。

          下面是pll的函數原型:

          int poll(struct pollfd *fds, nfds_t nfds, int timeout);
          
          typedef struct pollfd {
                  int fd;                         // 需要被檢測或選擇的文件描述符
                  short events;                   // 對文件描述符fd上感興趣的事件
                  short revents;                  // 文件描述符fd上當前實際發生的事件
          } pollfd_t;

          poll改變了文件描述符集合的描述方式,使用了pollfd結構而不是select的fd_set結構,使得poll支持的文件描述符集合限制遠大于select的1024

          【參數說明】struct pollfd *fds fds是一個struct pollfd類型的數組,用于存放需要檢測其狀態的socket描述符,并且調用poll函數之后fds數組不會被清空;一個pollfd結構體表示一個被監視的文件描述符,通過傳遞fds指示 poll() 監視多個文件描述符。其中,結構體的events域是監視該文件描述符的事件掩碼,由用戶來設置這個域,結構體的revents域是文件描述符的操作結果事件掩碼,內核在調用返回時設置這個域

          nfds_t nfds 記錄數組fds中描述符的總數量

          【返回值】int 函數返回fds集合中就緒的讀、寫,或出錯的描述符數量,返回0表示超時,返回-1表示出錯;

          Epoll

          epoll在Linux2.6內核正式提出,是基于事件驅動的I/O方式,相對于select來說,epoll沒有描述符個數限制,使用一個文件描述符管理多個描述符,將用戶關心的文件描述符的事件存放到內核的一個事件表中,這樣在用戶空間和內核空間的copy只需一次。

          Linux中提供的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);
          1. epoll_create 函數創建一個epoll句柄,參數size表明內核要監聽的描述符數量。調用成功時返回一個epoll句柄描述符,失敗時返回-1。
          2. epoll_ctl 函數注冊要監聽的事件類型。四個參數解釋如下:epfd 表示epoll句柄op 表示fd操作類型,有如下3種EPOLL_CTL_ADD 注冊新的fd到epfd中EPOLL_CTL_MOD 修改已注冊的fd的監聽事件EPOLL_CTL_DEL 從epfd中刪除一個fdfd 是要監聽的描述符event 表示要監聽的事件epoll_event 結構體定義如下:
          struct epoll_event {
              __uint32_t events;  /* Epoll events */
              epoll_data_t data;  /* User data variable */
          };
          
          typedef union epoll_data {
              void *ptr;
              int fd;
              __uint32_t u32;
              __uint64_t u64;
          } epoll_data_t;
          1. epoll_wait 函數等待事件的就緒,成功時返回就緒的事件數目,調用失敗時返回 -1,等待超時返回 0。epfd 是epoll句柄events 表示從內核得到的就緒事件集合maxevents 告訴內核events的大小timeout 表示等待的超時事件

          epoll是Linux內核為處理大批量文件描述符而作了改進的poll,是Linux下多路復用IO接口select/poll的增強版本,它能顯著提高程序在大量并發連接中只有少量活躍的情況下的系統CPU利用率。原因就是獲取事件的時候,它無須遍歷整個被偵聽的描述符集,只要遍歷那些被內核IO事件異步喚醒而加入Ready隊列的描述符集合就行了。

          epoll除了提供select/poll那種IO事件的水平觸發(Level Triggered)外,還提供了邊緣觸發(Edge Triggered),這就使得用戶空間程序有可能緩存IO狀態,減少epoll_wait/epoll_pwait的調用,提高應用程序效率。

          • 水平觸發(LT):默認工作模式,即當epoll_wait檢測到某描述符事件就緒并通知應用程序時,應用程序可以不立即處理該事件;下次調用epoll_wait時,會再次通知此事件
          • 邊緣觸發(ET): 當epoll_wait檢測到某描述符事件就緒并通知應用程序時,應用程序必須立即處理該事件。如果不處理,下次調用epoll_wait時,不會再次通知此事件。(直到你做了某些操作導致該描述符變成未就緒狀態了,也就是說邊緣觸發只在狀態由未就緒變為就緒時只通知一次)。

          LT和ET原本應該是用于脈沖信號的,可能用它來解釋更加形象。Level和Edge指的就是觸發點,Level為只要處于水平,那么就一直觸發,而Edge則為上升沿和下降沿的時候觸發。比如:0->1 就是Edge,1->1 就是Level。

          ET模式很大程度上減少了epoll事件的觸發次數,因此效率比LT模式下高。

          總結

          一張圖總結一下select,poll,epoll的區別:


          select

          poll

          epoll

          操作方式

          遍歷

          遍歷

          回調

          底層實現

          數組

          鏈表

          哈希表

          IO效率

          每次調用都進行線性遍歷,時間復雜度為O(n)

          每次調用都進行線性遍歷,時間復雜度為O(n)

          事件通知方式,每當fd就緒,系統注冊的回調函數就會被調用,將就緒fd放到readyList里面,時間復雜度O(1)

          最大連接數

          1024(x86)或2048(x64)

          無上限

          無上限

          fd拷貝

          每次調用select,都需要把fd集合從用戶態拷貝到內核態

          每次調用poll,都需要把fd集合從用戶態拷貝到內核態

          調用epoll_ctl時拷貝進內核并保存,之后每次epoll_wait不拷貝

          epoll是Linux目前大規模網絡并發程序開發的首選模型。在絕大多數情況下性能遠超select和poll。目前流行的高性能web服務器Nginx正式依賴于epoll提供的高效網絡套接字輪詢服務。但是,在并發連接不高的情況下,多線程+阻塞I/O方式可能性能更好。


          主站蜘蛛池模板: 国内精品视频一区二区三区| 国产色综合一区二区三区| 99精品国产高清一区二区三区| 搡老熟女老女人一区二区| 国产成人AV区一区二区三| 国产美女在线一区二区三区| 国产精品无码一区二区三级| 伊人精品视频一区二区三区| 福利一区二区视频| 成人精品一区二区三区电影| 老熟女五十路乱子交尾中出一区| 国产精品丝袜一区二区三区 | 国产成人一区二区三区视频免费| 一区二区三区无码高清视频| 日本一区二区三区在线视频观看免费| 影院成人区精品一区二区婷婷丽春院影视| 无码精品一区二区三区免费视频| 国产丝袜视频一区二区三区| 国产一区二区内射最近更新| 日本亚洲国产一区二区三区| 夜夜添无码一区二区三区| 97久久精品午夜一区二区| 国产高清在线精品一区小说| 一本色道久久综合一区| 精品日本一区二区三区在线观看| av无码一区二区三区| 91国在线啪精品一区| 国产一区二区三区在线2021| 久久se精品一区二区国产| 日韩人妻精品一区二区三区视频| 麻豆天美国产一区在线播放| 中文字幕日韩一区二区不卡| 日本人的色道www免费一区| 日本视频一区二区三区| 91国在线啪精品一区| 亚洲国产一区国产亚洲 | 国产一区二区女内射| 久久99热狠狠色精品一区| 日韩人妻不卡一区二区三区 | 亚洲免费视频一区二区三区| 中文字幕一区二区三区四区 |