zoukankan      html  css  js  c++  java
  • 【JAVA NIO】java NIO

            本文是博主深入学习Netty前的一些铺垫,之前只是使用Netty,用的很粗暴,导包,上网找个DEMO就直接用,对Netty中的组件了解并不深入。

            于是再此总结下基础,并对一些核心组件作如下记录:

    1. 概述

    java NIO核心的APIChannel,Buffer 和 Selector 

    所有的 IO 在NIO 中都从一个Channel 开始。Channel 有点象流。 数据可以从Channel读到Buffer中,也可以从Buffer 写到Channel中。

    使用Selector,得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新连接进来,数据接收等。

    2.详述

    摘抄以下详细说明,摘抄地址:https://blog.csdn.net/anxpp/article/details/51512200

        JDK 1.4中的java.nio.*包中引入新的Java I/O库,其目的是提高速度。实际上,“旧”的I/O包已经使用NIO重新实现过,即使我们不显式的使用NIO编程,也能从中受益。速度的提高在文件I/O和网络I/O中都可能会发生,但本文只讨论后者。

        2.1、简介
        NIO我们一般认为是New I/O(也是官方的叫法),因为它是相对于老的I/O类库新增的(其实在JDK 1.4中就已经被引入了,但这个名词还会继续用很久,即使它们在现在看来已经是“旧”的了,所以也提示我们在命名时,需要好好考虑),做了很大的改变。但民间跟多人称之为Non-block I/O,即非阻塞I/O,因为这样叫,更能体现它的特点。而下文中的NIO,不是指整个新的I/O库,而是非阻塞I/O。

        NIO提供了与传统BIO模型中的Socket和ServerSocket相对应的SocketChannel和ServerSocketChannel两种不同的套接字通道实现。

        新增的着两种通道都支持阻塞和非阻塞两种模式。

        阻塞模式使用就像传统中的支持一样,比较简单,但是性能和可靠性都不好;非阻塞模式正好与之相反。

        对于低负载、低并发的应用程序,可以使用同步阻塞I/O来提升开发速率和更好的维护性;对于高负载、高并发的(网络)应用,应使用NIO的非阻塞模式来开发。

        下面会先对基础知识进行介绍。

        2.2、缓冲区 Buffer
        Buffer是一个对象,包含一些要写入或者读出的数据。

        在NIO库中,所有数据都是用缓冲区处理的。在读取数据时,它是直接读到缓冲区中的;在写入数据时,也是写入到缓冲区中。任何时候访问NIO中的数据,都是通过缓冲区进行操作。

        缓冲区实际上是一个数组,并提供了对数据结构化访问以及维护读写位置等信息。

        具体的缓存区有这些:ByteBuffe、CharBuffer、 ShortBuffer、IntBuffer、LongBuffer、FloatBuffer、DoubleBuffer。他们实现了相同的接口:Buffer。

        2.3、通道 Channel
        我们对数据的读取和写入要通过Channel,它就像水管一样,是一个通道。通道不同于流的地方就是通道是双向的,可以用于读、写和同时读写操作。

        底层的操作系统的通道一般都是全双工的,所以全双工的Channel比流能更好的映射底层操作系统的API。

        Channel主要分两大类:

        SelectableChannel:用户网络读写
        FileChannel:用于文件操作
        后面代码会涉及的ServerSocketChannel和SocketChannel都是SelectableChannel的子类。

        2.4、多路复用器 Selector
        Selector是Java  NIO 编程的基础。

        Selector提供选择已经就绪的任务的能力:Selector会不断轮询注册在其上的Channel,如果某个Channel上面发生读或者写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

        一个Selector可以同时轮询多个Channel,因为JDK使用了epoll()代替传统的select实现,所以没有最大连接句柄1024/2048的限制。所以,只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端。

    3.Java NIO创建的过程

             以下是java 提供的API 创建NIO server处理类的过程,各个API之间处理过程如下,可以看出比传统BIO Socket编程要多出很多对象,创建过程也比较复杂。

    博主简化了关键代码用于方便阅读,完整代码链接:https://blog.csdn.net/the_fool_/article/details/80700558

    //创建选择器
            Selector selector = Selector.open();
            //打开ServerSocketChannel,监听客户端连接
            ServerSocketChannel serverChannel = ServerSocketChannel.open();
            //如果为 true,则此通道将被置于阻塞模式;如果为 false,则此通道将被置于非阻塞模式
            serverChannel.configureBlocking(false);
            //绑定端口 backlog设为1024
            serverChannel.socket().bind(new InetSocketAddress(port), 1024);
            //监听客户端连接请求
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
            while (true) {
                //无论是否有读写事件发生,selector每隔1s被唤醒一次
                selector.select(1000);
                //返回已此通道已准备就绪的键集
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                //处理所有的
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    //处理Key中的信息
                    if (key.isValid()) {
                        //处理新接入的请求消息
                        if (key.isAcceptable()) {
                            ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                            //通过ServerSocketChannel的accept创建SocketChannel实例
                            //完成该操作意味着完成TCP三次握手,TCP物理链路正式建立
                            SocketChannel sc = ssc.accept();
                            //设置为非阻塞的
                            sc.configureBlocking(false);
                            //注册为读
                            sc.register(selector, SelectionKey.OP_READ);
                        }
                        //读消息
                        if (key.isReadable()) {
                            SocketChannel sc = (SocketChannel) key.channel();
                            //创建ByteBuffer,并开辟一个1M的缓冲区
                            ByteBuffer buffer = ByteBuffer.allocate(1024);
                            //读取请求码流,返回读取到的字节数
                            int readBytes = sc.read(buffer);
                            //读取到字节,对字节进行编解码
                            if (readBytes > 0) {
                                //将缓冲区当前的limit设置为position=0,用于后续对缓冲区的读取操作
                                buffer.flip();
                                //根据缓冲区可读字节数创建字节数组
                                byte[] bytes = new byte[buffer.remaining()];
                                //将缓冲区可读字节数组复制到新建的数组中
                                buffer.get(bytes);
                                String expression = new String(bytes, "UTF-8");
                                System.out.println("SERVER GET MSSG:" + expression);
                                //处理数据
                                String result = "server got ";
                                //发送应答消息
                                responseToClient(sc, result);
                            }
    
                            //链路已经关闭,释放资源
    
                        }
                    }
                }
            }


     

  • 相关阅读:
    120. Triangle
    Effective C++ 条款36 绝不重新定义继承而来的non-virtual函数
    Effective C++ 条款31 将文件中间的编译依存关系降至最低
    Effective C++ 条款35 考虑virtual函数以外的其他选择
    Effective C++ 条款34 区分接口继承和实现继承
    Effective C++ 条款33 避免遮掩继承而来的名称
    Effective C++ 条款32 确定你的public继承塑模出is-a关系
    Effective C++ 条款30 透彻了解inlining的里里外外
    Effective C++ 条款29 为"异常安全"而努力是值得的
    Effective C++ 条款28 避免返回handles指向对象内部成分
  • 原文地址:https://www.cnblogs.com/the-fool/p/11054095.html
Copyright © 2011-2022 走看看