netty权威指南第一章

本章内容如下:

  1. 5种网络I/O模型的介绍
  2. I/O多路复用的介绍

1、I/O基础入门

在Java1.4之前,Java对I/O的支持不完善,开发人员在开发高性能I/O的程序时,会面临以下问题:

  • 没有数据缓冲区,I/O性能存在问题
  • 没有C++中的Channel概念,只有输入和输出流
  • 同步阻塞式I/O通信(BIO),会导致通信线程被长时间阻塞
  • 支持的字符集有限,硬件可移植性不好

1.1 Linux网络I/O模型简介

Linux的内核将所有外部设备当做一个文件来处理,对一个文件的读写操作会调用内核提供的系统命令,返回一个file descriptor(fd,文件描述符)。对一个socket的读写也会有相应的描述符,称为socketfd(socket描述符),描述符是一个数字,指向内核中的一个结构体(文件路径、数据区等属性)。

根据UNIX网络编程对I/O模型的分类,UNIX提供了以下5种I/O模型:

  1. 阻塞I/O模型(默认)
    以套接字接口为例讲解此模型:在进程空间中调用recvfrom,其系统调用直到数据包到达且被复制到应用进程的缓冲区中或者发生错误时才被返回,进程从调用recvfrom开始到返回都是被阻塞的。
    netty权威指南第一章_第1张图片
  2. 非阻塞I/O模型
    recvfrom从应用层到内核的时候,如果该缓冲区没有数据,就直接返回ewouldblock错误,一般都轮询检查内核是否有数据到来。
    netty权威指南第一章_第2张图片
  3. I/O复用模型
    Linux提供select/poll,进程通过将一个或多个fd传递给select或poll调用,阻塞在select操作上,这样select/poll可以侦测多个fd是否就绪。select/poll是顺序扫描fd是否就绪,支持的fd数量有限。Linux还提供了epoll系统调用,epoll使用基于事件驱动方式代替顺序扫描,性能更高。当有fd就绪时,立即调用回调函数rollback。
    netty权威指南第一章_第3张图片
  4. 信号驱动I/O模型
    首先开启套接口信号驱动I/O功能,通过系统调用sigaction执行一个信号处理函数(系统调用立即返回,进程继续工作,非阻塞)。当数据准备就绪时,就为该进程生成一个sigio信号,通过信号回调通知应用程序调用recvfrom读取数据,并通知主循环函数处理数据。
    netty权威指南第一章_第4张图片
  5. 异步I/O
    告知内核启动某操作,并让内核在操作完成后(数据从内核复制到用户的缓冲区)通知我们。此模型与信号驱动模型的区别是:信号驱动I/O由内核通知我们何时开始一个I/O操作;异步I/O由内核通知我们I/O操作何时已经完成。
    netty权威指南第一章_第5张图片

1.2 I/O多路复用技术

在I/O编程中,需要同时处理多个客户端接入请求时,可以利用多线程或I/O多路复用处理。I/O多路复用将多个I/O阻塞到一个select上,从而使得系统在单线程下可同时处理多个客户端请求。与传统的多线程/多进程模型比,I/O多路复用的优势是系统开销小,系统不需要创建额外的进程或者线程,也不需要维护这些进程和线程的运行,降低系统维护的工作量,节省系统资源。I/O多路复用的场景有:

  • 服务器需同时处理多个处于监听状态或者多个连接状态的套接字
  • 服务器需同时处理多种网络协议的套接字

目前支持I/O多路复用的系统调用有select、pselect、poll、epoll。select先出现,因其固有缺陷(支持的fd数量有限),被epoll代替。epoll的改进点如下:

  1. 进程打开的socket描述符(fd)不受限制
    fd数目仅受限于操纵系统的最大文件句柄数,例如,1GB内存的机器上大约是10万个句柄,通常该值与系统内存的关注较大。
  2. I/O效率不会随着fd数目的增加而线性下降
    使用select/poll时,当socket的集合很大时,由于网络延时护着链路空闲,任一时刻只有少部分的socket是“活跃”的,select/poll每次调用都会线性扫描全部的集合,导致效率线性下降。而epoll只会对“活跃”的socket进行操作,这是因为在内核实现中epoll是根据fd上的callback函数实现的,只有“活跃”的socket才会主动调用callback函数。在这点上,epoll实现了一个伪AIO。对epoll和select性能对比的测试结果表明:
  • 若所有的socket都处于活跃态,epoll比select/poll的效率高的不多;相反,若过多的使用epoll-ctl,效率会稍微下降
  • 若非活跃的socket慢慢增加的时候,epoll的效率就会远在select/poll之上
  1. 使用mmap加速内核与用户空间的消息传递
    select,poll和epoll均需内核将fd消息通知给用户空间,epoll通过内核和用户空间mmap使用同一块内存避免内核态和用户态的内存复制。
  2. epoll的API更加简单
    epoll的API有创建epoll描述符、添加监听事件、阻塞等待所监听的事件发生、关闭epoll描述符等。

2、Java的I/O演进

从BIO到NIO是Java通信类库迈出的一小步,却对Java在高性能通信领域的发展起到了关键性的推动作用。随着基于NIO的各类NIO框架的发展,以及基于NIO的Web服务器的发展,Java在很多领域取代了C和C++,成为企业服务端应用开发的首选语言。

在JDK1.4推出Java NIO之前,基于Java的所有socket通信都采用同步阻塞模式BIO,这种一对一的请求应答的通信模型简化了上层的应用开发,但在性能和可靠性方面存在着巨大瓶颈。因此,很长一段时间内,大型的应用服务器采用C或者C++开发,因为C和C++可直接使用系统提供的异步I/O或者AIO。当并发访问量增大、响应时间延迟增大后,采用BIO开发的服务端软件只能通过硬件的扩容来满足高并发和低时延,增大企业成本,随着集群规模的扩大,系统的维护性将会面临巨大挑战,只能通过采购性能更高的硬件服务器解决问题,导致恶性循环。这一切使得人们对Java非阻塞I/O的呼声日渐高涨,最终,JDK1.4提供了NIO类库,Java终于可以支持非阻塞I/O了。

参考:
《Netty权威指南》

你可能感兴趣的:(netty)