在Linux网络编程过程中,为了能够获得高性能,往往会使用的是非阻塞io,其中每一个io都对应着一个特定的socket连接,而每个Socket连接都会返回(或者对应)一个特定的文件描述符FD并产生对应的事件,知道了每个连接(或fd)的事件,内核就能够对io进行高效的调度,所以对这些事件的监控就显得尤为重要。在linux中,这些负责监控的监听器就包含了select,poll和epoll。
而在服务器应用中,每时每刻都有很多的连接同时访问服务器,但是并不是每条连接每时每刻都在传数据,所以作为服务器方,需要知道此时是否有连接在'变化',也就是由新的数据输入和输出,然后就可以对应的去处理这些连接,响应请求。最直接的方式就是服务器自己每个瞬间一个一个连接的去检查:
for x in open_connections: if has_new_input(x): process_input(x)
但是连接数目一多,就太消耗CPU资源了。所以后来服务器学会了求助操作系统:“跟我说下哪些连接变化了?”由操作系统来帮服务器处理,这样往往会更快,服务器的压力也会变小。而操作系统内核使用的“探测工具”就有select,poll跟epoll。
文件描述符(FileDesciptor)
内核利用文件描述符(file descriptor)来访问文件(流的一种)。文件描述符是非负整数。打开现存文件或新建文件时,内核会返回一个文件描述符。读写文件也需要使用文件描述符来指定待读写的文件。另外,监听器也属于文件描述符。
select监听器?
在上个世纪八十年代,没有高并发,没有分布式,没有异步IO,网络编程还是单进程的,select就在这个时候出现了,而自从那时开始它的select()接口就没有改变过。
select的原理很简单,就是对于所有的文件描述符(代表IO连接)遍历一次,找出有变化的,返回给调用者。
要使用select监听器,首先要设置文件描述符的大小,然后调用内核提供的select()接口。
以下是一次监听的片段:
这里也暴露了select的几个缺点:
1. 因为select是逐个检测文件描述符(file Descriptor)来找到有事件触发的IO,而且所能轮询的文件描述符只有1024个,这里就有个缺点,就是随着文件描述符的增加,select很可能无法胜任轮询全部的fd,并且在有限的这1024个fd中也不是全部都有产生事件的,因此必然导致cpu计算资源的浪费。
2. 因为每次调用select()都会改变fd_set的值,所以每次调用都需要重新创建文件描述符集合,或者从FD_COPY中复制,大大降低了效率。
3. 当文件描述符很多时,select需要比较文件描述符以得到最大的一个作为入口参数。
4.也是在多进程情况下,如果另外一个进程对连接进行关闭等干扰操作,必然会产生阻塞等问题,即不是原子操作。
这时你们可能会想,既然已经有了poll和epoll(即将介绍),为何select还存在着?这就有历史的原因了:
1. 上面说到,select是上世纪八十年代就已经有的产物,这从侧面反映了它也有过独霸江湖的辉煌历史,而目前还会有很多的场合使用到了老式机器与操作系统,此时在涉及到网络通信的地方,只能使用到select。而poll在WindowsVista及以上可以使用,epoll也是在最近的linux内核版本才出现的。综上,历史遗留问题使得select监听器不会完全被淘汰。
2. 第二个原因不是那么明显,其实在连接数目比较少,比如才几个连接的时候,使用select和poll其实差别并不大,因为select的缺点都是在多线程或者多进程上,所以如果不是上述场合,那你也可以使用select,个人喜好罢了。
poll监听器
poll监听器是select的改进版本,它的功能和使用过程也和select差不多,而且select和poll的源码也很像,都调用了很多系统的函数。
使用如下:
poll监听器相对于select的优势有以下几点:
1. 因为在使用完连接后会初始化连接事件,调用过程也没有修改文件描述符,因此不会额外重新产生文件描述符。
2. 文件描述符(反映为IO连接)没有1024的限制。
3. 对事件的控制更多。
当然,poll也有一些不足:
1. 无法在windowsvista之前的系统上运行。
2. 底层仍是遍历文件描述符,对于多线程多进程的场景表现仍然不佳。因此,poll不适合在p2p等多连接场合下使用。
水平触发与边缘触发
讲的是监听器监听事件时获得事件变化的通知方式。
水平触发:返回你所‘感兴趣’的事件描述符的列表,里面有的事件描述符触发了事件,有的则不一定触发了,但都会返回。
边缘触发:一旦有文件描述符触发了事件,就立即返回通知。但不一定返回全部。
epoll监听器
epoll相对于上面两个监听器是比较新的了,在2002年的时候出现。
首先,这个监听器(包括它的包装库libevent)相比于上面两个的一个特点是只能用在Linux上,而select和epoll可以用在任一种Unix操作系统上,而select和epoll可以用在一种
调用的步骤依次为:调用epoll_create创建监听器,此时内核会返回一个id;然后调用epoll_ctl监听事件;最后调用epoll_wait。
epoll的优点如下:
1. epoll会对每个文件描述符和相应的事件即IO的信息缓存起来(在红黑树中),因此对于极多IO连接的场合特别适用,尤其是每个连接的时间都比较长的场景。
2. 无需遍历文件描述符,只返回有产生事件的文件描述符。所以像node.js这样的服务器就考虑到性能原因使用epoll而不是select或者poll。
3. 可以自行绑定数据等到被监听的事件。
4. 异步非阻塞IO。
当然,epoll的缺点也有,比如调用复杂,需要额外的代码以成功使用epoll。但相比优点,这个缺点也显得不是那么重要吧。
总结:三种监听器都有各自的优缺点,各有各的使用场景,具体使用应该根据实际应用需求来。但大体的情况就是:select很少用,默认的是poll,处理极多连接用epoll。
,