网络编程的无冕之王-Netty入门和核心组件介绍

news2024/11/18 15:40:22

最近我在研究Netty,之前只是经常听说,并没有实际做过研究,为什么突然要好好研究一下它,主要是因为前段时间,我在看RocketMQ底层原理的时候发现它的底层的网络通信都是基于Netty,然后网上一查,果然,大家太多的耳熟能详的工具组件,都是基于Netty做的开发。大家看看:
在这里插入图片描述
可以看到太多的分布式或者微服务组件都是基于它,因为分布式/微服务的根基在于网络编程,而Netty就是一款非常成熟的网络编程工具。所以它在网络编程学习中不可避免的学习目标。

接下来我会用几篇文章为大家分享一下我最近的学习成果,给大家做了很好的总结,希望能够给大家带来帮助。

1. 概述

1.1 概念

Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。

Netty框架是基于Java原生NIO技术的进一步封装,对Java-NIO技术做了进一步增强,充分结合了Reactor线程模型,将Netty变为了一个基于异步事件驱动的网络框架。

Netty至今共发布了五个大版本,目前最常用的并非是最新的5.x系列,而是4.x系列的版本,原因是Netty本身就是基于Java-NIO封装的,而JDK本身又很稳定,再加上5.x版本并未有太大的性能差异,因此4.x系列才是主流。

1.2 应用场景

  • 互联网行业:在分布式系统中,各个节点之间需要远程服务调用,高性能的 RPC 框架必不可少,Netty 作为异步高性能的通信框架,往往作为基础通信组件被这些 RPC 框架使用。典型的有:阿里的Dubbo,Rocketmq底层也是用的Netty作为基础通信组件。

  • 游戏行业:无论是手游服务端还是大型的网络游戏,Java 语言得到了越来越广泛的应用。Netty 作为高性能的基础通信组件,它本身提供了 TCP/UDP 和 HTTP 协议栈。

  • 大数据领域:经典的Hadoop 的高性能通信,默认采用 Netty 进行跨界点通信,它的Netty Service 基于Netty 框架二次封装实现。

1.3 入门实操

说千遍还不如让我们一起来动手感受一遍,现在就直接先实操一番快速入门。

1 引入依赖

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.43.Final</version>
</dependency>

2 服务端

然后先创建NettyServer服务端,代码如下:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.util.CharsetUtil;

public class NettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建两个EventLoopGroup,boss:处理连接事件,worker处理I/O事件
        EventLoopGroup boss = new NioEventLoopGroup();
        EventLoopGroup worker = new NioEventLoopGroup();
        // 创建一个ServerBootstrap服务端(同之前的ServerSocket类似)
        ServerBootstrap server = new ServerBootstrap();
        try {
            // 将前面创建的两个EventLoopGroup绑定在server上
            server.group(boss,worker)
                    // 指定服务端的通道为Nio类型
                    .channel(NioServerSocketChannel.class)
                    // 为到来的客户端Socket添加处理器
                    .childHandler(new ChannelInitializer<NioSocketChannel>() {
                        // 这个只会执行一次(主要是用于添加更多的处理器)
                        @Override
                        protected void initChannel(NioSocketChannel ch) {
                            // 添加一个字符解码处理器:对客户端的数据解码
                            ch.pipeline().addLast(
                                new StringDecoder(CharsetUtil.UTF_8));
                            // 添加一个入站处理器,对收到的数据进行处理
                            ch.pipeline().addLast(
                                new SimpleChannelInboundHandler<String>() {
                                // 读取事件的回调方法
                                @Override
                                protected void channelRead0(ChannelHandlerContext
                                    ctx, String msg) {
                                    System.out.println("收到客户端信息:" + msg);
                                }
                            });
                        }
                    });
            // 为当前服务端绑定IP与端口地址(sync是同步阻塞至连接成功为止)
            ChannelFuture cf = server.bind("127.0.0.1",8888).sync();
            // 关闭服务端的方法(之后不会在这里关闭)
            cf.channel().closeFuture().sync();
        }finally {
            // 优雅停止之前创建的两个Group
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }
    }
}

3 客户端

再构建一个NettyClient客户端,代码如下:

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

public class NettyClient {
    public static void main(String[] args) {
        // 由于无需处理连接事件,所以只需要创建一个EventLoopGroup
        EventLoopGroup worker = new NioEventLoopGroup();
        // 创建一个客户端(同之前的Socket、SocketChannel)
        Bootstrap client = new Bootstrap();
        try {
            client.group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel sc)
                            throws Exception {
                            // 添加一个编码处理器,对数据编码为UTF-8格式
                            sc.pipeline().addLast(new
                                    StringEncoder(CharsetUtil.UTF_8));
                        }
                    });
            // 与指定的地址建立连接
            ChannelFuture cf = client.connect("127.0.0.1", 8888).sync();
            // 建立连接成功后,向服务端发送数据
            System.out.println("正在向服务端发送信息......");
            cf.channel().writeAndFlush("我是技术闲聊DD!");
        } catch (Exception e){
          e.printStackTrace();
        } finally {
            worker.shutdownGracefully();
        }
    }
}

4 启动运行

我们先启动服务端,再启动客户端,就会看到如下:
在这里插入图片描述
在这里插入图片描述
上面的案例,就是利用了Netty实现了简单的对端通信,实现的功能很简单。

5 代码流程

接下来我为大家解释一下Netty里面的一些核心概念。

  • EventLoopGroup:负责管理Channel的事件处理任务。管理多个EventLoop的组件。它为网络应用程序提供了更好的并发支持和处理请求的能力
  • ServerBootstrap/Bootstrap:意思是引导,一个 Netty 应用通常由一个 Bootstrap 开始,主要作用是配置整个 Netty 程序,串联各个组件,Netty 中 Bootstrap 类是客户端程序的启动引导类,ServerBootstrap 是服务端启动引导类。
  • childHandler:可以理解成过滤器,在我们以前学习的Servlet编程中,新请求到来都会经过一个个的过滤器,而这个处理器也类似于之前的过滤器,新连接到来时,也会经过添加好的一系列处理器。

然后我为大家把上面案例的完整流程分析一下:

  1. 先创建两个EventLoopGroup事件组,然后创建一个ServerBootstrap服务端。
  2. 将创建的两个事件组bossworker绑定在服务端上,并指定服务端通道为NIO类型
  3. 在server上添加处理器,对新到来的Socket连接进行处理,在这里主要分为两类:
    • ChannelInitializer:连接到来时执行,主要是用于添加更多的处理器(只触发一次)
    • addLast():通过该方式添加的处理器不会立马执行,而是根据处理器类型择机执行
  4. 为创建好的服务端绑定IP及端口号,调用sync()意思是阻塞至绑定成功为止。
  5. 再创建一个EventLoopGroup事件组,并创建一个Bootstrap客户端。
  6. 将事件组绑定在客户端上,由于无需处理连接事件,所以只需要一个事件组。
  7. 指定Channel通道类型为NIO、添加处理器…(同服务端类似)
  8. 与前面服务端绑定的地址建立连接,由于默认是异步的,也要调用sync()阻塞。
  9. 建立连接后,客户端将数据写入到通道准备发送,首先会先经过添加好的编码处理器,将数据的格式设为UTF-8。
  10. 服务器收到数据后,会先经过解码处理器,然后再去到入站处理,执行对应的Read()方法逻辑。
  11. 客户端完成数据发送后,先关闭通道,再优雅关闭创建好的事件组。
  12. 同理,服务端工作完成后,先关闭通道再停止事件组。

大家可以先看完我上面写的流程,然后再回到案例代码上去看,就会感觉到比较清晰。
注意:Netty大部分操作都是异步的,比如地址绑定、客户端连接。就类似于调用connect()方法与服务端建立连接时,主线程自己并不会去执行这个动作,而是会把这个工作交给事件组中的线程去完成,所以此刻如果主线程直接去向通道中写入数据,有几率会出现报错,在实际生产环境中,可能由于网络延迟导致连接建立的时间有些长,此时通道并未建立成功,因此尝试发送数据时就会有问题。

2. 核心组件 - 启动器与事件组

2.1 启动器 ServerBootstrap和Bootstrap

首先我们先看一下ServerBootstrap及Bootstrap的类继承结构图:
在这里插入图片描述
我们可以看一个表格,就会对其有所了解。
在这里插入图片描述

2.2 事件组EventLoopGroupEventLoop

EventLoopGroup 是一组 EventLoop 的集合,可以理解为 EventLoop 的管理器。其作用是负责创建和管理一组 EventLoop 对象,其中每个 EventLoop 对象都对应一个线程,用于处理网络 I/O 事件(例如读写数据、连接建立、连接关闭等)。在 Netty 中,通常会使用两种不同的 EventLoopGroupBossEventLoopGroupWorkerEventLoopGroup。其中 BossEventLoopGroup 负责处理连接建立事件,而 WorkerEventLoopGroup 负责处理连接已经建立后的数据读写事件。这样可以将连接建立处理与数据读写处理分开,提高并发性能。

EventLoop 则是一个重要的核心组件,用于处理网络 I/O 事件。它负责执行一系列的 I/O 操作,例如读取数据、写入数据、注册感兴趣的事件以及调度用户自定义的任务等。每个 EventLoop 都有一个独立的任务队列和计时器队列,并且运行在一个独立的线程上。同时,每个 EventLoop 还会被绑定到一个 NIO Selector 对象上,用于轮询网络 I/O 事件,处理事件后再将相应的任务加入到任务队列中。

注意:既然EventLoop/EventLoopGroup继承自JDK原生的定时线程池,那也就代表着,它拥有JDK线程池中所有提供的方法,同时也应该会支持执行异步任务、定时任务的功能。

 public static void main(String[] args) {
        EventLoopGroup threadPool = new NioEventLoopGroup();
        // 递交Runnable类型的普通异步任务
        threadPool.execute(()->{
            System.out.println("execute()方法提交的任务....");
        });
        // 递交Callable类型的有返回异步任务
        threadPool.submit(() -> {
            System.out.println("submit()方法提交的任务....");
            return "我是执行结果噢!";
        });
        // 递交Callable类型的延时调度任务
        threadPool.schedule(()->{
            System.out.println("schedule()方法提交的任务,三秒后执行....");
            return "调度执行后我会返回噢!";
        },3, TimeUnit.SECONDS);
        // 递交Runnable类型的延迟间隔调度任务
        threadPool.scheduleAtFixedRate(()->{
            System.out.println("scheduleAtFixedRate()方法提交的任务....");
        },3,1,TimeUnit.SECONDS);
    }

在这里插入图片描述

执行结果如下:
    立即执行:
        execute()方法提交的任务....
        submit()方法提交的任务....
    
    延时三秒后执行:
        schedule()方法提交的任务....
        scheduleAtFixedRate()方法提交的任务....
    
    之后没间隔一秒执行:
        scheduleAtFixedRate()方法提交的任务....
        scheduleAtFixedRate()方法提交的任务....

上述我们创建了一个EventLoopGroup事件循环组,然后通过之前JDK线程池提供的一系列的提交任务的方法,向其递交了几个异步任务,然后运行该程序,显然,EventLoopGroup确实可以当做JDK原生线程池来使用。
还有几个方法:

  • EventLoop.inEventLoop(Thread):判断一个线程是否属于当前EventLoop。
  • EventLoop.parent():判断当前EventLoop属于哪一个事件循环组。
  • EventLoopGroup.next():获取当前事件组中的下一个EventLoop(线程)。

大家还记得在前面的示例代码中,我们定义了两个组,为什么吗?主要是定义两个组的好处在于:可以让Group中的每个EventLoop分工更加明确,不同的Group分别处理不同类型的事件,各司其职。

为服务端绑定了两个事件循环组,也就代表着会根据ServerSocketChannel上触发的不同事件,将对应的工作分发到这两个Group中处理,其中boss主要负责客户端的连接事件,而worker大多数情况下负责处理客户端的IO读写事件。

过程是:当客户端的SocketChannel连接到来时,首先会将这个注册事件的工作交给boss处理,boss会调用worker.register()方法,将这条客户端连接注册到worker工作组中的一个EventLoop上。前面提到过,EventLoop内部会维护一个Selector选择器,因此实际上也就是将客户端通道注册到其内部中的选择器上。

注意:将一个Socket连接注册到一个EventLoop上之后,这个客户端连接则会和这个EventLoop绑定,以后这条通道上发生的所有事件,都会交由这个EventLoop处理。

大家有没有思考一个问题,就是由于EventLoopGroup本质上可以理解成一个线程池,其中存在的线程资源自然是有限的,那如果过来的客户端连接大于线程数量怎么办呢?答案是因为Netty本身是基于Java-NIO封装的,而NIO底层又是基于多路复用模型实现的,天生就能实现一条线程管理多个连接的功能,所以就算连接数大于线程数,也完全可以Hold住。

总结:简单来说可以EventLoop理解成有一条线程专门维护的Selector选择器,而EventLoopGroup则可以理解成一个有序的定时调度线程池,负责管理所有的EventLoop

下一篇会为大家介绍Netty的通道处理器以及缓冲区,希望大家多多关注。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【软件设计师暴击考点】网络安全等杂项高频考点暴击系列

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;软件…

读发布!设计与部署稳定的分布式系统(第2版)笔记11_无限长的结果集

1. 无限长的结果集是导致响应缓慢的常见原因 1.1. 当违反稳态模式时&#xff0c;就可能产生无限长的结果集 1.2. 当调用方允许另一个系统支配调用时&#xff0c;就会出现一个无限长的结果集 2. 数据库突然返回500万行&#xff0c;而不是通常的100多行时会发生什么&#xff1…

密码找回流程绕过测试-业务安全测试实操(20)

密码找回流程绕过测试 测试原理和方法 很多网站的密码找回功能一般有以下几个步骤 (1) 用户输入找回密码的账号: (2) 校验凭证:向用户发送短信验证码或者找回密码链接,用户回填验证码或单击链接进入密码重置页面,以此方式证明当前操作用户是账号主人;(3) 校验成功进入重置密…

重构项目的十大注意事项

文章目录 1. 确认重构的目的和范围2. 建立好重构计划3. 检查重构前的代码4. 测试重构后的代码5. 避免过度重构6. 保持团队成员沟通7. 使用重构工具8. 使用版本控制系统9. 持续监控重构进度10. 不断改进技能 1. 确认重构的目的和范围 在开始重构之前&#xff0c;需要明确重构的…

Spring(五)基于注解的自动装配

注解&#xff1a;和XML配置文件一样&#xff0c;注解本身并不能执行&#xff0c;注解本身仅仅只是做一个标记&#xff0c;具体的功能是框架检测到注解标记的位置&#xff0c;然后针对这个位置按照注解标记的功能来执行具体操作。 本质上&#xff1a;所以一切的操作都是java代码…

K8S调度器之污点和容忍

1. Taint和Toleration 节点亲和性&#xff0c;是pod的一种属性(偏好或硬性要求),它使pod被吸引到一类特定的节点。Taint则相反&#xff0c;它使节点能够排斥一类特定的pod。Taint和Toleration相互配合&#xff0c;可以用来避免pod被分配到不合适的节点上。每个节点上都可以应用…

阿里云ECS服务器vCPU什么意思?

阿里云ECS服务器vCPU和CPU是什么意思&#xff1f;CPU和vCPU有什么区别&#xff1f;一台云服务器ECS实例的CPU选项由CPU物理核心数和每核线程数决定&#xff0c;CPU是中央处理器&#xff0c;一个CPU可以包含若干个物理核&#xff0c;通过超线程HT&#xff08;Hyper-Threading&am…

chatgpt赋能python:Python中如何合并相同key的元素?

Python 中如何合并相同 key 的元素&#xff1f; 在 Python 编程中&#xff0c;很多时候需要对列表或字典进行合并相同 key 的操作&#xff0c;这篇文章将介绍合并相同 key 的方法及应用。 什么是相同 key 合并&#xff1f; 相同 key 合并指的是将具有相同 key 的元素合并为一…

关于防火墙配置长连接的设置

长连接的使用场景 当业务中客户端和服务器长时间无数据交互&#xff0c;空闲时间超过1800秒&#xff0c;会话会因超时被清除。后续客户端没有重新发起连接&#xff0c;直接发送控制报文时导致数据不通。常见于数据库连接。 重点说明 以天为单位的会话超时需要开启长效会话比例…

考研高数考点总结

一.极限 1.函数的四性&#xff1a; 单调性、周期性、奇偶性、有界性&#xff1a; 周期性、奇偶性各记住一个结论。 有界性判定&#xff1a; 1.定义法&#xff1a;-M<绝对值<M2.函数性质&#xff1a;函数在闭区间上连续一定有界 闭区间连续》开区间连续加左端点右极限…

ML算法——最优化|凸优化随笔【机器学习】【端午节创作】

文章目录 数学预备知识1、最优化问题2、凸优化2.1、梯度下降2.2、牛顿法2.3、阻尼牛顿法2.4、拟牛顿法2.5、总结 数学预备知识 1、最优化问题 最优化问题指的是在给定条件下&#xff0c;找到一个目标函数的最优解&#xff0c;即找到能够使目标函数取得最大值或最小值的变量取…

​LeetCode解法汇总1254. 统计封闭岛屿的数目

目录链接&#xff1a; 力扣编程题-解法汇总_分享记录-CSDN博客 GitHub同步刷题项目&#xff1a; https://github.com/September26/java-algorithms 原题链接&#xff1a;力扣 描述&#xff1a; 二维矩阵 grid 由 0 &#xff08;土地&#xff09;和 1 &#xff08;水&#xf…

HuggingFace-RL-Unit2-Part2——初探Q-Learning

初探Q-Learning 文章目录 初探Q-Learning什么是Q-Learning&#xff1f;Q-Learning 算法第一步: 初始化Q-表第二步: 使用epsilon贪心策略选择一个动作第三步: 执行动作At, 得到奖励Rt1和下一个状态St1第四步: 更新Q(St, At) 异策略 vs 同策略Q-Learning算法实例第一步: 初始化Q-…

drone、gogs、docker与项目集成实现自动化部署

目录 前言项目目录结构目录结构测试文件 文件内容Dockerfilerun.shdrone.yml 测试打包部署中查看容器访问项目成功 常见问题Gogs 推送 URL 被解析到默认禁用的本地网络地址1、drone登录没有权限2、cannot ping the remote server3、推送代码以后不能自动clone4、maven编译报错F…

SynchronousQueue源码

介绍 SynchronousQueue作为阻塞队列的时候&#xff0c;对于每一个take的线程会阻塞直到有一个put的线程放入元素为止&#xff0c;反之亦然。在SynchronousQueue内部没有任何存放元素的能力。所以类似peek操作或者迭代器操作也是无效的&#xff0c;元素只能通过put类操作或者ta…

JDBC 和数据库连接池

JDBC 和数据库连接池 1. JDBC 概述 1.1 基本介绍 JDBC为访问不同的数据库提供了统一的接口&#xff0c;为使用者屏蔽了细节问题Java程序员使用JDBC&#xff0c;可以连接任何提供了JDBC驱动程序的数据库系统&#xff0c;从而完成对数据库的各种操作。JDBC的基本原理图[重要!]…

【编译、链接、装载十三】内存中的栈——图解栈的运行过程

【编译、链接、装载十三】内存中的栈——图解栈的运行过程 一、程序的内存布局二、栈1、什么是栈2、寄存器 三、函数执行四、结合汇编——分析函数调用1、demo2、反汇编3、反汇——图解反汇编、栈帧、寄存器 一、程序的内存布局 看看加上动态链接之后进程的地址空间是如何分布…

详解Spring配置文件

⭐作者介绍&#xff1a;大二本科网络工程专业在读&#xff0c;持续学习Java&#xff0c;努力输出优质文章 ⭐作者主页&#xff1a;逐梦苍穹 ⭐所属专栏&#xff1a;JavaEE、Spring Spring配置文件 1、简介2、XML3、\<Bean\>3.1、Bean标签范围配置3.2、生命周期3.3、⭐实例…

word文档批量生成工具(附免费软件)(按Excel表格内容自动替换内容生成文档)

批量生成word文档是让人无比厌恶但有时又不得不做的事情。比如学校要给拟录取的学生发通知书&#xff0c;就可能需要批量生成一批只有“姓名”、“学院”和“专业”不同&#xff0c;其他内容都相同的word文档以供打印&#xff08;事实上直接生成pdf是更好的选择&#xff0c;这个…

chatgpt赋能python:Python如何计算p值?

Python如何计算p值&#xff1f; 在统计学中&#xff0c;p值是估计观察到的结果是由随机因素导致的概率。在Python中&#xff0c;我们可以使用一些统计库来计算p值。 常见的统计库 Python中有很多统计库可以用来计算p值。其中&#xff0c;SciPy是最常用的统计库之一。它包含了…