NETTY入门

前言

业精于勤,荒于嬉;行成于思,毁于随。方今圣贤相逢,治具毕张。拔去凶邪,登崇畯良。占小善者率以录,名一艺者无不庸。爬罗剔抉,刮垢磨光。盖有幸而获选,孰云多而不扬?诸生业患不能精,无患有司之不明;行患不能成,无患有司之不公
我们都知道,为了实现高性能的通信服务器,BIO在高并发的情况下会出现性能急剧下降的问题,甚至会由于创建过多线程而导致系统OOM。因此在Java业界,BIO的性能问题一直被开发者所诟病,所幸的是,JDK1.4推出了NIO,NIO基本解决了BIO的性能问题,是目前实现Java高性能服务器的基础框架。NIO官方的叫法叫做New IO,而对应于操作系统层面来说其实也是Non-Blocking IO。
大名鼎鼎的Netty就是NIO框架,而目前很多开源框架比如Dubbo,RocketMQ,Seata,Spark,Flink都是采用Netty作为基础通信组件。因此,学好Netty很重要,但是NIO作为Netty的基础,这里想说的是学好NIO也一样重要!
学好NIO,那么必须先理解操作系统层面的5种网络IO模型。

I/O模型

五种I/O模型 同步阻塞(BIO)、同步非阻塞、IO复用(NIO)、信号驱动、异步IO(AIO)

1.同步阻塞(BIO)

进程发起IO系统调用后,进程被阻塞,转到内核空间处理,整个IO处理完毕后返回进程,操作成功则进程获取到数据。
在这里插入图片描述
可以看到在整个IO过程中线程大部分都是在等待数据状态,造成资源浪费。

当大量的请求连接服务器端时服务端时,服务端也会创建大量线程来处理请求,服务端压力会剧增,也可能会因为创建的线程过多出现OOM。
上个代码感受一下

public static void main(String[] args) throws Exception{
        //使用线程池机制优化
        ExecutorService threadPool = Executors.newFixedThreadPool(20);
        ServerSocket serverSocket = new ServerSocket(9989);
        while (true){
            //阻塞 scoket一直处于监听状态
            Socket socket = serverSocket.accept();
            System.out.println("得到一个客户端链接 地址为"+socket.getLocalAddress());
            threadPool.submit(()->{
                try {
                    handle(socket);
                } catch (Exception e) {
                    e.printStackTrace();
                }finally {
                    try {
                        socket.close();
                        System.out.println("客户端链接 已关闭");
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
        }
    }


    public static void handle(Socket socket)throws Exception{
        InputStream inputStream = socket.getInputStream();
        byte[] b = new byte[1024];
        int i = 0;
        // read  阻塞 等待客户端发消息
        while ((i = inputStream.read(b)) != -1){
            System.out.println(new String(b,0,i));
        }
    }

2.同步非阻塞

和上面的阻塞IO模型相比,非阻塞IO模型在内核数据没准备好,需要进程阻塞的时候,就返回一个错误,以使得进程不被阻塞。
● 进程发起IO系统调用后,如果内核缓冲区没有数据,需要到IO设备中读取,进程返回一个错误而不会被阻塞。
● 进程发起IO系统调用后,如果内核缓冲区有数据,内核就会把数据返回进程。
这种工作方式下需要不断轮询查看状态
在这里插入图片描述

3.I/O复用模型(NIO)

多个的进程的IO可以注册到一个复用器(selector)上,然后用一个线程调用该selector,selector会监听所有注册进来的IO。
如果selector监听的IO在内核缓冲区都没有可读数据,selector调用线程会被阻塞;而当任一IO在内核缓冲区中有可读数据时,selector调用就会返回;而后selector调用进程可以自己或通知另外的线程(注册线程)来再次发起读取IO,读取内核中准备好的数据。
Linux中IO复用的实现方式主要有Select,Poll和Epoll:
Select:注册IO、阻塞扫描,监听的IO最大连接数不能多于FD_ SIZE(1024)。
Poll:原理和Select相似,没有数量限制,但IO数量大,扫描线性性能下降。
Epoll :事件驱动不阻塞,mmap实现内核与用户空间的消息传递,数量很大,Linux2.6后内核支持。
Netty 就是基于NIO开发的
上代码

public static void main(String[] args) throws Exception{
        //NIO三大组件 建立一个数据通道channel
        ServerSocketChannel serverSocketChannel
                = ServerSocketChannel.open();
        //NIO三大组件  构建选择器selector
        Selector selector = Selector.open();
        //绑定服务端端口号
        serverSocketChannel.bind(new InetSocketAddress(9989));
        //开启非阻塞模式
        serverSocketChannel.configureBlocking(false);
        //将channel 注册到selector 上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
        //死循环监听 selector 事件 当有事件发生再处理,避免阻塞
        while (selector.select() > 0){
            Iterator<SelectionKey> selectedKeys =selector.selectedKeys().iterator();
            while (selectedKeys.hasNext()){
                SelectionKey key = selectedKeys.next();
                if (key.isAcceptable()){//有客户端链接 处理连接事件
                    System.out.println("get 一个客户端");
                    // 若选择键的  IO 事件是“连接就绪”事件,就获取客户端连接
                    SocketChannel socket =serverSocketChannel.accept();
                    // 将新连接切换为非阻塞模式
                    socket.configureBlocking(false);
                    //将该新连接的通道的可读事件,注册到选择器上
                    socket.register(selector, SelectionKey.OP_READ);
                }else if (key.isReadable()){ //当有读事件时触发
                    // 若选择键的  IO 事件是“可读”事件, 读取数据
                    SocketChannel socket =(SocketChannel) key.channel();
                    // 读取数据,然后丢弃
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    int length = 0;
                    while ((length =socket.read(byteBuffer)) >0)
                    {
                        byteBuffer.flip();
                        System.out.println(new String(byteBuffer.array(), 0, length));
                        byteBuffer.clear();
                    }

                    // 将新连接切换为非阻塞模式
                    socket.configureBlocking(false);
                    //将该新连接的通道的可读事件,注册到选择器上
                    socket.register(selector, SelectionKey.OP_READ);
                }else if (key.isWritable()){
                    System.out.println("isWritable");
                }else if (key.isValid()){
                    System.out.println("isValid");
                }
                selectedKeys.remove();
            }
        }
    }

4.信号阻塞式IO

进程发起一个IO操作,会向内核注册一个信号处理函数,然后进程返回不阻塞;当内核数据就绪时会发送一个信号给进程,进程便在信号处理函数中调用IO读取数据。
在这里插入图片描述

5.异步IO(AIO)

当进程发起一个IO操作,进程返回(不阻塞),但也不能返回结果。内核把整个IO处理完后,会通知进程结果,如果IO操作成功则进程直接获取到数据。
在这里插入图片描述

可以看到异步 IO 同样使用了信号机制, 但与 Signal Driven IO 不同, 前者属于同步 IO, 从内核空间拷贝数据到用户空间需要应用进程调用 recvfrom() 实现, 而在异步 IO 中所有的一切都是由内核独立完成

总结

在“发出IO请求”到收到“IO完成”的这段时间里,同步IO模型下,主线程只能挂起,但异步IO模型下,主线程并没有休息,而是继续处理其他消息。这样,在异步IO模型下,一个线程就可以同时处理多个IO请求,并且没有切换线程的操作。对于大多数IO密集型的应用程序,使用异步IO将大大提升系统的多任务处理能力。

NIO

netty 是根据NIO开发出的一套网络编程框架,因此我们详细讲一下NIO模型。NIO中有三大核心组件,选择器selector、通道 channel、缓冲区bytebuffer

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值