您的位置:首页 > 理论基础 > 计算机网络

epoll:EPOLLLT和EPOLLET的区别

2015-12-31 00:38 363 查看
概念:

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
.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
.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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>
#include <string.h>

#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 <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/epoll.h>
#include <fcntl.h>
#include <errno.h>

#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
.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
.events & EPOLLIN) {
char buf[2] = {0};
int fd = events
.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
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 网络编程 epoll tcp