linux select poll epoll simple study
This project is maintained by wangfakang
linux的I/O复用技术
关于select poll epoll的讲解如下:
int select(int nfds,fd_set*readfd,fd_set*writefd,fd_set*expectd,struct timeval *timeout);
函数参数:
返回值:
一:当为-1的时候表示出错
二:当为0的时候表示超时
三:成功的话是一个大于0的整数
注意:最多表示1024个集合(32个long型的 即:long bits[32];一共1024位)
select函数的应用:
主要运用与并发式的服务器端编程
其基本思路是: 首先是把要检测的文件描述符加载到fd_set类型的集合中,然后调用select函数检测加载到集合中的文件
描述符是否有(可读,可写,特殊异常)信息,再然后调用FD_ISSET函数判断到底是哪一个文件描述符变成了(可读,可写,特 殊异常)的了。
注意:
(在每次调用select之前都要对fdset集合进行FD_ZERO(&fdset)操作);进行清零
总结:
虽然多线程还有多进程都可以实现并发式服务器端编程,但是相对于select来说其花费的代价太高了,说白了select是以空
间来换取时间的,当然select实现的并发式服务器端编程也是有缺点的,原因是:由于 假设你只有两个客户端在和服务器端交互,
但是你每次都得遍历整个数组(或是链表)来寻找其相应的文件描述符
select的几大缺点:
(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大
(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大
(3)select支持的文件描述符数量太小了,默认是1024
其服务器端代码设计如下:
struct timeval tv;
int fds[MAXSIZE];
memset(fds,-1,sizeof(fds));
fd_set fdset;
fds[0] = sockfd;
while( 1 )
{
FD_ZERO(&fdset);
int i = 0;
int fdmax = fds[0];
for( ; i < MAXSIZE; i++ )
{
if ( fds[i] != -1 )
{
FD_SET(fds[i],&fdset);
if ( fdmax < fds[i] )
{
fdmax = fds[i];
}
}
}
tv.tv_sec = 2;
tv.tv_usec = 0;
int res = select( fdmax+1,&fdset,NULL,NULL,&tv);
assert( res != -1 );
if ( res == 0 )
{
printf("timeout\n");
}
else
{
int i = 0;
for( ; i < MAXSIZE ; i++ )
{
if ( fds[i] == -1 )
{
continue;
}
if ( FD_ISSET( fds[i], &fdset) )
{
if ( fds[i] == sockfd )
{
int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
if ( c >= 0 )
{
if ( addfd(c,fds) == 0 ) //把c加载到数组中去
{
close(c);
}
}
}
else
{
char buff[256]= {0};
int n = read( fds[i],buff,255);
if ( n > 0 )
{
printf("read:%s\n",buff);
write(fds[i],"OK",2);
}
else if( n == 0 )
{
delfd(fds[i],fds);
}
}
}
}
}
#include<poll.h>
typedef unsigned long int nfds_t;
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
struct pollfd
{
int fd;
short events;
short revents;
};
int poll(struct pollfd* fds,nfds_t nfds,int timeout);
函数参数:
参数一:表示一个pollfd结构的数组,用来保存想要监听的文件描述符及其注册(绑定)的相应事件
参数二:表示监听事件集合的大小
参数三:指定poll的超时值,当timeout为-1的时候,就会一直阻塞,直到某个事件发生,当为0的时候表示立即返回
返回值:
当为-1的时候表示失败,当为0的时候表示超时,当为大于0的整数的时候表示执行成功,表示文件描述符的个数
poll的编程实例:
struct pollfd fds[10]={0};
fds_init(fds);
fds_add(fd,fds,POLLIN);
while(1)
{
int n=poll(fds,10,-1);
assert(-1!=n);
if(fds[i].revents &POLLRDHUP)
{
fds_del(fds[i],fds);
continue;
}
int i=0;
for(;i<10;++i)
{
if(fds[i].revents &POLLIN)
{
if(fds[i].fd ==fd)
{
int c=accept(fd,(struct sockaddr*)&ca,&len);
if(c>0)
{
fds_add(c,fds,POLLIN|POLLRDHUP);
}
}
else
{
char buff[128]={0};
进行读写操作
}
}
}
}
总结:
poll的特点:
首先和select的区别是:没有了最大文件描述符范围的规定了,而且和select相比,不再需要三个fd_set 的指针,只需要使
用一个 struct pollfd的指针即可
在然后就是由于每次调用select的时候在返回的时候都会对fdset集合进行修改,所以每次调用select的时候都要对fdset集合 进行FD_ZERO(),然后在把其文件描述加载到fdset集合中去,再然后调用select(即:监听我所设置文件描述符的所有相应 读事件),poll在这点上做了一定的优化采用了struct pollfd
{
int fd;
short events;
short revents;
};
events来告诉poll监听fd上的那些事件,而revents是内核修改的,用来通知应用程序fd上实际发生的事件,从而避免了反复的 对集合的清零与重置
#include<sys/epoll.h>
int epoll_create(int size);
参数一:size 并不起作用,只是给内核一个提示,说创建多大一个事件表
返回值:表示事件表的文件描述符
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
参数一:epfd表示事件表的文件描述符
参数二:何种操作
EPOLL_CTL_ADD
EPOLL_CTL_MOD//进行修改原有文件描述符上的事件
EPOLL_CTL_DEL
参数三:要操作的文件描述符
参数四:指定事件
struct epoll_event
{
int events;
epoll_data_t data;//是一个union
};
返回值:成功为0 失败-1
int epoll_wait(int epfd,struct epoll_event *events ,int maxevents,int timeout)
参数二:当有相应的事件发生时,会把事件表中的放到events当中去
返回值:当为-1的时候表示失败,当为0的时候表示超时,当为大于0的整数的时候表示执行成功,表示文件描述符的个数
epoll的特点:
首先epoll达到了O(1)的时间复杂度,epoll把用户关心的文件描述符和想要监听的事件放在了内核的一张事件表中,而不必 要每次都要传入(文件描述符集和事件集)这样就不像select与poll那样每次都要把文件描述从用户态考到内核态(相对提高 了效率,实际上是通过共享内存实现的),之所以在达到了O(1)的时间复杂度是因为:在调用epoll_wait函数的时候只会把 就绪的事件从事件表中考到events当中去(其实是通过回调函数自己实现的)
其实epoll所采用就是一种分治法的思想,达到了各层的相互独立,比如使用epoll_create来创建事件表从而避免了select与poll 的缺点(每次都要从用户空间把相应的文件描述符拷到内核空间去)
注意:有关epoll的两种模式(LT ET)
LT模式(电平触发)特点:当epoll_wait检测到某个文件描述符上的事件发生的时候,应用程序可以不立刻来处理他,当下次调 用epoll_wait的时候还会再次响应次描述符上的此事件
ET模式(边沿触发)特点:意思就是当某个描述符上有事件发生的时候只会通知应用程序一次,之后再不会通知,所以:在使用ET 模式的时候就要使用while循环来读取数据(所以还有设置文件描述符为非阻塞的)避免在循环读取的时候造成阻塞
ET要比LT模式好:原因是在ET模式下减少了epoll事件的触发次数.
注意:
有时候epoll不一定比select和poll的效率高,比如这样的场景下:当活动连接数比较高的时候此时epoll会经常触发回调函数
,此时在性能上还是有一定的损失.epoll适用于连接数量多,但是活跃的连接少.
在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流