BIO、NIO、Netty演化总结

关于BIO(关于Java NIO的的思考-CSDN博客)和NIO(关于Java NIO的的思考-CSDN博客)在之前的博客里面已经有详细的讲解,这里再总结一下最近学习netty源码的的心得体会

在之前的NIO博客中我们知道接受客户端连接和IO事件的线程是同一个线程,那么就会存在一个问题,就是如果某一个socket连接在读完数据之后,写数据之前,有比较耗时的逻辑,在这个逻辑执行完成之前,都会导致线程无法继续接受客户端请求以及处理其他socket的io事件,那么就会导致整个应用程序性能下降,于是我们可以做进一步的优化,把接受客户端请求和读写时间的处理分开为两组线程来处理,彼此不会相互影响,通常接受客户端的请求时间是一个很快的操作,这样就可以提高应用程序的连接数量,将之前NIO的代码进步一改进如下:

public class AsyncNonBlockingServerWithThreadPool {
    private Selector selector;
    private ServerSocketChannel serverChannel;
    private ByteBuffer buffer;
    private ExecutorService executorService;

    public AsyncNonBlockingServerWithThreadPool(int port) throws IOException {
        // 创建选择器和服务器通道
        selector = Selector.open();
        serverChannel = ServerSocketChannel.open();
        serverChannel.bind(new InetSocketAddress(port));
        serverChannel.configureBlocking(false);
        // 注册服务器通道到选择器,并注册接收连接事件
        serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        buffer = ByteBuffer.allocate(1024);
        // 创建线程池,用于处理事件
        executorService = Executors.newFixedThreadPool(10);
    }

    public void start() throws IOException {
        System.out.println("Server started.");
        while (true) {
            // 阻塞等待事件发生
            selector.select();
            // 处理事件
            Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
            while (keyIterator.hasNext()) {
                SelectionKey key = keyIterator.next();
                keyIterator.remove();
                if (key.isAcceptable()) {
                    // 接收连接事件
                    handleAccept(key);
                } else if (key.isReadable()) {
                    // 可读事件
                    handleRead(key);
                }
            }
        }
    }

    private void handleAccept(SelectionKey key) throws IOException {
        ServerSocketChannel serverChannel = (ServerSocketChannel) key.channel();
        SocketChannel clientChannel = serverChannel.accept();
        clientChannel.configureBlocking(false);
        clientChannel.register(selector, SelectionKey.OP_READ);
        System.out.println("New client connected: " + clientChannel.getRemoteAddress());
    }

    private void handleRead(SelectionKey key) throws IOException {
        // 将事件处理的代码提交到线程池中
        executorService.submit(() -> {
            SocketChannel clientChannel = (SocketChannel) key.channel();
            buffer.clear();
            int bytesRead = 0;
            try {
                bytesRead = clientChannel.read(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
            if (bytesRead == -1) {
                // 客户端关闭连接
                key.cancel();
                try {
                    clientChannel.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
                try {
                    System.out.println("Client disconnected: " + clientChannel.getRemoteAddress());
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return;
            }
            buffer.flip();
            byte[] data = new byte[buffer.remaining()];
            buffer.get(data);
            System.out.println("Received message from client: " + new String(data));
        });
    }

    public static void main(String[] args) {
        try {
            AsyncNonBlockingServerWithThreadPool server = new AsyncNonBlockingServerWithThreadPool(8080);
            server.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

这里可以看到在读取完数据之后,将数据交给另外一个线程池去执行其他的业务逻辑,这样就不会影响主线程接受客户端的请求了,这个代码也是一个最简单的Reactor模式的实现,甚至可以说是一个简单的Netty实现(雏形),也就是下面这张图:mainreactor负责接收客户端连接,然后将连接交给subreactor作进一步的数据读写操作

 这个模型也是实现netty的基石,只是netty在次基础上做了很多进一步的优化,我们来看看一个简单的netty实现的程序:

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建 BossGroup 和 WorkerGroup
        // 说明
        // 1.创建两个线程组 BossGroup 和 WorkerGroup
        // 2. BossGroup 只是处理连接请求,真正的和客户端业务处理,会交给 WorkerGroup 完成
        // 3. 两个都是无线循环
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            // 创建服务器端的启动对象,配置启动参数
            ServerBootstrap bootstrap = new ServerBootstrap();
            // 使用链式编程进行设置
            bootstrap.group(bossGroup, workerGroup) // 设置两个线程组
                    .channel(NioServerSocketChannel.class) // 使用 ioServerSocketChannel 作为服务器通道实现
                    .option(ChannelOption.SO_BACKLOG, 128) // 设置线程队列等待连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)  // 设置保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {// 创建一个通道初始化对象(匿名对象)
                        // 给 pipeline 设置处理器
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            socketChannel.pipeline().addLast(new NettyServerHandler());
                        }
                    });  // 给我们的WorkerGroup 的 EventLoop 对应的管道设置处理器
            System.out.println(".....服务器 is ready.....");

            // 绑定一个端口,并且同步,生成一个ChannelFuture对象
            // 启动服务器
            ChannelFuture cf = bootstrap.bind(6668).sync();
// 给 cf 注册监听器,监控我们关心的事件
            cf.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture future) throws Exception {
                    if (cf.isSuccess()) {
                        System.out.println("监听端口 6668 成功");
                    } else {
                        System.out.println("监听端口 6668 失败");
                    }
                }
            });
            // 对关闭通道进行监听
            cf.channel().closeFuture().sync();
        }finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

其中bossGroup就是mainreactor,也就是负责接收客户端的连接的,workerGroup就是subreactor,也就是专门负责读写数据的。

netty的源码很多很长,但是我们要有一个清晰的认知:

1、netty是基于Jdk原生的NIO来实现的(封装了原生NIO),并不是重新实现了一套IO框架

2、NIO网络编程关键的三个组件:

      多路复用器(selector):负责挑选出就绪的事件对应的channe

      通道(channel):包括服务端的Serversocketchannel和客户端的socketchannel

      缓冲区(bytebuff):读写数据,socket数据的都是通过channe向缓冲区读写

netty相比于原生NIO的优势:

1、采用reactor模型,所有的操作均采用事件通知机制来实现(包括连接和读写数据,基于jdk的future之上做了封装,运用观察者模式和装饰器模式,避免future.get带来的程序阻塞)

2、缓冲区的操作不需要手动反转,并且在申请内存时会根据上一次申请的内存大小动态调整

3、在从线程池中挑选线程处理任务时的算法优化

4、一个EventLoop(可以理解为其实就是一个线程),可以绑定多个socketchannel,也就是一个线程可以同时处理多个连接的数据的读写

5、一个socketchannel只会与一个EventLoop绑定,一个EventLoop独立拥有一个selector,这样就避免了多线程之间的竞争

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/384843.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Vulnhub靶机:hacksudo-ProximaCentauri

一、介绍 运行环境&#xff1a;Virtualbox 攻击机&#xff1a;kali&#xff08;10.0.2.15&#xff09; 靶机&#xff1a;hacksudo-ProximaCentauri&#xff08;10.0.2.51&#xff09; 目标&#xff1a;获取靶机root权限和flag 靶机下载地址&#xff1a;https://www.vulnhu…

算法学习——LeetCode力扣二叉树篇4

算法学习——LeetCode力扣二叉树篇4 222. 完全二叉树的节点个数 222. 完全二叉树的节点个数 - 力扣&#xff08;LeetCode&#xff09; 描述 给你一棵 完全二叉树 的根节点 root &#xff0c;求出该树的节点个数。 完全二叉树 的定义如下&#xff1a;在完全二叉树中&#xf…

二叉树、堆和堆排序(优先队列)

前言&#xff1a; 本章会讲解二叉树及其一些相关练习题&#xff0c;和堆是什么。 二叉树&#xff1a; 二叉树的一些概念&#xff1a; 一棵二叉树是有限节点的集合&#xff0c;该集合可能为空。二叉树的特点是每一个节点最多有两个子树&#xff0c;即二叉树不存在度大于2的节点…

中科大计网学习记录笔记(十):P2P 应用

前言&#xff1a; 学习视频&#xff1a;中科大郑烇、杨坚全套《计算机网络&#xff08;自顶向下方法 第7版&#xff0c;James F.Kurose&#xff0c;Keith W.Ross&#xff09;》课程 该视频是B站非常著名的计网学习视频&#xff0c;但相信很多朋友和我一样在听完前面的部分发现信…

全坚固平板EM-I12U,全新升级后的优质体验

平板终端机在户外勘探、制造业、畜牧业、银行金融行业当中都不是陌生的&#xff0c;能采集各种数据来转换成信息流向企业和行业的各个分支当中&#xff0c;在整个行业发展、社会推动上面都起着关键性作用&#xff0c;而平板终端机的升级也就意味着未来的这些行业发展会进入一个…

【51单片机】LED点阵屏(江科大)

9.1LED点阵屏 1.LED点阵屏介绍 LED点阵屏由若干个独立的LED组成,LED以矩阵的形式排列,以灯珠亮灭来显示文字、图片、视频等。 2.LED点阵屏工作原理 LED点阵屏的结构类似于数码管,只不过是数码管把每一列的像素以“8”字型排列而已。原理图如下 每一行的阳极连在一起,每一列…

C++ //练习 6.3 编写你自己的fact函数,上机检查是否正确。

C Primer&#xff08;第5版&#xff09; 练习 6.3 练习 6.3 编写你自己的fact函数&#xff0c;上机检查是否正确。 环境&#xff1a;Linux Ubuntu&#xff08;云服务器&#xff09; 工具&#xff1a;vim 代码块 /********************************************************…

VMware虚拟机安装openEuler系统(二)(2024)

下面我们进行openEuler系统的一些简单配置。 1. 开启openEuler系统 在VMware Workstation Pro虚拟机软件中找到安装好的openEuler操作系统虚拟机并开启。 等待开启。 2. 安装配置 进入后选择第一个“Install openEuler 20.03-LTS”。 3. 选择系统语言 为虚拟机设置系统语言…

Unity学习笔记(零基础到就业)|Chapter02:C#基础

Unity学习笔记&#xff08;零基础到就业&#xff09;&#xff5c;Chapter02:C#基础 前言一、复杂数据&#xff08;变量&#xff09;类型part01&#xff1a;枚举数组1.特点2.枚举&#xff08;1&#xff09;基本概念&#xff08;2&#xff09;申明枚举变量&#xff08;3&#xff…

C++多态重难点

CSDN上已经有很多关于C多态方面的一些系统介绍了&#xff0c;但是我看了一下一些有关于多态问题的细节问题文章较少&#xff0c;因此我想要出一片文章重点讲一讲我认为比较重点且容易被遗忘的知识点&#xff0c;一些比较基本的知识这里就不过多赘述了&#xff0c;可以参考其他优…

LabVIEW智能温度监控系统

LabVIEW智能温度监控系统 介绍了一个基于LabVIEW的智能温度监控系统&#xff0c;实现对工业环境中温度的实时监控与调控。通过集成传感器技术和LabVIEW软件平台&#xff0c;系统能够自动检测环境温度&#xff0c;及时响应温度变化&#xff0c;并通过图形用户界面(GUI)为用户提…

【51单片机】AT24C02(江科大、爱上半导体)

一、AT24C02 1.AT24C02介绍 AT24C02是一种可以实现掉电不丢失的存储器,可用于保存单片机运行时想要永久保存的数据信息 存储介质:E2PROM 通讯接口:12C总线 容量:256字节 2.引脚即应用电路 本开发板AT24C02原理图 12C地址全接地,即全为0 WE接地,没有写使能 SCL接P21 S…

Obsidian Publish的开源替代品Perlite

前几天就有网友跟我说&#xff0c;freenom 的免费域名不可用了&#xff0c;10 号的时候老苏进后台看了一下&#xff0c;还有一半的域名显示为 ACTIVE&#xff0c;似乎是以 2024年6月 为限。但到 11 号&#xff0c;老苏发现博客 (https://laosu.cf) 已经访问不了了&#xff0c;这…

使用SpringMVC实现功能

目录 一、计算器 1、前端页面 2、服务器处理请求 3、效果 二、用户登陆系统 1、前端页面 &#xff08;1&#xff09;登陆页面 &#xff08;2&#xff09;欢迎页面 2、前端页面发送请求--服务器处理请求 3、效果 三、留言板 1、前端页面 2、前端页面发送请求 &…

大话设计模式——1.模板方法模式(Template Method Pattern)

定义&#xff1a;定义一个操作中的算法的骨架&#xff0c;而将一些步骤延迟到子类中。模板方法使得子类可以不改变一个算法的结构即可重定义该算法的某些特定步骤 例子&#xff1a;比较重大的考试往往有A、B两套试卷&#xff0c;其中一套出现问题可以立马更换另一套。 定义基…

spring aop @annotation的用法

直接看原文: spring aop annotation的用法-CSDN博客 -------------------------------------------------------------------------------------------------------------------------------- annotation用在定义连接点时&#xff0c;对连接点进行限制。比如我们想对标注了…

百度PaddleOCR字符识别推理部署(C++)

1 环境 1.opencv&#xff08;https://sourceforge.net/projects/opencvlibrary/&#xff09; 2.cmake&#xff08;https://cmake.org/download/&#xff09; 3.vs2019&#xff08;(https://github.com/PaddlePaddle/PaddleOCR/tree/release/2.1) 4.paddleOCR项目-建议2.0(http…

Python网络通信

目录 基本的网络知识 TCP/IP IP地址 端口 HTTP/HTTPS HTTP HTTPS 搭建自己的Web服务器 urllib.request模块 发送GET请求 发送POST请求 JSON数据 JSON文档的结构 JSON数据的解码 下载图片示例 返回所有备忘录信息 此文章讲解如何通过Python访问互联网上的资源&a…

每日一练:LeeCode-617、合并二叉树【二叉树+DFS】

本文是力扣LeeCode-617、合并二叉树【二叉树DFS】 学习与理解过程&#xff0c;本文仅做学习之用&#xff0c;对本题感兴趣的小伙伴可以出门左拐LeeCode。 给你两棵二叉树&#xff1a; root1 和 root2 。 想象一下&#xff0c;当你将其中一棵覆盖到另一棵之上时&#xff0c;两…

15 ABC基于状态机的按键消抖原理与状态转移图

1. 基于状态机的按键消抖 1.1 什么是按键&#xff1f; 从按键结构图10-1可知&#xff0c;按键按下时&#xff0c;接点&#xff08;端子&#xff09;与导线接通&#xff0c;松开时&#xff0c;由于弹簧的反作用力&#xff0c;接点&#xff08;端子&#xff09;与导线断开。 从…