zoukankan      html  css  js  c++  java
  • Netty之 BIO与NIO

    BIO

    BIO 会导致线程逐渐增多,消耗cpu

    BIO模型

    通过线程池机制编写简单demon

    @Slf4j
    public class Server {

    public static void main(String[] args) throws Exception {
        //创建一个缓存线程池
        ExecutorService cachedThreadPool = Executors.newCachedThreadPool();
        //serverSocket
        ServerSocket serverSocket = new ServerSocket(6666);
    
        while (true){
            final  Socket socket = serverSocket.accept();//等待客户端进来
            log.info("连接到一个线程");
            // 加载到线程池里
            cachedThreadPool.execute(new Runnable() {
                @Override
                public void run() {
                    try {
                        handler(socket);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }
    
    public static void handler(Socket socket) throws IOException {
        // 接收数据
        byte[] bytes = new byte[1024];
        // 通过socket 获取输入流
        InputStream inputStream = null;
        int read = 0;
        try {
            inputStream = socket.getInputStream();
            read = inputStream.read(bytes);
            while (true){
                if(read != -1 ){
                    log.info("获取到客户端数据的数据"+new String(bytes,0,read) );
                }else{
                    break; // 退出
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            inputStream.close();
            socket.close();
        }
    }
    

    }

    客户端如何证明 访问的时候是一个单独的一个线程?

    通过输出线程的id 和名字

    System.out.println("线程id"+Thread.currentThread().getId()+"名字"+Thread.currentThread().getName() );

    当没有进入数据发送的时候 会默认产生阻塞效果 。 因此nio模型是事件驱动的。

    bio jdk1.4之前独有。

    客户端cmd 连接命令 telnet 127.0.0.1 6666

    NIO同步非阻塞模型

    同步非阻塞,服务器端是实现了一个线程进行多个处理,客户端发送的连接都会路由到多路复用上,多路复用在通过轮询对连接的请求进行io的读写操作。

    什么是堆外内存和堆内内存?

    堆内内存是被jvm管理的,堆外则不是。堆外是由操作系统管理的。

    服务端产生一个selector 轮询器,把客户端的请求进行分发和处理,当客户端有事件发生的时候选择器会进行轮询获取。

    nio 是jdk1.7以后,而且nio没有广泛应用,netty就是应用了NIO。

    简单了解javaAIO就是NIO.2:异步非阻塞,对请求进行一个判断,连接使用较长的应用。 主要学习NIO

    NIO 在1.4以后是同步非阻塞的,nio的类在java.nio.的包下

    NIO3大核心

    Channel(通道),Buffer(缓冲区),Selector(选择器)

    在NIO里 selector 与 channel交互,channel与buffer交互,channel 是一个通道可以创建读写,buffer与客户端进行交互的一个场景。 通过buffer就可以实现非阻塞。一个selector 可以管理多个通道。

    NIO面向缓冲或者面向块编程,数据读取到缓冲区,需要时在缓冲区去读取,从而实现同步非阻塞。

    javaNio是非阻塞,如果没有数据可用的时候,就什么也不会获取,而不是保持阻塞的状态。

    selector 当这个通道里没有消息执行的时候,就会去执行其他通道的消息,当其他通道也没有消息的时候,那么他就去执行其他任务。因此线程就是不会阻塞。

    http2.0之后支持一个服务器去服务多个浏览器,做到一个连接 处理多个并发请求的情况。

    selector 对应多个channel ,一个selector对应一个线程,一个channel 对应一个缓存buffer。

    selector的切换根据channel通道的事件进行切换,(Event) ,selector 根据不同的事件在各个通道上切换。

    buffer就是一个内存块, 底层是有一个数组的,在nio里数据的读取,数据的读写通过buffer和bio的io输入 输出是有去别的。但nio的buffer 是支持双向流动可以读也可以写的,需要bufffer.flip()方法来切换。其中channel 也是双向的非阻塞的。 channel 可以反应底层操作系统。其中linux的底层操作系统就是双向的。

    buffer与channel是双向的,那NIO的是只能读或者只能写。 而且读写的时候必须经过buffer。

    buffer 代码实现

    public static void main(String[] args) {
    // 创建一个缓存 支持存放3个 int
    IntBuffer bufffer = IntBuffer.allocate(3);
    //allocate.capacity() 容量
    for (int i = 0; i < bufffer.capacity(); i++) {
    bufffer.put(i*2);
    }
    // 读取数据 读写切换 当读或者写的时候 要用这个切换一下
    bufffer.flip();
    while (bufffer.hasRemaining()){
    int i = bufffer.get();
    System.out.println(i);
    }
    }

    缓冲区基本介绍

    缓冲区本质上是一个读写内存的块,可以理解成一个数组容器,能够对数据进行操控 。

    buffer底层

    ctrl+h 显示子类和方法

    ctrl+n 查找所有

    buffer 是一个抽象类,他的子类有IntBuffer,stringBuffer ,Charbuffer,LongBuffer,Bytebuffer

    等,网络传输都是用byteBuffer,底层都有hb数组

    buffer 定义的4下面四个属性是提供相关的数据元素的信息。

    Capacity 一个容量 在缓冲区创建后 不可以改变

    limit 是一个可以改变不能对缓冲区的极限进行读写

    Postiion 就是当前的位置,下一个要被读或者写要发生变化的数据 ,为其做准备。

    Mark 是一个标记

    Channel 通道

    channel是一个接口,然后channel是可以对数据进行读写的,而bio只能单向的,其中channel的主要实现类有

    FileChannel ,DatagramChannel,ServersocketChannel,SocketChannel 。

    FileChannel :用于文件的数据读写

    DatagramChannel :用于UDP

    ServersocketChannel、SocketChannel :做网络的TCP,ServersocketChannel 类似ServerSocket ,SocketChannel 类似于Socket

    当连接进入的时候 先去寻找主线程,ServerSocketChannel 会生成一个SocketChannel


    FileChannel demon

    这里面 在缓冲区写入管道后要有一个缓存buffer.flip() 进行反转;

     public static void main(String[] args) throws Exception{
            // 一个汉字3个字节
            String str ="jhha";
            // 创建输出流管道
            FileOutputStream fileOutputStream = new FileOutputStream("D://111.txt");
            // 得到 Channel
            FileChannel channel = fileOutputStream.getChannel();
    
    
            //创建 缓存buffer
            ByteBuffer allocate = ByteBuffer.allocate(1024);
            // 将数据加入缓存中
            allocate.put(str.getBytes());
            // 反转
            allocate.flip();
            // 写入管道
            channel.write(allocate);
            fileOutputStream.close();
     }
    

    读取文件流

       public static void main(String[] args) throws IOException {
        File  file = new File("D://111.txt");
    
        FileInputStream fileInputStream = new FileInputStream(file);
    
        FileChannel channel = fileInputStream.getChannel();
    	
        ByteBuffer byteBuffer =ByteBuffer.allocate((int) file.length());
    
        int read = channel.read(byteBuffer);
    
        // 把byte buffer 字节转换数组
        System.out.println(new String(byteBuffer.array()));
    
        fileInputStream.close();
    }
    

    BIO拷贝文件

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

    FileInputStream fileInputStream = new FileInputStream("1.txt");
    FileChannel channel = fileInputStream.getChannel();
    FileOutputStream fileOutputStream = new FileOutputStream("2.txt");
    FileChannel channel1 = fileOutputStream.getChannel();
    
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    while (true){
        //先复位
        buffer.clear();
        int read = channel.read(buffer);
        if (read == -1) break;
        buffer.flip();
        channel1.write(buffer);
    }
    channel.close();
    channel1.close();
    

    }

    transferFrom实现拷贝功能
    public static void main(String[] args)throws Exception {
    // c创建流
    FileInputStream in = new FileInputStream("D://1.JPG");

        FileOutputStream out = new FileOutputStream("D://A2.JPG");
    
        FileChannel inChannel = in.getChannel();
    
        FileChannel outChannel = out.getChannel();
    
        // 使用transferFrom
        outChannel.transferFrom(inChannel,0,inChannel.size());
    
        // 关闭相关通道和流
        inChannel.close();
        outChannel.close();
        in.close();
        out.close();
    }
    
    buffer与channel

    1 buffer 中 ByteBuffer 放入什么类型,那么取出的时候就要取出什么类型 。 如果不按顺序取出 会发生异常。

    2 可以将buffer 转换成一个只读buffer buffer.asReadOnlyBuffer 类型是HeapReadBuffer 是只读的意思

    ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
    

    3 nio提供了MappedByteBuffer,可以让文件在内存中进行修改。

    4 nio 支持多个buffer 进行Scattering和Gathering 。

    channel 与channel 如何进行通信

    在FileInputStream通过 outChannel.transferFrom(inChannel,0,inChannel.size());

    NIO 之Selector

    selector 基本介绍

    selector 由一个线程创建,实现同步非阻塞的模型,可以管理多个客户端的链接 。

    1 selector 可以管理通道 ,并且检测是否有事件发生 。 也就是多个Channel 发生事件的时候 selector是可以监听到的。

    2 只有在连接通道真正有事件发生啥的时候 才会进行读写,这样提高了性能的使用。

    3 避免了频繁的多线程切换。

    selector 特点

    1 selector 可以处理成千上百的链接

    2 空闲的时候可以做别的事情,哪个io有事件就去处理哪个。

    3 读写操作本身是带有缓冲的操作。,避免了io频繁访问,导致挂起。

    selector类和 方法

    Selector 是一个抽象类

    public abstract class Selector implements Closeable {                          }
    

    常用方法有

    1 open()得到一个选择器 创建一个选择器

    2 select () 这个是调用的阻塞方法,当通道没有事件发生的时候 一直处于阻塞状态,当有一个以上的事件发生则将 selectKey 加入到内部集合队列中,之后在通过内部集合队列去把对应发生的channel管道对应数据取出来。

    3 select(Long time) long time 是加入阻塞的时间,例如传入1秒那么则堵塞一秒钟

    4 selectNow()直接获取当前堵塞的事件

    5 selectedKeys() 查看所有管道的注册的seletorkey

    6 wakeup() 唤醒在阻塞的。

    NIO非阻塞网络编程

    1 selector 注册之后 serverSocketChannel分配一个socketChannel

    2 创建一个管道后同时在selector选择器内注册 交由selector管理

    3 当管道有读写发生的时候selector 会监听到,得到一个selectKey

    4 selector通过selectKey去得到对应管道的数据

    BIO NIO AIO 场景 ?

    bio是一个传统连接数较小,比较固定,在jdk1.4之前都是bio。易于了解

    NIO连接数比较多,弹幕,聊天系统等,连接时间较短。

    aio 连接时间较长,数目较多。

    clear() : 情况buffer 进行复位,如果不复位的话 读取到最后 position 与limit 就想等,那么相等则就无数据 导致read 是 0 ,所以没有机会read =-1 就会导致死循环 。

    代码实现 业务逻辑梳理服务端

    服务端

    1 先创建一个ServerSocketChannel

    2 设置为 非阻塞状态

    3 创建一个selector 选择器

    4注册 serverSocketeChannel 加入到selector 选择器

    5 创建端口号

    6 轮询监听

    7 监听事件 selector阻塞时间判断

    8 创建socket通道

    9注册

    10 设置非阻塞

    客户端

    1 serverSocket 通道

    2 添加链接端口

    3 发送数据

    服务端代码
    @Slf4j
    public class NioServer {
    
    public static void main(String[] args) throws Exception {
        // 创建 ServerSocketChannel
        ServerSocketChannel  serverSocketChannel = 	        ServerSocketChannel.open();
    
        // 创建 一个selector
        Selector selector = Selector.open();
        // 设置非阻塞
        serverSocketChannel.configureBlocking(false);
        // 注册
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
        // 绑定端口号
        serverSocketChannel.socket().bind(new InetSocketAddress(6666));
    
        while (true){
    
            if(selector.select(1000) == 0){
                log.info("服务器等待了一秒,无连接");
                continue;
            }
            // 如果返回大于0 ,那么获取对应管道的key
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            Iterator<SelectionKey> keys = selectionKeys.iterator();
            while (keys.hasNext()){
                SelectionKey key = keys.next();
                // 根据key 对应的通道事件
    
                // 1 有新的客户端链接 产生新的通道 ,分配给客户端
                if( key.isAcceptable()){
                    // 生成一个socketChannel
                    SocketChannel channel = serverSocketChannel.accept();
                    channel.configureBlocking(false);
                    // 关注读事件
                    channel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
                }
    
                if(key.isReadable()){
                     // 得到通道
                    SocketChannel channel =(SocketChannel)key.channel();
                    // 获取当前的buffer
                    ByteBuffer byteBuffer =(ByteBuffer) key.attachment();
                    // 读入 buffer
                    channel.read(byteBuffer);
                    log.info("客户端读入数据"+new String(byteBuffer.array()));
                }
                //移除当前key
                keys.remove();
            }
    
        }
      }
    }
    
    客户端代码
    public class NIOClient {
    
    public static void main(String[] args) throws Exception {
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);
        InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
    
        if(!socketChannel.connect(inetSocketAddress)){
            while (!socketChannel.finishConnect()){
                System.out.println("等待链接中");
            }
        }
    
        String str = "您好!";
        ByteBuffer wrap = ByteBuffer.wrap(str.getBytes());
    
        int write = socketChannel.write(wrap);
        //让代码停在这里
        System.in.read();
    }
    }
    
  • 相关阅读:
    暗影精灵3安装无线网卡驱动(ubuntu16.04)
    装饰器之基本
    pyhton代码规范
    2.线程
    文件拾遗
    闭包函数
    6.文件基本操作
    1.socket网络编程
    9.异常处理
    Python语言规范
  • 原文地址:https://www.cnblogs.com/chianw877466657/p/12733823.html
Copyright © 2011-2022 走看看