在網絡編程中,會涉及到水平觸發與邊緣觸發的概念,工程中以邊緣觸發較爲常見,本文講述了邊緣觸發與水平觸發的概念,並給出代碼示例,經過代碼能夠很清楚的看到它們之間的區別。編程
水平觸發(level-trggered)bash
邊緣觸發(edge-triggered)微信
水平觸發是隻要讀緩衝區有數據,就會一直觸發可讀信號,而邊緣觸發僅僅在空變爲非空的時候通知一次,舉個例子:網絡
因此邊緣觸發須要一次性的把緩衝區的數據讀完爲止,也就是一直讀,直到讀到EGAIN
(EGAIN
說明緩衝區已經空了)爲止,由於這一點,邊緣觸發須要設置文件句柄爲非阻塞。socket
ET模式在很大程度上減小了epoll事件被重複觸發的次數,所以效率要比LT模式高。epoll工做在ET模式的時候,必須使用非阻塞套接口,以免因爲一個文件句柄的阻塞讀/阻塞寫操做把處理多個文件描述符的任務餓死。ui
這裏只簡單的給出了水平觸發與邊緣觸發的處理方式的不一樣,邊緣觸發相對水平觸發處理的細節更多一些,spa
//水平觸發
ret = read(fd, buf, sizeof(buf));
//邊緣觸發(代碼不完整,僅爲簡單區別與水平觸發方式的代碼)
while(true) {
ret = read(fd, buf, sizeof(buf);
if (ret == EAGAIN) break;
}
複製代碼
經過下面的代碼示例,可以看到水平觸發與邊緣觸發代碼的不一樣以及觸發次數的不一樣。經過這個示例可以加深你對邊緣觸發與水平觸發的理解。code
/* echo server*/
#include<sys/epoll.h>
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<fcntl.h>
#include<errno.h>
#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<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<netdb.h>
#include<stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#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
,服務端運行結果以下:cdn
能夠看到邊緣觸發只觸發一次server
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
複製代碼
關注微信公衆號,天天進步一點點!
![]()