epoll は、select/poll などの IO イベントのレベル トリガー (Level Triggered) を提供するだけでなく、エッジ トリガー (Edge Triggered) も提供します。これにより、ユーザー空間プログラムが IO ステータスをキャッシュし、epoll_wait/epoll_pwait の呼び出しを減らし、アプリケーションの効率を向上させます。
[なぜ epoll には EPOLLET トリガーモードがあるのですか? 】:
水平トリガに設定されているため、バッファ領域にデータがある限り epoll_wait がトリガされます epoll_wait はシステムコールですので、できるだけ呼び出さないようにしてください
そこで、エッジ トリガを使用してみてください。エッジ トリガは、データを 1 回だけトリガします。このとき、データを一度に読み取る必要があるため、while ループで読み取り、最後の読み取りはデフォルトでブロックされます。読み取り監視できないためブロックできません。cfd を非ブロックに設定し、最後の戻り値が -1 であることを読み取ります。errno の値が EAGAIN であると判断します。これは、データが正常に読み取られたことを意味します。
イベントモデル
EPOLL イベントには 2 つのモデルがあります。
エッジ トリガー (ET) エッジ トリガーは、バッファーにデータがまだあるかどうかに関係なく、データが到着した場合にのみトリガーされます。
レベルトリガー (LT) データがある限りレベルトリガーがトリガーされます。
LT はデフォルトのモード、ET は「高速」モードです
LT (水平トリガー) モードでは、ファイル記述子に読み取り可能なデータがある限り、epoll_wait は毎回そのイベントを返し、ユーザー プログラムに操作を思い出させます。
ET (エッジ トリガー) モードでは、I/O イベントを検出すると、イベント通知を含むファイル記述子が epoll_wait 呼び出しを通じて取得されます。通知された各ファイル記述子について、読み取り可能な場合、ファイル記述子はファイル記述子は空になり、errno が EAGAIN を返すまで読み込まれます。そうでない場合、次の epoll_wait は残りのデータを返さず、イベントは失われます。ET モードがノンブロッキングではない場合、常に読み取りまたは書き込みを行うと、必ず最後にブロックされます。
ケース 1:パイプラインepoll ET/LT トリガー モード
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <errno.h>
#include <unistd.h>
#define MAXLINE 10
int main(int argc, char *argv[])
{
int efd, i;
int pfd[2];
pid_t pid;
char buf[MAXLINE], ch = 'a';
pipe(pfd);
pid = fork();
if (pid == 0) { //子 写
close(pfd[0]);
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(pfd[1], buf, sizeof(buf));
sleep(5);
}
close(pfd[1]);
} else if (pid > 0) { //父 读
struct epoll_event event;
struct epoll_event resevent[10]; //epoll_wait就绪返回event
int res, len;
close(pfd[1]);
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; // ET 边沿触发
// event.events = EPOLLIN; // LT 水平触发 (默认)
event.data.fd = pfd[0];
epoll_ctl(efd, EPOLL_CTL_ADD, pfd[0], &event);
while (1) {
res = epoll_wait(efd, resevent, 10, -1);
printf("res %d\n", res);
if (resevent[0].data.fd == pfd[0]) {
len = read(pfd[0], buf, MAXLINE/2);
write(STDOUT_FILENO, buf, len);
}
}
close(pfd[0]);
close(efd);
} else {
perror("fork");
exit(-1);
}
return 0;
}
ケース 2: ネットワーク C/S モデルに基づく epoll ET トリガー モード (移行的なケース、通常はこのように記述されません)
ET モードがノンブロッキングでない場合、常に読み取りまたは書き込みを行うと、必ず最後にブロックされます。
ET モードはノンブロッキング モードのみをサポートします
/* block_epoll_server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
struct epoll_event event;
struct epoll_event resevent[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发, 默认 LT 水平触发 */
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);//自己建立连接,不用监听listenfd了
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event);
while (1) {
//如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞(阻塞在epoll_wait)。
res = epoll_wait(efd, resevent, 10, -1); //只监听cfd
printf("res %d\n", res);
if (resevent[0].data.fd == connfd) {
//如果ET模式不是非阻塞的,那这个一直读或一直写势必会在最后一次阻塞。(阻塞在epoll_wait)
len = read(connfd, buf, MAXLINE/2); //readn(500)
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
ケース 3: ネットワーク C/Sノンブロッキングモデルに基づく epoll ET トリガー モード(ヘビー)
ET モードはノンブロッキング モードのみをサポートします
/* noblock_epoll_server.c */
#include <stdio.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAXLINE 10
#define SERV_PORT 8000
int main(void)
{
struct sockaddr_in servaddr, cliaddr;
socklen_t cliaddr_len;
int listenfd, connfd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int efd, flag;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(SERV_PORT);
bind(listenfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
listen(listenfd, 20);
///
struct epoll_event event;
struct epoll_event res_event[10];
int res, len;
efd = epoll_create(10);
event.events = EPOLLIN | EPOLLET; /* ET 边沿触发,默认是水平触发 */
//event.events = EPOLLIN;
printf("Accepting connections ...\n");
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);
printf("received from %s at PORT %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
ntohs(cliaddr.sin_port));
flag = fcntl(connfd, F_GETFL); /* 修改connfd为非阻塞读 */ //是指所读套接字变为非阻塞
flag |= O_NONBLOCK;
fcntl(connfd, F_SETFL, flag);
event.data.fd = connfd;
epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &event); //将connfd加入监听红黑树 //是指所读套接字变为非阻塞
while (1) {
printf("epoll_wait begin\n");
res = epoll_wait(efd, res_event, 10, -1); //最多10个, 阻塞监听
printf("epoll_wait end res %d\n", res);
if (res_event[0].data.fd == connfd) {
while ((len = read(connfd, buf, MAXLINE/2)) >0 ) //非阻塞读, 轮询
write(STDOUT_FILENO, buf, len);
}
}
return 0;
}
/* client.c */
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#define MAXLINE 10
#define SERV_PORT 9000
int main(int argc, char *argv[])
{
struct sockaddr_in servaddr;
char buf[MAXLINE];
int sockfd, i;
char ch = 'a';
sockfd = socket(AF_INET, SOCK_STREAM, 0);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &servaddr.sin_addr);
servaddr.sin_port = htons(SERV_PORT);
connect(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr));
while (1) {
//aaaa\n
for (i = 0; i < MAXLINE/2; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//bbbb\n
for (; i < MAXLINE; i++)
buf[i] = ch;
buf[i-1] = '\n';
ch++;
//aaaa\nbbbb\n
write(sockfd, buf, sizeof(buf));
sleep(5);
}
close(sockfd);
return 0;
}