《Nio系列四》- Nio2、Aio实现时间查询服务

在前两节中,主要介绍了Bio、Nio实现时间查询服务的细节,本节将开始介绍Aio的相关知识,Nio是通过轮询的机制检查channel的事件响应,而Aio是真正意义上的事件触发,通过回调完成响应的操作。在Aio中不需要专门的IO线程。由于Aio设计到较多的回调,因此在学习改内容之前,希望大家多Future的相关知识有所了解,不了解的可以查看我的《线程池系列》的文章中的相关知识,了解了Future之后,再来学习Aio会比较轻松。Aio是java7提出的,我们也称之为Nio 2,它才是真正意义上的异步非阻塞IO

服务端实现

不管是哪一种IO,其实现的逻辑都是一样的。

  1. 绑定端口号
  2. 接收用户连接

Aio的处理逻辑从接收用户连接开始,就开始进入了各种回调, 回调接口为CompletionHandler, 其中V为处理的结果,A为携带的信息,可以是任意对象。处理流程如下:接收连接-回调中读取请求-回调中处理请求产生响应并发送响应数据-回调中继续读取用户请求。详细的步骤如下:

  1. 当用户连接时,会进入连接成功的回调方法中,在回调方法中,需要重新执行一下accept()方法,目的是可以让其他客户端能够连接到服务器。其次需要读取用户的请求
  2. 当读取数据完成后(不一定是真正的读取完,可能会存在读半包的问题,暂时不用考虑),会回调响应的方法,解析一些请求数据,并进行业务逻辑处理,整理好响应数据,发送响应数据
  3. 响应数据发送完成后,可以判断数据是否发送完成,如果没有则继续发送,保证数据发送完成。

服务端代码如下:

public class AioServer implements CompletionHandler{

    private int port;
    private AsynchronousServerSocketChannel channel;

    public AioServer(int port) throws IOException {
        this.port = port;
        channel = AsynchronousServerSocketChannel.open();
    }

    public void start(){

        try {
            channel.bind(new InetSocketAddress(port));
            channel.accept(this, this);
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public void completed(AsynchronousSocketChannel result, AioServer attachment) {
        attachment.channel.accept(this, this);
        //链接成功,读数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        result.read(byteBuffer, byteBuffer, new ReadCompletionHandler(result));
    }

    @Override
    public void failed(Throwable exc, AioServer attachment) {
        try {
            channel.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        try {
            AioServer server = new AioServer(8080);
            server.start();
            Thread.sleep(1000000);
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

该类同时作为accept链接的回调类,实现了CompletionHandler接口,其中结果为接收的Channel对象,即AsynchronousSocketChannel, 携带信息在本例中携带的是该类的对象。链接成功后执行completed()方法,在该方法中要再次调用一下accept(),否则后续的客户端就链接不上服务器了(准确的说是连接上服务器了,但服务器不会去处理该连接)。然后读取数据即可。在读取数据时,也需要注册回调result.read(byteBuffer, byteBuffer, new ReadCompletionHandler(result)), 其中,第一个参数为读取数据到的buffer,第二个参数为携带信息,第三个参数为回调对象。读回调的代码如下:

public class ReadCompletionHandler implements CompletionHandler{

    private AsynchronousSocketChannel channel;

    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {

        attachment.flip();
        byte[] bytes = new byte[attachment.remaining()];
        attachment.get(bytes);
        String message = null;
        try {
             message = new String(bytes, "utf-8");
            System.out.println("获取到请求:" + message);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //处理业务逻辑
        if("时间".equals(message)){
            String response = (new Date()).toString();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put(response.getBytes());
            byteBuffer.flip();
            channel.write(byteBuffer, byteBuffer, new WriterCompletionHandler(channel));
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

        try {
            channel.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

当读取完成后,可以从Buffer中把收据取出来(但是这里读取出来的数据不一定是完整了,我们并没有做这个处理),解析请求,做出响应,并执行write(),进行响应,write()方法与read()类似,不在详细讲解。写回调的代码如下:

public class ReadCompletionHandler implements CompletionHandler{

    private AsynchronousSocketChannel channel;

    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {

        attachment.flip();
        byte[] bytes = new byte[attachment.remaining()];
        attachment.get(bytes);
        String message = null;
        try {
             message = new String(bytes, "utf-8");
            System.out.println("获取到请求:" + message);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        //处理业务逻辑
        if("时间".equals(message)){
            String response = (new Date()).toString();
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            byteBuffer.put(response.getBytes());
            byteBuffer.flip();
            channel.write(byteBuffer, byteBuffer, new WriterCompletionHandler(channel));
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

        try {
            channel.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

写完成后,只是不断的判断有没有全部发送数据而已,没有做其他的操作,说明一次请求得到响应后,可以断就没法再和服务端交互了,目前的实现是这样的,因为现在的实现都是在accept的驱动下完成的,自己可以动手修改一下,这里只是简单的介绍原理。

客户端实现

客户端的实现与服务端实现的原理是一样的,稍有不同的是处理的信息流不一样,客户端的信息流如下:链接服务端-回调中发送数据-回调中读取响应。具体的细节与服务端一样,直接上代码:

public class AioClient implements CompletionHandler{

    private int port;
    private String hostname;

    public AioClient(String hostname, int port) {
        this.hostname = hostname;
        this.port = port;

    }

    public void start(){
        try {
            AsynchronousSocketChannel channel = AsynchronousSocketChannel.open();
            channel.connect(new InetSocketAddress(hostname, port), channel, this);

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    /**
     * 链接成功执行的操作
     * @param result
     * @param attachment
     */
    @Override
    public void completed(Void result, AsynchronousSocketChannel attachment) {
        String message = "时间";
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        byteBuffer.put(message.getBytes());
        byteBuffer.flip();
        attachment.write(byteBuffer, byteBuffer, new WriteCompletionHanlder(attachment));
    }

    @Override
    public void failed(Throwable exc, AsynchronousSocketChannel attachment) {
        try {
            attachment.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {

        AioClient client = new AioClient("127.0.0.1", 8080);
        client.start();

        try {
            Thread.sleep(1000000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

链接成功之后,执行写操作发起请求,写操作的回调代码如下:

public class WriteCompletionHanlder implements CompletionHandler {

    AsynchronousSocketChannel channel;

    public WriteCompletionHanlder(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {

        if(attachment.hasRemaining()){
            channel.write(attachment, attachment, this);
        }else{
            ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
            channel.read(byteBuffer, byteBuffer, new ReadCompletionHandler(channel));
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

        try {
            channel.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

在写会调用多了一个判断,数据有误发送完成,如果没有发送完成就继续发送,只有请求发送成功了才能获取到响应。发送完请求后,执行读操作,等待响应,读回调的实现如下:

public class ReadCompletionHandler implements CompletionHandler {

    AsynchronousSocketChannel channel;

    public ReadCompletionHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }

    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        attachment.flip();
        byte[] message = new byte[attachment.remaining()];
        attachment.get(message);
        try {
            String str = new String(message, "utf-8");
            System.out.println("获取响应:"+ str);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {

        try {
            channel.close();
            exc.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

总结

Aio会使用大量的回调,千万不要被回调给绕晕了,一定要梳理出其处理逻辑,了解数据是怎么流向的,了解了数据的流向后才能真正的用好Aio

在下一章节,将会介绍Netty实现时间查询服务的细节,从下一节开始,该系列将会介绍Netty的相关知识。

欢迎扫描下方二维码,关注公众号,我们可以进行技术交流,共同成长

《Nio系列四》- Nio2、Aio实现时间查询服务_第1张图片
qrcode_for_gh_5580beb3cba1_430.jpg

你可能感兴趣的:(《Nio系列四》- Nio2、Aio实现时间查询服务)