• 目录:

    select、epoll


    讲网络IO模型,先区分下两个层级,一是操作系统层,二是我们的用户代码层。

    一切都依赖于操作系统的内核代码的发展,

    BIO

    早期,操作系统只提供阻塞式的系统调用。用户代码也就只好用 “来一个socket连接,就为它单独起一个线程来接收数据” 的线程模型。不单独分配线程的话,程序阻塞在那,一次就只能处理一个请求。这个阶段最突出的问题,在于大量线程的创建、回收、切换的损耗。

    NIO

    后来发展出了非阻塞的系统调用。也就是 accept() 方法会立即返回了,只不过没有连接的时候返回的是null。recv() 也支持立即返回,可以去询问某个连接是不是可以读数据了。这解决了大量线程的创建、回收、切换的问题。用户程序可以在一个线程里循环 accept(),不是null就放到列表里存起来。甚至再拮据一点,就在这段循环里把列表里的连接挨个拿到操作系统那边询问,是不是可以读数据了。一个线程可以维护多个连接了。这个阶段最突出的问题,在于大量向操作系统询问连接是否可用的操作,系统调用的消耗极大,又只有少数的询问能得到肯定回复。

    NIO 中的多路复用器

    再后来,多路复用器出现了,select() 支持一次传多个连接做参数,虽然还是需要挨个连接判断一遍,但这个遍历过程全程在内核态完成。减少了系统调用的次数。这个阶段最突出的问题,在于内核内部依然需要遍历所有连接。poll() 这个多路复用器会更新一点,但它的实现也是需要遍历所有连接。

    “路” 指的是 用户程序,调用操作系统的系统调用接口,读取scoket 中的数据 的路

    多路复用器的更新

    再后来,多路复用器出了一个新的版本,叫 epoll。

    epoll_create() 创建一个实例,

    epoll_ctl() 往里添加要监控的连接,

    epoll_wait() 去询问有没有可读的连接。

    比起 select() 的直接询问,epoll 的调用方式多了创建实例和添加被监控连接这两个前置步骤。在前置的步骤里,它需要给这个epoll实例分配一颗红黑树和一个列表,把连接放进树里。当网卡的中断来临,select 需要的只是有人能把连接状态标记为可以读数据就行了。而 epoll 还多了一步,维护红黑树的节点,把这个可读的连接移动到专门的列表里。用户程序来询问的时候,直接查看可读列表的数据,不需要遍历所有节点了。

    New IO 还是 Non-Blocking IO ?

    NIO 这个词,操作系统里叫 Non-Blocking IO,指的是提供了一套非阻塞的系统调用。java 里叫 New IO,它伴随着操作系统网络IO的发展,新封装了操作系统的调用,装成统一的java的 Selector类,底下实际用的是select 还是 epoll,又或者是windows才有的其他多路复用器,要看操作系统的支持。