NIO,BIO,AIO,JAVA通讯编程学习笔记3

下文内容摘自《Netty 权威指南》


JDK1.7升级了NIO类库,升级后的NIO类库被称为NIO2.0,引人注目的是Java正式提供了异步文件IO操作,同时提供了与Unix网络编程事件驱动IO对应的AIO,下面的2.4章节我们学习下如何利用NIO2.0编写AIO程序,我们还是以时间服务器为例进行讲解。


AIO编程

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

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

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

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

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

AIO 创建的TimeServer源码分析

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

程序1

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

程序2

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进行分析:

程序3

首先看构造方法,我们将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版本的时间服务器服务端已经介绍完毕,下面我们继续看客户端的实现


首先看下客户端主函数的实现,AIO时间服务器客户端  TimeClient:

程序4

第15行我们通过一个独立的IO线程创建异步时间服务器客户端handler,在实际项目中,我们不需要独立的线程创建异步连接对象,因为底层都是通过JDK的系统回调实现的,在后面运行时间服务器程序的时候,我们会抓取线程调用堆栈给大家展示。继续看代码, AsyncTimeClientHandler的实现类源码如下:

程序5

由于在AsyncTimeClientHandler中大量使用了内部匿名类,所以代码看起来稍微有些复杂,下面我们就对主要代码进行详细解说。

9-17行是构造方法,首先通过AsynchronousSocketChannel的open方法创建一个新的AsynchronousSocketChannel对象。然后跳到第36行,创建CountDownLatch进行等待,防止异步操作没有执行完成线程就退出。第37行通过connect方法发起异步操作,它有两个参数,分别如下:

1)      A attachment : AsynchronousSocketChannel的附件,用于回调通知时作为入参被传递,调用者可以自定义;

2)      CompletionHandler<Void,? super A> handler:异步操作回调通知接口,由调用者实现。

在本例程中,我们的两个参数都使用AsyncTimeClientHandler类本身,因为它实现了CompletionHandler接口。

接下来我们看异步连接成功之后的方法回调completed方法,代码第39行,我们创建请求消息体,对其进行编码,然后拷贝到发送缓冲区writeBuffer中,调用AsynchronousSocketChannel的write方法进行异步写,与服务端类似,我们可以实现CompletionHandler<Integer, ByteBuffer>接口用于写操作完成后的回调,代码第45-47行,如果发送缓冲区中仍有尚未发送的字节,我们继续异步发送,如果已经发送完成,则执行异步读取操作。

代码第64-97行是客户端异步读取时间服务器服务端应答消息的处理逻辑,代码第49行我们调用AsynchronousSocketChannel的read方法异步读取服务端的响应消息,由于read操作是异步的,所以我们通过内部匿名类实现CompletionHandler<Integer, ByteBuffer>接口,当读取完成被JDK回调时,我们构造应答消息。第56-63行我们从CompletionHandler的ByteBuffer中读取应答消息,然后打印结果。

第197-96行,当读取发生异常时,我们关闭链路,同时调用CountDownLatch的countDown方法让AsyncTimeClientHandler线程执行完毕,客户端退出执行。

需要指出的是,正如之前的NIO例程,我们并没有完整的处理网络的半包读写,当对例程进行功能测试的时候没有问题,但是,如果对代码稍加改造,进行压力或者性能测试,就会发现输出结果存在问题。

由于半包的读写会作为专门的小节在Netty的应用和源码分析章节进行详细讲解,在NIO的入门章节我们就不详细展开介绍,以便读者能够将注意力集中在NIO的入门知识上来。


服务端程序:

package AIO;

import java.io.IOException;

public class TimeServer {

/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {
int port = 8580;
if (args != null && args.length > 0) {
try {
port = Integer.valueOf(args[0]);
} catch (NumberFormatException e) {
// 采用默认值
}
}
AsyncTimeServerHandler timeServer = new AsyncTimeServerHandler(port);
new Thread(timeServer, "AIO-AsyncTimeServerHandler-001").start();
}
}

================================================================

package AIO;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.util.concurrent.CountDownLatch;

public class AsyncTimeServerHandler implements Runnable {
       private int port;
       CountDownLatch latch;
       AsynchronousServerSocketChannel asynchronousServerSocketChannel;
       public AsyncTimeServerHandler(int port) {
       this.port = port;
       try {
           asynchronousServerSocketChannel = AsynchronousServerSocketChannel
               .open();
           asynchronousServerSocketChannel.bind(new InetSocketAddress(port));
           System.out.println("The time server is start in port : " + port);
       } catch (IOException e) {
           e.printStackTrace();
       }
       }
       /*
        * (non-Javadoc)
         *
         * @see java.lang.Runnable#run()
         */
        @Override
        public void run() {
        latch = new CountDownLatch(1);
        doAccept();
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        }
        public void doAccept() {
        asynchronousServerSocketChannel.accept(this,
            new AcceptCompletionHandler());
        }
       

}

================================================================

package AIO;

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class AcceptCompletionHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncTimeServerHandler> {  
      
    @Override  
    public void completed(AsynchronousSocketChannel result, AsyncTimeServerHandler attachment) {  
        attachment.asynchronousServerSocketChannel.accept(attachment, this);  
        ByteBuffer buffer = ByteBuffer.allocate(1024);  
        result.read(buffer, buffer, new ReadCompletionHandler(result));  
    }  
 
    @Override  
    public void failed(Throwable exc, AsyncTimeServerHandler attachment) {  
        exc.printStackTrace();  
        attachment.latch.countDown();  
    }  
 
}  

================================================================

package AIO;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;

public class ReadCompletionHandler implements CompletionHandler<Integer, ByteBuffer> {  
      
    private AsynchronousSocketChannel channel;  
      
    public ReadCompletionHandler(AsynchronousSocketChannel channel) {  
        if (this.channel == null) {  
            this.channel = channel;  
        }      
    }  
      
    private void doWrite(String currentTime) {  
        if (currentTime != null && currentTime.trim().length() > 0) {  
            byte[] bytes = (currentTime).getBytes();  
            ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);  
            writeBuffer.put(bytes);  
            writeBuffer.flip();  
            channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {  
                @Override  
                public void completed(Integer result, ByteBuffer buffer) {  
                    // 如果没有发送完成,继续发送  
                    if (buffer.hasRemaining()) {  
                        channel.write(buffer, buffer, this);  
                    }     
                }  
 
                @Override  
                public void failed(Throwable exc, ByteBuffer attachment) {  
                    try {  
                        channel.close();  
                    } catch (IOException e) {  
                        // ingnore on close  
                    }  
                }  
            });  
        }  
    }  
      
    @Override  
    public void completed(Integer result, ByteBuffer attachment) {  
        attachment.flip();  
        byte[] body = new byte[attachment.remaining()];  
        attachment.get(body);  
        try {  
            String req = new String(body, "UTF-8");  
            System.out.println("The time server receive order : " + req);  
            String currentTime = "QUERY TIME ORDER".equalsIgnoreCase(req) ? new java.util.Date(System.currentTimeMillis()).toString() : "BAD ORDER";  
            doWrite(currentTime);  
        } catch (UnsupportedEncodingException e) {  
            e.printStackTrace();  
        }  
    }  
 
    @Override  
    public void failed(Throwable exc, ByteBuffer attachment) {  
        try {  
            this.channel.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
 


================================================================


客户端程序:

package AIO;

public class TimeClient {  
      
    public static void main(String[] args) {  
        int port = 8580;  
        if (args != null && args.length > 0) {  
            try {  
                port = Integer.valueOf(args[0]);  
            } catch (NumberFormatException e) {  
                // 采用默认值  
            }  
 
        }  
        new Thread(new AsyncTimeClientHandler("127.0.0.1", port), "AIO-AsyncTimeClientHandler-001").start();  
    }  
 

================================================================

package AIO;

import java.io.IOException;  
import java.io.UnsupportedEncodingException;  
import java.net.InetSocketAddress;  
import java.nio.ByteBuffer;  
import java.nio.channels.AsynchronousSocketChannel;  
import java.nio.channels.CompletionHandler;  
import java.util.concurrent.CountDownLatch;  
 
public class AsyncTimeClientHandler implements CompletionHandler<Void, AsyncTimeClientHandler>, Runnable {  
 
    private AsynchronousSocketChannel client;  
    private String host;  
    private int port;  
    private CountDownLatch latch;  
 
    public AsyncTimeClientHandler(String host, int port) {  
        this.host = host;  
        this.port = port;  
        try {  
            client = AsynchronousSocketChannel.open();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
      
    @Override  
    public void run() {  
        latch = new CountDownLatch(1);  
        client.connect(new InetSocketAddress(host, port), this, this);  
        try {  
            latch.await();  
        } catch (InterruptedException e1) {  
            e1.printStackTrace();  
        }  
        try {  
            client.close();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
 
    @Override  
    public void completed(Void result, AsyncTimeClientHandler attachment) {  
        byte[] req = "QUERY TIME ORDER".getBytes();  
        ByteBuffer writeBuffer = ByteBuffer.allocate(req.length);  
        writeBuffer.put(req);  
        writeBuffer.flip();  
        client.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {  
                @Override  
                public void completed(Integer result, ByteBuffer buffer) {  
                if (buffer.hasRemaining()) {  
                    client.write(buffer, buffer, this);  
                } else {  
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);  
                    client.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {  
                        @Override  
                        public void completed(Integer result,  
                            ByteBuffer buffer) {  
                            buffer.flip();  
                            byte[] bytes = new byte[buffer.remaining()];  
                            buffer.get(bytes);  
                            String body;  
                            try {  
                                body = new String(bytes,"UTF-8");  
                                System.out.println("Now is : " + body);  
                                latch.countDown();  
                            } catch (UnsupportedEncodingException e) {  
                                e.printStackTrace();  
                            }  
                        }  
      
                        @Override  
                        public void failed(Throwable exc, ByteBuffer attachment) {  
                            try {  
                                client.close();  
                                latch.countDown();  
                            } catch (IOException e) {  
                                // ingnore on close  
                            }  
                        }  
                    });  
                }  
            }  
      
            @Override  
            public void failed(Throwable exc, ByteBuffer attachment) {  
                try {  
                    client.close();  
                    latch.countDown();  
                } catch (IOException e) {  
                    // ingnore on close  
                }  
            }  
        });  
    }  
 
    @Override  
    public void failed(Throwable exc, AsyncTimeClientHandler attachment) {  
        exc.printStackTrace();  
        try {  
            client.close();  
            latch.countDown();  
        } catch (IOException e) {  
            e.printStackTrace();  
        }  
    }  
 
}  


运行结果:

服务器端输出:

The time server is start in port : 8580
The time server receive order : QUERY TIME ORDER

客户端输出:

Now is : Wed Nov 02 13:59:35 CST 2016


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值