概念:
Level-triggered :水平触发,缺省模式
edge-triggered :边缘触发
比如redis用LT模式,nginx用ET模式
通知模式:
LT模式时,事件就绪时,假设对事件没做处理,内核会反复通知事件就绪
ET模式时,事件就绪时,假设对事件没做处理,内核不会反复通知事件就绪
事件通知的细节:
1.调用epoll_ctl,ADD或者MOD事件EPOLLIN
LT:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会持续返回EPOLLIN
ET:如果此时缓存区没有可读数据,则epoll_wait不会返回EPOLLIN,如果此时缓冲区有可读数据,则epoll_wait会返回一次EPOLLIN
2.调用epoll_ctl,ADD或者MOD事件EPOLLOUT
LT:如果不调用epoll_ctl将EPOLLOUT修改为EPOLLIN,则epoll_wait会持续返回EPOLLOUT(前提条件是写缓冲区未满)
ET:epoll_wait只会返回一次EPOLLOUT
针对TCP的测试详请,都是non-blocking:
1.listenfd设置为LT
struct epoll_event ev, events[MAX_EVENTS];
int epollfd = epoll_create(10);
if (-1 == epollfd) {
perror("epoll_create fail");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN; // LT
ev.data.fd = listenfd;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {
perror("epoll_ctl: listenfd fail");
exit(EXIT_FAILURE);
}
for (;;) {
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
表现:当3次握手完成,如果不进行accept操作,那么内核会反复通知
2.listenfd设置为ET
struct epoll_event ev, events[MAX_EVENTS];
int epollfd = epoll_create(10);
if (-1 == epollfd) {
perror("epoll_create fail");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN | EPOLLET; // ET
ev.data.fd = listenfd;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {
perror("epoll_ctl: listenfd fail");
exit(EXIT_FAILURE);
}
for (;;) {
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
表现:当3次握手完成,如果不进行accept操作,那么内核只会通知一次
3.connectfd设置为LT
if (events[n].data.fd == listenfd) {
int connfd = accept(listenfd, NULL, NULL);
if (-1 == connfd) {
perror("accept fail");
continue;
}
setnonblocking(connfd);
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = connfd;
if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) {
perror("epoll_ctl: sock");
continue;
}
表现:
1.客户端发送了24个字节
2.服务器一次只读取8个字节
3.内核会连续通知,epoll_wait会持续返回,直到缓冲区的数据被读取完毕或者说socket不在处于readable/writable状态
4.connectfd设置为ET
if (events[n].data.fd == listenfd) {
int connfd = accept(listenfd, NULL, NULL);
if (-1 == connfd) {
perror("accept fail");
continue;
}
setnonblocking(connfd);
struct epoll_event ev;
ev.events = EPOLLIN | EPOLLET;
ev.data.fd = connfd;
if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &ev)) {
perror("epoll_ctl: sock");
continue;
}
表现:
1.客户端发送了24个字节
2.服务器一次只读取8个字节
3.内核只会通知一次,剩下的数据会留在缓冲区中
在ET模式下何时会再次通知可读写事件,也是epoll_wait有返回?
只有socket从unreadable/unwritable变为readable/writable状态:
4.当客户端再次发送数据时,则内核会继续通知可读事件
5.当服务器在每次读取完数据,显式的调用epoll_ctl来注册可读事件,如果缓冲区有可读数据则内核会继续通知可读事件
下面提供简单的测试源码,需要微调才能测试完以上几种情况:
------------------------------------------------------------------------------------------------------------------------------------------------
客户端代码:client.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 10
int main()
{
// socket
struct sockaddr_in servaddr;
short port = 9527;
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
servaddr.sin_port = htons(port);
if (connect(sockfd, (sockaddr *) &servaddr, sizeof(sockaddr_in)) < 0) {
perror("connect fail");
exit(EXIT_FAILURE);
}
const char* buf = "daiyudong";
for (;;) {
int len = (int)write(sockfd, buf, strlen(buf));
if (len > 0) {
printf("write len=%d\n", len);
}
sleep(1);
}
}
服务器代码:server.cpp
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 10
static void setnonblocking(int fd) {
int flag = fcntl(fd, F_GETFL, 0);
if (flag < 0) {
perror("fcntl F_GETFL:");
return;
}
if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0) {
perror("fcntl F_SETFL:");
}
}
static int epoll_add(int efd, int sock) {
struct epoll_event ev;
ev.events = EPOLLIN;
ev.data.fd = sock;
if (-1 == epoll_ctl(efd, EPOLL_CTL_ADD, sock, &ev)) {
perror("epoll_ctl: sock");
return 1;
}
return 0;
}
static void epoll_write(int efd, int sock, bool enable) {
struct epoll_event ev;
ev.events = EPOLLIN | (enable ? EPOLLOUT : 0);
ev.data.fd = sock;
epoll_ctl(efd, EPOLL_CTL_MOD, sock, &ev);
}
static void epoll_del(int efd, int sock) {
epoll_ctl(efd, EPOLL_CTL_DEL, sock , NULL);
}
int main()
{
// socket
int listenfd;
struct sockaddr_in servaddr;
short port = 9527;
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(port);
listenfd = socket(AF_INET, SOCK_STREAM, 0);
setnonblocking(listenfd);
int res = bind(listenfd, (sockaddr *)&servaddr, sizeof(sockaddr_in));
if (0 == res)
printf("server bind success, 0.0.0.0:%d\n", port);
else {
perror("bind fail");
exit(EXIT_FAILURE);
}
res = listen(listenfd, 100);
if (0 == res)
printf("server listen success\n");
else {
perror("listen fail");
exit(EXIT_FAILURE);
}
// epoll
struct epoll_event ev, events[MAX_EVENTS];
int epollfd = epoll_create(10);
if (-1 == epollfd) {
perror("epoll_create fail");
exit(EXIT_FAILURE);
}
ev.events = EPOLLIN; // LT
ev.data.fd = listenfd;
if (-1 == epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev)) {
perror("epoll_ctl: listenfd fail");
exit(EXIT_FAILURE);
}
for (;;) {
int nfds = epoll_wait(epollfd, events, MAX_EVENTS, -1);
if (-1 == nfds) {
perror("epoll_wait fail");
exit(EXIT_FAILURE);
}
for (int n = 0; n < nfds; ++n) {
if (events[n].data.fd == listenfd) {
int connfd = accept(listenfd, NULL, NULL);
if (-1 == connfd) {
perror("accept fail");
continue;
}
setnonblocking(connfd);
epoll_add(epollfd, connfd);
printf("connfd:%d\n", connfd);
}
else if (events[n].events & EPOLLIN) {
char buf[2] = {0};
int fd = events[n].data.fd;
size_t count = 1;
int len = (int)read(fd, buf, count);
if (len > 0) {
printf("read len=%d buf=%s\n", len, buf);
//epoll_write(epollfd, fd, false);
}
else if (len < 0) {
switch(errno) {
case EINTR:
case EAGAIN:
printf("try again\n");
//epoll_write(epollfd, fd, false);
break;
default:
epoll_del(epollfd, fd);
close(fd);
printf("game over\n");
}
}
else if (len == 0) {
epoll_del(epollfd, fd);
close(fd);
printf("game over\n");
}
}
else {
// pass
}
}
}
}
小结:
针对listenfd,默认使用LT模式,ET模式并无实际意义,也无收益
针对connectfd,使用ET模式则需要注意到与LT模式的读写逻辑不同,比如读取数据,则需要在一个事件内读取完毕
参考man 7 epoll
http://linux.die.net/man/7/epoll
---------------------
作者:带鱼兄
来源:CSDN
原文:https://blog.csdn.net/daiyudong2020/article/details/50439029
版权声明:本文为博主原创文章,转载请附上博文链接!