Java IO 学习(二)select/poll/epoll
如上文所说,select/poll/epoll本质上都是同步阻塞的,但是由于实现了IO多路复用,在处理聊天室这种需要处理大量长连接但是每个连接上数据事件较少的场景时,相比最原始的为每个连接新开一个线程的服务模式要高效许多。
但是我们也经常听到一个说法:select效率低下,在工程实践中从不使用select,而是使用效率更高的epoll
本文会尝试分析一下造成这种现象的原因
SELECT
select范例
先给出select的官方文档
可以看到关键函数如下:
int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
先举个简单的实例帮助理解
场景:使用select来监听多个fd上的read事件
1. 创建一个fd_set
2. 调用FD_ZERO清空这个fd_set
3. 多次调用FD_SET将需要监听的fd写入fd_set中
4. 调用select方法,传入fd_set
5. select函数会在有fd可读的时候返回,返回值为可读的fd的个数
6. 用户需要调用FD_ISSET来遍历之间写入fd_set中的所有fd,如果某个fd被设置,说明这个fd可读,于是可以读取数据了
select存在的问题
1. select支持的fd数量太少
默认情况下fd的数值只能小于1024,也就是说同时支持的fd总数必然小于1024(一般0,1,2号fd都是标准输入/输出/错误),这样也就直接导致如果使用select来做网络服务器的话,一个进程最多只能支持1000个左右的并发连接。
官方文档对此的描述如下:
An fd_set is a fixed size buffer. Executing FD_CLR() or FD_SET() with a value of fd that is negative or is equal to or larger than FD_SETSIZE will result in undefined behavior. Moreover, POSIX requires fd to be a valid file descriptor.
这种现象的原因是fd_set内部维护了一个长度为FD_SETSIZE的bitmap,所以如果传入的fd的数值 >= FD_SETSIZE,会出现越界行为。
但是我也看到某些fd_set的实现有所不同,内部维护了一个长度为FD_SETSIZE的int数组,在这种实现中就能存储数值上不受限的fd了。
但是出于保险起见,还是不要存储数值上大于等于1024的fd为好。
2. select开销过高
a. 每次调用select都需要重新设置一次fd_set
b. 每次调用select,都需要将整个fd_set对象在用户空间与kernel中来回拷贝
c. 内核中需要遍历整个fd_set才能知道是否有fd准备就绪
d. select返回后,又需要遍历所有的fd才能知道具体是哪个fd真正就绪了
3. 总结
select效率低下,不适合于高连接数的应用场景。
如果是自己写的测试代码,拿来玩玩倒是不错的,毕竟逻辑简单,易于理解。
POLL
与select相比,poll主要修正了fd的数值必须小于FD_SETSIZE的问题,本质上并没有太大的区别
select中存在的几个问题poll都有,在这里我们就不详细介绍了
EPOLL
linux kernel 2.6中引入了epoll,它彻底解决了select/poll中存在的问题,是真正实用的、可以处理大连接数的IO复用工具
epoll范例
epoll的关键函数如下:
int epoll_create1(int flags);
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_create1函数会创建一个epoll的fd并返回,在使用完epoll后必须将其关闭,否则会一直占用这个fd
epoll_ctl可以对传入的fd进行监听操作,各个参数含义如下:
- epfd:是epoll_create()的返回值。
- op:表示op操作,用三个宏来表示:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
- fd:是需要监听的fd(文件描述符)
- epoll_event:是告诉内核需要监听什么类型的事件,struct epoll_event结构如下:
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};//events可以是以下几个宏的集合,可以用 | 运算符组合多种事件:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
epoll_wait则是在epfd上等待,直到有关联的fd就绪为止,返回值为就绪的fd的数目,参数events则包含了就绪的fd的信息
如果使用epoll来监听多个fd上的read事件,其工作流程如下所示:
1. 调用epoll_create1创建一个epoll的fd
2. 多次调用epoll_ctl,将需要监听的fd与上一步中创建的epfd关联起来,同时将epoll_ctl的event参数的data.fd域设置为需要监听的fd
3. 循环调用epoll_wait
4. epoll_wait返回且返回值大于0,说明已经有fd准备就绪,根据返回值遍历epoll_wait的参数events,获取所有准备就绪的fd
5. 从fd上读取数据
epoll的优势
epoll解决了上文提到的select中存在的所有问题
1. 对于需要监听的fd,只需要在初始化的时候调用一次epoll_ctl将fd与epfd相关联,后续就能循环调用epoll_wait监听事件了。无需像select一样,每次调用select方法的时候都要重复设置并传入待监听的fd集合。这样可以减少重复设置fd_set、以及将fd_set在用户空间与kernel之间来回拷贝带来的开销
2. epoll_wait方法返回的时候,可以直接从events参数中获取就绪的fd的信息,无需遍历整个fd集合。这样可以减少遍历fd_set带来的开销
3. 在调用epoll_create1时,会在kernel中建立一颗fd红黑树与一个就绪fd链表,后续调用epoll_ctl中放入的fd会被挂载到这棵树上,同时也会在kernel的中断处理函数中注册一个回调函数。一旦某个正在监听的fd上有数据可读,kernel在把数据拷贝到内核缓存区中之后,还会将这个fd插入到就绪fd链表中。这样kernel就不用在有fd就绪的时候遍历整个fd集合,从而减少开销。
水平触发与边缘触发
Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你。如果系统中有大量你不需要读写的就绪文件描述符,而它们每次都会返回,这样会大大降低处理程序检索自己关心的就绪文件描述符的效率。
Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。如果这次没有把数据全部读写完(如读写缓冲区太小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你。这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符。
这两种触发的区别与epoll的实现机理有关。
在ET模式下,每次调用epoll_wait方法的时候,系统会直接将就绪链表清空,这样只有新就绪的fd才会被插入就绪链表并返回。
在LT模式下,每次调用epoll_wait方法时,系统只将就绪链表中的事件已经被处理完毕的fd(socket关联的kernel缓冲区数据已经被读取完毕)移除,如果某个fd上还有未被处理的数据,它会被保留在就绪链表中,并在epoll_wait返回时放在events参数中回送给用户。
ps. Java的nio就是用的水平触发。
总结
在高连接数,且大部分连接都不活跃的应用场景中(聊天室),epoll是实现IO多路复用的最优解。在经过调优的系统上,使用epoll可以无压力的处理百万级别的长连接。
但在连接数很少,且每个连接都处于高度活跃状态的应用场景(内网下载文件),select可能就是更好的选择了。
参考资料
Java IO 学习(二)select/poll/epoll的更多相关文章
- Linux IO模式以及select poll epoll详解
一 背景 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案是不同的.所以先限定一下本文的上下文. 本文讨论的背景是Linux环境下的network ...
- IO模型与select,poll,epoll
五种:阻塞,非阻塞,IO复印,信号驱动,异步. select,poll,epoll select: 典型用32个32位的整数表示1024个描述符,并发的局限. poll:功能同上,但数据结构不一样(链 ...
- Python学习笔记整理总结【网络编程】【线程/进程/协程/IO多路模型/select/poll/epoll/selector】
一.socket(单链接) 1.socket:应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口.在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socke ...
- python开发学习-day10(select/poll/epoll回顾、redis、rabbitmq-pika)
s12-20160319-day10 *:first-child { margin-top: 0 !important; } body>*:last-child { margin-bottom: ...
- IO多路复用之select poll epoll
参考文档: http://blog.csdn.net/tennysonsky/article/details/45745887 select(),poll(),epoll()都是I/O多路复用的机制. ...
- 多路IO复用模型--select, poll, epoll
select 1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数 2.解决1024以下客户端时使用se ...
- Java IO学习--(二)文件
在Java应用程序中,文件是一种常用的数据源或者存储数据的媒介.所以这一小节将会对Java中文件的使用做一个简短的概述.这篇文章不会对每一个技术细节都做出解释,而是会针对文件存取的方法提供给你一些必要 ...
- Linux网络通信编程(套接字模型TCP\UDP与IO多路复用模型select\poll\epoll)
Linux下测试代码: http://www.linuxhowtos.org/C_C++/socket.htm TCP模型 //TCPClient.c #include<string.h> ...
- 文档-linux io模式及select,poll,epoll
文档-Linux IO模式详解 1. 概念说明 在进行解释之前,首先要说明几个概念:- 用户空间和内核空间- 进程切换- 进程的阻塞- 文件描述符- 缓存 I/O 1.1 用户空间与内核空间 现在操作 ...
- 多进程、协程、事件驱动及select poll epoll
目录 -多线程使用场景 -多进程 --简单的一个多进程例子 --进程间数据的交互实现方法 ---通过Queues和Pipe可以实现进程间数据的传递,但是不能实现数据的共享 ---Queues ---P ...
随机推荐
- linux下的source命令
Linux Source命令及脚本的执行方式解析 当我修改了/etc/profile文件,我想让它立刻生效,而不用重新登录:这时就想到用source命令,如:source /etc/profile ...
- java并发面试题-基础
多线程 java中有几种方法可以实现一个线程? 1.直接继承thread类:2.实现runnable接口: 如何停止一个正在运行的线程?可以使用正在运行的线程,支持线程中断,通常是定义一个volati ...
- Java-数据结构之栈练习
栈(stack)可以看做是特殊类型的线性表,访问.插入和删除其中的元素只能在栈尾(栈顶)进行. 队列(queue)表示一个等待的线性表,它也可以看做是一种特殊类型的线性表,元素只能从队列的末端(队列尾 ...
- privoxy+ss5实现 HTTP 代理协议转socks5代理
一.系统准备资源 二.ss5安装部署 1.SOCK5代理服务器部署环境准备 IP:10.0.0.100 官网: http://ss5.sourceforge.net/ 下载 yum - ...
- xcode6没有prefix.pch预编译文件解决办法
注意到Xcode6创建的工程没有prefix.pch. 于是手动创建. 在other下选择pch文件 接着到工程的build setting下设置开启预编译并配置路径(文件的路径.因为我新建在cofi ...
- ECMAScript5.1
http://lzw.me/pages/ecmascript/ ECMAScript5.1中文版 https://msdn.microsoft.com/zh-cn/library/dn656907. ...
- OpenStack之虚机热迁移代码解析
OpenStack之虚机热迁移代码解析 话说虚机迁移分为冷迁移以及热迁移,所谓热迁移用度娘的话说即是:热迁移(Live Migration,又叫动态迁移.实时迁移),即虚机保存/恢复(Save/Res ...
- leetcode 【 Reverse Words in a String 】python 实现
题目: Given an input string, reverse the string word by word. For example,Given s = "the sky is b ...
- IOS开发学习笔记033-UIScrollView
1.滚动显示图片 如果图片过大,则需要滚动显示,这是需要用到类UIScrollView,可是实现控件的水平和垂直滚动. 可用三步实现:1 设置UIScrollView,2 设置UIImageView, ...
- python2.X中文乱码
在IDE下,加上# -- coding: UTF-8 -- 并且保证IDE也是utf-8编码. 在CMD下,这样执行会有乱码,为啥呢,因为cmd下是gbk编码的,你写的代码必须也是gbk编码的,你可以 ...