在网络编程中,会涉及到水平触发与边缘触发,工程中以边缘触发较为常见,本文讲述了边缘触发与水平触发的概念,并给出代码示例,通过代码可以很清楚的看到它们之间的区别。
水平触发(level-trggered)
边缘触发(edge-triggered)
水平触发是只要读缓冲区有数据,就会一直触发可读信号,而边缘触发仅仅在空变为非空的时候通知一次,举个例子:
所以边缘触发需要一次性的把缓冲区的数据读完为止,也就是一直读,直到读到EGAIN
(EGAIN
说明缓冲区已经空了)为止,因为这一点,边缘触发需要设置文件句柄为非阻塞。
ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
这里只简单的给出了水平触发与边缘触发的处理方式的不同,边缘触发相对水平触发处理的细节更多一些,
//水平触发
ret = read(fd, buf, sizeof(buf));
//边缘触发(代码不完整,仅为简单区别与水平触发方式的代码)
while(true) {
ret = read(fd, buf, sizeof(buf);
if (ret == EAGAIN) break;
}
通过下面的代码示例,能够看到水平触发与边缘触发代码的不同以及触发次数的不同。
下面这段代码是echo server代码:
/* echo server*/
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX_EVENTS 1024
#define LISTEN_PORT 33333
#define MAX_BUF 1024
// #define LEVEL_TRIGGER
int setnonblocking(int sockfd);
int events_handle_level(int epfd, struct epoll_event ev);
int events_handle_edge(int epfd, struct epoll_event ev);
void run();
int main(int _argc, char* _argv[]) {
run();
return 0;
}
void run() {
int epfd = epoll_create1(0);
if (-1 == epfd) {
perror("epoll_create1 failure.");
exit(EXIT_FAILURE);
}
char str[INET_ADDRSTRLEN];
struct sockaddr_in seraddr, cliaddr;
socklen_t cliaddr_len = sizeof(cliaddr);
int listen_sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
seraddr.sin_addr.s_addr = htonl(INADDR_ANY);
seraddr.sin_port = htons(LISTEN_PORT);
int opt = 1;
setsockopt(listen_sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
if (-1 == bind(listen_sock, (struct sockaddr*)&seraddr, sizeof(seraddr))) {
perror("bind server addr failure.");
exit(EXIT_FAILURE);
}
listen(listen_sock, 5);
struct epoll_event ev, events[MAX_EVENTS];
ev.events = EPOLLIN;
ev.data.fd = listen_sock;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, listen_sock, &ev)) {
perror("epoll_ctl add listen_sock failure.");
exit(EXIT_FAILURE);
}
int nfds = 0;
while (1) {
nfds = epoll_wait(epfd, events, MAX_EVENTS, -1);
if (-1 == nfds) {
perror("epoll_wait failure.");
exit(EXIT_FAILURE);
}
for ( int n = 0; n < nfds; ++n) {
if (events[n].data.fd == listen_sock) {
int conn_sock = accept(listen_sock, (struct sockaddr *)&cliaddr, &cliaddr_len);
if (-1 == conn_sock) {
perror("accept failure.");
exit(EXIT_FAILURE);
}
printf("accept from %s:%d\n", inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)), ntohs(cliaddr.sin_port));
setnonblocking(conn_sock);
#ifdef LEVEL_TRIGGER
ev.events = EPOLLIN;
#else
ev.events = EPOLLIN | EPOLLET;
#endif
ev.data.fd = conn_sock;
if (-1 == epoll_ctl(epfd, EPOLL_CTL_ADD, conn_sock, &ev)) {
perror("epoll_ctl add conn_sock failure.");
exit(EXIT_FAILURE);
}
} else {
#ifdef LEVEL_TRIGGER
events_handle_level(epfd, events[n]);
#else
events_handle_edge(epfd, events[n]);
#endif
}
}
}
close(listen_sock);
close(epfd);
}
int setnonblocking(int sockfd){
if (fcntl(sockfd, F_SETFL, fcntl(sockfd, F_GETFD, 0)|O_NONBLOCK) == -1) {
return -1;
}
return 0;
}
int events_handle_level(int epfd, struct epoll_event ev) {
printf("events_handle, ev.events = %d\n", ev.events);
int fd = ev.data.fd;
if (ev.events == EPOLLIN) {
char buf[MAX_BUF];
bzero(buf, MAX_BUF);
int n = 0;
n = read(fd, buf, 5);
printf("step in level_trigger, read bytes:%d\n", n);
if (n < 0) {
perror("read fd failure.");
if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) {
perror("epoll_ctl del fd failure.");
exit(EXIT_FAILURE);
}
return -1;
}
if (0 == n) {
if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) {
perror("epoll_ctl del fd failure.");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
printf("recv from client: %s\n", buf);
write(fd, buf, n);
return 0;
}
return 0;
}
int events_handle_edge(int epfd, struct epoll_event ev) {
printf("events_handle, ev.events = %d\n", ev.events);
int fd = ev.data.fd;
if (ev.events == EPOLLIN) {
char* buf = (char*)malloc(MAX_BUF);
bzero(buf, MAX_BUF);
int count = 0;
int n = 0;
while (1) {
n = read(fd, (buf + n), 5);
printf("step in edge_trigger, read bytes:%d\n", n);
if (n > 0) {
count += n;
} else if (0 == n) {
break;
} else if (n < 0 && EAGAIN == errno) {
printf("errno == EAGAIN, break.\n");
break;
} else {
perror("read failure.");
break;
}
}
if (0 == count) {
if (-1 == epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev)) {
perror("epoll_ctl del fd failure.");
exit(EXIT_FAILURE);
}
close(fd);
return 0;
}
printf("recv from client: %s\n", buf);
write(fd, buf, count);
free(buf);
return 0;
}
return 0;
}
客户端代码:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define SERVER_PORT 33333
#define MAXLEN 1024
void client_handle(int sock);
int main(int argc, char* argv[]) {
for (int i = 1; i < argc; ++i) {
printf("input args %d: %s\n", i, argv[i]);
}
struct sockaddr_in seraddr;
int server_port = SERVER_PORT;
if (2 == argc) {
server_port = atoi(argv[1]);
}
int sock = socket(AF_INET, SOCK_STREAM, 0);
bzero(&seraddr, sizeof(seraddr));
seraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr);
seraddr.sin_port = htons(server_port);
connect(sock, (struct sockaddr *)&seraddr, sizeof(seraddr));
client_handle(sock);
return 0;
}
void client_handle(int sock) {
char sendbuf[MAXLEN], recvbuf[MAXLEN];
bzero(sendbuf, MAXLEN);
bzero(recvbuf, MAXLEN);
int n = 0;
while (1) {
if (NULL == fgets(sendbuf, MAXLEN, stdin)) {
break;
}
// 按`#`号退出
if ('#' == sendbuf[0]) {
break;
}
write(sock, sendbuf, strlen(sendbuf));
n = read(sock, recvbuf, MAXLEN);
if (0 == n) {
break;
}
write(STDOUT_FILENO, recvbuf, n);
}
close(sock);
}
客户端连接服务端,分别向服务端发送123
和123456
,服务端运行结果如下:
可以看到边缘触发只触发一次
sl@Li:~/Works/study/epoll$ ./server
accept from 127.0.0.1:38170
events_handle, ev.events = 1
step in edge_trigger, read bytes:4
step in edge_trigger, read bytes:-1
errno == EAGAIN, break.
recv from client: 123
events_handle, ev.events = 1
step in edge_trigger, read bytes:5
step in edge_trigger, read bytes:2
step in edge_trigger, read bytes:-1
errno == EAGAIN, break.
recv from client: 123456
重新编译服务端程序,再次发送123
和123456
,服务端运行结果如下:
可以看到,在接收123456
时,触发了两次,而边缘触发只触发一次。
sl@Li:~/Works/study/epoll$ ./server
accept from 127.0.0.1:38364
events_handle, ev.events = 1
step in level_trigger, read bytes:4
recv from client: 123
events_handle, ev.events = 1
step in level_trigger, read bytes:5
recv from client: 12345
events_handle, ev.events = 1
step in level_trigger, read bytes:2
recv from client: 6
参考文档:epoll事件通知机制详解,水平触发和边沿触发的区别