java网络编程(四)----异步非阻塞aio及proactor模型

(aio)NIO 2.0引入了新的异步通道的概念,并提供了异步文件通道和异步套接字通道的实现。异步的套接字通道时真正的异步非阻塞I/O,对应于UNIX网络编程中的事件驱动I/O(AIO)。他不需要过多的Selector对注册的通道进行轮询即可实现异步读写,从而简化了NIO的编程模型。

jdk7以前的nio是非阻塞IO,操作系统底层比方说linux,是用IO复用select或者epoll实现的,也不是异步IO啊。异步IO在linux上目前仅限于文件系统,并且还没有得到广泛应用,很多平台都没有这玩意。
java aio在windows上是利用iocp实现的,这是真正的异步IO。而在linux上,是通过epoll模拟异步的。


AIO使用

AIO 提供四种类型的异步通道以及不同的 I/O 操作:
AsynchronousSocketChannel:connect,read,write
AsynchronousFileChannel:lock,read,write
AsynchronousServerSocketChannel:accept
AsynchronousDatagramChannel:read,write,send,receive

当然我们重点还是将网络IO(tcp)方面的使用,先关注 AsynchronousSocketChannel ,首先简单浏览一下该类型的 API。

AsynchronousSocketChannel类定义

public abstract class AsynchronousSocketChannel implements AsynchronousByteChannel, NetworkChannel

创建一个异步网络通道,并且绑定到一个默认组。

public static AsynchronousSocketChannel open() throws IOException

接着我们可以调用AsynchronousSocketChannel 的各种方法进行异步操作,调用时候需要传入一个CompletionHandler接口参数作为回调接口。

1.将异步网络通道连接到远程服务器,使用指定的 CompletionHandler 听候完成通知。

public abstract  void connect(SocketAddress remote,  A attachment, CompletionHandlersuper A> handler)

2.从异步网络通道读取数据到指定的缓冲区,使用指定的 CompletionHandler 听候完成通知。

public final  void read(ByteBuffer dst, A attachment,  CompletionHandlersuper A> handler)

3.向异步网络通道写缓冲区中的数据,使用指定的 CompletionHandler 听候完成通知。

public final  void write(ByteBuffer src, A attachment, CompletionHandlersuper A> handler)

CompletionHandler接口,作为异步执行之后的处理接口,正确完成时回调completed(V result, A attachment)方法,执行失败回调void failed(Throwable exc, A attachment)方法。

public interface CompletionHandler {

    //Invoked when an operation has completed.
    void completed(V result, A attachment);
    //Invoked when an operation fails.
    void failed(Throwable exc, A attachment);
}

用起来很简单,调用异步方法然后将实现的回调接口作为参数传递就什么都不用理了等程序自己执行完毕回调。
aio还有另外一种通过Future的使用方式,因为hi出现阻塞的情况这里就不讲述这个方法了。


Proactor

Proactor模式包含如下角色:

  • Handle:句柄;用来标识socket的连接或者读写操作;
  • Asynchronous Operation Processor:异步操作处理器;负责执行异步操作,一般由操作系统内核实现;
  • Asynchronous Operation:异步操作
  • Completion Event Queue:完成事件队列;异步操作完成的结果放到队列中等待后续使用
  • Proactor:主动器;为应用程序进程提供事件循环;从完成事件队列中取出异步操作的结果,分发调用相应的后续处理逻辑;
  • Completion Handler:完成事件接口;一般是由回调函数组成的接口;
  • Concrete Completion Handler:完成事件处理的具体逻辑实现;实现回调接口,定义特定的应用处理逻辑。


执行过程:

  1. 应用程序启动,调用异步操作处理器提供的异步操作接口函数,调用之后应用程序和异步操作处理就独立运行;应用程序可以调用新的异步操作,而其它操作可以并发进行;
  2. 应用程序启动Proactor主动器,进行无限的事件循环,等待完成事件到来;
  3. 异步操作处理器执行异步操作,完成后将结果放入到完成事件队列;
    主动器从完成事件队列中取出结果,分发到相应的完成事件回调函数处理逻辑中;

我们在java aio程序中能接触到的只有Handle和Completion Handler,使用时调用
AsynchronousSocketChannel:connect,read,write和
AsynchronousServerSocketChannel:accept方法作为句柄Handle;接着程序就会自己按照上面的流程帮我们实现异步操作,最后在proactor再将执行结果分发到对应的Completion Handler中,回调我们实现的completed或者failed函数。

在Proactor模式中,事件处理者(或者代由事件分发器发起)直接发起一个异步读写操作(相当于请求),而实际的工作是由操作系统来完成的。发起时,需要提供的参数包括用于存放读到数据的缓存区、读的数据大小或用于存放外发数据的缓存区,以及这个请求完后的回调函数等信息。事件分发器得知了这个请求,它默默等待这个请求的完成,然后转发完成事件给相应的事件处理者或者回调。举例来说,在Windows上事件处理者投递了一个异步IO操作(称为overlapped技术),事件分发器等IO Complete事件完成。这种异步模式的典型实现是基于操作系统底层异步API的,所以我们可称之为“系统级别”的或者“真正意义上”的异步,因为具体的读写是由操作系统代劳的。


aio代码实现

由于代码量太多,这里只贴服务器端的代码,客户端的代码在下面链接的aio部分,我的代码也是根据下面的代码改的,多了很多注释便于理解。
来源:http://blog.csdn.net/anxpp/article/details/51512200

public class Server {
    private static int DEFAULT_PORT = 12345;
    private static AsyncServerHandler serverHandle;
    public volatile static long clientCount;
    public static void start(){
        start(DEFAULT_PORT);
    }
    public static synchronized void start(int port) {
        if(null != serverHandle)
            return;
        serverHandle = new AsyncServerHandler(port);
        /开启服务器线程
        new Thread(serverHandle,"Server").start();
    }
    public static void main(String[] args){
        Server.start();
    }
}
  1. 服务器线程实现,创建serverSocket并异步执行其accept方法
public class AsyncServerHandler implements Runnable {
    public CountDownLatch latch;
    public AsynchronousServerSocketChannel channel;
    public AsyncServerHandler(int port) {
        try {
            //创建服务端通道
            channel = AsynchronousServerSocketChannel.open();
            //绑定端口
            channel.bind(new InetSocketAddress(port));
            System.out.println("服务器已启动,端口号:" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void run() {
        //CountDownLatch初始化
        latch = new CountDownLatch(1);
        //用于接收客户端的连接
        //这句代码只能使用一次,接收一个客户端的请求
        //要实现多请求处理可以看AcceptHandler()参数的实现
        channel.accept(this,new AcceptHandler());
        System.out.println("主线程继续执行");
        try {
            //让现场在此阻塞,防止服务端执行完成后退出
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2.服务器接收到连接之后回调实现了CompletionHandler的AcceptHandler 类。

//作为handler接收客户端连接
public class AcceptHandler implements CompletionHandler<AsynchronousSocketChannel, AsyncServerHandler> {
    @Override
    public void completed(AsynchronousSocketChannel channel,AsyncServerHandler serverHandler) {
        //继续接受其他客户端的请求
        Server.clientCount++;
        System.out.println("连接的客户端数:" + Server.clientCount);

        //!!!!这行是继续异步接受其他请求的关键
        //每接收一个连接之后就再执行一次异步连接请求,这样就能一直处理多个连接了
        serverHandler.channel.accept(serverHandler, this);

        //创建新的Buffer
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        //异步读,第三个参数为接收消息回调的业务Handler
        channel.read(buffer, buffer, new ReadHandler(channel));
    }
    @Override
    public void failed(Throwable exc, AsyncServerHandler serverHandler) {
        exc.printStackTrace();
        serverHandler.latch.countDown();
    }
}

3.上面的AcceptHandler异步调用了读操作,于是读操作执行成功后调用ReadHandler 的completed()方法。另外写操作对应的回调接口作为内部类实现了。

public class ReadHandler implements CompletionHandler {
    //用于读取半包消息和发送应答
    private AsynchronousSocketChannel channel;
    public ReadHandler(AsynchronousSocketChannel channel) {
        this.channel = channel;
    }
    //读取到消息后的处理
    @Override
    public void completed(Integer result, ByteBuffer attachment) {
        //flip操作
        attachment.flip();
        //根据
        byte[] message = new byte[attachment.remaining()];
        attachment.get(message);
        try {
            String expression = new String(message, "UTF-8");
            System.out.println("服务器收到消息: " + expression);
            String calrResult = "这时来自服务器的消息";

            //向客户端发送消息
            doWrite(calrResult);
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
    }

    //发送消息
    private void doWrite(String result) {
        byte[] bytes = result.getBytes();
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        writeBuffer.put(bytes);
        writeBuffer.flip();
        //异步写数据 参数与前面的read一样
        //将回调接口作为内部类实现了
        channel.write(writeBuffer, writeBuffer,new CompletionHandler() {
            @Override
            public void completed(Integer result, ByteBuffer buffer) {
                //如果没有发送完,就继续发送直到完成
                if (buffer.hasRemaining())
                    channel.write(buffer, buffer, this);
                else{
                    //创建新的Buffer
                    ByteBuffer readBuffer = ByteBuffer.allocate(1024);
                    //异步读  第三个参数为接收消息回调的业务Handler
                    channel.read(readBuffer, readBuffer, new ReadHandler(channel));
                }
            }
            @Override
            public void failed(Throwable exc, ByteBuffer attachment) {
                try {
                    channel.close();
                } catch (IOException e) {
                }
            }
        });
    }
    @Override
    public void failed(Throwable exc, ByteBuffer attachment) {
        try {
            this.channel.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Reactor和Proactor对比

https://segmentfault.com/a/1190000002715832#articleHeader7

你可能感兴趣的:(java网络编程)