BIO、NIO、AIO

先叙述一下网络编程,而后进入BIO、NIO、AIO讲解

1.网络编程

网络编程从大的方面说就是对数据的发送与接收。

网络编程就是将运行在多个设备(计算机)的程序通过网络连接起来,进行通信。

网络编程的基本模型是C/S模型,也就是两个进程之间进行相互通信,其中服务器端提供位置信息(绑定的IP地址和监听端口),客户端通过连接操作向服务端监听的地址发起连接请求,通过三次握手建立连接,如果连接建立成功,双方就可以通过网络套接字(Socket)进行通信。

Windows Socket是Windows环境下,网络应用程序编程接口,即网络编程的解决方案。由于TCP/IP的核心内容被封装在操作系统中,如果应用程序要使用TCP/IP,可以通过系统提供的TCP/IP的编程接口来实现,Windows环境下我们使用的是Socket。下面讲述的BIO、NIO、AIO都是通过使用Socket实现的网络编程,但是它们是以不同的方式实现的。

2.BIO、NIO、AIO概述

先叙述一下同步,异步,阻塞,非阻塞四个概念

同步和异步:

同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。

阻塞和非阻塞:

阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。

 

BIO编程:同步阻塞式

JDK 1.4版本之前,传统的BIO编程方式是常用的编程方式。编程实现过程为:服务端启动一个ServerSocket负责绑定IP地址,启动监听端口,客户端启动Socket负责发起连接操作。连接成功后双方通过输入、输出流进行同步阻塞试通信。BIO模式下,服务器端以阻塞方式监听客户端连接,收到连接后便停止阻塞,创建一个新的线程进行处理,处理完成后,通过输出流返回应答给客户端,线程销毁。这就是典型的一请求一应答通信模型。线程个数与客户端并发访问数呈1:1的正比关系。

NIO编程:同步非阻塞式

JDK 1.4 中引入的NIO。NIO 弥补了原来的 I/O 的不足,它在标准 Java 代码中提供了高速的、面向块的 I/O。NIO使用缓冲区,通道,多路复用器Selector技术,实现了同步非阻塞式的通信。

AIO编程:异步非阻塞式

JDK 1.7中新增了一些与文件(网络)I/O相关的一些API。这些API被称为NIO.2,或称为AIO(Asynchronous I/O)。异步通信通常有两种方式,将来式,回调式。这里我们讲解回调式。AIO使用CompletionHandler实现了回调式的异步非阻塞式的通信。

同步、异步点:服务器端在收到客户端请求时进行处理,触发I/O操作。如果服务器端在触发I/O之后,用户进程可以继续执行下面的代码,称为异步。如果服务器端在触发I/O之后,要等待或者轮询I/O操作结果,不可以继续执行下面代码,称为同步。

阻塞、非阻塞点:Socket的阻塞点其实就是在两个函数上面,读写操作read()、write()方法。

3.BIO

BIO实现:

(1)服务器端:创建一个ServerSocket对象server,并绑定端口。在一个while无限循环里,调用server的accept()方法,监听客户端连接。如果没有客户端连接请求,进程阻塞在accept()操作上。一旦监听到客户端连接,accept()方法返回一个Socket对象,创建一个线程并传入这个Socket对象去进行处理(数据的读取和写入)。

(2)客户端:创建一个Socket对象socket,并尝试通过套接字(IP:端口)去连接服务器端。然后进行处理(数据的写入和读取)。

读取和写入操作:通过输入输出流完成。

 

阻塞:在read()、write()方法时会阻塞。

 

下面是BIO的一个demo实现。

Server端:

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class TimeServer {
    public static void main(String[] args) {
        ServerSocket server = null;
        try {
            server = new ServerSocket(5600);
            Socket socket = null;
            while (true) {
                System.out.println("服务器开始监听");
                socket = server.accept();
                System.out.println(socket.getInetAddress().getHostAddress());
                System.out.println("服务器监听到客户端连接请求");
                new Thread(new TimeServerHandle(socket)).start();
            }
        } catch (IOException e) {
            System.out.println("The time server close");
            e.printStackTrace();
        } finally {
            System.out.println("The time server close");
            if (server != null) {
                try {
                    server.close();
                    System.out.println("The time server close");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
}
}

Server端处理函数:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class TimeServerHandle implements Runnable {
    private Socket socket;
    public TimeServerHandle(Socket socket) {
        this.socket = socket;
    }
    public void run() {
        BufferedReader in = null;
        PrintWriter out = null;
        try {
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            out = new PrintWriter(socket.getOutputStream(),true);
            System.out.println("到达读入前");
            String read = in.readLine();
            System.out.println("收到信息为:"+read);
            out.println("第一次信息回应");
            System.out.println("回应数据已发出");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                out.close();
            }
            if (this.socket != null) {
                try {
                    this.socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Client端:

import java.io.*;
import java.net.Socket;
import java.util.concurrent.CountDownLatch;

public class TimeClient {
    public static void main(String[] args) throws IOException {
        Socket socket = null;
        BufferedReader in = null;
        BufferedWriter out = null;
        //BufferedOutputStream out = null;
        //PrintWriter out = null;
        try {
            socket = new Socket("127.0.0.1",5600);
            System.out.println(socket.getLocalAddress().getHostAddress());
            in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            //out = new PrintWriter(socket.getOutputStream(),true);
            out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            //out = new BufferedOutputStream(socket.getOutputStream());
            out.write("第一次信息发送");
            out.newLine();
            out.flush();
            //out.println("第一次信息发送");
            System.out.println("呼叫数据已发出");
            String read = in.readLine();
            System.out.println("收到服务器回应");
            System.out.println("回应信息为:"+read);
            CountDownLatch count = new CountDownLatch(1);
            count.await();
            System.out.println("第一次检查"+socket.isClosed());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if (out != null) {
                out.close();
                System.out.println("第二次检查"+socket.isClosed());
            }
            if (socket != null) {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.NIO

NIO有三个核心技术,缓冲区Buffer、通道Channel、多路复用器Selector。

缓冲区Buffer:

Buffer 是一个对象, 它包含一些要写入或者刚读出的数据。 在 NIO 中加入 Buffer 对象,体现了新库与原 I/O 的一个重要区别。在面向流的 I/O 中,您将数据直接写入或者将数据直接读到 Stream 对象中。在 NIO 库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的。在写入数据时,它是写入到缓冲区中的。任何时候访问 NIO 中的数据,您都是将它放到缓冲区中。

缓冲区实质上是一个数组。通常它是一个字节数组,但是也可以使用其他种类的数组。但是一个缓冲区不 仅仅 是一个数组。缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程。

通道Channel:

Channel是一个通道,它就像自来水管一样,网络数据通过Channel读取和写入。通道与流的不同之处在于通道是双向的,流只是一个方向上移动(一个流要么是输入流,要么是输出流),而通道可以用于读、写,是全双工的。

多路复用器Selector:

异步 I/O 中的核心对象名为 SelectorSelector 就是您注册对各种 I/O 事件的兴趣的地方,而且当那些事件发生时,就是这个对象告诉您所发生的事件。

(1)服务器端:

我们需要做的第一件事就是创建一个 Selector

1

Selector selector = Selector.open();

然后,我们将对不同的通道对象调用 register() 方法,以便注册我们对这些对象中发生的 I/O 事件的兴趣。register() 的第一个参数总是这个 Selector

打开一个 ServerSocketChannel

为了接收连接,我们需要一个 ServerSocketChannel。事实上,我们要监听的每一个端口都需要有一个 ServerSocketChannel 。对于每一个端口,我们打开一个ServerSocketChannel,如下所示:

1

2

3

4

5

6

ServerSocketChannel ssc = ServerSocketChannel.open();

ssc.configureBlocking( false );

 

ServerSocket ss = ssc.socket();

InetSocketAddress address = new InetSocketAddress( ports[i] );

ss.bind( address );

第一行创建一个新的 ServerSocketChannel ,最后三行将它绑定到给定的端口。第二行将 ServerSocketChannel 设置为 非阻塞的 。我们必须对每一个要使用的套接字通道调用这个方法,否则异步 I/O 就不能工作。

选择键

下一步是将新打开的 ServerSocketChannels 注册到 Selector上。为此我们使用 ServerSocketChannel.register() 方法,如下所示:

1

SelectionKey key = ssc.register( selector, SelectionKey.OP_ACCEPT );

register() 的第一个参数总是这个 Selector。第二个参数是 OP_ACCEPT,这里它指定我们想要监听 accept 事件,也就是在新的连接建立时所发生的事件。这是适用于 ServerSocketChannel 的唯一事件类型。

请注意对 register() 的调用的返回值。 SelectionKey 代表这个通道在此Selector 上的这个注册。当某个 Selector 通知您某个传入事件时,它是通过提供对应于该事件的 SelectionKey 来进行的。SelectionKey 还可以用于取消通道的注册。

内部循环

现在已经注册了我们对一些 I/O 事件的兴趣,下面将进入主循环。使用 Selectors的几乎每个程序都像下面这样使用内部循环:

1

2

3

4

5

6

7

8

9

int num = selector.select();

 

Set selectedKeys = selector.selectedKeys();

Iterator it = selectedKeys.iterator();

 

while (it.hasNext()) {

     SelectionKey key = (SelectionKey)it.next();

     // ... deal with I/O event ...

}

首先,我们调用 Selector 的 select() 方法。这个方法会阻塞,直到至少有一个已注册的事件发生。当一个或者更多的事件发生时, select() 方法将返回所发生的事件的数量。

接下来,我们调用 Selector 的 selectedKeys() 方法,它返回发生了事件的SelectionKey 对象的一个 集合 

我们通过迭代 SelectionKeys 并依次处理每个 SelectionKey 来处理事件。对于每一个 SelectionKey,您必须确定发生的是什么 I/O 事件,以及这个事件影响哪些 I/O 对象。

监听新连接

程序执行到这里,我们仅注册了 ServerSocketChannel,并且仅注册它们“接收”事件。为确认这一点,我们对 SelectionKey 调用 readyOps() 方法,并检查发生了什么类型的事件:

1

2

3

4

5

6

if ((key.readyOps() & SelectionKey.OP_ACCEPT)

     == SelectionKey.OP_ACCEPT) {

 

     // Accept the new connection

     // ...

}

可以肯定地说, readOps() 方法告诉我们该事件是新的连接。

接受新的连接

因为我们知道这个服务器套接字上有一个传入连接在等待,所以可以安全地接受它;也就是说,不用担心 accept() 操作会阻塞:

1

2

ServerSocketChannel ssc = (ServerSocketChannel)key.channel();

SocketChannel sc = ssc.accept();

下一步是将新连接的 SocketChannel 配置为非阻塞的。而且由于接受这个连接的目的是为了读取来自套接字的数据,所以我们还必须将 SocketChannel 注册到Selector上,如下所示:

1

2

sc.configureBlocking( false );

SelectionKey newKey = sc.register( selector, SelectionKey.OP_READ );

注意我们使用 register() 的 OP_READ 参数,将 SocketChannel 注册用于 读取 而不是 接受 新连接。

删除处理过的 SelectionKey

在处理 SelectionKey 之后,我们几乎可以返回主循环了。但是我们必须首先将处理过的 SelectionKey 从选定的键集合中删除。如果我们没有删除处理过的键,那么它仍然会在主集合中以一个激活的键出现,这会导致我们尝试再次处理它。我们调用迭代器的 remove() 方法来删除处理过的 SelectionKey

1

it.remove();

现在我们可以返回主循环并接受从一个套接字中传入的数据(或者一个传入的 I/O 事件)了。

传入的 I/O

当来自一个套接字的数据到达时,它会触发一个 I/O 事件。这会导致在主循环中调用 Selector.select(),并返回一个或者多个 I/O 事件。这一次, SelectionKey将被标记为 OP_READ 事件,如下所示:

1

2

3

4

5

6

} else if ((key.readyOps() & SelectionKey.OP_READ)

     == SelectionKey.OP_READ) {

     // Read the data

     SocketChannel sc = (SocketChannel)key.channel();

     // ...

}

与以前一样,我们取得发生 I/O 事件的通道并处理它。在本例中,由于这是一个 echo server,我们只希望从套接字中读取数据并马上将它发送回去。关于这个过程的细节,请参见 参考资料 中的源代码 (MultiPortEcho.java)。

回到主循环

每次返回主循环,我们都要调用 select 的 Selector()方法,并取得一组SelectionKey。每个键代表一个 I/O 事件。我们处理事件,从选定的键集中删除SelectionKey,然后返回主循环的顶部。

这个程序有点过于简单,因为它的目的只是展示异步 I/O 所涉及的技术。在现实的应用程序中,您需要通过将通道从 Selector 中删除来处理关闭的通道。而且您可能要使用多个线程。这个程序可以仅使用一个线程,因为它只是一个演示,但是在现实场景中,创建一个线程池来负责 I/O 事件处理中的耗时部分会更有意义。

 

非阻塞:在执行read()、write()方法时,不用担心操作阻塞。因为我们已经用selector监听到了读、写事件。

 

(2)客户端:
省略。

下面是NIO的一个demo实现。

Server端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class ServerDemo{

    private ByteBuffer readBuffer = ByteBuffer.allocateDirect(1024);
    private ByteBuffer writeBuffer = ByteBuffer.allocateDirect(1024);
    private Selector selector;

    public ServerDemo() throws IOException{
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.configureBlocking(false);
        ServerSocket serverSocket = serverSocketChannel.socket();
        serverSocket.bind(new InetSocketAddress(8080));
        System.out.println("监听端口:8080");

        this.selector = Selector.open();

        // 绑定channel的accept
        System.out.println("服务器开始监听");
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
    }

    public static void main(String[] args) throws Exception{
        new ServerDemo().go();
    }

    private void go() throws Exception{

        // block api
        while(selector.select()>0){

            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while(iterator.hasNext()){
                SelectionKey selectionKey = iterator.next();
                iterator.remove();
                // 新连接
                if(selectionKey.isAcceptable()){
                    System.out.println("此密钥的通道已准备好接受新的套接字连接");
                    ServerSocketChannel server = (ServerSocketChannel)selectionKey.channel();

                    // 新注册channel
                    SocketChannel socketChannel  = server.accept();
                    System.out.println("服务器监听到客户端连接请求");
                    if(socketChannel==null){
                        continue;
                    }
                    socketChannel.configureBlocking(false);
                    // 注意!这里和阻塞io的区别非常大,在编码层面之前的等待输入已经变成了注册事件,这样我们就可以在等待的时候做别的事情,
                    // 比如监听更多的socket连接,也就是之前说了一个线程监听多个socket连接。这也是在编码的时候最直观的感受
                    socketChannel.register(selector, SelectionKey.OP_READ| SelectionKey.OP_WRITE);


                    ByteBuffer buffer = ByteBuffer.allocateDirect(1024);
                    buffer.put("第一次信息回应".getBytes());
                    //调换这个buffer的当前位置,并且设置当前位置是0
                    buffer.flip();
                    //从给定的缓冲区向该通道写入一个字节序列
                    socketChannel.write(buffer);
                    System.out.println("回应数据已发出");
                }

                // 服务端关心的可读,意味着有数据从client传来了,根据不同的需要进行读取,然后返回
                if(selectionKey.isReadable()){
                    System.out.println("isReadable");
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

                    readBuffer.clear();
                    socketChannel.read(readBuffer);
                    readBuffer.flip();

                    String receiveData= Charset.forName("UTF-8").decode(readBuffer).toString();
                    System.out.println("receiveData:"+receiveData);

                    // 把读到的数据绑定到key中
                    selectionKey.attach("server message echo:"+receiveData);
                }

                // 实际上服务端不在意这个,这个写入应该是client端关心的,这只是个demo,顺便试一下selectionKey的attach方法
                if(selectionKey.isWritable()){
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();

                    String message = (String) selectionKey.attachment();
                    if(message==null){
                        continue;
                    }
                    selectionKey.attach(null);

                    writeBuffer.clear();
                    writeBuffer.put(message.getBytes());
                    writeBuffer.flip();
                    while(writeBuffer.hasRemaining()){
                        socketChannel.write(writeBuffer);
                    }
                }
            }
        }
    }

}

Client端:

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;
import java.util.Iterator;

public class ClientDemo{

    private final ByteBuffer sendBuffer=ByteBuffer.allocate(1024);
    private final ByteBuffer receiveBuffer=ByteBuffer.allocate(1024);
    private Selector selector;

    public ClientDemo()throws IOException{
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.connect(new InetSocketAddress(InetAddress.getLocalHost(),8080));
        socketChannel.configureBlocking(false);
        System.out.println("与服务器的连接建立成功");
        selector=Selector.open();
        socketChannel.register(selector,SelectionKey.OP_READ | SelectionKey.OP_WRITE);
    }

    public static void main(String[] args) throws Exception{
        final ClientDemo client=new ClientDemo();
       // Thread receiver=new Thread(client::receiveFromUser);
        System.out.println("1111");

        //receiver.start();
        client.talk();
    }

    private void talk()throws IOException {
        while (selector.select() > 0 ){

            Iterator<SelectionKey> it = selector.selectedKeys().iterator();
            while (it.hasNext()){
                SelectionKey key = it.next();
                it.remove();

                if (key.isReadable()) {
                    receive(key);
                }
                // 实际上只要注册了关心写操作,这个操作就一直被激活
                if (key.isWritable()) {
                    send(key);
                }
            }

        }
    }

    private void send(SelectionKey key)throws IOException{
        SocketChannel socketChannel=(SocketChannel)key.channel();
        synchronized(sendBuffer){
            sendBuffer.flip(); //设置写
            while(sendBuffer.hasRemaining()){
                socketChannel.write(sendBuffer);
            }
            sendBuffer.compact();
        }
    }
    private void receive(SelectionKey key)throws IOException{
        SocketChannel socketChannel=(SocketChannel)key.channel();
        //从该通道读取到给定缓冲区的字节序列
        socketChannel.read(receiveBuffer);
        receiveBuffer.flip();
        String receiveData=Charset.forName("UTF-8").decode(receiveBuffer).toString();

        System.out.println("回应信息为:"+receiveData);
        receiveBuffer.clear();
    }

    private void receiveFromUser() {
        BufferedReader bufferedReader=new BufferedReader(new InputStreamReader(System.in));
        try{
            String msg;
            while ((msg = bufferedReader.readLine()) != null){
                synchronized(sendBuffer){
                    sendBuffer.put((msg+"\r\n").getBytes());
                }
            }
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

5.AIO

JDK7主要增加了三个新的异步通道:

  • AsynchronousFileChannel: 用于文件异步读写;
  • AsynchronousSocketChannel: 客户端异步socket;
  • AsynchronousServerSocketChannel: 服务器异步socket。

因为AIO的实施需充分调用OS参与,IO需要操作系统支持、并发也同样需要操作系统的支持,所以性能方面不同操作系统差异会比较明显。

下面讲解回调式异步处理。

 

异步:回调式处理技术,用户进程派一个侦查员CompletionHandler到独立的线程中执行IO操作,侦查员操作完成返回主进程,触发它自己的completed或failed方法。用户进程派出侦查员后可以继续执行下面代码。

 

(1)服务器端:

在回调式异步处理中,有一个核心接口CompletionHandler。

回调式所采用的事件处理技术类似于Swing UI编程采用的机制。基本思想是主线程会派一个侦查员CompletionHandler到独立的线程中执行IO操作。这个侦查员将带着IO的操作的结果返回到主线程中,这个结果会触发它自己的completed或failed方法(要重写这两个方法)。在异步IO活动结束后,接口java.nio.channels.CompletionHandler会被调用,其中V是结果类型,A是提供结果的附着对象。此时必须已经有了该接口completed(V,A)和failed(V,A)方法的实现,你的程序才能知道异步IO操作成功或失败时该如何处理。

(2)客户端:

省略。

下面是AIO的一个demo实现。

Server端:

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.StandardSocketOptions;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousChannelGroup;
import java.nio.channels.AsynchronousServerSocketChannel;
import java.nio.channels.AsynchronousSocketChannel;
import java.nio.channels.CompletionHandler;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

public class AioServer {

    public static AsynchronousServerSocketChannel serverSocketChannel;//第二次接收的时候会使用到,因此作为一个属性

    public static void main(String[] args) {
        //新建一个线程池,使得aio中的操作都使用这个线程池中的线程,而且,还传入一个ThreadFactory对象,自己去定义线程
        ExecutorService executorService = new ThreadPoolExecutor(10, 10, 0, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
        try {
            //创建使用的公共线程池资源
            AsynchronousChannelGroup channelGroup = AsynchronousChannelGroup.withThreadPool(executorService);
            //创建AsynchronousServerSocketChannel对象
            if(serverSocketChannel==null){
                serverSocketChannel = AsynchronousServerSocketChannel.open(channelGroup);
            }
            //设定一些参数
            //接收缓冲区的大小
            serverSocketChannel.setOption(StandardSocketOptions.SO_RCVBUF, 64 * 1024);
            //是否重用本地地址
            serverSocketChannel.setOption(StandardSocketOptions.SO_REUSEADDR, true);
            //绑定监听的端口号
            serverSocketChannel.bind(new InetSocketAddress("127.0.0.1",5432));
            //开始接收请求,第一个参数是根据自己的需求出入对应的对象
            //第二个参数CompletionHandler的子对象
            serverSocketChannel.accept(new AioServer(), new MyCompletion());
            System.out.println("服务器开始监听");
            //保持程序不停止
            waitRevicer();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public static void waitRevicer(){
        new Thread(new Runnable() {

            @Override
            public void run() {
                while(true){
                    //System.out.println("保持程序运行不停止...");
                    try {
                        Thread.sleep(3000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }

}

//用于消除异步I/O操作结果的处理程序
class MyCompletion implements CompletionHandler<AsynchronousSocketChannel,AioServer>{

    @Override
    public void completed(AsynchronousSocketChannel result, AioServer attachment) {
        System.out.println("completed");
        //用来缓存数据
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
        try {
            //读取客户端数据,存放到ByteBuffer对象中
            result.read(byteBuffer).get();
            //重新设置position和limit的值,为读取值做准备
            byteBuffer.flip();
            //根据值的长度创建对象,并将ByteBuffer中的数据存入,打印输出
            byte[] input = new byte[byteBuffer.remaining()];
            byteBuffer.get(input);
            System.out.println(new String(input));
            //重新设置position的值,并将客户端发送的值返回给客户端
            byteBuffer.position(0);
            result.write(byteBuffer);
//          String sayHello = new String("你好啊,客户端"+result.getRemoteAddress().toString());
//          System.out.println("sayHello:"+sayHello);
//          result.write(ByteBuffer.wrap(sayHello.getBytes("UTF-8")));
            attachment.serverSocketChannel.accept(attachment, this);//不重新设置接收的话就只能接收一次了
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    @Override
    public void failed(Throwable exc, AioServer attachment) {
        System.out.println("failed");
        System.out.println(attachment);
        System.out.println(exc);

    }

}

Client端:

import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousSocketChannel;
import java.util.Random;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;

public class AioClient {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        new AioClientThread().start();
        new AioClientThread().start();
        new AioClientThread().start();
    }

}

class AioClientThread extends Thread{

    @Override
    public void run() {
        super.run();
        try {
            //获取AsynchronousSocketChannel的实例,这里可以跟服务器端一样传入一个AsynchronousChannelGroup的对象
            AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
            //连接服务器
            Future<Void> future = socketChannel.connect(new InetSocketAddress("127.0.0.1",5432));
            //注意,这个get是一个阻塞的方法,只有当连接上了之后才会往下走
            //如果不执行get方法,等程序没有连接成功时进行发送数据的操作会发生错误
            future.get();
            //拼接发送给服务器的数据
            Random rom = new Random();
            StringBuffer writeBuf = new StringBuffer();
            writeBuf.append("this is client").append(rom.nextInt(1000));
            System.out.println(writeBuf.toString());
            ByteBuffer byteBuffer = ByteBuffer.wrap(writeBuf.toString().getBytes());
            //发送数据
            socketChannel.write(byteBuffer);
            //读取服务器的返回的数据
            read(socketChannel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void read(AsynchronousSocketChannel socketChannel){
        ByteBuffer byteBufer = ByteBuffer.allocate(10);
        try {
            StringBuffer strBuf = new StringBuffer("客户端:");
            boolean hasData = true;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            while(hasData){
                socketChannel.read(byteBufer).get();
                if(byteBufer.capacity()>byteBufer.position()){//当容器大小大于容器里面的数量的时候,认为读取完毕
                    hasData = false;
                }
                byteBufer.flip();
                byte[] buf = new byte[byteBufer.remaining()];
                byteBufer.get(buf);
                if(hasData){
                    System.out.println("数据没有读取完毕继续读取");
                }else{
                    System.out.println("数据读取完毕");
                }
                out.write(buf);
                byteBufer.clear();
            }
            strBuf.append(new String(out.toByteArray(),"UTF-8"));
            System.out.println(strBuf);
            //read(socketChannel);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

参考文献:

1. Netty权威指南,作者:李林锋

2. NIO入门,作者:Greg Travis,链接:https://www.ibm.com/developerworks/cn/education/java/j-nio/j-nio.html

3. java中的AIO,作者:林湾村龙猫,链接:https://www.jianshu.com/p/c5e16460047b

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值