色哟哟视频在线观看-色哟哟视频在线-色哟哟欧美15最新在线-色哟哟免费在线观看-国产l精品国产亚洲区在线观看-国产l精品国产亚洲区久久

0
  • 聊天消息
  • 系統消息
  • 評論與回復
登錄后你可以
  • 下載海量資料
  • 學習在線課程
  • 觀看技術視頻
  • 寫文章/發帖/加入社區
會員中心
創作中心

完善資料讓更多小伙伴認識你,還能領取20積分哦,立即完善>

3天內不再提示

epoll的LT模式總結

科技綠洲 ? 來源:Linux開發架構之路 ? 作者:Linux開發架構之路 ? 2023-11-10 15:35 ? 次閱讀

epoll的觸發模式是個引發討論非常多的話題網絡上這方面總結的文章也很多,首先從名字上就不是很統一,LT模式常被稱為水平觸發、電平觸發、條件觸發,而ET模式常被稱為邊緣觸發、邊沿觸發等,這些都是從英文翻譯過來的,只不過翻譯的時候有些差異,LT全稱 level-triggered,ET全稱 edge-triggered。

雖然這個知識點熱度很高,但很多人對于它的理解總是差那么一點,特別是在面試的時候,很多面試者總是處于一種回憶和背誦的狀態,其實這兩種模式真的不需要去死記硬背,下面說說我個人對這兩種模式的理解和記憶方法。

名稱的記憶

每次提到ET(邊沿觸發)首先映入我腦海的是大學里《數字邏輯電路》這門課程,里面會提到低電平、高電平,當電平從低到高時會有一個上升沿,而電平從高到低時會有一個下降沿,這個“沿”就是邊沿觸發時提到的“邊沿”,跟馬路邊的馬路牙子是同一種概念,也就是指狀態變化的時候。提起上升沿和下降沿我還是印象很深的,當時我可是占用了好幾節課的時間用Verilog語言寫了一個顯示“HELLO WORLD”的仿真波形,依靠的就是電平變化中的“沿”。

狀態變化

LT模式和ET模式可以類比電平變化來學習,但是在實際應用中概念卻不是完全一樣的,在epoll的應用中涉及到關于IO的讀寫,而讀寫的狀態變化有哪些呢?可讀、不可讀、可寫、不可寫,其實就是這四種狀態而已,以socket為例。

可讀:socket上有數據

不可讀:socket上沒有數據了

可寫:socket上有空間可寫

不可寫:socket上無空間可寫

對于水平觸發模式,一個事件只要有,就會一直觸發。對于邊緣觸發模式,只有一個事件從無到有才會觸發。

LT模式

對于讀事件 EPOLLIN,只要socket上有未讀完的數據,EPOLLIN 就會一直觸發;對于寫事件 EPOLLOUT,只要socket可寫(一說指的是 TCP 窗口一直不飽和,我覺得是TCP緩沖區未滿時,這一點還需驗證),EPOLLOUT 就會一直觸發。

在這種模式下,大家會認為讀數據會簡單一些,因為即使數據沒有讀完,那么下次調用epoll_wait()時,它還會通知你在上沒讀完的文件描述符上繼續讀,也就是人們常說的這種模式不用擔心會丟失數據。

而寫數據時,因為使用 LT 模式會一直觸發 EPOLLOUT 事件,那么如果代碼實現依賴于可寫事件觸發去發送數據,一定要在數據發送完之后移除檢測可寫事件,避免沒有數據發送時無意義的觸發。

ET模式

對于讀事件 EPOLLIN,只有socket上的數據從無到有,EPOLLIN 才會觸發;對于寫事件 EPOLLOUT,只有在socket寫緩沖區從不可寫變為可寫,EPOLLOUT 才會觸發(剛剛添加事件完成調用epoll_wait時或者緩沖區從滿到不滿)

這種模式聽起來清爽了很多,只有狀態變化時才會通知,通知的次數少了自然也會引發一些問題,比如觸發讀事件后必須把數據收取干凈,因為你不一定有下一次機會再收取數據了,即使不采用一次讀取干凈的方式,也要把這個激活狀態記下來,后續接著處理,否則如果數據殘留到下一次消息來到時就會造成延遲現象。

這種模式下寫事件觸發后,后續就不會再觸發了,如果還需要下一次的寫事件觸發來驅動發送數據,就需要再次注冊一次檢測可寫事件。

數據的讀取和發送

關于數據的讀比較好理解,無論是LT模式還是ET模式,監聽到讀事件從socket開始讀數據就好了,只不過讀的邏輯有些差異,LT模式下,讀事件觸發后,可以按需收取想要的字節數,不用把本次接收到的數據收取干凈,ET模式下,讀事件觸發后通常需要數據一次性收取干凈。

而數據的寫不太容易理解,因為數據的讀是對端發來數據導致的,而數據的寫其實是自己的邏輯層觸發的,所以在通過網絡發數據時通常都不會去注冊監可寫事件,一般都是調用 send 或者 write 函數直接發送,如果發送過程中, 函數返回 -1,并且錯誤碼是 EWOULDBLOCK 表明發送失敗,此時才會注冊監聽可寫事件,并將剩余的服務存入自定義的發送緩沖區中,等可寫事件觸發后再接著將發送緩沖區中剩余的數據發送出去。

代碼實踐

基礎代碼

以下為一個epoll觸發模式測試的基礎代碼,也不算太長,直接拿來就可以測試:

#include //for socket
#include //for htonl htons
#include //for epoll_ctl
#include //for close
#include //for fcntl
#include //for errno
#include //for cout

class fd_object
{
public:
fd_object(int fd) { listen_fd = fd; }
~fd_object() { close(listen_fd); }
private:
int listen_fd;
};

/*
./epoll for lt mode
and
./epoll 1 for et mode
*/
int main(int argc, char* argv[])
{
//create a socket fd
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1)
{
std::cout << "create listen socket fd error." << std::endl;
return -1;
}
fd_object obj(listen_fd);

//set socket to non-block
int socket_flag = fcntl(listen_fd, F_GETFL, 0);
socket_flag |= O_NONBLOCK;
if (fcntl(listen_fd, F_SETFL, socket_flag) == -1)
{
std::cout << "set listen fd to nonblock error." << std::endl;
return -1;
}

//init server bind info
int port = 51741;
struct sockaddr_in bind_addr;
bind_addr.sin_family = AF_INET;
bind_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind_addr.sin_port = htons(port);
if (bind(listen_fd, (struct sockaddr *)&bind_addr, sizeof(bind_addr)) == -1)
{
std::cout << "bind listen socket fd error." << std::endl;
return -1;
}

//start listen
if (listen(listen_fd, SOMAXCONN) == -1)
{
std::cout << "listen error." << std::endl;
return -1;
}
else
std::cout << "start server at port [" << port << "] with [" << (argc <= 1 ? "LT" : "ET") << "] mode." << std::endl;

//create a epoll fd
int epoll_fd = epoll_create(88);
if (epoll_fd == -1)
{
std::cout << "create a epoll fd error." << std::endl;
return -1;
}

epoll_event listen_fd_event;
listen_fd_event.data.fd = listen_fd;
listen_fd_event.events = EPOLLIN;
if (argc > 1) listen_fd_event.events |= EPOLLET;

//add epoll event for listen fd
if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, listen_fd, &listen_fd_event) == -1)
{
std::cout << "epoll ctl error." << std::endl;
return -1;
}

while (true)
{
epoll_event epoll_events[1024];
int n = epoll_wait(epoll_fd, epoll_events, 1024, 1000);

if (n < 0)
break;
else if (n == 0) //timeout
continue;

for (int i = 0; i < n; ++i)
{
if (epoll_events[i].events & EPOLLIN)//trigger read event
{
if (epoll_events[i].data.fd == listen_fd)
{
//accept a new connection
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
int client_fd = accept(listen_fd, (struct sockaddr*)&client_addr, &client_addr_len);
if (client_fd == -1)
continue;

socket_flag = fcntl(client_fd, F_GETFL, 0);
socket_flag |= O_NONBLOCK;
if (fcntl(client_fd, F_SETFL, socket_flag) == -1)
{
close(client_fd);
std::cout << "set client fd to non-block error." << std::endl;
continue;
}

epoll_event client_fd_event;
client_fd_event.data.fd = client_fd;
client_fd_event.events = EPOLLIN | EPOLLOUT;
if (argc > 1) client_fd_event.events |= EPOLLET;

if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, client_fd, &client_fd_event) == -1)
{
std::cout << "add client fd to epoll fd error." << std::endl;
close(client_fd);
continue;
}

std::cout << "accept a new client fd [" << client_fd << "]." << std::endl;
}
else
{
std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

char recvbuf[1024] = { 0 };
int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
{
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;
close(epoll_events[i].data.fd);
}

std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}
}
else if (epoll_events[i].events & EPOLLOUT)
{
if (epoll_events[i].data.fd == listen_fd) //trigger write event
continue;

std::cout << "EPOLLOUT event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;
}
}
}

return 0;
}

簡單說下這段代碼的測試方法,可以使用 g++ testepoll.cpp -o epoll 進行編譯,編譯后通過 ./epoll 運行為LT模式,通過 ./epoll et模式運行為ET模式,我們用編譯好的epoll程序作為服務器,使用nc命令來模擬一個客戶端。

測試分類

1.編譯后直接./epoll,然后在另一個命令行窗口用 nc -v 127.0.0.1 51741 命令模擬一次連接,此時 ./epoll 會產生大量的 EPOLLOUT event triggered for client fd ...,那是因為在LT模式下,EPOLLOUT會被一直觸發。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLOUT event triggered for client fd [5].
...

2.注釋包含 EPOLLOUT event triggered for client fd 輸出內容的第152行代碼,編譯后 ./epoll運行,然后在另一個命令行窗口用 nc -v 127.0.0.1 51741 模擬一次連接后,輸入abcd回車,可以看到服務器./epoll輸出內容,EPOLLIN被觸發多次,每次讀取一個字節。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll
start server at port [51741] with [LT] mode.
accept a new client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [d].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].

3.還原剛才注釋的那行代碼,編譯后執行 ./epoll et 啟動服務器,然后在另一個命令行窗口用 nc -v 127.0.0.1 51741 模擬一次連接后,然后在另一個命令行窗口用 nc -v 127.0.0.1 51741 模擬一次連接,服務器窗口顯示觸發了EPOLLOUT事件

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].

在此基礎上,從剛剛運行nc命令的窗口中輸入回車、輸入回車、輸出回車,那么epoll服務器窗口看到的是觸發了三次EPOLLIN事件,每次收到一個回車:

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].

但是如果在nc模擬的客戶端里輸出abcd回車,那么在epoll服務器窗口觸發一次EPOLLIN事件接收到一個a之后便再也不會觸發EPOLLIN了,即使你在nc客戶端在此輸入也沒有用,那是因為在接受的緩沖區中一直還有數據,新數據來時沒有出現緩沖區從空到有數據的情況,所以在ET模式下也注意這種情況。

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].

怎么解決ET觸發了一次就不再觸發了

改代碼唄,ET模式在連接后觸發一次EPOLLOUT,接收到數據時觸發一次EPOLLIN,如果數據沒收完,以后這兩個事件就再也不會被觸發了,要想改變這種情況可以再次注冊一下這兩個事件,時機可以選擇接收到數據的時候,所以可以修改這部分代碼:

else
{
std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

char recvbuf[1024] = { 0 };
int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
{
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;
close(epoll_events[i].data.fd);
}

std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}

添加再次注冊的邏輯:

else
{
std::cout << "EPOLLIN event triggered for client fd [" << epoll_events[i].data.fd << "]." << std::endl;

char recvbuf[1024] = { 0 };
int m = recv(epoll_events[i].data.fd, recvbuf, 1, 0); // only read 1 bytes when read event triggered

if (m == 0 || (m < 0 && errno != EWOULDBLOCK && errno != EINTR))
{
if (epoll_ctl(epoll_fd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
std::cout << "the client fd [" << epoll_events[i].data.fd << "] disconnected." << std::endl;
close(epoll_events[i].data.fd);
}

epoll_event client_fd_event;
client_fd_event.data.fd = epoll_events[i].data.fd;
client_fd_event.events = EPOLLIN | EPOLLOUT;
if (argc > 1) client_fd_event.events |= EPOLLET;

epoll_ctl(epoll_fd, EPOLL_CTL_MOD, epoll_events[i].data.fd, &client_fd_event);

std::cout << "recv data from client fd [" << epoll_events[i].data.fd << "] and data is [" << recvbuf << "]." << std::endl;
}

這次以./epoll et方式啟動服務器,使用nc -v 127.0.0.1 51741模擬客戶端,輸入abc回車發現,epoll服務器輸出顯示觸發的事件變了:

albert@home-pc:/mnt/d/data/cpp/testepoll$ ./epoll et
start server at port [51741] with [ET] mode.
accept a new client fd [5].
EPOLLOUT event triggered for client fd [5].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [a].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [b].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [c].
EPOLLIN event triggered for client fd [5].
recv data from client fd [5] and data is [
].
EPOLLOUT event triggered for client fd [5].

總結

  • LT模式會一直觸發EPOLLOUT,當緩沖區有數據時會一直觸發EPOLLIN
  • ET模式會在連接建立后觸發一次EPOLLOUT,當收到數據時會觸發一次EPOLLIN
  • LT模式觸發EPOLLIN時可以按需讀取數據,殘留了數據還會再次通知讀取
  • ET模式觸發EPOLLIN時必須把數據讀取完,否則即使來了新的數據也不會再次通知了
  • LT模式的EPOLLOUT會一直觸發,所以發送完數據記得刪除,否則會產生大量不必要的通知
  • ET模式的EPOLLOUT事件若數據未發送完需再次注冊,否則不會再有發送的機會
  • 通常發送網絡數據時不會依賴EPOLLOUT事件,只有在緩沖區滿發送失敗時會注冊這個事件,期待被通知后再次發送
聲明:本文內容及配圖由入駐作者撰寫或者入駐合作網站授權轉載。文章觀點僅代表作者本人,不代表電子發燒友網立場。文章及其配圖僅供工程師學習之用,如有內容侵權或者其他違規問題,請聯系本站處理。 舉報投訴
  • LT
    LT
    +關注

    關注

    0

    文章

    168

    瀏覽量

    30703
  • 電平
    +關注

    關注

    5

    文章

    361

    瀏覽量

    39969
  • 代碼
    +關注

    關注

    30

    文章

    4823

    瀏覽量

    68894
  • Verilog語言
    +關注

    關注

    0

    文章

    113

    瀏覽量

    8288
  • epoll
    +關注

    關注

    0

    文章

    28

    瀏覽量

    2974
收藏 人收藏

    評論

    相關推薦

    epoll的使用

    以下內容是參考華清遠見《linux/unix系統編程手冊》對epoll的一個個人總結,是我在華清遠見比較全面的總結。一、epoll的優點同I/O多路復用和信號驅動I/O一樣,linux
    發表于 05-11 13:22

    我讀過的最好的epoll講解

    =EAGAIN。而epoll只關心緩沖區非滿和緩沖區非空事件)。 一個epoll模式的代碼大概的樣子是:while true { active_stream[] =epoll_wait
    發表于 05-12 15:30

    epoll使用方法與poll的區別

    因為epoll的觸發機制是在內核中直接完成整個功能 那個事件準備就緒我就直接返回這個IO事件
    發表于 07-31 10:03

    epoll_wait的事件返回的fd為錯誤是怎么回事?

    netlink 的 socket 連接 的 fd 為18,但是添加到epollepoll_wait()返回的fd 為 0為什么會出現這樣的現象?補充 說明:1、 epoll_wait返回
    發表于 06-12 09:03

    揭示EPOLL一些原理性的東西

    =EAGAIN。而epoll只關心緩沖區非滿和緩沖區非空事件)。一個epoll模式的代碼大概的樣子是:while true {active_stream[] = epoll
    發表于 08-24 16:32

    【米爾王牌產品MYD-Y6ULX-V2開發板試用體驗】socket通信和epoll

    ;gt;#include &lt;sys/epoll.h>#include "ssd1306.h"const int PORT = 8888
    發表于 11-10 15:31

    poll&&epollepoll實現

    poll&&epollepoll實現
    發表于 05-14 14:34 ?2813次閱讀
    poll&&<b class='flag-5'>epoll</b>之<b class='flag-5'>epoll</b>實現

    Linux中epoll IO多路復用機制

    epoll 是Linux內核中的一種可擴展IO事件處理機制,最早在 Linux 2.5.44內核中引入,可被用于代替POSIX select 和 poll 系統調用,并且在具有大量應用程序請求時能夠
    發表于 05-16 16:07 ?714次閱讀
    Linux中<b class='flag-5'>epoll</b> IO多路復用機制

    epoll LT和ET方式下的讀寫差別

    epoll接口是為解決Linux內核處理大量文件描述符而提出的方案。該接口屬于Linux下多路I/O復用接口中select/poll的增強。
    的頭像 發表于 07-07 10:34 ?2202次閱讀

    一文詳解epoll的實現原理

    本文以四個方面介紹epoll的實現原理,1.epoll的數據結構;2.協議棧如何與epoll通信;3.epoll線程安全如何加鎖;4.ET與LT
    的頭像 發表于 08-01 13:28 ?4156次閱讀

    epoll來實現多路復用

    本人用epoll來實現多路復用,epoll觸發模式有兩種: ET(邊緣模式LT(水平模式
    的頭像 發表于 11-09 10:15 ?546次閱讀
    用<b class='flag-5'>epoll</b>來實現多路復用

    epoll 的實現原理

    今兒我們就從源碼入手,來幫助大家簡單理解一下 epoll 的實現原理,并在后邊分析一下,大家都說 epoll 性能好,那到底是好在哪里。 epoll 簡介 1、epoll 的簡單使用
    的頭像 發表于 11-09 11:14 ?571次閱讀
    <b class='flag-5'>epoll</b> 的實現原理

    epoll的基礎數據結構

    一、epoll的基礎數據結構 在開始研究源代碼之前,我們先看一下 epoll 中使用的數據結構,分別是 eventpoll、epitem 和 eppoll_entry。 1、eventpoll 我們
    的頭像 發表于 11-10 10:20 ?837次閱讀
    <b class='flag-5'>epoll</b>的基礎數據結構

    epoll的觸發模式介紹

    前言 epoll的觸發模式是個引發討論非常多的話題,網絡上這方面總結的文章也很多,首先從名字上就不是很統一,LT模式常被稱為水平觸發、電平觸
    的頭像 發表于 11-10 14:54 ?706次閱讀

    epoll源碼分析

    Linux內核提供了3個關鍵函數供用戶來操作epoll,分別是: epoll_create(), 創建eventpoll對象 epoll_ctl(), 操作eventpoll對象
    的頭像 發表于 11-13 11:49 ?1096次閱讀
    <b class='flag-5'>epoll</b>源碼分析
    主站蜘蛛池模板: 狠狠色丁香婷婷久久综合 | 99久久亚洲精品日本无码 | 边摸边吃奶玩乳尖视频 | 日本XXXXZZX片免费观看 | MD传媒在线观看佳片 | 丰满女朋友在线观看中文 | 儿媳妇完整版视频播放免费观看 | 亚洲欧美一区二区三区九九九 | 色欲国产麻豆一精品一AV一免费 | MD传媒在线观看佳片 | 97在线精品视频免费 | 广东95后小情侣酒店自拍流出 | 偷拍国产精品在线播放 | 黄色片网站下载 | 色视频色露露永久免费观看 | 美女扒开屁股让男人桶 | 成人免费公开视频 | 在线观看日本免费 | 中文字幕在线观看亚洲 | 广东95后小情侣酒店自拍流出 | jizz非洲| 深夜释放自己污在线看 | 免费99精品国产人妻自在线 | 午夜福利免费院 | 美娇妻的性奴史1一4 | 国产又粗又猛又爽又黄的免费视频 | c了瑜伽老师嗷嗷叫一节课视频 | 2019一級特黃色毛片免費看 | 爱啪国产精品视频在线 | 把腿张开再深点好爽宝贝 | 色偷偷91综合久久噜噜 | 97se se| 在线自拍亚洲视频欧美 | 久久精品国产视频澳门 | 国产AV国产精品国产三级在线L | 入禽太深在线观看免费高清 | 国产成人高清精品免费观看 | 69丰满少妇AV无码区 | 国产人妻精品无码AV在线五十路 | 爽爽窝窝午夜精品一区二区 | 亚洲国产成人精品不卡青青草原 |