1.水平触发和边沿触发
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。
Level Triggered (LT) 水平触发只要有数据都会触发。
LT模式是默认的工作模式,在这种模式下epoll相当于一个效率较高的poll。使用ET模式需要在内核的事件表注册文件描述符的EPOLLET事件。
2.水平触发下的epoll服务器
编写epoll服务器的核心步骤:
- 创建绑定监听IP和端口的套接字lfd
- 创建内核事件表epfd
- 注册lfd 到内核事件表epfd
- 循环监听内核事件触发epoll_wait()
- 触发事件的套接字连接分为监听套接字和连接套接字
- 处理监听套接字(accept--->新连接注册到事件表)
- 处理连接套接字(read读数据、处理数据)
#include<stdio.h>
#include<string.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<stdlib.h>
#include<libgen.h>
#include "pub.h"
#define OPENMAX 1024
int main(int argc, char *argv[])
{
if(argc<3)
{
printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
return -1;
}
char *ip = argv[1];
int port = atoi(argv[2]);
int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
// 绑定、监听
lfd = socketBind(ip, port);
if(-1 == lfd)
return -1;
epfd = epoll_create(OPENMAX);
if(epfd < 0)
{
perror("epoll_create");
return -1;
}
// 向epoll事件表注册lfd和lfd的事件(上树)
struct epoll_event tmp_ev, client_ev[OPENMAX];
memset(client_ev, 0, sizeof(client_ev));
tmp_ev.events = EPOLLIN;
tmp_ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
char buf[1024] = "";
// 循环处理epoll事件
while(1)
{
nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
if(-1 == nready)
{
perror("epoll_wait");
break;
}
for(int i=0; i<nready; i++)
{
cfd = client_ev[i].data.fd;
// lfd请求连接事件
if(cfd == lfd)
{
cfd = Accept_with_print(lfd);
if(-1 == cfd)
return -1;
// 注册cfd
tmp_ev.events = EPOLLIN;
tmp_ev.data.fd = cfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
}
// cfd 读取事件
else
{
int num = read(cfd, buf, sizeof(buf));
if(num < 0)
{
perror("read");
return -1;
}
else if(num == 0)
{
// 先将cfd删除注册,再关闭cfd。
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
close(cfd);
printf("client closed\n");
}
else
{
write(cfd, buf, num);
}
}
}
}
// 先将lfd删除注册,再关闭lfd
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
close(lfd);
close(epfd);
return 0;
}
2.边缘触发下的epoll服务器
边缘触发与水平触发服务器的核心步骤基本相似,其不同在于注册新连接事件时需要注册边缘EPOLLET模式,此外读写数据需要采用循环的方式,因此文件描述符应该设置为非阻塞模式。
编写边缘epoll服务器的核心步骤:
- 创建绑定监听IP和端口的套接字lfd
- 创建内核事件表epfd
- 注册lfd 到内核事件表epfd
- 循环监听内核事件触发epoll_wait()
- 触发事件的套接字连接分为监听套接字和连接套接字
- 处理监听套接字(accept--->新连接注册到事件表(注册EPOLLET模式))
- 处理连接套接字(循环read读数据(非阻塞)、处理数据)
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。
#include<stdio.h>
#include<string.h>
#include<sys/epoll.h>
#include<unistd.h>
#include<stdlib.h>
#include<libgen.h>
#include "pub.h"
#include<fcntl.h>
#define OPENMAX 1024
// 边缘模式ET,需要将fd设为非阻塞读取,避免循环读数据时阻塞。
int setnonblocking(int fd)
{
int old_flag = fcntl(fd, F_GETFL);
int new_flag = old_flag | O_NONBLOCK;
fcntl(fd, F_SETFL, new_flag);
return new_flag;
}
int main(int argc, char *argv[])
{
if(argc<3)
{
printf("usage: ./%s ip_address port_num\n", basename(argv[0]));
return -1;
}
char *ip = argv[1];
int port = atoi(argv[2]);
int lfd = -1, cfd = -1, epfd = -1, nready=-1, ret = -1;
// 绑定、监听
lfd = socketBind(ip, port);
if(-1 == lfd)
return -1;
epfd = epoll_create(OPENMAX);
if(epfd < 0)
{
perror("epoll_create");
return -1;
}
// 向epoll事件表注册lfd和lfd的事件(上树)
struct epoll_event tmp_ev, client_ev[OPENMAX];
memset(client_ev, 0, sizeof(client_ev));
tmp_ev.events = EPOLLIN;
tmp_ev.data.fd = lfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp_ev);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
char buf[10] = "";
// 循环处理epoll事件
while(1)
{
nready = epoll_wait(epfd, client_ev, OPENMAX, -1);
if(-1 == nready)
{
perror("epoll_wait");
break;
}
for(int i=0; i<nready; i++)
{
cfd = client_ev[i].data.fd;
// lfd请求连接事件
if(cfd == lfd)
{
cfd = Accept_with_print(lfd);
if(-1 == cfd)
return -1;
//cfd设为非阻塞
setnonblocking(cfd);
// 注册cfd 使用边沿触发的方式
tmp_ev.events = EPOLLIN | EPOLLET;
tmp_ev.data.fd = cfd;
ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp_ev);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
}
// cfd 读取事件
else
{
while(1)
{
int num = read(cfd, buf, sizeof(buf));
if(num < 0)
{
/* 数据读取完毕返回值小于0,errno设置为EAGAIN或者EWOULDBLOCK */
if((errno == EAGAIN) || (errno == EWOULDBLOCK))
{
break;
}
perror("read");
return -1;
}
else if(num == 0)
{
// 先将cfd删除注册,再关闭cfd。
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
close(cfd);
printf("client closed\n");
break;
}
else
{
write(cfd, buf, num);
}
}
}
}
}
// 先将lfd删除注册,再关闭lfd
ret = epoll_ctl(epfd, EPOLL_CTL_DEL, lfd, NULL);
if(-1 == ret)
{
perror("epoll_ctl");
return -1;
}
close(lfd);
close(epfd);
return 0;
}