一、多路复用之——epoll
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);
1、epoll_create:创建一个epoll句柄,size参数可以忽略
当创建一个句柄后,会占用一个fd值,故在使用完后,要close(fd)。
2、epoll_ctl:注册要监听的事件类型
参数:(1)epfd:epoll_create的返回值;
(2)op:EPOLL_CTL_ADD 注册新fd到epfd中
EPOLL_CTL_MOD 修改已经注册的fd
EPOLL_CTL_DEL 从epfd中删除一个fd
(3)fd:要监听的fd;
(4)event:监听的事件;
struct epoll_event{
_uint32_t events;
epoll_data_t data;
}
typedef union epoll_data{
void* ptr;
int fd;
_uint32_t U32;
_uint64_t U64;
}
(5) events集合:
EPOLLIN:表示对应的文件描述符可以读;
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急事件可读;
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:将EPOLL设为边缘触发模式;
EPOLLLT:将EPOLL设为水平触发模式;
EPOLLONESHOT:只监听一次事件;
3、epoll_wait:收集在epoll监控的事件中已经发生的事件
参数:(1)epfd:创建的epoll句柄;
(2)events:已分配好的epoll_events结构体,epoll会将发生的事件放到events中;
(3)maxevents:events的大小;
(4)timeout:NULL:没有timeout,一直阻塞等待知道某个事件发生;
0:仅检测描述符集合的状态,然后立即返回;
特定值:等待timeout时间,如果没有发生,超时返回。
4、epoll的两种工作模式:
epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读、阻塞写操作把处理多个文件描述符的任务饿死。
epoll工作在LT模式的时候,在收到多个数据的时候仍然会产生多个事件,支持阻塞和非阻塞接口,这样,内核告诉你一个文件描述符是否就绪了,然后你可以进行I/O,若你不做任何操作,内核还是会继续通知你的,错误率小。
ET与LT的区别在于,当一个新事件到来时,ET模式下当然可以从epoll_wait调用中获取到这个事件,可是如果这次没有把这个事件对应的套接字缓冲区处理完,在这个套接字中没有新事件到来时,ET模式下无法再次获取数据。但LT正好相反,只要一个事件对应的套接字缓冲区还有数据,就能够获取。
5、epoll模型的优点:
(1)使用内存映射(mmap)技术,避免用户到内存的拷贝;
(2)epoll事先通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait时使得到通知;
(3)监视的文件描述符数量不受限制,它所支持的fd上限是最大可以打开文件的数目;
(4)I/O的效率不会随着fd数量的增加而下降,select、poll实现需要自己不断轮询所有fd集合,指到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能睡眠和唤醒多次交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只需要遍历就绪链表是否为空就行了。这节省了大量的CPU时间。这就是回调机制的性能提升。
代码示例:
epoll_server.c
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<fcntl.h>
#define MAX_NUM 64
int startup(int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0){
perror("socket");
exit(1);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=htonl(INADDR_ANY);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0){
perror("bind");
exit(2);
}
if(listen(sock,5)<0){
perror("listen");
exit(3);
}
return sock;
}
//set file descriptor for no blocking mode
void set_nonoblock(int _fd)
{
int fl=fcntl(_fd,F_GETFL);
if(fl<0){
perror("fcntl");
return;
}
fcntl(_fd,fl | O_NONBLOCK);
return;
}
//recv data
int read_data(int _fd,char* buf,int len)
{
ssize_t _size=-1;
int total=0;
while((_size=recv(_fd,buf+total,len-1,MSG_DONTWAIT))){
if(_size>0){
total+=_size;
}else if(_size==0){//file end
return 0;
}else{
return -1;
}
}
}
int main()
{
short port=8080;
int listen_sock=startup(port);
struct sockaddr_in client;
socklen_t len=sizeof(client);
int epoll_fd=epoll_create(256);//create epoll handle
int timeout=1000;
if(epoll_fd<0){
perror("epoll_create");
exit(1);
}
struct epoll_event _ev;//save care fd
_ev.events=EPOLLIN;
_ev.data.fd=listen_sock;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&_ev)<0){
perror("epoll_ctl");
goto LABLE;
}
struct epoll_event _ev_out[MAX_NUM];//save ready fd
char buf[1024*5];
memset(buf,'\0',sizeof(buf));
int ready_num=-1;//save ready_fd num
while(1){
switch(ready_num=epoll_wait(epoll_fd,_ev_out,MAX_NUM,timeout)){
case 0:
printf("timeout\n");
break;
case -1:
perror("epoll_wait");
break;
default:
{
int i=0;
for(;i<ready_num;i++){
int _fd=_ev_out[i].data.fd;
//listen_sock
if(_fd==listen_sock && (_ev_out[i].events & EPOLLIN)){
int new_sock=accept(_fd,(struct sockaddr*)&client,&len);
if(new_sock<0){
perror("accept");
continue;
}
printf("get a new connect...\n");
set_nonoblock(new_sock);
_ev.events=EPOLLIN | EPOLLET;
_ev.data.fd=new_sock;
if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&_ev)<0){
perror("epoll_ctl");
close(new_sock);
continue;
}
continue;
}
//data_sock
if(_ev_out[i].events & EPOLLIN){
if(read_data(_fd,buf,sizeof(buf))==0){
printf("client close...");
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,_fd,NULL);
}
printf("%s\n",buf);
}
}
}
break;
}
}
LABLE:
close(epoll_fd);
return 0;
}
Makefile:
epoll_server:epoll_server.c
gcc -o $@ $^
.PHONY:clean
clean:
rm -rf epoll_server