Epoll水平觸發(Level Triggered)工作模式和邊緣觸發(Edge Triggered)工作模式區別
注意:此文章適合對Epoll有初步瞭解的同行觀看,如果還沒了解epoll工作模式的同行,建議看一下連結:
LT模式(預設方式)
LT模式即Level Triggered工作模式。
與ET模式不同的是,以LT方式呼叫epoll介面的時候,它就相當於一個速度比較快的poll,無論後面的資料是否被使用。
LT(level triggered):LT是預設的工作方式,並且同時支援block和no-block socket。在這種做法中,核心告訴你一個檔案描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,核心還是會繼續通知你的,所以,這種模式程式設計出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。
水平觸發圖例:
ET模式
ET(edge-triggered):ET是高速工作方式,只支援no-block socket。
在這種模式下,當描述符從未就緒變為就緒時,核心通過epoll告訴你。然後它會假設你知道檔案描述符已經就緒,並且不會再為那個檔案描述符傳送更多的就緒通知。請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),核心不會傳送更多的通知(only once)。
邊緣觸發圖例:
總結:
水平觸發,是相對比較安全的,因為當核心有事件被喚醒的時候,linux系統就會將被喚醒的事件拷貝到使用者態,讓使用者對被喚醒的事件進行處理,如果該事件沒有處理,那麼下一次等待中,linux核心依舊會拷貝到使用者態中,保證每一次事件都能抵達使用者態。
邊緣觸發,只會在核心被喚醒事件從無到有的那一刻,才會將事件拷貝給使用者態,雖然它減少了linux核心拷貝到使用者態的次數,但帶來的後果有可能在linux中部分事件已經被喚醒,但是沒有被獲取得到。
epoll 水平觸發程式碼:
#include "../common.h"
int set_NonBlock(int fd )
{
int flags = fcntl(fd, F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd , F_SETFL, flags);
return 0;
}
int main()
{
int fd = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = 0;
int ret = bind(fd ,(struct sockaddr*)&addr, sizeof(addr));
if (ret != 0)
{
perror("bind");
close(fd);
return ret;
}
ret = listen(fd, 1024);
if (ret != 0)
{
perror("listen");
close(fd);
return ret;
}
set_NonBlock(fd);
int epfd = epoll_create(1024);
struct epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
if (ret != 0)
{
perror("epoll_ctl");
close(fd);
return 0;
}
while ( 1 )
{
struct epoll_event ev[8];
ret = epoll_wait(epfd, ev, 8, 5000);
if (ret != 0)
{
if (errno == EINTR)
continue;
}
for (int i = 0 ; i < ret ; i++)
{
int newfd;
if (ev[i].data.fd == fd)
{
//socket fd
newfd = accept(ev[i].data.fd,NULL,NULL);
event.data.fd = newfd;
event.events = EPOLLIN;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, newfd, &event);
if (ret != 0)
{
perror("epoll_ctl");
close(newfd);
close(fd);
return -1;
}
}
else
{
int connectfd = ev[i].data.fd;
char buf[1024] = {0};
if (read( connectfd, buf,sizeof(buf)) > 0)
{
printf("recv buf:%s\n",buf);
}
else
{
close(connectfd);
}
}
}
}
return 0;
}
epoll 邊緣觸發程式碼:
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/epoll.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void sig_handle(int sig)
{
printf("recv signal :%d\n",sig);
}
int main(int argc, char * argv[])
{
signal(SIGPIPE,sig_handle);
if (argc<2)
{
printf("usage:%s + [count]\n",argv[0]);
return 0;
}
unlink("dbg.txt");
int dbg = open("dbg.txt",O_CREAT|O_APPEND|O_RDWR,0666);
int count = atoi(argv[1]);
int fd = socket(AF_INET,SOCK_STREAM,0);
struct sockaddr_in addr;
memset(&addr,0,sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(9988);
int ret = bind(fd,(struct sockaddr*)&addr,sizeof(addr));
if (ret ==-1)
{
perror("bind");
return 0;
}
listen(fd,250);
int is_child_process = 0;//判斷在哪個程式中,父程式0,子程式1
for (int i = 0 ; i < count ; i++)
{
pid_t pid = fork();
if (pid==0)
{
is_child_process = 1;
break;
}
}
struct epoll_event ev;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = fd;
int epfd = epoll_create(1024);//建立epfd的描述符
int flags = fcntl(fd,F_GETFL);
flags |= O_NONBLOCK;
fcntl(fd,F_SETFL,flags);
epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev);
while (1)
{
struct epoll_event evs[10];
int process_count = epoll_wait(epfd,evs,10,5000);
if (process_count == 0) continue;//如果監聽的程式都沒有事件產生,則再次進入迴圈,繼續監聽
for (int i = 0 ; i < process_count ;i++)
{
if (evs[i].data.fd == fd)
{
//當程式中的socket描述符是server的socket本身時候,則accept否則就直接操作
int ret = accept(evs[i].data.fd,NULL,NULL);
if (ret == -1)
{
printf("errno:%s",strerror(errno));
//其他錯誤,直接exit
break;
}
ev.data.fd = ret;
epoll_ctl(epfd,EPOLL_CTL_ADD,ret,&ev);
}
else
{
//read or write
char buf[1024];
int ret = read(evs[i].data.fd,buf,sizeof(buf));
if (ret == -1)
{
perror("read");
if (errno == EINTR)
break;
exit(0);
}
else if (ret == 0)
{
//normal exit
close(evs[i].data.fd);
break;
}
//printf("recv data %s from pid:%d\n",buf,getpid());
write(dbg,"1",1);
}
}
}
if (!is_child_process)
{
for (int i = 0 ; i < count; i ++)
{
wait(NULL);//等待所有的子程式退出為止
}
}
return 0;
}
個人感覺:
其實我使用了很久ET和LT兩種模式,但是呢,ET是否就會高效率過LT?這個也不好說,其實如果讀者是一個有心人的話,那麼你們也可以去看看libevent的開源庫,你會發現,其實他們底層的epoll,也是採用LT模式而已,所以呢,他們兩者的差別具體在哪裡。真不好說,希望有大牛指導指導!
相關文章
- 邊緣觸發ET和水平觸發LT
- epoll 非阻塞IO 邊沿觸發模式模式
- 面試— !Doctype的作用,嚴格模式和混雜模式的區別、以及如何觸發兩種模式面試模式
- 【Linux網路程式設計-7】epoll邊沿觸發Linux程式設計
- mvvm模式 事件觸發器[wpf]MVVM模式事件觸發器
- SAP工作流觸發總結
- Oracle觸發器觸發級別Oracle觸發器
- 施密特觸發器電路及工作原理觸發器
- go併發-工作池模式Go模式
- 小米4c邊緣觸控怎麼用 小米4c邊緣觸控使用教程
- 透過觸發器禁止模式物件的DDL操作觸發器模式物件
- 快速掌握 Go 工作區模式Go模式
- Doctype的作用?嚴格模式與混合模式,如何觸發者這兩種模式,區分它們有何意義?模式
- mysql觸發器案例分析以及before和after的區別MySql觸發器
- 行為和觸發器觸發器
- Nginx 工作模式和程式模型Nginx模式模型
- 【SQL Server】-- 一觸即發之觸發器SQLServer觸發器
- 雲端計算設計模式-邊緣快取模式設計模式快取
- AI 開發的捷徑:工作流模式AI模式
- Go 1.18:工作區模式workspace modeGo模式
- SQL Sever 2000中的前觸發器和後觸發器SQL觸發器
- IE雙倍邊距BUG 觸發 解決方案
- Java發票查驗介面讓財務告別低效的工作模式Java模式
- MVC模式和MVP模式的區別MVC模式MVP
- Oracle觸發器6(建立系統事件觸發器)Oracle觸發器事件
- 根據業務寫觸發器(oracle觸發器片)觸發器Oracle
- 取消事件觸發事件
- mysql——觸發器MySql觸發器
- mysql 觸發器MySql觸發器
- SQL觸發器SQL觸發器
- Mysql觸發器:MySql觸發器
- Oracle觸發器Oracle觸發器
- mysql觸發器MySql觸發器
- 防火牆模式工作模式簡介防火牆模式
- 微軟開始測試 Win11/10 Edge 瀏覽器專用觸控模式微軟瀏覽器模式
- 關於 Laravel increment 與 decrement 不能觸發觀察者模式的解決方案LaravelREM模式
- mysql繞過行觸發器,實現語句觸發器MySql觸發器
- 產品經理工作的初級接觸