linux网络编程-select、poll、epoll

select、poll、epoll是linux下I/O多路转接的三种方式。其中epoll在高并发的服务器中用的比较多。

1. select

函数定义:

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

函数功能是将要监控的文件描述符集fd_set传入内核,内核根据fd_set监控对应的文件描述符是否有相应事件发生,有则返回,无则一直阻塞等待。具体使用查看man手册。

select的缺点:

  1. fd_set结构存储监视的文件描述符,fd_set是一个结构体,内部定义受限于FD_SETSIZE(1024)所以单进程select能够监视的最大文件描述符个数为1024。改变FD_SETSIZE需要重新编译内核。内核监视文件描述符上有无事件发生采用的是轮询方式。
  2. fd_set结构体需要在内核和用户空间来回拷贝,开销大。
  3. select传出的fd_set结构体,应用程序需要遍历其才能发现哪些文件描述符上发生了事件。
  4. select触发方式是水平触发,如果没有对已经就绪的文件描述符进行io操作,则之后再调用select还会将这些未处理的描述符通知进程。

2. poll

函数定义

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 文件描述符*/
short events; /* 监控的事件*/
short revents; /* 监控事件中满足条件返回的事件*/
};

poll的原理与select类似,只是可以采用数组(struct pollfd* fds)来存储文件描述符,因此没有了像select一样监视文件描述符数量的限制,但是受限于每个进程能够打开的最大文件描述符数量,通过查看/proc/sys/fd/file-max的定义获取最大量。其余的缺点跟select类似。

3.epoll

epoll比poll和select都要高效,是大规模并发服务器的热门模型。

函数定义

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);

函数功能

epoll_create:新建epoll文件描述符,新版本不关心size。
epoll_ctrl:添加、修改、删除文件描述符向/从epfd中,epfd指epoll_create返回的红黑树。
epoll_wait:等待监视的文件描述符ready。

epoll高效的原因

  1. epoll_wait只需向内核传入epfd,不像select和poll一样,需要在内核与用户之间传递大量数据(比如poll的pollfd结构体,用户像内核传入fd和event,从内核接收revent),每次调用poll如果有事件ready都会来回传递,而epoll则是用红黑树保存需要监听的事件,不需要每次wait都向内核传递event。
  2. epoll通过mmap实现一块内核空间和用户空间同时映射到物理内存(具体不清楚映射的内存区用来存放什么数据??buffer缓存??)
  3. 采用回调机制,epoll_ctrl添加进来的事件放入红黑树中,然后事件与网卡驱动程序建立回调机制,一旦事件发生时,会调用回调函数然后将事件加入到链表中,epoll_wait只需查询链表是否有节点并返回。(内核维护一个事件就绪链表)。
    以上是大致理解,因为没有具体去看内核代码,都是从网上各个博客总结来的。

et模式与lt模式

et模式指边沿触发,lt模式指电平触发。

et与lt模式下epoll_wait就绪返回的区别?

首先明确fd对应buffer的readable和writeable含义:对于读buffer,当buffer为空时其状态为unreadable,不为空时状态为readable;对于写buffer,当buffer满时为unwriteable,不满时为writeable。
对于lt模式读,buffer为非空时wait返回就绪,buffer空时不返回就绪。
对于lt模式写,buffer不满时wait返回就绪,buffer满时不返回就绪。
对于et模式读,buffer由空变为非空,或者有新数据达到即buffer内容变多时wait返回就绪。否则一直不返回。
对于et模式写,buffer由满变为不满,或者有数据被读走即buffer内容变少时wait返回就绪,否则不返回。
在这里插入图片描述
在这里插入图片描述
图片源自博客lvyilong316

et模式比lt模式高效的原因

考虑write操作,lt模式下当fd的buffer不满的时候,epoll_wait会一直返回就绪通知此fd是可写的,但此时应用程序并没有数据可写,因此此时epoll_wait的返回就是非必要的。而et模式则不存在这个问题,即使buffer是不满的时候,epoll_wait也不会返回,因此减少了epoll_wait的触发次数,实现了高效。
lt模式下这个问题的解决方法:
1.是当有数据要写时,把fd加入epoll,等待可写事件(就绪返回),写完后再将fd移出epoll。
2.开始fd不加入epoll,当有数据要写时,先写,如果返回EAGAIN(说明buffer已满),再加入epoll,在epoll的驱动下写数据,当全部数据写完后,再移出epoll。这种方法优点是当有少量数据要写的时候,不需要加入epoll即可写完。比较高效。

et模式下的读写方式

一般采用非阻塞方式,读就绪返回时就一直读,直到read返回0或者errno设为EAGAIN。写就绪返回时就一直写,直到数据写完或者errno设为EAGAIN。写时可能存在的问题:buffer写满时如果数据还没有写完,errno设为了EAGAIN。可以记录当前写了多少数据,等buffer可写时epoll_wait会返回,再接着写。
附:对于非阻塞socket,read/write socket返回-1并且errno设置为EAGAIN时,表示buffer以空(read)或者已满(write)。

et模式下accept问题

考虑这种情况:多个连接同时到达,服务器的 TCP 就绪队列瞬间积累多个就绪连接,由于是边缘触发模式,epoll只会通知一次,如果每次通知只accept一次,会导致 TCP 就绪队列中剩下的连接都得不到处理,解决方法是while(accept)多次,当accpet返回-1并且errno设置为EAGAIN时,说明全部连接处理完毕。

阻塞accpet存在的问题:

考虑这种情况:当有连接到达时,epoll_wait/select/poll返回,在服务器程序调用accept之前,客户端终止了连接,此时服务器端内核会将该连接从TCP就绪队列中移出,如果此时就绪队列中不存在连接,当调用accept时,程序阻塞,导致不能继续wait其他fd,因此正确的处理方式是用非阻塞accept。

参考:
epoll系列博客
以及各种其他博客,发现貌似都出自一处,看过最早发的一个是09年的,不过好像也不是原创,不知道都转自哪里。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值