Netty基础总结(1)

144 阅读9分钟

本文参与「新人创作礼」活动,一起开启掘金创作之路。

我的变强之路

下一篇:Netty基础总结(2)

Netty基础总结

1、IO类型及说明

类型含义说明使用场景
BIO同步并阻塞IO服务器实现模式为一个连接对应一个线程,
客户端连接过多会严重影响性能
连接数少且固定的架构,对服务器资源要求高,并发局限于应用中,如数据库连接
NIO同步非阻塞IO服务器实现模式为一个线程处理多个连接,
即客户端发送的连接请求都会注册到多路复用上;
多路复用器轮询到有IO请求就进行处理
连接数多且连接较短的架构,如聊天、弹幕、服务器间通讯
AIO异步非阻塞IO无论是客户端的连接请求还是读写请求都会异步执行;连接数较多且连接较长的架构,充分调用操作系统参与

2、BIO基本介绍

1、传统的Java io编程、其相关的类和接口在java.io包下

2、同步阻塞。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销

3、执行流程:

在这里插入图片描述

服务器端启动一个ServerSocket

客户端启动Socket对服务器进行通信,默认情况下服务器端需要对每个客户端建立一个线程与之通讯

客户端发出请求后,先咨询服务器是否有线程响应,如果没有则会等待或者被拒绝  如果有响应,客户端会等待请求结束后才继续执行

4、模型:

在这里插入图片描述

3、NIO基本介绍

1、原生jdk io上的一个改进 同步非阻塞!

2、三大核心组件:

类型含义理解
Channel通道一个Channel就代表一个连接在这里插入图片描述
Buffer缓冲区用于临时存储的一块内存在这里插入图片描述
Selector选择器对Channel的一个管理器,监听通道的事件

3、模型:

在这里插入图片描述

4、特性:

    a. 面向缓冲,数据读取到一个它稍后处理的缓冲区,需要时可以在缓冲区中前后移动,这就增加了处理过程中的灵活性,可以提供非阻塞的高伸缩性网格;

    b. 非阻塞;

    c. 通过选择器来模拟多线程;

4、NIO和BIO区别

1、BIO以流的方式处理数据,NIO以块的方式处理数据,块的效率 > 流的效率

2、BIO阻塞 NIO非阻塞

3、BIO基于字节流和字符流进行操作;NIO基于Channel(通道)和Buffer(缓冲区)进行操作,数据总是从通道内读取到缓冲区或者从缓冲区写入到通道。

4、BIO监听一个连接需要一个线程,NIO中一个线程可以监听多个连接

5、NIO三大核心原理

1、每个channel都对应一个buffer

2、selector对应一个线程,一个线程对应多个channel

3、selector选择哪个channel是由事件决定的

4、数据的读写都是操作的buffer

5、BIO中要么是输入流要么是输出流,NIO中的Buffer可读也可写

6、Buffer和Channel的注意细节

1、ByteBuffer支持类型化的put和get,put放入的是什么数据类型 get就应该使用相应的类型来取出

2、可以改变Buffer的可读性

3、NIO提供MappedByteBuffer,可以直接让文件在内存中修改(堆外内存),相当于直接在操作系统上操作文件而不用再拷贝一份进行操作,效率快

4、读写操作可以通过一个Buffer来操作,NIO还支持通过多个Buffer操作即Buffer数组形式操作

7、NIO部分demo代码

package com.dwk.nio;

import java.io.*;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.IntBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;

/**
 * nio 案例实现
 */
public class NioDemo {

    public static void main(String[] args) {
        //buffer的基本使用
        //intBuffTest(5);

        //channel的基本使用
        //channelTest();

        //文件拷贝
        //transferFormTest("","");

        //buffer的存取
        //bufferPutGet();

        //buffer的只读
        //readOnlyBuffer();

        //使用byteBuffer数组读取进行数据交互
        bufferArray();
    }

    public static void intBuffTest(int capacity) {
        //创建一个容量大小为capacity的buff
        IntBuffer allocate = IntBuffer.allocate(capacity);
        for (int i = 0; i < capacity; i++) {
            allocate.put(i * 2);
        }
        //将buffer切换,读写切换  处理buffer内的标记
        allocate.flip();
        //指定开始读取的位置
        //allocate.position(1);
        //指定结束读取的位置
        //allocate.limit(2);
        while (allocate.hasRemaining()){
            System.out.println(allocate.get());
        }
    }

    public static void channelTest(){
        //DatagramChannel  用于UDP数据的读写,ServerSocketChannel/SocketChannel用于TCP的数据读写

        //文件写通道
        fileChannelWriteTest();
        //文件读通道
        fileChannelReadTest();
        //使用一个通道完成文件的读写 - 文件拷贝
        fileChannelWriteAndReadTest();

    }

    /**
     * 文件写入
     */
    public static void fileChannelWriteTest(){
        FileOutputStream fileOutputStream = null;
        FileChannel fileChannel = null;
        ByteBuffer byteBuffer;
        try {
            String str = "fileChannelTest";
            fileOutputStream = new FileOutputStream("C:\\duwk\\code\\myself\\frame-master\\netty\\src\\main\\resources\\file\\FileChannel.txt");
            //获取通道
            fileChannel = fileOutputStream.getChannel();
            //创建缓冲区
            byteBuffer = ByteBuffer.allocate(1024);
            //写入缓冲区
            byteBuffer.put(str.getBytes("UTF-8"));
            //缓冲区索引重置
            byteBuffer.flip();
            //缓冲区数据写入通道
            fileChannel.write(byteBuffer);
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (UnsupportedEncodingException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                fileOutputStream.close();
                fileChannel.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 文件读取
     */
    public static void fileChannelReadTest(){
        FileInputStream fileInputStream = null;
        ByteBuffer byteBuffer = null;
        FileChannel channel = null;
        try {
            String filePath = "C:\\duwk\\code\\myself\\frame-master\\netty\\src\\main\\resources\\file\\FileChannel.txt";
            File file = new File(filePath);
            fileInputStream = new FileInputStream(file);
            //通道读取文件
            channel = fileInputStream.getChannel();
            //缓冲区读取通道
            byteBuffer = ByteBuffer.allocate((int) file.length());
            channel.read(byteBuffer);
            byteBuffer.flip();
            //缓冲区数据输出
            System.out.println(new String(byteBuffer.array(),"UTF-8"));
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                fileInputStream.close();
                channel.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 文件拷贝
     */
    public static void fileChannelWriteAndReadTest(){
        FileOutputStream outputStream = null;
        FileInputStream inputStream = null;
        ByteBuffer byteBuffer = null;
        try {
            String fileName = "C:\\duwk\\code\\myself\\frame-master\\netty\\src\\main\\resources\\file\\FileChannel.txt";
            String targetFileName = "C:\\duwk\\code\\myself\\frame-master\\netty\\src\\main\\resources\\file\\FileChannel副本.txt";
            File file = new File(fileName);
            File fileClone = new File(targetFileName);
            if (fileClone.exists()) {
                fileClone.delete();
                fileChannelWriteAndReadTest();
            }
            inputStream = new FileInputStream(file);
            //读取源文件流到通道
            FileChannel inChannel = inputStream.getChannel();
            //通道中的数据流写入到缓冲区
            byteBuffer = ByteBuffer.allocate(1024);
            inChannel.read(byteBuffer);
            byteBuffer.flip();
            //将缓冲区中的数据流写入到另一个通道
            outputStream = new FileOutputStream(fileClone);
            FileChannel outChannel = outputStream.getChannel();
            outChannel.write(byteBuffer);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try {
                outputStream.close();
                inputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }

    }

    /**
     * buffer的类型存取  按顺序存取
     */
    public static void bufferPutGet(){
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);

        byteBuffer.putInt(1);
        byteBuffer.putLong(1L);
        byteBuffer.putChar('A');
        byteBuffer.putShort((short) 1);

        byteBuffer.flip();

        //正常取出
        int anInt = byteBuffer.getInt();
        long aLong = byteBuffer.getLong();
        char aChar = byteBuffer.getChar();
        short aShort = byteBuffer.getShort();

        System.out.println(anInt);
        System.out.println(aLong);
        System.out.println(aChar);
        System.out.println(aShort);

        System.out.println("======================");

        //乱序取出  有异常
        short bShort = byteBuffer.getShort();
        char bChar = byteBuffer.getChar();
        long bLong = byteBuffer.getLong();
        int bnInt = byteBuffer.getInt();

        System.out.println(bnInt);
        System.out.println(bLong);
        System.out.println(bChar);
        System.out.println(bShort);
    }

    /**
     * 设置buffer只读
     */
    public static void readOnlyBuffer(){
        ByteBuffer byteBuffer = ByteBuffer.allocate(64);
        byteBuffer.putInt(1);
        //设置只读
        byteBuffer.asReadOnlyBuffer();
        int anInt = byteBuffer.getInt();
        System.out.println("buffer只读 ==>" + anInt);
    }

    /**
     * 使用通道的transferFrom方法拷贝文件
     * @param sourcePath 源文件路径
     * @param targetPath 目标文件路径
     */
    public static void transferFormTest(String sourcePath,String targetPath){
        FileInputStream inputStream = null;
        FileOutputStream outputStream = null;
        FileChannel inputStreamChannel = null;
        FileChannel outputStreamChannel = null;
        try {
            //创建文件流
            inputStream = new FileInputStream(sourcePath);
            outputStream = new FileOutputStream(targetPath);
            //信道
            inputStreamChannel = inputStream.getChannel();
            outputStreamChannel = outputStream.getChannel();
            //拷贝  参数:src = 源通道   position = 文件内开始转移的位置,必须是非负数   count = 最大的转换字节数,必须非负数
            outputStreamChannel.transferFrom(inputStreamChannel,0,inputStreamChannel.size());

        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                //关闭通道和流
                inputStreamChannel.close();
                outputStreamChannel.close();
                inputStream.close();
                outputStream.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * 零拷贝
     * mappedByteBuffer - 可以让文件直接在内存(堆外内存)中进行修改,操作系统不必拷贝一次  NIO同步到文件
     * 相当于直接操作源文件,性能高,但是不安全
     */
    public static void mappedByteBufferTest(){
        RandomAccessFile randomAccessFile = null;
        try {
            //name : 文件名  mode:模式(r,rw,rws,rwd)
            randomAccessFile = new RandomAccessFile("", "");
            //获取通道
            FileChannel channel = randomAccessFile.getChannel();
            //MapMode mode   模式, long position  可以直接修改的起始位置, long size   映射到内存的大小(不是索引位置)即文件的多少个字节映射到内存
            MappedByteBuffer map = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            //修改文件
            map.put(0,(byte) 'A');
            map.put(10,(byte) 'B');
        } catch (FileNotFoundException e) {
            throw new RuntimeException(e);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                randomAccessFile.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

    /**
     * buffer的分散和聚集
     * buffer数组操作   Scattering:将数据写入到buffer时可以采用buffer数组依次写入  Gathering:从buffer读取数据时依次读取buffer数组
     */
    public static void bufferArray(){
        //服务端通道
        ServerSocketChannel serverSocketChannel = null;
        //客户端通道
        SocketChannel socketChannel = null;
        try {
            //创建服务端通道
            serverSocketChannel = ServerSocketChannel.open();
            //指定端口
            InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
            //绑定端口并启动
            serverSocketChannel.socket().bind(inetSocketAddress);
            //服务端的buffer数组
            ByteBuffer[] byteBuffers = new ByteBuffer[2];
            //初始化buffer大小
            byteBuffers[0] = ByteBuffer.allocate(5);
            byteBuffers[1] = ByteBuffer.allocate(3);
            //等待客户端连接
            socketChannel = serverSocketChannel.accept();
            //每次从客户端通道读取的字节数
            int countByte = 8;
            //获取客户端发送的数据,,循环读取
            while (true){
                //统计读取的字节数
                int byteRead = 0;
                while (byteRead < countByte){
                    //从客户端通道读取字节到buffer数组
                    long read = socketChannel.read(byteBuffers);
                    byteRead += read;
                    System.out.println("累计读取的字节:" + byteRead);
                    Arrays.stream(byteBuffers).map(buffer -> "position = " + buffer.position() + "    limit = " + buffer.limit()).forEach(System.out::println);
                }
                //将所有的buffer反转
                Arrays.stream(byteBuffers).forEach(buffer -> {buffer.flip();});
                //将数据读出显示到客户端
                long byteWrite = 0;
                while (byteWrite < countByte){
                    long writeByte = socketChannel.write(byteBuffers);
                    byteWrite += writeByte;
                }
                //将所有的buffer清除
                Arrays.stream(byteBuffers).forEach(buffer -> {buffer.clear();});

                System.out.println("byteRead : " + byteRead + "  byteWrite : " + byteWrite + "  byteCount : " + countByte);
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                serverSocketChannel.close();
                socketChannel.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }

}

8、Selector选择器基本介绍

1、Java的NIO用非阻塞的IO方式。可以用一个线程处理多个客户端的连接,就会使用到Selector(选择器)

2、Selector能检测多个通道上是否有事件发生(多个Channel以事件的方式可以注册到同一个Selector),如果有事件发生,便获取事件然后针对每个事件进行相应的处理。这样就可以只用一个单线程去管理多个通道,也就是管理多个连接和请求。

3、只用在真正有读写事件发生时,才会进行读写,减少系统开销,不用为每个连接都创建一个线程

4、避免了线程上下文切换的问题

5、Selector示意图和特点说明

在这里插入图片描述

9、NIO非阻塞网络编程原理分析

在这里插入图片描述

Selector、SelectorKey、ServerSocketChannel、SocketChannel之间的关系如图;

1、当客户端连接时,会通过ServerSocketChannel得到SocketChannel;

2、将SocketChannel注册到指定的Selector上,一个Selector上可以注册多个SocketChannel;

3、注册后返回一个SelectionKey会和该Selector关联(集合);

4、Selector进行监听select方法,返回有事件发生的通道的个数;

5、进一步得到各个SelectionKey(有事件发生);

6、再通过SelectionKey反向获取SocketChannel;

7、可以通过得到的SocketChannel完成业务处理;

8、demo代码:

package com.dwk.netty;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Set;

/**
 * netty  demo
 */
public class NettyDemo {

    public static void main(String[] args) {
        chatting();
    }

    /**
     * 非阻塞实现服务端和客户端之间通信
     */
    public static void chatting () {
        Thread thread = new Thread(() -> {
            serverTest();
        });
        Thread thread1 = new Thread(() -> {
            clientTest();
        });
        thread.start();
        //等待两秒启动客户端
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        thread1.start();
    }


    /**
     * 服务器端
     */
    public static void serverTest(){
        try {
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            Selector selector = Selector.open();
            InetSocketAddress inetSocketAddress = new InetSocketAddress(6666);
            serverSocketChannel.socket().bind(inetSocketAddress);
            //设置非阻塞
            serverSocketChannel.configureBlocking(false);
            //服务端的socketChannel注册到selector 并设置监听事件为准备连接事件
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            //循环等待客户端连接
            while (true){
                //阻塞一秒后事件数量若为0则没有连接事件发生
                boolean nothing = selector.select(1000) == 0;
                if (nothing){
                    System.out.println("服务器等待了1秒,无连接");
                    continue;
                }
                //有事件发生,获取到事件的selectionKey集合
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                //通过selectionKey反向获取通道
                selectionKeys.forEach(selectionKey -> {
                    //判断事件类型
                    //客户端连接事件
                    boolean acceptable = selectionKey.isAcceptable();
                    //
                    boolean connectable = selectionKey.isConnectable();
                    //客户端写事件
                    boolean writable = selectionKey.isWritable();
                    //
                    boolean valid = selectionKey.isValid();
                    //客户端读事件
                    boolean readable = selectionKey.isReadable();

                    if (acceptable){
                        //处理连接事件
                        try {
                            //客户端连接事件,,给客户端生成一个非阻塞的SocketChannel
                            SocketChannel socketChannel = serverSocketChannel.accept();
                            socketChannel.configureBlocking(false);
                            //将socketChannel注册到selector,设置监听事件为准备读事件,并关联一个Buffer
                            socketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    if (readable){
                        try {
                            //处理读取事件
                            SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                            //获取channel关联的buffer
                            ByteBuffer buffer = (ByteBuffer) selectionKey.attachment();
                            socketChannel.read(buffer);
                            System.out.println("客户端发送的数据:" + new String(buffer.array()));
                        } catch (IOException e) {
                            throw new RuntimeException(e);
                        }
                    }

                    //移除集合中的selectionKey,防止重复操作
                    selectionKeys.remove(selectionKey);
                });
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 客户端
     */
    public static void clientTest(){
        String data = "我是数据!";
        try {
            SocketChannel socketChannel = SocketChannel.open();
            //设置非阻塞
            socketChannel.configureBlocking(false);
            //设置服务器端的ip和端口
            InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
            //连接服务器
            boolean connect = socketChannel.connect(inetSocketAddress);
            if (!connect){
                System.out.println("客户端正在连接...");
                while (!socketChannel.finishConnect()){
                    //客户端还没有连接成功,可以先处理其他逻辑
                    System.out.println("客户端还没有连接成功,还在连接!");
                }
            }
            System.out.println("客户端连接成功!发送数据给服务端...");
            ByteBuffer byteBuffer = ByteBuffer.wrap(data.getBytes());
            int write = socketChannel.write(byteBuffer);
            if (write == data.getBytes().length){
                System.out.println("客户端数据发送完成!");
            }
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

}

10、ServerSocketChannel和SocketChannel

ServerSocketChannelSocketChannel
作用在服务器端监听新的客户端Socket连接,偏向连接网络IO通道,具体负责读写操作,NIO把缓冲区的数据写入通道或者把通道内的数据读入缓冲区
偏向数据的读写、有分散和聚集操作
类图在这里插入图片描述在这里插入图片描述
方法在这里插入图片描述在这里插入图片描述