在2011年7月28日,jdk1.7被正式发布。他的一个最大的亮点就是将原来的NIO类库生成到了NIO2.0,也被叫做AIO。这篇文章将通过案例对AIO进行一个讲解。

一、IO的演进

在jdk1.4之前,java中的IO类库实在是超级原始,很多我们现在熟知的概念都还没有出现,比如说管道、缓冲区等等。正是由于这些等等原因,C语言和C++一直都是IO方面的首选。这是原始的IO方式,也叫作BIO,它的原理很简单,我们使用一张图来表示一下:

图片


也就是说BIO时代,每次有一个客户端连接进来的时候,都会有一个新的线程去处理,缺点显而易见,如果连接比较多的时候,我们就要建立大量的线程去一一处理。

几年之后,2002年,jdk1.4开始被正式发布了,做出的一个巨大的改变就是新增了NIO包。它提供了很多异步的IO操作方法,比如说缓冲区ByteBuffer、Pipe、Channel还有多路复用器Selector等等。新的NIO类库的出现,极大地促进了java对异步非阻塞式编程的发展。NIO的原理也是很简单。在这里同样使用一张图来演示一遍:

图片


现在我们可以看到,所有的客户端连接都可以只用一个线程就可以实现了。

不过时代总是在一点一点的变化,逐渐的java官方为我们提供的NIO类库越来越不能满足需求,比如说不支持异步文件读写操作、没有统一的文件属性等等。于是过了几年,在2011年7月28日,官方将用了将近十年的NIO类库做了升级,也被称为NIO2.0。后来也叫作AIO。AIO的原理是在之前的基础上进行的改进,意思是异步非阻塞式IO,也就是说你的客户端在进行读写操作的时候,只需要给服务器发送一个请求,不用一直等待回答就可以去做其他的事了。

下面我们使用代码敲一遍来看看如何实现AIO。

二、AIO的实现

这个案例很简单,就是服务端和客户端一个简单的通信。我们先把代码写好,然后再去分析代码的含义。

1、服务端

第一步:定义Server启动类

 1class AioServer{
2    public static void main(String[] args){
3        new AioServerHandle().start();   
4        try {
5            Thread.sleep(10000000L);
6        } catch (InterruptedException e) {
7            e.printStackTrace();
8        }
9    }
10 }

在这里我们定义了一个AioServerHandle线程去处理服务器端的逻辑,在这里我们还休眠了很长时间,这是为了避免没有客户端连接时,程序运行结束。现在我们最主要的就是AioServerHandle的代码逻辑了。

第二步:AioServerHandle类实现

 1public class AioServerHandle extends Thread {
2    AsynchronousServerSocketChannel serverSocketChannel;
3    public AioServerHandle() {
4        try {
5            serverSocketChannel = AsynchronousServerSocketChannel.open();
6            serverSocketChannel.bind(new InetSocketAddress("127.0.0.1"8888));    
7            System.out.println("服务端初始化成功");
8        } catch (IOException e) {
9            e.printStackTrace();
10        }         
11    }
12    @Override
13    public void run() {
14        serverSocketChannel.accept(thisnew AcceptCompleteHandler(serverSocketChannel));
15        try {
16            Thread.sleep(100000000L);
17        } catch (InterruptedException e) {
18            e.printStackTrace();
19        }
20    }
21}

我们分析一下这段代码,首先我们定义了一个AsynchronousServerSocketChannel,他表示的就是异步的ServerSocketChannel。然后我们在构造方法中打开连接,绑定地址和端口。最后run方法中new了一个AcceptCompleteHandler来处理接入的客户端。现在就像踢皮球一样,真正的处理逻辑又给了新的类AcceptCompleteHandler,我们再来看。

第三步:AcceptCompleteHandler的实现

 1public class AcceptCompleteHandler implements CompletionHandler<AsynchronousSocketChannelAioServerHandle{
2    //第一部分
3    private AsynchronousServerSocketChannel serverSocketChannel;
4    public AcceptCompleteHandler(AsynchronousServerSocketChannel serverSocketChannel) {
5        this.serverSocketChannel = serverSocketChannel;
6    }
7    //第二部分
8    @Override
9    public void completed(final AsynchronousSocketChannel channel, AioServerHandle attachment) {
10        //第二部分第一小节
11        attachment.serverSocketChannel.accept(attachment, this);
12        System.out.println("有客户端链接进来");
13        ByteBuffer readBuffer = ByteBuffer.allocate(1024);
14        //第二部分第二小节
15        channel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
16            @Override
17            public void completed(Integer result, ByteBuffer attachment) {
18                attachment.flip();
19                byte[] bytes = new byte[attachment.remaining()];
20                attachment.get(bytes);
21                System.out.println("客户端发送来的数据是:" + new String(bytes));
22
23                String sendMsg = "服务端返回的数据:java的架构师技术栈";
24                ByteBuffer writeBuffer = ByteBuffer.allocate(sendMsg.getBytes().length);
25                writeBuffer.put(sendMsg.getBytes());
26                writeBuffer.flip();
27                channel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
28                    @Override
29                    public void completed(Integer result, ByteBuffer attachment) {
30                        if (attachment.hasRemaining()) {
31                            channel.write(attachment, attachment, this);
32                        }
33                    }
34
35                    @Override
36                    public void failed(Throwable exc, ByteBuffer attachment) {
37                        try {
38                            System.out.println("服务端出现写数据异常");
39                            channel.close();
40                        } catch (IOException e) {
41                            e.printStackTrace();
42                        }
43                    }
44                });
45            }
46            //第二部分第三小节
47            @Override
48            public void failed(Throwable exc, ByteBuffer attachment) {
49                try {
50                    System.out.println("服务端读取数据异常");
51                    serverSocketChannel.close();
52                } catch (IOException e) {
53                    e.printStackTrace();
54                }
55            }
56        });
57    }
58    //第三部分
59    @Override
60    public void failed(Throwable exc, AioServerHandle attachment) {
61        System.out.println("服务端链接异常");
62    }
63}

第一部分:

通过构造方法来接受传递过来的AsynchronousServerSocketChannel。

第二部分第一小节:

serverSocketChannel继续接受传递过来的客户端,为什么呢?因为调用了AsynchronousServerSocketChannel的accept方法之后,如果有新的客户端连接进来,系统会回调我们的CompletionHandler得completed方法。但是一个AsynchronousServerSocketChannel往往能接受成千上万个客户端,所以在这里继续调用了Accept方法。以便于接受其他客户端的链接。

第二部分第二小节:

channel.read方法读取客户端传递过来的数据,而且在内部还有一个channel.write方法,表示返回给客户端的信息。代码逻辑是一样的。

第二部分第三小节:

在这里表示读取信息失败,内部也有一个failed方法表示的就是写入信息失败。

第三部分:

这也是一个failed方法,表示的是链接客户端失败。

到这里我们会看到,AIO的代码逻辑很复杂,在这里只是实现一个最简单的通信例子就这么麻烦,稍微增加点功能代码逻辑会让我们发疯。不过为了保持代码的完整性,我们还是要给出客户端的实现。

2、客户端

客户端的实现就比较简单了。

第一步:创建客户端入口类

 1class AioClient{
2    public static void main(String[] args){
3        new AioClientHandle().start();
4        try {
5            Thread.sleep(100000000L);
6        } catch (InterruptedException e) {
7            e.printStackTrace();
8        }
9     }
10}

在这里我们同样使用一个AioClientHandle来处理客户端的代码逻辑,现在我们继续看代码。

第二步:AioClientHandle类实现:

 1public class AioClientHandle extends Thread implements CompletionHandler<VoidAioClientHandle{
2    private AsynchronousSocketChannel socketChannel;
3    public AioClientHandle() {
4        try {
5            socketChannel = AsynchronousSocketChannel.open();
6            System.out.println("客户端初始化成功");
7        } catch (IOException e) {
8            e.printStackTrace();
9        }
10    }
11    @Override
12    public void run() {
13        socketChannel.connect(new InetSocketAddress("127.0.0.1"8888), thisthis);
14    }
15    @Override
16    public void completed(Void result, AioClientHandle attachment) {
17        System.out.println("client链接成功");
18        String sendMsg = "我是:java的架构师技术栈,服务端你好";
19        ByteBuffer writeBuffer = ByteBuffer.allocate(sendMsg.getBytes().length);
20        writeBuffer.put(sendMsg.getBytes());
21        writeBuffer.flip();
22        socketChannel.write(writeBuffer, writeBuffer, new CompletionHandler<Integer, ByteBuffer>() {
23            @Override
24            public void completed(Integer result, ByteBuffer attachment) {
25                if (attachment.hasRemaining()) {
26                    socketChannel.write(writeBuffer, attachment, this);
27                } else {
28                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
29                    socketChannel.read(readBuffer, readBuffer, new CompletionHandler<Integer, ByteBuffer>() {
30                        @Override
31                        public void completed(Integer result, ByteBuffer attachment) {
32                            readBuffer.flip();
33                            byte[] bytes = new byte[readBuffer.remaining()];
34                            readBuffer.get(bytes);
35                            System.out.println("客户端读取数据:" + new String(bytes));
36                        }
37
38                        @Override
39                        public void failed(Throwable exc, ByteBuffer attachment) {
40                            try {
41                                System.out.println("客户端读取数据失败");
42                                socketChannel.close();
43                            } catch (IOException e) {
44                                e.printStackTrace();
45                            }
46                        }
47                    });
48                }
49            }
50            @Override
51            public void failed(Throwable exc, ByteBuffer attachment) {
52                try {
53                    System.out.println("客户端写数据失败");
54                    socketChannel.close();
55                } catch (IOException e) {
56                    e.printStackTrace();
57                }
58            }
59        });
60    }
61
62    @Override
63    public void failed(Throwable exc, AioClientHandle attachment) {
64        try {
65            System.out.println("客户端出现异常");
66            socketChannel.close();
67        } catch (IOException e) {
68            e.printStackTrace();
69        }
70    }
71}

这个代码逻辑和服务端的差不多,在这里就不说了。下面我们主要分析一下为什么不用AIO。

三、AIO的缺点

上面BB了这么久就是为了说明为什么不使用他,你千万别急,因为知己知彼才能百战不殆。你只有理解了AIO才能知道工作中应该用什么,

1、实现复杂

上面的代码量你已经看到了,恶心到不能恶心。实现这么一个简单的功能就要写这么多。

2、需要额外的技能

也就是说你想要学号AIO,还需要java多线程的技术做铺垫才可以。否则我们很难写出质量高的代码。

3、一个著名的Selector空轮询bug

它会导致CPU100%,之前在我的群里面,有人曾经遇到过这个问题,而且官方说在1.6的版本中解决,但是现在还有。遇到的时候我们虽然可以解决但是不知道的人会很痛苦。

4、可靠性差

也就是说我们的网络状态是复杂多样的,会遇到各种各样的问题,比如说网断重连、缓存失效、半包读写等等。可靠性比较差。稍微出现一个问题,还需要大量的代码去完善。

当然还有很多其他的缺点,不过就单单第一条估计就很难发展。后来出现了更加牛的网络通信框架netty。很好的解决了上面的问题,也是目前最主流的框架。更多内容,在后续文章中推出。今天的文章先到这,感谢支持。


更多相关文章

  1. 数字签名的原理是什么?这篇文章给你答案(java代码实现)
  2. java远程调用之RMI(终于可以自己写代码控制别人电脑了)
  3. 客户端请求服务器时的状态码讲解
  4. JeecgBoot低代码快速开发框架,用于生产环境必须改造的9个关键点
  5. 常用数据结构的 JavaScript 实现代码[每日前端夜话0xED]
  6. 一段神奇的监视 DOM 的代码[每日前端夜话0xE4]
  7. SpringBoot结合MyBatis Plus 自动生成代码
  8. 10 行 Java 代码实现最近被使用( LRU )缓存
  9. Java 8 Lambda 表达式和流操作如何让你的代码变慢 5 倍

随机推荐

  1. Android示例大全教学视频
  2. Android应用的LinearLayout中嵌套Relativ
  3. Android SDK版本名和API level对照表
  4. SDK1.5下 android判断是否存在网络
  5. android文件操作OpenFileInput OpenFileO
  6. android 判断是白天还是晚上,然后设置地图
  7. Android 系统开发学习杂记
  8. Android Gradle 构建工具(Android Gradle
  9. Android开发中如何定义和使用数组
  10. Failed to install the following Androi