1. 程式人生 > >Epoll在LT和ET模式下的讀寫方式和區別

Epoll在LT和ET模式下的讀寫方式和區別

LT模式:epoll就是一個快速版poll,可讀可寫就緒條件和傳統poll一致
ET模式:為了避免Starvation,建議 
         1)檔案描述符設定為非阻塞 
         2)只在read或write返回EAGAIN後,才能呼叫下一次epoll_wait 
         3)應用層維護一個就緒連結串列,進行輪詢,可以防止大量IO時在一個描述符上長期read或write(因為只有等到read 
                或 write返回EAGAIN後才表示該描述符處理完畢)而令其它描述符starve

      理解ET的含義後,上面那些操作其實都是顯然的。以wirte為例說明,LT時只要有一定範圍的空閒寫快取區,每次epoll_wait都是可寫條件就 緒,但是ET時從第一次可寫就緒後,epoll_wait不再得到該描述符可寫就緒通知直到程式使描述符變為非可寫就緒(比如write收到 EAGAIN)後,epoll_wait才可能繼續收到可寫就緒通知(比如有空閒可寫快取)

      其實ET相對於LT來說,把檔案描述符狀態跟蹤的部分責任由核心空間推到使用者空間,核心只關心狀態切換即從未就緒到就緒切換時才通知使用者,至於保持就緒 狀態的,核心不再通知使用者,這樣在實現非阻塞模型時更方便,不需要每次操作都先檢視檔案描述符狀態。

ET模式:

因為ET模式只有從unavailable到available才會觸發,所以

1、讀事件:需要使用while迴圈讀取完,一般是讀到EAGAIN,也可以讀到返回值小於緩衝區大小;

如果應用層讀緩衝區滿:那就需要應用層自行標記,解決OS不再通知可讀的問題

2、寫事件:需要使用while迴圈寫到EAGAIN,也可以寫到返回值小於緩衝區大小

如果應用層寫緩衝區空(無內容可寫):那就需要應用層自行標記,解決OS不再通知可寫的問題。

LT模式:

因為LT模式只要available就會觸發,所以:

1、讀事件:因為一般應用層的邏輯是“來了就能讀”,所以一般沒有問題,無需while迴圈讀取到EAGAIN;

如果應用層讀緩衝區滿:就會經常觸發,解決方式如下;

2、寫事件:如果沒有內容要寫,就會經常觸發,解決方式如下。

LT經常觸發讀寫事件的解決辦法:修改fd的註冊事件,或者把fd移出epollfd。

總結:

目前好像還是LT方式應用較多,包括redis、libuv等。(nginx使用ET)

LT模式的優點在於:事件迴圈處理比較簡單,無需關注應用層是否有緩衝或緩衝區是否滿,只管上報事件。缺點是:可能經常上報,可能影響效能。

在一個非阻塞的socket上呼叫read/write函式, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK)
從字面上看, 意思是:EAGAIN: 再試一次,EWOULDBLOCK: 如果這是一個阻塞socket, 操作將被block,perror輸出: Resource temporarily unavailable

總結:
這個錯誤表示資源暫時不夠,能read時,讀緩衝區沒有資料,或者write時,寫緩衝區滿了。遇到這種情況,如果是阻塞socket,read/write就要阻塞掉。而如果是非阻塞socket,read/write立即返回-1, 同時errno設定為EAGAIN。
所以,對於阻塞socket,read/write返回-1代表網路出錯了。但對於非阻塞socket,read/write返回-1不一定網路真的出錯了。可能是Resource temporarily unavailable。這時你應該再試,直到Resource available。

綜上,對於non-blocking的socket,正確的讀寫操作為:
讀:忽略掉errno = EAGAIN的錯誤,下次繼續讀
寫:忽略掉errno = EAGAIN的錯誤,下次繼續寫

對於select和epoll的LT模式,這種讀寫方式是沒有問題的。但對於epoll的ET模式,這種方式還有漏洞。

epoll的兩種模式LT和ET
二者的差異在於level-trigger模式下只要某個socket處於readable/writable狀態,無論什麼時候進行epoll_wait都會返回該socket;而edge-trigger模式下只有某個socket從unreadable變為readable或從unwritable變為writable時,epoll_wait才會返回該socket。

所以,在epoll的ET模式下,正確的讀寫方式為:
讀:只要可讀,就一直讀,直到返回0,或者 errno = EAGAIN
寫:只要可寫,就一直寫,直到資料傳送完,或者 errno = EAGAIN

正確的讀

n =0;
while((nread = read(fd, buf + n, BUFSIZ-1))>0){
    n += nread;
}
if(nread ==-1&& errno != EAGAIN){
    perror("read error");
}

正確的寫

int nwrite, data_size = strlen(buf);
n = data_size;
while(n >0){
    nwrite = write(fd, buf + data_size - n, n);
if(nwrite < n){
if(nwrite ==-1&& errno != EAGAIN){
            perror("write error");
}
break;
}
    n -= nwrite;
}

正確的accept,accept 要考慮 2 個問題
(1) 阻塞模式 accept 存在的問題
考慮這種情況:TCP連線被客戶端夭折,即在伺服器呼叫accept之前,客戶端主動傳送RST終止連線,導致剛剛建立的連線從就緒佇列中移出,如果套介面被設定成阻塞模式,伺服器就會一直阻塞在accept呼叫上,直到其他某個客戶建立一個新的連線為止。但是在此期間,伺服器單純地阻塞在accept呼叫上,就緒佇列中的其他描述符都得不到處理。

解決辦法是把監聽套介面設定為非阻塞,當客戶在伺服器呼叫accept之前中止某個連線時,accept呼叫可以立即返回-1,這時源自Berkeley的實現會在核心中處理該事件,並不會將該事件通知給epool,而其他實現把errno設定為ECONNABORTED或者EPROTO錯誤,我們應該忽略這兩個錯誤。

(2)ET模式下accept存在的問題
考慮這種情況:多個連線同時到達,伺服器的TCP就緒佇列瞬間積累多個就緒連線,由於是邊緣觸發模式,epoll只會通知一次,accept只處理一個連線,導致TCP就緒佇列中剩下的連線都得不到處理。

解決辦法是用while迴圈抱住accept呼叫,處理完TCP就緒佇列中的所有連線後再退出迴圈。如何知道是否處理完就緒佇列中的所有連線呢?accept返回-1並且errno設定為EAGAIN就表示所有連線都處理完。

綜合以上兩種情況,伺服器應該使用非阻塞地accept,accept在ET模式下的正確使用方式為:

while((conn_sock = accept(listenfd,(struct sockaddr *)&remote,(size_t *)&addrlen))>0){
    handle_client(conn_sock);
}
if(conn_sock ==-1){
if(errno != EAGAIN && errno != ECONNABORTED && errno != EPROTO && errno != EINTR)
    perror("accept");
}

一道騰訊後臺開發的面試題
使用Linuxepoll模型,水平觸發模式;當socket可寫時,會不停的觸發socket可寫的事件,如何處理?

第一種最普遍的方式:
需要向socket寫資料的時候才把socket加入epoll,等待可寫事件。
接受到可寫事件後,呼叫write或者send傳送資料。
當所有資料都寫完後,把socket移出epoll。

這種方式的缺點是,即使傳送很少的資料,也要把socket加入epoll,寫完後在移出epoll,有一定操作代價。

一種改進的方式:
開始不把socket加入epoll,需要向socket寫資料的時候,直接呼叫write或者send傳送資料。如果返回EAGAIN,把socket加入epoll,在epoll的驅動下寫資料,全部資料傳送完畢後,再移出epoll。

這種方式的優點是:資料不多的時候可以避免epoll的事件處理,提高效率。

最後貼一個使用epoll,ET模式的簡單HTTP伺服器程式碼:

#include<sys/socket.h>
#include<sys/wait.h>
#include<netinet/in.h>
#include<netinet/tcp.h>
#include<sys/epoll.h>
#include<sys/sendfile.h>
#include<sys/stat.h>
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<strings.h>
#include<fcntl.h>
#include<errno.h>
#define MAX_EVENTS 10
#define PORT 8080
//設定socket連線為非阻塞模式
void setnonblocking

相關推薦

Epoll在LTET模式方式區別

LT模式:epoll就是一個快速版poll,可讀可寫就緒條件和傳統poll一致 ET模式:為了避免Starvation,建議           1)檔案描述符設定為非阻塞           2)只在read或write返回EAGAIN後,才能呼叫下一次epoll

epoll在LTET模式方式

在一個非阻塞的socket上呼叫read/write函式, 返回EAGAIN或者EWOULDBLOCK(注: EAGAIN就是EWOULDBLOCK) 從字面上看, 意思是: * EAGAIN: 再試一次 * EWOULDBLOCK: 如果這是一個阻塞socket

Spark -14:spark Hadoop 高可用模式hdfs

  第一種,通過配置檔案   val sc = new SparkContext()     sc.hadoopConfiguration.set("fs.defaultFS", "hdfs://cl

epoll學習筆記(ET模式事件觸發原理資料收發存在的問題)

這篇文章所講的例子和情況可以結合《epoll的LT模式和ET模式 》這篇看。 epoll有兩種模式,Edge Triggered(簡稱ET) 和 Level Triggered(簡稱LT).在採用這兩種模式時要注意的是,如果採用ET模式,那麼僅當狀態發生變化時才會通知,而採

Modbus關於ASCII模式RTU模式兩種傳輸方式區別

支持 系統 asc 方便 設備 優點 兩種 進制數 應用 常用的MODBUS通訊規約有兩種,一種是MODBUS ASCII,一種是MODBUS RTU。每個設備必須都有相同的傳輸模式。所有設備都支持RTU模式,ASCII傳輸模式是選項。除此之外ASCII模式和RTU模式還有

android5及以前的版本useruserdebug模式remount修改/system許可權

user和userdebug模式下/system預設是隻讀的,即RO。但是很多時候都需要拷貝一些檔案到system/bin、system/etc、system/lib,結果嘛,很明顯就是提示。。。read only!!! 如果只是簡單的電腦連線裝置,然後使用adb push

Epoll-ET模式非阻塞之Buffer的封裝

先說說Epoll的ET模式 epoll預設的模式是LT,要說ET不得不提到LT,LT與ET的區別可以用一句話概括: LT模式下只要socket處於可讀狀態(新增EPOLLIN事件時)或可寫

epoll:EPOLLET模式的正確方式

1.EPOLLLT和EPOLLET最大的區別在於事件的通知機制,看這個文章EPOLLLT和EPOLLET的區別 2.EPOLLET模式下並不意味著要迴圈讀取完緩衝區的所有資料,貼出一段讀取程式碼: n = 0; while ((nread = read(fd, buf

csv、parquet、orc效能方式

背景      最近在做一個大資料分析平臺的專案,專案開發過程中使用spark來計算工作流工程中的每一個計算步驟,多個spark submit計算提交,構成了一個工作流程的計算。其中使用csv來作為多個計算步驟之間的中間結果儲存檔案,但是csv

apache FileUtils IOUtils 工具類 改寫普通檔案方式 提高效率

不重複開發的輪子,既然人家已經寫好了通用成熟的工具,與其自己吭哧吭哧寫通用類,還不如拿來用即可。但是用歸用,不瞭解還是不行滴,咻咻, apache下 開源了許多實用的工具類,一般都是在commons包下。 這裡講到了:  IOUtils 和 FileUtils 。

c++ 以二進位制以文字方式檔案的區別

  在c++專案開發中,時常涉及到檔案讀寫操作。因此在這裡先簡單梳理和回顧一下文字模式和二進位制模式在進行檔案讀寫上的區別。   1.linux平臺下文字檔案和二進位制檔案的讀寫     在linux平臺下進行檔案讀寫時,文字模式和二進位制模式沒有區別。在檔案讀寫時,呼叫fopen,無論以文字模式還是二進位制

python檔案處理,b模式,rb, wb,編碼的兩種方式

字串轉二進位制的辦法 bytes(字串,encoding='編碼') 字串.encode('編碼') 主要通過這兩種辦法可以讓字串轉為bytes型別 為什麼要用二進位制的讀寫? 因為圖片視訊不是字串方式能顯示的,所以只能用b的方式來. 另外二進位制資料可以跨

Linux實現 以方式掛載NTFS格式磁碟 使用ntfs-3g

     在裝有雙系統的主機上,Linx下掛載windows系統下的磁碟很有必要,可以使用mount命令來實現。      mount命令的格式為:                      mount [選項] <-t 型別> [-o 掛載選項] <裝置&

POI事件驅動模式Excel格式設定及2007EXCEL解析SAXParser類找不到

POI事件驅動模式讀寫Excel 目前處理Excel的開源javaAPI主要有兩種,一是Jxl(Java Excel API),Jxl只支援Excel2003以下的版本。另外一種是Apache的Jakarta POI,相比於Jxl,POI對微軟辦公文件的支援更加強大,但

Spring配置動態數據源-分離多數據源

brush ride 常開 resolve ttr 表達 customer 事務管理 cda   在現在互聯網系統中,隨著用戶量的增長,單數據源通常無法滿足系統的負載要求。因此為了解決用戶量增長帶來的壓力,在數據庫層面會采用讀寫分離技術和數據庫拆分等技術。讀寫分離就是就是一

Python常用的文件操作字符串操作

dir info load char 編碼 lines resolve values ror 文件讀寫操作 fileUtils.py # -*- coding: utf-8 -*- import os def getFileList(dir, fileList=[]):

python_py2py3文本區別

nbsp 編碼格式 需要 指定 nic 解碼 py3 bytes 寫入文件 python2和python3的區別? python 2 str    對應 python3 bytes python 2 uincod

UNICODE環境txt文件操作

clas c程序 delete tag 編碼 eno 字符編碼 empty readline 內容轉載自http://blog.sina.com.cn/s/blog_5d2bad130100t0x9.html UNICODE環境下讀寫txt文件操作 (2011-07

自旋鎖,順序鎖的實現原理

並且 保護 表達 min 返回 create creat rwlock ini 常用的同步原語鎖,到多核處理器時代鎖已經是必不可少的同步方式之一了。無論設計多優秀的多線程數據結構,都避不開有競爭的臨界區,此時高效的鎖顯得至關重要。鎖的顆粒度是框架/程序設計者所關註的,

vim編輯命令模式的實踐

pass 比較 編輯模式 root 粘貼 vim編輯 令行 發現 區別 編輯模式 i 在光標當前的地方開始編輯文檔 I 在光標所在的行首開始編輯 o 從光標所在的行的下面一行開始編輯 O 從光標所在的行的上面一行開始編輯 a 在光標所