Linux epoll
的用法
epoll
epollfd_create
函數
epollfd_create
#include <sys/epoll.h>
int epoll_create (int __size)
參數 | 含義 |
---|---|
__size | 此參數從Linux 2.6.8後就不再使用了,但必須設定成大于零的值 |
傳回值 | 含義 |
---|---|
>0 | 可用的epollfd |
-1 | 調用失敗 |
epollfd_ctl
函數
epollfd_ctl
有了
epollfd
,我們需要将要檢測事件的fd綁定到這個
epollfd
上,或者修改或者移除,使用
epollfd_ctl
完成
int epoll_ctl (int __epfd, int __op, int __fd,
struct epoll_event *__event)
參數 | 含義 |
---|---|
__epfd | 上文中的epollfd |
__op | 操作類型1.(EPOLLFD_CTL_ADD)添加2.(EPOLLFD_CTL_MOD)修改3.(EPOLLFD_CTL_DEL)移除 |
__fd | 需要被操作的描述符fd |
epoll_event *__event | 這是一個epollfd_event結構體位址,下文解釋 |
struct epoll_event
{
uint32_t events; /* 需要檢測fd事件标志 */
epoll_data_t data; /* 使用者自定義的資料 */
}
其中的
epoll_data_t
typedef union epoll_data
{
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
傳回值 | 含義 |
---|---|
成功 | |
-1 | 失敗 |
epollfd_wait
函數
epollfd_wait
int epoll_wait (int __epfd, struct epoll_event *__events,
int __maxevents, int __timeout);
參數 | 含義 |
---|---|
epoll_event *__events | 輸出參數,在函數調用成功後, 中存放的是與就緒事件相關的 結構體數組 |
__maxevents | 上述數組中元素的個數 |
傳回值 | 含義 |
---|---|
>0 | 有事件的fd的數量 |
逾時 | |
-1 | 失敗 |
示例:
int main()
{
epoll_event epoll_events[1024];
int n = epoll_wait(epollfd,epoll_events,1024,1000);
if(n<0)
{
//被信号中斷
if(errno == EINTR)
{
//...
}
}
else if(n ==0)
{
//...
}
for (size_t i = 0; i < n; ++i)
{
if(epoll_events[i].events & EPOLLIN)
{
//處理可讀事件
} else if (epoll_events[i].events & EPOLLOUT)
{
//處理可寫事件
} else if (epoll_events[i].events & EPOLLERR)
{
//處理出錯事件
}
}
}
poll
與 epoll_wait
函數的差別
poll
epoll_wait
邊緣觸發模式(ET) 和 水準觸發模式 (LT)
水準觸發模式:一個事件隻要有,就會一直觸發
邊緣觸發模式:一個事件從無到有才會觸發
想不出好例子,摘抄一個吧
水準觸發
兒子:媽媽,我收到了500元的壓歲錢。
媽媽:嗯,省着點花。
兒子:媽媽,我今天花了200元買了個變形金剛。
媽媽:以後不要亂花錢。
兒子:媽媽,我今天買了好多好吃的,還剩下100元。
媽媽:用完了這些錢,我可不會再給你錢了。
兒子:媽媽,那100元我沒花,我攢起來了
媽媽:這才是明智的做法!
兒子:媽媽,那100元我還沒花,我還有錢的。
媽媽:嗯,繼續保持。
兒子:媽媽,我還有100元錢。
媽媽:…
接下來的情形就是沒完沒了了:隻要兒子一直有錢,他就一直會向他的媽媽彙報。LT模式下,隻要核心緩沖區中還有未讀資料,就會一直傳回描述符的就緒狀态,即不斷地喚醒應用程序。在上面的例子中,兒子是緩沖區,錢是資料,媽媽則是應用程序了解兒子的壓歲錢狀況(讀操作)。
邊緣觸發
兒子:媽媽,我收到了500元的壓歲錢。
媽媽:嗯,省着點花。
(兒子使用壓歲錢購買了變形金剛和零食。)
兒子:
媽媽:兒子你倒是說話啊?壓歲錢呢?
這個就是ET模式,兒子隻在第一次收到壓歲錢時通知媽媽,接下來兒子怎麼把壓歲錢花掉并沒有通知媽媽。即兒子從沒錢變成有錢,需要通知媽媽,接下來錢變少了,則不會再通知媽媽了。在ET模式下, 緩沖區從不可讀變成可讀,會喚醒應用程序,緩沖區資料變少的情況,則不會再喚醒應用程序。
/**
* 驗證epoll的LT與ET模式的差別,epoll_server.cpp
* zhangyl 2019.04.01
*/
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<fcntl.h>
#include<sys/epoll.h>
#include<poll.h>
#include<iostream>
#include<string.h>
#include<vector>
#include<errno.h>
#include<iostream>
int main()
{
//建立一個監聽socket
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
if (listenfd == -1)
{
std::cout << "create listen socket error" << std::endl;
return -1;
}
//設定重用ip位址和端口号
int on = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (char*)&on, sizeof(on));
setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, (char*)&on, sizeof(on));
//将監聽socker設定為非阻塞的
int oldSocketFlag = fcntl(listenfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if (fcntl(listenfd, F_SETFL, newSocketFlag) == -1)
{
close(listenfd);
std::cout << "set listenfd to nonblock error" << std::endl;
return -1;
}
//初始化伺服器位址
struct sockaddr_in bindaddr;
bindaddr.sin_family = AF_INET;
bindaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bindaddr.sin_port = htons(3000);
if (bind(listenfd, (struct sockaddr*)&bindaddr, sizeof(bindaddr)) == -1)
{
std::cout << "bind listen socker error." << std::endl;
close(listenfd);
return -1;
}
//啟動監聽
if (listen(listenfd, SOMAXCONN) == -1)
{
std::cout << "listen error." << std::endl;
close(listenfd);
return -1;
}
//建立epollfd
int epollfd = epoll_create(1);
if (epollfd == -1)
{
std::cout << "create epollfd error." << std::endl;
close(listenfd);
return -1;
}
epoll_event listen_fd_event;
listen_fd_event.data.fd = listenfd;
listen_fd_event.events = EPOLLIN;
//取消注釋掉這一行,則使用ET模式
//listen_fd_event.events |= EPOLLET;
//将監聽sokcet綁定到epollfd上去
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &listen_fd_event) == -1)
{
std::cout << "epoll_ctl error" << std::endl;
close(listenfd);
return -1;
}
int n;
while (true)
{
epoll_event epoll_events[1024];
n = epoll_wait(epollfd, epoll_events, 1024, 1000);
if (n < 0)
{
//被信号中斷
if (errno == EINTR)
continue;
//出錯,退出
break;
}
else if (n == 0)
{
//逾時,繼續
continue;
}
for (size_t i = 0; i < n; ++i)
{
//事件可讀
if (epoll_events[i].events & EPOLLIN)
{
if (epoll_events[i].data.fd == listenfd)
{
//偵聽socket,接受新連接配接
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr);
int clientfd = accept(listenfd, (struct sockaddr*)&clientaddr, &clientaddrlen);
if (clientfd != -1)
{
int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);
int newSocketFlag = oldSocketFlag | O_NONBLOCK;
if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1)
{
close(clientfd);
std::cout << "set clientfd to nonblocking error." << std::endl;
}
else
{
epoll_event client_fd_event;
client_fd_event.data.fd = clientfd;
client_fd_event.events = EPOLLIN;
//取消注釋這一行,則使用ET模式
//client_fd_event.events |= EPOLLET;
if (epoll_ctl(epollfd, EPOLL_CTL_ADD, clientfd, &client_fd_event) != -1)
{
std::cout << "new client accepted,clientfd: " << clientfd << std::endl;
}
else
{
std::cout << "add client fd to epollfd error" << std::endl;
close(clientfd);
}
}
}
}
else
{
std::cout << "client fd: " << epoll_events[i].data.fd << " recv data." << std::endl;
//普通clientfd
char ch;
//每次隻收一個位元組
int m = recv(epoll_events[i].data.fd, &ch, 1, 0);
if (m == 0)
{
//對端關閉了連接配接,從epollfd上移除clientfd
if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
}
close(epoll_events[i].data.fd);
}
else if (m < 0)
{
//出錯
if (errno != EWOULDBLOCK && errno != EINTR)
{
if (epoll_ctl(epollfd, EPOLL_CTL_DEL, epoll_events[i].data.fd, NULL) != -1)
{
std::cout << "client disconnected,clientfd:" << epoll_events[i].data.fd << std::endl;
}
close(epoll_events[i].data.fd);
}
}
else
{
//正常收到資料
std::cout << "recv from client:" << epoll_events[i].data.fd << ", " << ch << std::endl;
}
}
}
else if (epoll_events[i].events & POLLERR)
{
// TODO 暫不處理
}
}
}
close(listenfd);
return 0;
}
現在采用的是一個水準模式,隻要有資料可讀,就會觸發事件
現在采用邊緣觸發模式
采用邊緣觸發模式,隻有有新資料到來才會觸發,是以就有了上面的現象