I/O复用—-epoll系统调用
#include<sys/epoll.h>//使用epoll的3个函数需加的头文件。
实现:epoll与select,poll有很大的不同,epoll是使用一组函数实现的,而不是某一个函数。
亮点:内核事件表(epfd)就是在内核中建立一个表,专门放事件,底层数据结构是一个红黑树。
数据结构:要知道用了什么数据结构就要先看他涉及到的函数是怎么调用的。实现epoll我们总共涉及到三个函数:
第一个函数
1、int epoll_create(int size);
int epfd=epoll_create(1);//一般用法
创建内核事件表,返回值为内核事件表的文件描述符。(一般为4)
size参数并不起作用,只是给内核一个提醒告诉它内核事件表的大小,为什么不起作用呢?因为,底层数据结构是红黑树,是动态开辟的,所以给他指定大小并没有什么意义。3个i/o复用系统调用中,除了select是固定的大小因为它的底层是数组,其他两个都是动态开辟的。
2、int epoll_ctl(int epfd,int op,struct epoll_event *event)
用来操作内核事件表的,增删改:
EPOLL_CTL_ADD//添加fd上的事件
EPOLL_CTL_MOD//修改
EPOLL_CTL_DEL//删除
epfd:内核事件表文件描述符
op:指定操作类型(增删改)
fd:操作哪个文件描述符
event:指定事件,这是一个结构体(struct epoll_event)
struct epoll_event
{
_uint32_t events;//epoll事件
epoll_data_t data_t;//数据结构 而这又是一个联合体
}
返回值:成功返回0,失败返回-1;
typedef union epoll_data
{
void *ptr;
int fd; //文件描述符,但跟epoll_clt()函数的第3个参数意义不同。如果要删除一个数据就不需要这个第4个参数了。
uint32_t u32;
uint64_t u64;
}epoll_data_t;
3、int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);//内核事件表;就绪事件结构体数组;数组最大大小;超时时间
events:这个函数中的结构体数组并不是epoll_clt()函数中第4个参数event。那个是一个结构体,就是将要操作的关于一个文件描述符的事情。而这里这个events是就绪文件描述符的数组。这就是这个epoll的聪明之处,这个函数是核心函数,调用返回之后,并不像那两个把所有都返回回来(还需要循环将所有文件描述符遍历一遍看是否有文件描述符就绪),而是,只返回就绪的文件描述符,方便了很多,只要返回的就都是就绪的,只要循环处理就行了,循环的次数大大减少。
maxevents:前一个数组的最大大小。
timeout:超时时间,单位毫秒,置-1为阻塞。
返回值:就绪文件描述符个数。
原理:多了一个内核事件表(用epoll_create()函数创建),将文件描述符放入内核事件表中监听起来,内核事件表怎么标识呢?因为一切皆文件,所以我们为内核事件表专门分配一个文件描述符(一般为4,因为前三个为标准输入0输出1错误2,listenfd为3,所以为4),我们将要监听的文件描述符(用epoll_event来存放文件描述符及其事件),即将结构体event用epoll_clt()放入内核事件表中监听起来,最后用epoll_wait ()来处理内核事件表中的事件,再将表中就绪的事件通过epoll_wait()函数返回回来,这次返回回来,我们知道有n个就绪(通过返回值知道,int型),也知道是哪几个就绪(放入epoll_wait(),第二个我们传进去的参数events数组),之后直接循环n次,直接处理就行了。
讲完原理,我们还是一样,上代码:
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
#include<unistd.h>
#include<string.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/socket.h>
#include<sys/epoll.h>
#define MAX 1024
int main()
{
int listenfd=socket(AF_INET,SOCK_STREAM,0);
assert(listenfd!=-1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res=bind(listenfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(listenfd,5);
int epfd=epoll_create(1); //创建内核事件表epfd
assert(epfd!=-1);
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=listenfd; 初始化一个关于listenfd的event结构体
res=epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev); //将关于listenfd的结构体放入内核事件表
assert(res!=-1);
struct epoll_event event[MAX]; //下面epoll_wait()要将就绪事件都放入该数组中返回回来
while(1)
{
int n=epoll_wait(epfd,event,MAX,-1); //核心函数;返回就绪文件描述符个数
if(n==-1)
{
printf("error!\n");
exit(0);
}
if(n==0)
{
printf("timeout\n");
continue;
}
int i=0;
for(;i<n;++i)
{
int fd=event[i].data.fd;
if(event[i].events & EPOLLRDHUP) //cli输入“end”
{
printf("break\n");
close(fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL); 将关于fd的结构体从epfd中删除
continue;
}
if(event[i].events & EPOLLIN)
{
if(fd==listenfd)
{
int len=sizeof(cli);
int c=accept(listenfd,(struct sockaddr*)&cli,&len);
assert(c!=-1);
printf("link succese\n");
ev.events=EPOLLIN|EPOLLRDHUP;
ev.data.fd=c;
res=epoll_ctl(epfd,EPOLL_CTL_ADD,c,&ev);
assert(res!=-1);
}
else
{
char buff[128]={0};
int num=recv(fd,buff,127,0);
assert(num!=-1);
printf("%d:%s",fd,buff);
send(fd,"ok",2,0);
}
}
}
}
}
看完代码有没有很清晰呢?