1、前言
之前我有写过 利用多路转接的select的TCP_server,但当时我们提到了很多关于select的缺点:
1、select可监听的文件描述符有上限制;
2、因为select参数是输入输出型的,所以每次重新设置select时,都需遍历式设置,对性能有一定的影响
3、用户增多时,多次重复遍历和频繁内核与进程数据拷贝(多次的返回)
4、需要自己维护一个数组/链表,对文件描述符的管理,实现也比较复杂
5、每次多需要重新设置select—将fd设置从用户拷贝到内核
基于这么多的缺和实现的复杂,所以我们基本不会使用select来实现,而今天的主题epoll对这些问题都进行解决
2、epoll
epoll man手册上说linux2.6后性能最好的!!!
为什么性能好呢?
我们来看张图:
再来学习epoll的函数:
epoll有三个函数:
1、int epoll_create(int size); //创建epoll(创建红黑树)
- 参数size :对内核的提醒(建议)空间大小,man手册解释size可以被忽略
- 返回值: 返回一个epoll句柄,用于对红黑树的操作,在使用完epoll之后,因使用close()关闭;
2、int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //设置红黑树
- 参数:epfd , 红黑树的句柄
- 参数:op,操作方式,有以下三种:
EPOLL_CTL_ADD :添加事件(在红黑树上添加节点)
EPOLL_CTL_MOD:更改事件(改变红黑树指定节点事件发生条件)
EPOLL_CTL_DEL:删除事件(删除节点)- 返回值: 成功返回 0、失败返回-1;
3、int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);//等待事件就绪函数
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* 事件,用专门的宏来设置*/
epoll_data_t data; /* User data variable */
};
- 参数 epfd epoll句柄!!!
- 参数 events,是用户创建的结构体数据组,是一个输出型参数,当内核的就绪时间队列中有事件时,将事件带回。
- maxevents events最多可以带回多少事件,0 < maxevents < size(前面创建epoll是的大小)
- 参数:timeout 超时时间,-1: 阻塞,0: 不阻塞
- 返回值,就绪事件的个数
- 函数功能:epoll_wait如果监测到事件就将所有就绪的事件传送到第二个参数struct epoll_event 结构体数组中,每次调用epoll_wait返回链表是从内核返回给用户空间的,每次从内核传送到用户空间的描述符并不是很多,epoll的时间复杂度是O(1).
讲完所有的接口是不是对EPOLL为什么效率高的原因,有了一定的了解;
3、总结epoll的高效性:
1、内核创建红黑树
2、不需要每次都对每个事件重新设置(比较于select)
3、 操作系统在检测文件描述符采用回调函数
4、用户查找就绪文件符的复杂度O(1),—利用队列遍历
5、用户与内核采用内存映射,看到同意内存,不需要拷贝
4、利用epoll编写TCP_server
下面的代码可以实现 server—client的交互式通信
while(1)
{
int n = epoll_wait(epfd, rev, 64, -1);//等待就绪事件
switch(n){ //
case 0:{
printf("time out\n");
continue;
}
case -1:{
perror("epoll_wait");
return 5;
}
default:{//有事件就绪
int i = 0;
for(i=0; i<n; ++i)//遍历数组,时间复杂度O(n)=O(1)
{
if(rev[i].data.fd == listen_sock)
{
struct sockaddr_in client;
size_t len = sizeof(client);
int new_sock = accept(listen_sock, (struct sockaddr*)&client, &len);
if(new_sock < 0)
{
perror("accept");
continue;
}
printf("get a new sock:%s, %d\n",\
inet_ntoa(client.sin_addr), ntohs(client.sin_port));
ev.events = EPOLLIN;//添加新的套接字读事件
ev.data.fd = new_sock;
epoll_ctl(epfd, EPOLL_CTL_ADD, new_sock, &ev);
}
else if(rev[i].events & EPOLLIN){//读事件就绪
char buf[1024];
ssize_t s = read(rev[i].data.fd, buf, sizeof(buf)-1);
if(s < 0){//当读取错误或者对端关闭连接时,删除该套接字
perror("read");
close(rev[i].data.fd);
ev.events = EPOLLIN;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
continue;
}
else if(s == 0){
close(rev[i].data.fd);
ev.events = EPOLLIN;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_DEL, ev.data.fd, &ev);
printf("client quit\n");
continue;
}
buf[s] = '\0';
printf("clinet say#%s\n",buf);
//读取数据完成后,更改套接字读事件为写事件
ev.events = EPOLLOUT;
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}
else{//写事件就绪
char *str = "wlcome to sock ";
write(rev[i].data.fd, str, strlen(str));
ev.events = EPOLLIN;//数据回馈完成后,更改套接字事件为读
ev.data.fd = rev[i].data.fd;
epoll_ctl(epfd, EPOLL_CTL_MOD, ev.data.fd, &ev);
}
}
}
}