• 网络I/O模型--06异步I/O


            异步I/O (又称为 AIO )则是采用“订阅一通知”工作模式 : 即应用程序向操作系统注册I/O监听,然后继续做自己的事情。当操作系统发生I/O事件,并且准备好数据后 , 再主动通知应用程序,触发相应的函数

            异步I/O也必须由操作系统进行支持 。 微软Windows系统提供了一种异步I/O技术 : IOCP (I/O Completion Port, I/O完成端口〉; 多个Linux 操作系统版本(例如 CentOS 5等)下由于没有这种异步I/O技术,所以使用的是 epoll对异步I/O 进行模拟

            但是在 Java AIO 框架中 , 由于应用程序不是“轮询”方式,而是“订阅一通知” 方式,所以不再需要“ Selector ” (选择器)了, 改由 Channel 通道直接到操作系统注册监昕 在 Java AIO 框架中, 只实现了两种网络 I/O 通道“ AsynchronousServerSocketChannel ”(服务器监听通道)、 “ AsynchronousSocketChannel” (Socket 套接字通道)。但是无论哪种通道它们都有独立的 fileDescriptor (文件标识符)、 attachment (附件,可以是任意对象,类似“通道上下文”),并被独立的 SocketChannelReadHandle 类实例引用 。

    image

    服务端代码

    1.SocketServer6NIO

    package testBlockSocket;
    
    import java.io.IOException;
    import java.io.UnsupportedEncodingException;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.URLDecoder;
    import java.net.URLEncoder;
    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.nio.channels.SelectableChannel;
    import java.nio.channels.SelectionKey;
    import java.nio.channels.Selector;
    import java.nio.channels.ServerSocketChannel;
    import java.nio.channels.SocketChannel;
    import java.util.Iterator;
    import java.util.Map;
    import java.util.concurrent.ConcurrentHashMap;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    public class SocketServer6NIO {
        private final static Logger LOGGER = LoggerFactory.getLogger(SocketServer6NIO.class);
        private static final Object waitObject = new Object();
    
        public static void main(String[] args) throws Exception {
            /*
             * 对于使用的线程池技术 : 1. Executors 是线程池生成工具,通过这个工具我们可以很轻松地生成“固定大 小的线程池”、 “调度池”、
             * “可伸缩线程数量的池”。 2. 当然你也可以通过 ThreadPoolExecutor 直接实例化线程池 3. 这个线程池是用来得到操作系统的“
             * I/O事件通知”的,不是用来进行“得到I/O数据后的业务处理的”。要进行后者的操作,你可以再使用另一个池(最好不要混用〉 4.
             * 也可以不使用线程池〈不推荐),如果决定不使用线程池,那么直接调用 AsynchronousServerSocketChannel.open()就行了
             */
    
            ExecutorService threadPool = Executors.newFixedThreadPool(20);
            AsynchronousChannelGroup group = AsynchronousChannelGroup.withThreadPool(threadPool);
            final AsynchronousServerSocketChannel serverSocket = AsynchronousServerSocketChannel.open(group);
    
            // 设置要监听的端口“0.0.0.0”代表本机所有 IP 设备
            serverSocket.bind(new InetSocketAddress("0.0.0.0", 8888));
            // 为 AsynchronousServerSocketChannel 注册监听,并不包括为随后客户端和服务器 socketChannel通道注册的监听
            serverSocket.accept(null, new ServerSocketChannelHandle(serverSocket));
            // 阻塞,以便 debug 时观察现象
            synchronized (waitObject) {
                waitObject.wait();
            }
        }
    }
    
    

    2.ServerSocketChannelHandle

    // 这个处理器类,专门用来响应 ServerSocketChannel 的事件。ServerSocketChannel 只有一种事件 :接收容户端的连接
    class ServerSocketChannelHandle implements CompletionHandler<AsynchronousSocketChannel, Void> {
        private static final Logger LOGGER = LoggerFactory.getLogger(ServerSocketChannelHandle.class);
        private AsynchronousServerSocketChannel serverSocketChannel;
    
        //
        public ServerSocketChannelHandle(AsynchronousServerSocketChannel serverSocketChannel) {
            this.serverSocketChannel = serverSocketChannel;
        }
    
        // 注意,分别查看 this 、 socketChannel 、 attachment
        // 三个对象的ID来观察不同客户端连接到达时,这三个对象的变化,以说明ServerSocketChannelHandle 的监昕模式
        @Override
        public void completed(AsynchronousSocketChannel socketChannel, Void attachment) {
            ServerSocketChannelHandle.LOGGER.info("completed(AsynchronousSocketChannel result , ByteBuffer attachment)");
            this.serverSocketChannel.accept(attachment, this);
            // 为这个新的 SocketChannel
            // 注册“read”事件,以便操作系统在收到数据并准备好后,主动通知应用程序在这里,由于我们要将这个客户端多次传输的数据累加起来一起处理,所以将一个StringBuffer对象作为一个
            // “附件”依附在这个 Channel 上
            ByteBuffer readBuffer = ByteBuffer.allocate(50);
            socketChannel.read(readBuffer, new StringBuffer(), new SocketChannelReadHandle(socketChannel, readBuffer));
        }
    
        @Override
        public void failed(Throwable exc, Void attachment) {
            ServerSocketChannelHandle.LOGGER.info("failed(Throwable exc,ByteBuffer attachment)");
        }
    }

    3.SocketChannelReadHandle

    /**
     * 负责对每一个 SocketChannel 的数据获取事件进行监听 。 重要说明 : 每个 SocketChannel都会有一个独立工作的
     * SocketChannelReadHandle 对象(CompletionHandler 接口的实现)
     * 其中又都将独享一个“文件状态标识”对象FileDescriptor,还有一个独立的自程序员定义的 Buffer 缓存(这里我们使用的是
     * ByteBuffer),所以不用担心在服务器端会出现“窃对象”这种情况,因为 Java AIO 框架已经帮你组织好了 另 一个重要的点是,用子生成
     * Channel 的对象AsynchronousChannelProvider 是单例模式,无论在哪组 SocketChannel都是一个对象引用
     */
    class SocketChannelReadHandle implements CompletionHandler<Integer, StringBuffer> {
        private static final Logger LOGGER = LoggerFactory.getLogger(SocketChannelReadHandle.class);
    
        private AsynchronousSocketChannel socketChannel;
    
        /**
         * 专门用于进行这个通道数据缓存操作的 ByteBuffer,你也可以作为 CompletionHandler的 attachment 形式传入
         */
        private ByteBuffer byteBuffer;
    
        public SocketChannelReadHandle(AsynchronousSocketChannel socketChannel, ByteBuffer byteBuffer) {
            this.socketChannel = socketChannel;
            this.byteBuffer = byteBuffer;
        }
    
        @Override
        public void completed(Integer result, StringBuffer historyContext) {
            // 如果条件成立,说明客户端主动终止了 TCP 套接字,这时服务器端终止就可以了
            if (result == -1) {
                try {
                    this.socketChannel.close();
                } catch (IOException e) {
                    SocketChannelReadHandle.LOGGER.error(e.getMessage());
                }
                return;
            }
            SocketChannelReadHandle.LOGGER.info("completed (integer result ,Void attachment) :然后我们来取出通道中准备好的值");
    
            /*
             * 实际上,由于我们从 Integer result 知道了本次 Channel 从操作系统获取数据总长度,所以我们不需要切换成“读模式” ,
             * 但是为了保证编码的规范性,还是建议进行切换 无论是 Java AIO 框架还是 Java NIO 框架,都会出现“Buffer
             * 的总容量”小于“当前从操作系统获取到的总数据量”的情况, 但区别是, Java AIO 框架不需要专门考虑处理这样的情况,因为 Java AIO
             * 框架已经帮我们做了处理
             **/
            this.byteBuffer.flip();
            byte[] contexts = new byte[1024];
            this.byteBuffer.get(contexts, 0, result);
            this.byteBuffer.clear();
            try {
                String nowContent = new String(contexts, 0, result, "UTF-8");
                historyContext.append(nowContent);
                SocketChannelReadHandle.LOGGER.info("================目前的传输结果" + historyContext);
            } catch (UnsupportedEncodingException e) {
                SocketChannelReadHandle.LOGGER.error(e.getMessage());
            }
    
            // 如果条件成立,则说明还没有接收到“结束标记”
            if (historyContext.indexOf("over") == -1) {
                return;
            }
            // 我们以“ over ”符号作为客户端完整信息的标记
            SocketChannelReadHandle.LOGGER.info("==:收到完整信息,开始处理业务-==");
            historyContext = new StringBuffer();
            // 还要继续监听( 一次监昕一次通知)
            this.socketChannel.read(this.byteBuffer, historyContext, this);
        }
    
        @Override
        public void failed(Throwable exc, StringBuffer historyContext) {
            SocketChannelReadHandle.LOGGER.info("=====发现客户端异常关闭,服务器将关闭 TCP 通道 ");
            try {
                this.socketChannel.close();
            } catch (IOException e) {
                SocketChannelReadHandle.LOGGER.error(e.getMessage());
    
            }
        }
    
    }

            服务器处理每一个客户端通道所使用的SocketChannelReadHandle (处理器)对象都是独立的,并且所引用的 SocketChannel 对象也都是独立的 Java NIO 和 Java AIO 框架,除了因为操作系统的实现不一样而去掉了 Selector,其他的重要概念都是相似的 。实际上 Java NIO 和 Java AIO 框架各位读者可以看成是一套完整的“高并发 I/O 处理”的实现 。

  • 相关阅读:
    Ansible 详细用法说明(一)
    Puppet基于Master/Agent模式实现LNMP平台部署
    推荐-zabbix原理篇
    Centos 6.x 安装Nagios及WEB管理nagiosql实现windows及linux监控指南
    CentOS 7下安装Logstash ELK Stack 日志管理系统(下)
    【Python基础学习二】定义变量、判断、循环、函数基本语法
    【Python基础学习一】在OSX系统下搭建Python语言集成开发环境 附激活码
    内联函数
    2016 科大讯飞校招研发一面二面 10.13
    hiho #1151 : 骨牌覆盖问题·二 (递推,数论)
  • 原文地址:https://www.cnblogs.com/gispathfinder/p/9032374.html
Copyright © 2020-2023  润新知