深入理解select/epoll/poll

一、I/O复用技术

1、概念:把多个I/O的阻塞复用到同一个select的阻塞上,使得系统在单线程的情况下可以同时处理多个客户端的请求。
2、场景:当多个客户端同时向服务端发出请求时,当前的代码模式依旧无法满足要求,所以引入I/O复用,使程序同时监听多个文件描述符。

二、引入I/O复用的原因

1.TCP服务器同时处理监听套接字和链接套接字。
2.服务器要同时处理TCP请求和UDP请求等多个请求。
3.程序要同时处理用户输入和网络输入。
4.客户端程序要同时处理用户输入和网络连接。
5、服务器要同时接听多个端口。

三、I/O复用注意点

1.I/O复用虽然可以同时监听多个文件描述符,但其本质是阻塞的。
2.当多个文件描述符同时就绪时,若不采取额外措施,程序就只能按照顺序依次处理其中的每一个文件描述符,这使服务器看起来是串行的。
3.若想提高并发处理的能力,可以配合使用多线程和多进程。

四、函数原型

1.select函数
功能:使用select函数可以完成非阻塞方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。 在这里插入图片描述
函数原型

int select(int nfds, fd_set *readfds, fd_set *writefds,
            fd_set *exceptfds, struct timeval *timeout);

参数介绍
nfds:指定被监听的文件描述符的总数,一般指定为监听队列中文件描述符的最大值+1。为了提高select底层的执行效率,这样可以使得内核不需要每次都在fd_set中全部扫描一遍。

fd_set *readfds:指向可读事件对应的文件描述符集合,如果这个集合中有一个文件可读,select就会返回一个大于0的值,表示有文件可读,如果没有可读的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set *writefds:指向可读事件对应的文件描述符集合,如果这个集合中有一个文件可写入,select就会返回一个大于0的值,表示有文件有写入权限,如果没有可一写的文件,则根据timeout参数再判断是否超时,若超出timeout的时间,select返回0,若发生错误返回负值。可以传入NULL值,表示不关心任何文件的读变化。

fd_set *exceptfds:这个参数用来检测文件有无异常情况发生。

struct timeval *timeout:这个地方根据填入的值不同,可以设置三种select的工作方式:
a.此处填入NULL,使用阻塞的方式,一直到检测到文件描述符有变化才返回。
b.若填入0秒0毫秒,就变成非阻塞函数,不管文件描述符是否有变化,都会立刻返回继续执行,文件无变化返回0,有变化返回一个正值。
c.timeout的值大于0,该值就是等待的超时时间,即select在timeout时间内阻塞,超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回,文件无变化返回0,有变化返回一个正值。

select用到的函数

FD_CLR(inr fd,fd_set* set)//用来清除描述词组set中相关fd的位

FD_ISSET(int fd,fd_set *set)//用来测试描述词组set中相关fd的位是否为真

FD_SET(int fd,fd_set*set);//用来设置描述词组set中相关fd的位

FD_ZERO(fd_set *set);//用来清除描述词组set的全部位

使用到的结构体

The timeout
       The time structures involved are defined in <sys/time.h> and look like

           struct timeval {
               long    tv_sec;         /* seconds */
               long    tv_usec;        /* microseconds */
           };

       and

           struct timespec {
               long    tv_sec;         /* seconds */
               long    tv_nsec;        /* nanoseconds */
           };

2.poll函数
函数原型

int  poll(struct  pollfd  fds[], unsigned int nfds, long  timeout);//成功返回就绪文件描述符的总数,超时返回0,失败返-1

struct pollfd fds[]:结构体数组,用于指定文件描述符

{
	int  fd;//用户设置关注的描述符
	short  events;//指定用户关注的事件类型
	short  revents;//由内核填充就绪的事件类型
};

unsigned int nfds: 数组长度

long timeout:设置的接收时间,单位为毫秒,-1则代表不轮巡永久阻塞

工作流程:
(1)由用户定义pollfd数组,调用poll时传入;
(2)若有事件发生,则由内核对revents填充,然后需要进行轮巡查看。

4.poll与select对比
(1)同是返回就绪的文件描述符总数,都需要再进行一次轮巡,其时间复杂度为O(n);
(2)将用户关注的事件类型和就绪事件类型分开,这样在调用poll时就不需要重新填充事件类型了,因为events是不变的,只有用户去改;
(3)用short类型记录类型,就可以记录很多的事件类型;
(4)用户数组是由用户自己定义,增加了自由性。

3.epoll函数
epoll是Linux系统上专有的一种I/O复用机制,与select和poll方法也有较大差异,epoll是一组方法,由内核直接维护,在内核态存在,而上述两种方法是在用户态存在,每调用一次则需拷贝两次数据,一次在调用时,一次在返回时,所以其效率并不是很高。

select方法的回调机制:在每个文件描述符就绪时会绑定一个回调函数,在事件状态改变时回调函数会触发并通知内核,所以回调机制适用于关注的事件多,但触发的概率小的情况,反之轮巡机制适合关注的事件少但触发的概率高的类型

struct epoll_event结构

struct epoll_event
{
	short events;//事件类型,支持poll的事件类型,名称前加"E"即可。
	union epoll_data_t  data;//一般用以记录文件描述符
};
//事件类型中有两个额外的事件:EPOLLLET和EPOLLONESHOT

union联合体结构

typedef  union  epoll_data//
{
	void  *ptr;//
	int  fd;//指定文件描述符
	uint32_t  u32;//
	uint64_t  u64;//
}epoll_data_t;//

epoll_create();

int  epoll_create(int size);

创建内核事件表(用户关注的所有文件描述符以及关注的事件类型),成功返回内核事件表的标识符,失败返回-1
size:指定内核事件表的大小,个数,由于底层使用红黑树,这个参数已经不重要了

epoll_ctl()

int  epoll_ctl(int  epfd, int  fd, struct  epoll_event  *events);

管理内核事件表,增删改
(1)epfd:创建的内核事件表的文件描述符
(2)op:指定操作方式
①EPOLL_CTL_ADD:添加
②EPOLL_CTL_MOD:修改
③EPOLL_CTL_DEL:删除
(3)fd:要在内核事件表中操作的文件描述符
(4)event:指定事件(需要在用户态指定,只有在添加的时候会拷贝一次,所以效率较高)

epoll_wait()

int  epoll_wait(int  epfd, struct  epoll_event  events[], int maxevents, int  timeout);

(1)epfd:创建的内核事件表的文件描述符
(2)events:是一个用户数组,在调用时由内核在返回时自动填充有事件就绪的文件描述符和就绪的事件类型。(因此用户程序检索就绪事件的时间复杂度为O(1))
(3)maxevents:数组的长度,指定了一次epoll_wait最多返回的就绪事件数量
(4)timeout:轮巡时间,以毫秒为单位,-1为NULL。
(5)函数调用成功返回就绪个数,失败返回-1,超时返回0;

epoll对文件描述符的两种工作模式
(1)LT:是默认的工作模式,此模式下epoll相当于一个效率较高的poll,LT模式下事件通知后可以不立即处理,但下次epoll_wait将会继续通知此事件,直至被处理;
(2)ET:当往epoll内核事件中注册一个EPOLLET事件时,epoll将以ET模式工作,ET模式是epoll的高效工作模式,epoll_wait报告一个事件后,内核将立即处理此事件,因为后续epoll_wait将不再报告此事件;
①EPOLLONESHORT:即使使用ET模式,事件还是可能触发多次,当一个进程读取完数据后,又发来新的数据时,就有可能使一个socket被两个进程同时处理,使用EPOLLONESHORT则可以使事件被一个进程触发一次,一般使用在多进程中。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值