目录
epoll_create、epoll_ctl、epoll_wait
int epoll_create(int size); 创建一颗监听红黑树
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作监听红黑树
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epoll是Linux下多路复用IO接口的 select/poll 的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被监听的文件描述符集合,零一点原因就是获取事件的时候,无需遍历整个被监听的描述符集合,只要遍历那些被内核IO事件异步唤醒儿加入Ready队列中的描述符集合就行了
epoll函数
epoll_create、epoll_ctl、epoll_wait
int epoll_create(int size); 创建一颗监听红黑树
参数:
size:创建的红黑树的监听节点数量。(仅供内核参考)
返回值:
指向新创建的红黑树的根节点fd
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 操作监听红黑树
参数:
epfd:epoll_create(); 函数的返回值
op:对该监听红黑树所做的操作
EPOLL_CTL_ADD 添加fd到 监听红黑树
EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件
EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘除
fd:待监听的 fd
event:本质 struct epoll_event 结构体地址
typedef union epoll_data {
void *ptr;
int fd; /*对应监听事件的红黑树*/
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* EPOLLIN、EPOLLOUT、EPOLLERR*/
epoll_data_t data; /* User data variable */
};
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
阻塞监听
参数:
epfd:epoll_create(); 函数的返回值
events:传出参数【数组】,满足监听条件的 那些fd结构体
maxevents:数组元素的 总个数
struct epoll_events[1024]
timeout: -1:阻塞
0:不阻塞
>0:超时时间
返回值:
>0:满足坚挺的总个数。可以用做循环上限。
epoll模型图
epoll 实现多路IO转接思路
lfd = socket();
bind();
listen();
int epfd = epoll_create(); //设置红黑树结点
struct epoll_event tmp, ep[1024]; //tmp:用来设置单个fd属性 ep:epoll_wait函数返回值
tmp.events = EPOLLIN;
tmp.data.fd = lfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &tmp); //将lfd挂载到红黑树
while(1) {
ret = epoll_wait(epfd, ep, 1024, -1); //实时监听
for(i = 0; i < ret; i++) {
if(ep[i].data.fd == lfd) { //lfd满足读事件,有新的客户端要发生连接请求
cfd = accept();
tmp.events = EPOLLIN; //初始化cfd的监听属性
emp.data.fd = cfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &tmp); //将cfd挂载到树上
} else if { //cfd发生数据请求
int n = read(ep[i].data.fd, buf, sizeof(buf));
if( n == 0 ) {
close(ep[i].data.fd);
epoll_ctl(epfd, EPOLL_CTL_DEL, ep[i].data.fd, NULL);
} else if( n > 0) {
//数据交换
}
}
}
epoll 代码实现
#define MAXLINE 8192
#define SERV_PORT 8000
#define OPEN_MAX 5000
int main(int arg, char *argv[])
{
int i, listenfd, connfd, sockfd;
int n, num = 0;
sszie_t nready, efd, res;
char buf[MAXLINE], str [INET_ADDRSTRLEN];
socklen_t clien;
struct sockaddr_in client_addr, serv_addr;
struct epoll_envent tep, ep[OPEN_MAX];
listenfd = socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&serv_addr, sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(SERV_PORT);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, 128);
efd = epoll_create(OPEN_MAX);
if(efd == -1)
perror("epoll_create error");
tep.events = EPOLLIN;
tep.data.fd = listenfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, listenfd, &tep);
while(1) {
nready = epoll_wait(efd, ep, OPEN_MAX, -1);
for(i = 0; i < nready; i++) {
if(!(ep[i].events & EPOLLIN))
continue;
if(ep[i].data.fd == listenfd) {
clien = sizeof(client_addr);
connfd = accept(listenfd, (struct sockaddr *)&client_addr, &clien);
printf("cfd:%d==client %d\n", connfd, ++num);
tep.events = EPOLLIN;
tep.data.fd = connfd;
res = epoll_ctl(efd, EPOLL_CTL_ADD, connfd, &tep);
} else {
sockfd = ep[i].data.fd;
n = read(sockfd, buf, MAXLINE);
if(n == 0) {
res = epoll_ctl(efd, EPOLL_CTL_DEL, sockfd, NULL);
if(res == -1)
perror("epoll_ctl error");
close(sockfd);
}else {
for(i = 0; i < n; i++)
buf[i] = toupper(buf[i]);
write(STDOUT_FILENO, buf, n);
write(sockfd, buf, n);
}
}
}
}
}
epoll事件模型
EPOLL时间有两种模型
ET:边沿触发
缓冲区剩余未读尽的数据不会导致epoll_wait返回。新的读/写时间满足才会触发
LT:水平触发 ==默认采用模式, 支持block和no-block
缓冲区剩余未读尽的数据会导致epoll_wait 函数返回
结论:epoll的ET模式是高效模式,但是只支持非阻塞模式,可以通过fcntl改变文件属性
优点:高效。能突破文件描述符上限
缺点:不能跨平台。只能Linux