「Netty」Netty入门 认识NIO

        现在使用NIO的场景越来越多,很多网上的技术框架或多或少的使用NIO技术,譬如Tomcat,Jetty。学习和掌握NIO技术已经不是一个JAVA攻城狮的加分技能,而是一个必备技能

        Netty 底层实现就是基于我们的NIO,因为网络编程使用NIO非常复杂,容易写出Bub,netty封装了nio,大大的简化了学习成本与提高了编码效率。

概述 

        NIO主要有三大核心部分:Channel(通道),Buffer(缓冲区), Selector。传统IO基于字节流和字符流进行操作,而NIO基于Channel和Buffer(缓冲区)进行操作,数据总是从通道读取到缓冲区中,或者从缓冲区写入到通道中。Selector(选择区)用于监听多个通道的事件(比如:连接打开,数据到达)。因此,单个线程可以监听多个数据通道。

        NIO和传统IO(一下简称IO)之间第一个最大的区别是,IO是面向流的,NIO是面向缓冲区的。 Java IO面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。NIO的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

        IO的各种流是阻塞的。这意味着,当一个线程调用read() 或 write()时,该线程被阻塞,直到有一些数据被读取,或数据完全写入。该线程在此期间不能再干任何事情了。 NIO的非阻塞模式,使一个线程从某通道发送请求读取数据,但是它仅能得到目前可用的数据,如果目前没有数据可用时,就什么都不会获取。而不是保持线程阻塞,所以直至数据变得可以读取之前,该线程可以继续做其他的事情。 非阻塞写也是如此。一个线程请求写入一些数据到某通道,但不需要等待它完全写入,这个线程同时可以去做别的事情。 线程通常将非阻塞IO的空闲时间用于在其它通道上执行IO操作,所以一个单独的线程现在可以管理多个输入和输出通道(channel)。

Channel

首先说一下Channel,国内大多翻译成“通道”。Channel和IO中的Stream(流)是差不多一个等级的。只不过Stream是单向的,譬如:InputStream, OutputStream.而Channel是双向的,既可以用来进行读操作,又可以用来进行写操作。

Buffer

NIO中的关键Buffer实现有:ByteBuffer, CharBuffer, DoubleBuffer, FloatBuffer, IntBuffer, LongBuffer, ShortBuffer,分别对应基本数据类型: byte, char, double, float, int, long, short。

buffer对于传统IO(inputstream outputstream的区别),比较明显的就是buffer为双向的,读的同事也可以写

例如

public static void main(String[] args) {
        IntBuffer buffer = IntBuffer.allocate(10);
        // 写 
        buffer.put(1);
        buffer.put(4);
        buffer.put(3);
        buffer.put(2);

        // 读写切换*
        buffer.flip();

        // 读
        while (buffer.hasRemaining()) {
            System.out.println(buffer.get());
        }
    }

比较重要的一个就是buffer的读写切换,flip

Selector

Selector运行单线程处理多个Channel,如果你的应用打开了多个通道,但每个连接的流量都很低,使用Selector就会很方便。例如在一个聊天服务器中。要使用Selector, 得向Selector注册Channel,然后调用它的select()方法。这个方法会一直阻塞到某个注册的通道有事件就绪。一旦这个方法返回,线程就可以处理这些事件,事件的例子有如新的连接进来、数据接收等

三者对应关系如下图

「Netty」Netty入门 认识NIO_第1张图片
 

FileChannel 

传统IO与NIO读取一个文件的区别(代码演示)

传统IO

    public static void readFile(){
        InputStream in = null;
        try{
            in = new BufferedInputStream(new FileInputStream("/Users/duguotao/Desktop/data.txt"));
            byte [] buf = new byte[1024];
            int bytesRead = in.read(buf);
            while(bytesRead != -1){
                for(int i=0;i

NIO(channel结合buffer)

public static void main(String[] args) throws Exception {

        File file = new File("/Users/duguotao/Desktop/data.txt");
        FileInputStream in = new FileInputStream(file);
        // 获取通道
        FileChannel channel = in.getChannel();

        // 创建buffer从channel读取文件
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        channel.read(byteBuffer);

        System.out.println(new String(byteBuffer.array()));
        in.close();
    }

小栗子,利用buffer对文件进行copy

public static void main(String[] args) throws Exception {
        File file = new File("/Users/duguotao/Desktop/data.txt");
        FileInputStream in = new FileInputStream(file);
        FileChannel channel = in.getChannel();

        // 创建buffer从channel读取文件
        ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
        channel.read(byteBuffer);

        // buffer读写转换
        byteBuffer.flip();

        FileOutputStream outputStream = new FileOutputStream("/Users/duguotao/Desktop/data(2).txt");
        FileChannel fileChannel = outputStream.getChannel();
        fileChannel.write(byteBuffer);
        in.close();
        outputStream.close();
    }

socketChannl

        说完了FileChannel和Buffer, 大家应该对Buffer的用法比较了解了,这里使用SocketChannel来继续探讨NIO。NIO的强大功能部分来自于Channel的非阻塞特性,套接字的某些操作可能会无限期地阻塞。例如,对accept()方法的调用可能会因为等待一个客户端连接而阻塞;对read()方法的调用可能会因为没有数据可读而阻塞,直到连接的另一端传来新的数据。总的来说,创建/接收连接或读写数据等I/O调用,都可能无限期地阻塞等待,直到底层的网络实现发生了什么。慢速的,有损耗的网络,或仅仅是简单的网络故障都可能导致任意时间的延迟。然而不幸的是,在调用一个方法之前无法知道其是否阻塞。NIO的channel抽象的一个重要特征就是可以通过配置它的阻塞行为,以实现非阻塞式的信道。
                serverSocketChannel.configureBlocking(false);

在非阻塞式信道上调用一个方法总是会立即返回。这种调用的返回值指示了所请求的操作完成的程度。例如,在一个非阻塞式ServerSocketChannel上调用accept()方法,如果有连接请求来了,则返回客户端SocketChannel,否则返回null

传统BIO实现TCP服务器

@SuppressWarnings("all")
    public static void main(String[] args) throws Exception {
        ScheduledExecutorService threadPool = Executors.newScheduledThreadPool(10);
        // socket server
        ServerSocket socket = new ServerSocket(6666);
        System.out.println("启动socket服务器");

        // 等待client链接
        for (; ; ) {
            // 阻塞
            final Socket accept = socket.accept();
            System.out.println("链接一个客户端");

            // 与客户端通讯
            threadPool.execute(() -> {
                System.out.printf("当前线程:%s", Thread.currentThread().getId());

                try {
                    // 客户端发送到数据
                    byte[] data = new byte[64];
                    // 阻塞
                    InputStream inputStream = accept.getInputStream();
                    for (; ; ) {
                        if (inputStream.read(data) == -1) {
                            break;
                        }
                    }
                    System.out.printf("客户端发送的的数据: %s", new String(data, StandardCharsets.UTF_8));

                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    try {
                        socket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

NIO实现服务器端

public static void main(String[] args) throws Exception {
        // 服务器端
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));

        // 绑定port
        // 非阻塞
        serverSocketChannel.configureBlocking(false);
        Selector selector = Selector.open();
        // 服务端 关心链接事件
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        for (; ; ) {
            // 无事件发生
            if (selector.select(1000) == 0) {
                System.out.println("等待链接");
            }
            // SelectionKey 集合 有事件发生
            Set selectionKeys = selector.selectedKeys();
            Iterator keyIterator = selectionKeys.iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                // 链接事件
                if (key.isAcceptable()) {
                    // 新的客户端链接
                    // 给该客户端生成socket channel
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    System.out.println("客户端链接成功。。。");
                    // 非阻塞
                    socketChannel.configureBlocking(false);
                    // 注册到selector 读取事件 绑定buffer
                    socketChannel.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }

                // 读取数据
                if (key.isReadable()) {
                    // 读取channel数据
                    SocketChannel channel = (SocketChannel) key.channel();
                    // 上方设置的buffer
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    channel.read(buffer);
                    System.out.printf("客户端发来的数据:%s", new String(buffer.array()));
                }
                // 删除当前key 避免重复消费
                keyIterator.remove();
            }
        }
    }

以后阶段我会针对Netty进行学习。

会不定时的更新自己的学习笔记。

你可能感兴趣的:(分布式,Java,java,开发语言,后端)