《Netty 权威指南》—— AIO 创建的TimeServer源码分析

声明:本文是《Netty 权威指南》的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文。

NIO2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步通道提供两种方式获取获取操作结果:

  • 通过java.util.concurrent.Future类来表示异步操作的结果;
  • 在执行异步操作的时候传入一个java.nio.channels.

CompletionHandler接口的实现类作为操作完成的回调。

NIO2.0的异步套接字通道是真正的异步非阻塞IO,它对应Unix网络编程中的事件驱动IO(AIO),它不需要通过多路复用器(Selector)对注册的通道进行轮询操作即可实现异步读写,简化了NIO的编程模型。

下面还是通过代码来熟悉NIO2.0 AIO的相关类库,我们仍旧以时间服务器为例程进行讲解。

AIO 创建的TimeServer源码分析

首先看下时间服务器的主函数:

01 public class TimeServer {
02  
03 /**
04 * @param args
05 * @throws IOException
06 */
07 public static void main(String[] args) throws IOException {
08 int port = 8080;
09 if (args != null && args.length > 0) {
10 try {
11 port = Integer.valueOf(args[0]);
12 } catch (NumberFormatException e) {
13 // 采用默认值
14 }
15 }
16 AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
17 new Thread(timeServer, "AIO-AsyncTimeServerHandler-001").start();
18 }
19 }

我们直接从第16行开始看,首先创建异步的时间服务器处理类,然后启动线程将AsyncTimeServerHandler拉起,代码如下:

01 public class AsyncTimeServerHandler implements Runnable {
02  
03     private int port;
04  
05     CountDownLatch latch;
06     AsynchronousServerSocketChannel asynchronousServerSocketChannel;
07  
08     public AsyncTimeServerHandler(int port) {
09     this.port = port;
10     try {
11         asynchronousServerSocketChannel = AsynchronousServerSocketChannel
12             .open();
13         asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
14         System.out.println("The time server is start in port : " + port);
15     } catch (IOException e) {
16         e.printStackTrace();
17     }
18     }
19  
20     /*
21      * (non-Javadoc)
22      *
23      * @see java.lang.Runnable#run()
24      */
25     @Override
26     public void run() {
27  
28     latch = new CountDownLatch(1);
29     doAccept();
30     try {
31         latch.await();
32     } catch (InterruptedException e) {
33         e.printStackTrace();
34     }
35     }
36  
37     public void doAccept() {
38     asynchronousServerSocketChannel.accept(this,
39         new AcceptCompletionHandler());
40     }

我们重点对AsyncTimeServerHandler进行分析,首先看8-15行,在构造方法中,我们首先创建一个异步的服务端通道AsynchronousServerSocketChannel,然后调用它的bind方法绑定监听端口,如果端口合法且没被占用,绑定成功,打印启动成功提示到控制台。
在线程的run方法中,第26行我们初始化CountDownLatch对象,它的作用是在完成一组正在执行的操作之前,允许当前的线程一直阻塞。在本例程中,我们让线程在此阻塞,防止服务端执行完成退出。在实际项目应用中,不需要启动独立的线程来处理AsynchronousServerSocketChannel,这里仅仅是个demo演示。
第24行用于接收客户端的连接,由于是异步操作,我们可以传递一个
CompletionHandler<AsynchronousSocketChannel,? super A>类型的handler实例接收accept操作成功的通知消息,在本例程中我们通过AcceptCompletionHandler实例作为handler接收通知消息,下面,我们继续对AcceptCompletionHandler进行分析:

01 public class AcceptCompletionHandler implements
02     CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {
03  
04     @Override
05     public void completed(AsynchronousSocketChannel result,
06         AsyncTimeServerHandler attachment) {
07     attachment.asynchronousServerSocketChannel.accept(attachment, this);
08     ByteBuffer buffer = ByteBuffer.allocate(1024);
09     result.read(buffer, buffer, new ReadCompletionHandler(result));
10     }
11  
12     @Override
13     public void failed(Throwable exc, AsyncTimeServerHandler attachment) {
14     exc.printStackTrace();
15     attachment.latch.countDown();
16     }
17 }

CompletionHandler有两个方法,分别是:

1)  public void completed(AsynchronousSocketChannel result,

AsyncTimeServerHandler attachment);

2) public void failed(Throwable exc, AsyncTimeServerHandler attachment);

下面我们分别对这两个接口的实现进行分析:首先看completed接口的实现,代码7-10行,我们从attachment获取成员变量AsynchronousServerSocketChannel,然后继续调用它的accept方法。可能读者在此可能会心存疑惑,既然已经接收客户端成功了,为什么还要再次调用accept方法呢?原因是这样的:当我们调用AsynchronousServerSocketChannel的accept方法后,如果有新的客户端连接接入,系统将回调我们传入的CompletionHandler实例的completed方法,表示新的客户端已经接入成功,因为一个AsynchronousServerSocketChannel可以接收成千上万个客户端,所以我们需要继续调用它的accept方法,接收其它的客户端连接,最终形成一个循环。每当接收一个客户读连接成功之后,再异步接收新的客户端连接。
链路建立成功之后,服务端需要接收客户端的请求消息,代码第8行我们创建新的ByteBuffer,预分配1M的缓冲区。第8行我们通过调用AsynchronousSocketChannel的read方法进行异步读操作。下面我们看看异步read方法的参数:
1)   ByteBuffer dst:接收缓冲区,用于从异步Channel中读取数据包;

2)  A attachment:异步Channel携带的附件,通知回调的时候作为入参使用;

3)  CompletionHandler<Integer,? super A>:接收通知回调的业务handler,本例程中为ReadCompletionHandler。

下面我们继续对ReadCompletionHandler进行分析:

01 public class ReadCompletionHandler implements
02     CompletionHandler<Integer, ByteBuffer> {
03  
04     private AsynchronousSocketChannel channel;
05  
06     public ReadCompletionHandler(AsynchronousSocketChannel channel) {
07     if (this.channel == null)
08         this.channel = channel;
09     }
10  
11     @Override
12     public void completed(Integer result, ByteBuffer attachment) {
13     attachment.flip();
14     byte[] body = new byte[attachment.remaining()];
15     attachment.get(body);
16     try {
17         String req = new String(body, "UTF-8");
18         System.out.println("The time server receive order : " + req);
19         String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(
20             System.currentTimeMillis()).toString() : "BAD ORDER";
21         doWrite(currentTime);
22     } catch (UnsupportedEncodingException e) {
23         e.printStackTrace();
24     }
25     }
26  
27     private void doWrite(String currentTime) {
28     if (currentTime != null && currentTime.trim().length() > 0) {
29         byte[] bytes = (currentTime).getBytes();
30         ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
31         writeBuffer.put(bytes);
32         writeBuffer.flip();
33         channel.write(writeBuffer, writeBuffer,
34             new CompletionHandler<Integer, ByteBuffer>() {
35             @Override
36             public void completed(Integer result, ByteBuffer buffer) {
37                 // 如果没有发送完成,继续发送
38                 if (buffer.hasRemaining())
39                 channel.write(buffer, buffer, this);
40             }
41  
42             @Override
43             public void failed(Throwable exc, ByteBuffer attachment) {
44                 try {
45                 channel.close();
46                 } catch (IOException e) {
47                 // ingnore on close
48                 }
49             }
50             });
51     }
52     }
53  
54     @Override
55     public void failed(Throwable exc, ByteBuffer attachment) {
56     try {
57         this.channel.close();
58     } catch (IOException e) {
59         e.printStackTrace();
60     }
61     }
62 }

首先看构造方法,我们将AsynchronousSocketChannel通过参数传递到ReadCompletionHandler中当作成员变量来使用,主要用于读取半包消息和发送应答。本例程不对半包读写进行具体解说,对此感兴趣的可以关注后续章节对Netty半包处理的专题介绍。我们继续看代码,第12-25行是读取到消息后的处理,首先对attachment进行flip操作,为后续从缓冲区读取数据做准备。根据缓冲区的可读字节数创建byte数组,然后通过new String方法创建请求消息,对请求消息进行判断,如果是”QUERY TIME ORDER”则获取当前系统服务器的时间,调用doWrite方法发送给客户端。下面我们对doWrite方法进行详细分析。

跳到代码第28行,首先对当前时间进行合法性校验,如果合法,调用字符串的解码方法将应答消息编码成字节数组,然后将它拷贝到发送缓冲区writeBuffer中,最后调用AsynchronousSocketChannel的异步write方法。正如前面介绍的异步read方法一样,它也有三个与read方法相同的参数,在本例程中我们直接实现write方法的异步回调接口CompletionHandler,代码跳到第24行,对发送的writeBuffer进行判断,如果还有剩余的字节可写,说明没有发送完成,需要继续发送,直到发送成功。

最后,我们关注下failed方法,它的实现很简单,就是当发生异常的时候,我们对异常Throwable进行判断,如果是IO异常,就关闭链路,释放资源,如果是其它异常,按照业务自己的逻辑进行处理。本例程作为简单demo,没有对异常进行分类判断,只要发生了读写异常,就关闭链路,释放资源。

异步非阻塞IO版本的时间服务器服务端已经介绍完毕,下面我们继续看客户端的实现。 

时间: 2024-09-06 18:07:06

《Netty 权威指南》—— AIO 创建的TimeServer源码分析的相关文章

《Netty 权威指南》—— NIO创建的TimeServer源码分析

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 我们将在TimeServer例程中给出完整的NIO创建的时间服务器源码: public class TimeServer { /** * @param args * @throws IOException */ public static void main(String[] args) throws IOException { int port = 8080; if (args !=

《Netty 权威指南》—— AIO创建的TimeClient源码分析

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 异步非阻塞IO版本的时间服务器服务端已经介绍完毕,下面我们继续看客户端的实现. 首先看下客户端主函数的实现,AIO时间服务器客户端  TimeClient: 01 public class TimeClient { 02   03     /** 04      * @param args 05      */ 06     public static void main(String[

《Netty 权威指南》—— NIO创建的TimeClient源码分析

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 我们首先还是看下如何对TimeClient进行改造: public class TimeClient { /** * @param args */ public static void main(String[] args) { int port = 8080; if (args != null && args.length > 0) { try { port =

《Netty 权威指南》—— 4种IO的对比

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 2.5.1.概念澄清 为了防止由于对一些技术概念和术语的理解或者叫法不一致引起歧义,本小节特意对本书中的专业术语或者技术用语做下声明,如果它们与其它的一些技术书籍术语不一致,请以本小节的解释为准. 2.5.1.1. 异步非阻塞IO 很多人喜欢将JDK1.4提供的NIO框架称为异步非阻塞IO,但是,如果严格按照Unix网络编程模型和JDK的实现进行区分,实际上它只能被称为非阻塞IO,不能叫

《Netty权威指南》目录

<Netty权威指南>是全球第二本.中国第一本Netty教材,它由华为平台中间件资深架构设计师李林锋撰写,作者有6年多的NIO设计和开发实战经验,多次受邀进行Netty和 NIO编程培训. 本书基于最新的Netty5.0 版本撰写,从Netty开发环境的搭建,到第一个基于Netty的NIO服务端和客户端程序的开发,一步步的让读者从入门到精通,熟练的掌握基于Netty 的NIO开发,理解Netty的架构设计原理,可以对Netty进行深度的定制设计和开发. 本书共分为五部分:第一部分介绍 JAVA

《Netty 权威指南》样章

声明:本文是<Netty 权威指南>的样章目录,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 第 2 章  NIO入门 在本章节,我们分别对JDK的BIO.NIO和JDK1.7最新提供的NIO2.0的使用进行详细说明,通过流程图和代码讲解,让大家体会到随着Java IO类库的不断发展和改进,基于Java的网络编程会变得越来越简单,随着异步IO功能的增强,基于Java NIO开发的网络服务器甚至不逊色于采用C++开发的网络程序. 本章主要内容包括:  传统的同步阻塞式IO编程

《Netty 权威指南》—— 传统的BIO编程

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 网络编程的基本模型是Client/Server模型,也就是两个进程之间进行相互通信,其中服务端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信. 在基于传统同步阻塞模型开发中,ServerSocket负责绑定IP地址,启动监听端口,Socket负责发起连接操

《Netty 权威指南》—— 伪异步IO编程

声明:本文是<Netty 权威指南>的样章,感谢博文视点授权并发编程网站发布样章,禁止以任何形式转载此文. 为了解决同步阻塞IO面临的一个链路需要一个线程处理的问题,后来有人对它的线程模型进行了优化,后端通过一个线程池来处理多个客户端的请求接入,形成客户端个数M:线程池最大线程数N的比例关系,其中M可以远远大于N,通过线程池可以灵活的调配线程资源,设置线程的最大值,防止由于海量并发接入导致线程耗尽. 下面,我们结合连接模型图和源码,对伪异步IO进行分析,看它是否能够解决同步阻塞IO面临的问题.

Hadoop2源码分析-准备篇

1.概述 我们已经能够搭建一个高可用的Hadoop平台了,也熟悉并掌握了一个项目在Hadoop平台下的开发流程,基于Hadoop的一些套件我们也能够使用,并且能利用这些套件进行一些任务的开发.在Hadoop的应用级别上,我们接着往后面去研究学习,那就是Hadoop的源码了,作为Hadoop开发人员,我们得去学习和研究Hadoop得实现原理,底层框架的设计,编码的实现过程等等,下面就开始我们今天的Hadoop源码分析之旅. 2.准备 在分析源码之前,我们需要准备好分析源码的环境,以及如何去分析(分