本文将介绍Java中几种常见的网络编程模型
网络IO模型
对于一个网络IO而言,其操作流程大体可分为两个阶段。这里以read操作为例进行说明,write操作同理
- 数据准备阶段:等待数据从网络中到达,并将数据拷贝到内核的Socket接收缓冲区
- 数据拷贝阶段:将数据从内核空间拷贝到用户空间
对于阻塞IO、非阻塞IO而言,其描述的是在第一个阶段——数据准备阶段,发起IO请求的进程/线程是否会被阻塞。具体地,对于阻塞IO而言,如果内核接收缓冲区没有数据,则进程/线程会一直等待,直到内核接收缓冲区有数据为止;而对于非阻塞IO而言,如果内核接收缓冲区没有数据,则进程/线程会立即返回,而不是一直进行等待。故对于非阻塞IO而言,可通过轮询的方式检查当前是否有数据到达
而对于同步、异步IO而言,其描述的是在第二个阶段——数据拷贝阶段,是否需要由用户线程来执行。具体地,同步IO是由用户线程的内核态来执行第二阶段;而异步IO则是由内核自己完成第二个阶段。在内核完成数据拷贝后通知用户线程,同时将数据以回调的形式传递给用户线程。显然对于同步IO而言,第二个阶段的数据拷贝是由用户线程参与完成的,故在第二个阶段会发生阻塞;而对于异步IO而言,数据拷贝由于是内核自己完成的,故在第二个阶段不会发生阻塞
网络IO模型大致可分为两类五种,其中同步IO有四种,异步IO有一种:
- 对于Bloking IO阻塞IO模型而言,其在Java中就是经典的BIO。其特征为阻塞同步IO
- 对于Non-Blocking IO非阻塞IO模型而言,由于需要用户线程不断发起系统调用,频繁地在内核空间与用户空间之间进行切换,性能较低,故很少得到应用
- 相比较Non-Blocking IO非阻塞IO模型,取而代之的是Multiplexing IO多路复用IO技术。其将前者频繁地轮询操作交由操作系统内核来完成,是目前高并发网络应用的主流技术手段。其在Java中对应的就是Java 1.4中引入的NIO,其特征为非阻塞同步IO
- 对于Signal-Driven IO信号驱动IO模型而言,由于其不适用于TCP协议,故也很少被使用
- 对于Asynchronous IO异步IO模型来说,其在Java中对应的就是Java 1.7中引入的AIO,其特征为非阻塞异步IO
BIO
在Java 1.4之前BIO是Java网络编程唯一的选择,其特点是阻塞同步。由于accept、read方法均是阻塞操作。如果没有连接请求,accept方法阻塞;如果无数据可读取,read方法阻塞。故服务端侧需要为每一个客户端连接都提供一个线程。显然在BIO模型下,如果存在大量客户端连接,势必增大服务端的压力。甚至在极端情况下服务端会由于开启的线程过多而最终宕机,故为了保险起见。最佳的实践方式是通过线程池来提供、维护与客户端通信所需的线程
这里给出BIO模型下的服务端编程示例
1 | /** |
这里给出BIO模型下的客户端编程示例
1 | /** |
NIO
Java从1.4开始引入了非阻塞同步的NIO。不同于BIO,其支持面向Buffer缓冲区的、基于Channel通道的IO操作。在NIO模型中,有三个核心部分:Buffer缓冲区、Channel通道、Selector选择器
1. Buffer缓冲区
本质上是一块可以读取、写入数据的内存空间,其被包装为NIO Buffer对象。同时为了进一步方便操作,还提供了一组相应的API进行操作、管理。为了方便理解,这里通过实践的方式加强对缓冲区的认识
在Buffer中有两个最重要的游标:position、limit。前者表示下一个将要被写入或读取的元素索引,当调用get/put方法时会自动更新。其中,初始化值为0;后者表示读取、写入元素时的界限。当向Buffer写完数据、准备读取时,必须调用flip方法将其切换为可读模式。具体地,其会将buffer的limit设置为pos的值、将pos设置0,以实现从头读取。与此同时,可以通过clear方法将Buffer恢复至初始状态。具体地,将pos的值归零、limit设置为Buffer的容量值。这样即可再次写入数据以覆盖历史数据
1 |
|
测试结果,如下所示
在Buffer中,可通过mark、reset方法分别实现将当前pos值保存到mark变量中、将pos设置为mark变量的值。与此同时,还可以通过hasRemaining、remaining方法分别实现判断buffer中是否还有剩余元素、返回剩余元素的数量
1 |
|
测试结果,如下所示
事实上,Buffer实例所使用的内存分为两种:堆内内存(即非直接内存)、堆外内存(即直接内存)
1 |
|
测试结果,如下所示
2. Channel通道
相比较传统的单向流(输入流、输出流)而言,通道是双向的。既可以读数据,也可以写数据
3. Selector选择器
Selector选择器,也被称作为多路复用器,是Java NIO中最重要的部分。其可以同时管理多个通道,并确定其中的哪些通道已经准备好进行读取、写入操作。换言之,利用选择器可以实现通过一个线程管理多个通道,即管理多个客户端连接。NIO模型的示意图如下所示
这里给出NIO模型下的服务端编程示例
1 | /** |
这里给出NIO模型下的客户端编程示例
1 | /** |
AIO
Java从1.7开始引入了非阻塞异步的AIO,作为对NIO的改进、增强,故其也被称为NIO 2.0。其分别引入了服务端异步Socket通道AsynchronousServerSocketChannel、客户端异步Socket通道AsynchronousSocketChannel,前者负责服务端Socket的创建、监听;后者负责客户端消息的读写操作。同时提供了一个CompletionHandler,作为消息处理回调接口
这里给出AIO模型下的服务端编程示例
1 | /** |
当接受到客户端的连接请求后,我们需要提供一个相应的CompletionHandler实现类。进行业务逻辑处理。具体地,通过实现completed方法用于接受客户端请求、建立连接后的业务处理逻辑,通过实现failed方法用于进行服务端发生异常的处理逻辑。具体实现如下所示
1 | /** |
这里给出AIO模型下的客户端编程示例
1 | /** |
参考文献
- 凤凰架构 周志明著