目录
- 什么是epoll?
- 为什么要使用epoll?
- 如何使用epoll?
- 查看epoll官方帮助文档
- epoll版TCP回音客户端
- epoll版TCP回音服务端
<一> 什么是epoll?
epoll = enhance poll 增强版轮询,是poll/select 的升级版;
IO多路复用:同一个进程下处理多个IO数据流(单进程);
<二> 为什么要使用epoll?
使用epoll在对fd进行监视时,如果有满足条件的fd,则该fd会被添加到一个新的数组区域,该区域存放所有满足条件的fd;
获取/判断满足条件的fd时,遍历当前数组即可;
但上述原理和select原理基本一致,那epoll增强了什么?
epoll在轮询(poll)时,提供了两种触发方式:
- Level Triggered 水平触发(EPOLLLT)
- Edge Triggered 边缘触发(EPOLLET)
水平触发(LT):
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。
如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你。
边缘触发(ET):
当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。
如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。
<三> 如何使用epoll?
1> 首先需要创建一个epoll文件描述符,指定epoll可以监视fd的最大值
#define MAX_EVENTS 10
int epfd = epoll_create(MAX_EVENTS);
if(epfd == -1){
perror("epoll_create"); //stdio.h
exit(EXIT_FAILURE); //stdlib.h
}
2> 指定需要监视fd的活动(读/写/...)
struct epoll_event ev, events[MAX_EVENTS];
int fd = 0;
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = fd;
3> 将指定活动添加进入epoll实例
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,ev) == -1){
perror("epoll_ctl:0");
exit(EXIT_FAILURE);
}
4> 使用循环,开启epoll监视
for(;;){
int num = epoll_wait(epfd,rev,MAX_EVENTS,-1);
if(num == -1){
perror("epoll_wait");
break;
}
//...
int i=0;
for(;i<num;i++){
if(events[i].data.fd == fd){
size = read(0,buffer,sizeof(buffer));
}
//...
}
}
<四> 查看epoll官方帮助文档
man epoll
<五> epoll版TCP回音客户端
修改后客户端处理函数:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <fcntl.h>
#define MAX_EVENTS 2
/* 服务器对客户端请求的处理 */
void server_process(int sc){
int size = 0;
char buffer[1024];
for(;;){
size = read(sc,buffer,sizeof(buffer));
if(size == 0){
return;
}
write(sc,buffer,size);
write(1,buffer,size);
memset(buffer,0,sizeof(buffer));
}
}
/* 客户端处理数据 */
void client_process(int ss){
int size = 0;
char send_buffer[1024]; //发送缓冲区1K
char recv_buffer[1024]; //接收缓冲区1K
int epfd = epoll_create(2);
int fd = 0;
struct epoll_event ev1,ev2;
struct epoll_event events[MAX_EVENTS];
ev1.data.fd = fd;
ev1.events = EPOLLIN|EPOLLET;
ev2.data.fd = ss;
ev2.events = EPOLLIN|EPOLLET;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev1)==-1){
perror("epoll_ctl:0");
exit(EXIT_FAILURE);
}
if(epoll_ctl(epfd,EPOLL_CTL_ADD,ss,&ev2)==-1){
perror("epoll_ctl:ss");
exit(EXIT_FAILURE);
}
memset(send_buffer,0,sizeof(send_buffer));
memset(recv_buffer,0,sizeof(recv_buffer));
for(;;){
int num = epoll_wait(epfd,events,MAX_EVENTS,-1);
printf("%i\n",num);
if(num == -1){
perror("epoll_wait");
exit(EXIT_FAILURE);
}
int i=0;
for(;i<num;i++){
int tfd = events[i].data.fd;
if(tfd == fd){
printf("!!!\n");
size = read(fd,send_buffer,sizeof(send_buffer));
write(ss,send_buffer,size);
memset(send_buffer,0,sizeof(send_buffer));
}else
if(tfd == ss){
printf("???\n");
size = read(ss,recv_buffer,sizeof(recv_buffer));
write(1,recv_buffer,size);
memset(recv_buffer,0,size);
}
}
}
}
程序分析:
epoll_wait超时时间设置为-1,会立即阻塞所有的文件描述符(0被阻塞,终端等待输入数据),
读取后0中被写入数据,边缘被触发,再次read后会直接从有数据的0中读取,写入ss后,ss可读边缘触发,执行回显输出;
由于边缘触发ET只会提醒一次,所以需要确保缓冲区的内容被全部读取,否则会出现 "粘包" 现象,若解决 "粘包" 问题可取消边缘触发。
<六> epoll版TCP回音服务端
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define PORT 8888
#define IP "192.168.12.101"
#define BACKLOG 2
#define MAX_EVENTS 10
int main(){
int ss,sc; /*ss:socket-server sc:socket-client*/
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
int err; /*处理各个步骤的返回值,进行错误判断*/
pid_t pid; /*处理客户端连接后产生的子进程*/
struct epoll_event ev,events[MAX_EVENTS];
int epfd;
epfd = epoll_create(MAX_EVENTS);
ss = socket(AF_INET,SOCK_STREAM,0);
if(ss < 0){
perror("socket");
exit(EXIT_FAILURE);
}
bzero(&(server_addr.sin_zero),8);
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr(IP);
err = bind(ss,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(err == -1){
perror("bind");
exit(EXIT_FAILURE);
}
ev.data.fd = ss; /*listen‘s fd*/
ev.events = EPOLLIN;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,ss,&ev)==-1){
perror("epoll_ctl:ss");
exit(EXIT_FAILURE);
}
err = listen(ss,BACKLOG);
if(err == -1){
perror("listen");
exit(EXIT_FAILURE);
}
/*主循环*/
for(;;){
printf("hello\n");
int num = epoll_wait(epfd,events,MAX_EVENTS,-1);
if(num == -1){
perror("epoll_wait");
exit(EXIT_FAILURE);
}
printf("world!\n");
int i=0;
for(;i<num;i++){
if(events[i].data.fd == ss){
u_int addr_len = sizeof(struct sockaddr);
sc = accept(ss,(struct sockaddr*)&client_addr,&addr_len);
if(sc == -1){
perror("accept");
continue;
}
ev.events = EPOLLIN|EPOLLET;
ev.data.fd = sc;
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sc,&ev)==-1){
perror("epoll_ctl:sc");
exit(EXIT_FAILURE);
}
}else{
server_process(sc);
}
}
}
}
程序分析:
使用epoll监听ss,epoll_wait监听机制取代了fork线程机制,减少线程切换开销。