2.4.1 AIO创建的TimeServer源码分析
首先看下时间服务器的主函数。
代码清单2-11 AIO时间服务器服务端 TimeClientHandle
我们直接从第25行开始看,首先创建异步的时间服务器处理类,然后启动线程将AsyncTimeServerHandler拉起,代码如下。
代码清单2-12 AIO时间服务器服务端
我们重点对AsyncTimeServerHandler进行分析。首先看20~27行,在构造方法中,我们首先创建一个异步的服务端通道AsynchronousServerSocketChannel,然后调用它的bind方法绑定监听端口,如果端口合法且没被占用,绑定成功,打印启动成功提示到控制台。
在线程的run方法中,第40行我们初始化CountDownLatch对象,它的作用是在完成一组正在执行的操作之前,允许当前的线程一直阻塞。在本例程中,我们让线程在此阻塞,防止服务端执行完成退出。在实际项目应用中,不需要启动独立的线程来处理AsynchronousServerSocketChannel,这里仅仅是个demo演示。
第41行用于接收客户端的连接,由于是异步操作,我们可以传递一个CompletionHandler <AsynchronousSocketChannel,?super A>类型的handler实例接收accept操作成功的通知消息,在本例程中我们通过AcceptCompletionHandler实例作为handler来接收通知消息,下面继续对AcceptCompletionHandler进行分析。
代码清单2-13 AIO时间服务器服务端 AcceptCompletionHandler
CompletionHandler有两个方法,分别如下。
◎ public void completed(AsynchronousSocketChannel result,AsyncTimeServerHandler attachment);
◎ public void failed(Throwable exc,AsyncTimeServerHandler attachment).
下面我们分别对这两个接口的实现进行分析。首先看completed接口的实现,代码18~20行,我们从attachment获取成员变量AsynchronousServerSocketChannel,然后继续调用它的accept方法。有的读者在此可能会心存疑惑:既然已经接收客户端成功了,为什么还要再次调用accept方法呢?原因是这样的:当我们调用AsynchronousServerSocketChannel的accept方法后,如果有新的客户端连接接入,系统将回调我们传入的CompletionHandler实例的completed方法,表示新的客户端已经接入成功,因为一个AsynchronousServerSocket Channel可以接收成千上万个客户端,所以我们需要继续调用它的accept方法,接收其他的客户端连接,最终形成一个循环。每当接收一个客户读连接成功之后,再异步接收新的客户端连接。
链路建立成功之后,服务端需要接收客户端的请求消息,在代码第19行我们创建新的ByteBuffer,预分配1M的缓冲区。第20行我们通过调用AsynchronousSocketChannel的read方法进行异步读操作。下面我们看看异步read方法的参数。
◎ ByteBuffer dst:接收缓冲区,用于从异步Channel中读取数据包;
◎ A attachment:异步Channel携带的附件,通知回调的时候作为入参使用;
◎ CompletionHandler<Integer,?super A>:接收通知回调的业务handler,本例程中为ReadCompletionHandler。
下面我们继续对ReadCompletionHandler进行分析。
代码清单2-14 AIO时间服务器服务端 ReadCompletionHandler
首先看构造方法,我们将AsynchronousSocketChannel通过参数传递到ReadCompletion Handler中当作成员变量来使用,主要用于读取半包消息和发送应答。本例程不对半包读写进行具体说明,对此感兴趣的读者可以关注后续章节对Netty半包处理的专题介绍。我们继续看代码,第25~38行是读取到消息后的处理,首先对attachment进行flip操作,为后续从缓冲区读取数据做准备。根据缓冲区的可读字节数创建byte数组,然后通过new String方法创建请求消息,对请求消息进行判断,如果是"QUERY TIME ORDER"则获取当前系统服务器的时间,调用doWrite方法发送给客户端。下面我们对doWrite方法进行详细分析。
跳到代码第41行,首先对当前时间进行合法性校验,如果合法,调用字符串的解码方法将应答消息编码成字节数组,然后将它复制到发送缓冲区writeBuffer中,最后调用AsynchronousSocketChannel的异步write方法。正如前面介绍的异步read方法一样,它也有三个与read方法相同的参数,在本例程中我们直接实现write方法的异步回调接口CompletionHandler。代码跳到第51行,对发送的writeBuffer进行判断,如果还有剩余的字节可写,说明没有发送完成,需要继续发送,直到发送成功。
最后,我们关注下failed方法,它的实现很简单,就是当发生异常的时候,对异常Throwable进行判断,如果是I/O异常,就关闭链路,释放资源,如果是其他异常,按照业务自己的逻辑进行处理。本例程作为简单demo,没有对异常进行分类判断,只要发生了读写异常,就关闭链路,释放资源。
异步非阻塞I/O版本的时间服务器服务端已经介绍完毕,下面我们继续看客户端的实现。