(未完待续)【Netty专题】Netty实战与核心组件详解

news/2024/10/23 3:40:39/

目录

  • 前言
  • 阅读对象
  • 阅读导航
  • 前置知识
  • 课程内容
    • 一、Netty简介
      • 1.1 Netty是什么
      • 1.2 Netty有什么优势
    • 二、第一个Netty程序
      • 2.1 Netty简单使用示例
      • 2.2 代码解读
      • 2.3 Netty的特性
        • 2.3.1 Netty的事件
      • 2.4 Netty线程模型
    • 三、Netty核心组件详解(未完待续)
      • 3.1 EventLoopGroup和EventLoop
        • 3.1.1 EventLoop:事件循环
        • 3.1.2 NioEventLoopGroup:事件循环组
        • 3.1.3 所谓异步的体现
      • 3.2 Channel:通道
        • 3.2.1 Channel接口
      • 3.3 ChannelPipeline 和 ChannelHandlerContext
        • 3.3.1 ChannelPipeline 接口
  • 学习总结
  • 感谢

前言

Netty啊,真的是大名鼎鼎!Netty之于Java网络编程,相当于Spring之于Java开发。两者都是Java生态的金字塔尖的【框架】!所以,非常推荐Java程序员学习这个框架。

Netty有多牛逼?据说,曾经在性能上把谷歌公司一个用C++写的网络通信框架都给人干碎了。后来,后者参照了Netty的设计方案,才完成了超越。

阅读对象

需要有一定的网络编程基础。如若没有,请务必要学习下【阅读导航】中提到的系列上一篇文章。

另外,如果你们了解【设计模式】中的【责任链模式】就更好了。因为在Netty的开发中,Handler使用了【责任链模式】的方式,将各个Handler链化起来。

而且很负责地告诉大家,好多优秀的Java源码,都有【责任链模式】的影子。所以,去学习吧,能帮助你阅读源码以及提升自己的编程技巧。传送门:《史上最全设计模式导学目录(完整版)》

阅读导航

系列上一篇文章:《【Netty专题】【网络编程】从OSI、TCP/IP网络模型开始到BIO、NIO(Netty前置知识)》

前置知识

Reactor模型(未完待续)

课程内容

一、Netty简介

1.1 Netty是什么

Netty是由 JBOSS 提供的一个Java开源网络通信框架。它是一个【异步事件驱动】的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
也就是说,Netty 是一个基于NIO的客户、服务器端的编程通信框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。

上面有个2细节很重要:

  1. Netty是一个【异步事件驱动】的网络应用程序框架(异步,事件驱动,如何理解?)
  2. Netty 是一个基于NIO的客户、服务器端的编程通信框架。NIO,NIO,NIO,讲三遍
    (PS:有心的同学这个时候应该回忆以下,Java的NIO编程里面,有什么组件,或者细节来着?)

1.2 Netty有什么优势

相比传统Java Socket编程、Java NIO,Netty具有如下明显优势

  1. 提供了更高层次的封装,API使用简单,降低了网络编程开发门槛;

传统的NIO开发,你需要独自考虑、处理网络编程中遇到的一些常见问题。如:【断线重连】、【 网络闪断】、【心跳处理】、【粘包】、【半包读写】、【网络拥塞】和【异常流】

  1. 功能强大,预制了多种编解码功能, 支持多种主流的协议;

编解码:网络编程一定要处理的环节。因为数据在网络中传输是需要转换为二进制的,不可能是明文
支持的协议:传输层有TCP、UDP、本地传输;应用层有HTTP、WebSocket等

  1. 定制能力强,通过自定义的ChannelHandler实现更灵活的拓展
  2. 性能高,对比其他主流的NIO框架,Netty的综合性能更优
  3. 成熟、稳定。Netty目前基本没有大更新了,基本上已经修复了所有JDK NIO的BUG
  4. 社区活跃
  5. 已经经历了大规模商业应用的考验,质量有保证

二、第一个Netty程序

2.1 Netty简单使用示例

话不多说,我们先来简单使用一下,开始我们的第一个Netty程序,然后再一点一点推敲。
先导入pom:

<dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.42.Final </version><scope>compile</scope>
</dependency>

然后引入服务端代码:

/*** Netty服务端** @author zhangshen* @date 2023/10/21 14:52* @slogan 编码即学习,注释断语义**/
public class NettyServer {static final int PORT = 9999;public static void main(String[] args) throws InterruptedException {EventLoopGroup bossEventLoopGroup = new NioEventLoopGroup();EventLoopGroup wokerEventLoopGroup = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.group(bossEventLoopGroup, wokerEventLoopGroup).channel(NioServerSocketChannel.class).localAddress(new InetSocketAddress(PORT)).childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new NettyServerHandler());}});System.out.println("Netty服务端正在启动...");// 异步绑定到服务器,sync()会阻塞到完成ChannelFuture channelFuture = bootstrap.bind().sync();// 对通道关闭进行监听,closeFuture是异步操作,监听通道关闭// 通过sync()同步等待通道关闭处理完毕,这里会阻塞等待通道关闭完成channelFuture.channel().closeFuture().sync();} finally {bossEventLoopGroup.shutdownGracefully().sync();}}
}/*** Netty服务端,自定义handler** @author zhangshen* @date 2023/10/21 15:01* @slogan 编码即学习,注释断语义**/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {System.out.println("Netty服务器:客户端连接已建立");super.channelActive(ctx);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf in = (ByteBuf) msg;System.out.println("Netty服务器收到消息:" + in.toString(CharsetUtil.UTF_8));String responseMsg = "你好啊,Netty客户端";ByteBuf buf = Unpooled.copiedBuffer(responseMsg, CharsetUtil.UTF_8);ctx.writeAndFlush(buf);}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {super.exceptionCaught(ctx, cause);}
}

接着是Netty客户端代码示例:


/*** Netty客户端代码示例** @author zhangshen* @date 2023/10/21 15:05* @slogan 编码即学习,注释断语义**/
public class NettyClient {static final int NETTY_SERVER_PORT = 9999;public static void main(String[] args) throws InterruptedException {EventLoopGroup eventLoopGroup = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.group(eventLoopGroup).channel(NioSocketChannel.class).remoteAddress(new InetSocketAddress("127.0.0.1", NETTY_SERVER_PORT)).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel ch) throws Exception {ch.pipeline().addLast(new NettyClientHandler());}});// 异步连接到服务器,sync()会阻塞到完成,和服务器的不同点ChannelFuture channelFuture = bootstrap.connect().sync();// 阻塞当前线程,直到客户端的Channel被关闭channelFuture.channel().closeFuture().sync();} finally {eventLoopGroup.shutdownGracefully().sync();}}
}/*** Netty客户端代码,自定义handler** @author zhangshen* @date 2023/10/21 15:05* @slogan 编码即学习,注释断语义**/
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {System.out.println("客户端收到消息:" + msg.toString(CharsetUtil.UTF_8));}@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {String msgAfterTCP = "你好啊,Netty服务器";ctx.writeAndFlush(Unpooled.copiedBuffer(msgAfterTCP, CharsetUtil.UTF_8));ctx.alloc().buffer();}
}

2.2 代码解读

我们先来简单总结下服务端NettyServer的流程:

  1. 先声明一个ServerBootstrap(Bootstrap的一种)、EventLoopGroup。服务端声明了2个EventLoopGroup,其实1个也可以。这个跟Reactor模式有关
  2. 初始化bootstrap。通过链式调用设置一些属性,比如:group()、localAddress()、childHandler()(其实这些方法,我们可以看成Java POJO中的setXxx() )。最核心的是新增了一个自定义的NettyClientHandler
  3. 然后bootstrap.bind()
  4. 监听通道关闭
  5. 在finally块内关闭EventLoopGroup

而客户端NettyClient呢,它的流程如下:

  1. 先声明一个Bootstrap、EventLoopGroup
  2. 初始化bootstrap。通过链式调用设置一些属性,比如:group()、channel()、remoteAddress()、handler()(其实这些方法,我们可以看成Java POJO中的setXxx() )。最核心的是新增了一个自定义的NettyServerHandler
  3. 然后bootstrap.connect()
  4. 监听通道关闭
  5. 在finally块内关闭EventLoopGroup

看看,从代码主流程来看,其实客户端跟服务端基本没什么很大的区别。当然这不是主要的东西。最重要的是,大家发现没有,这里出现了好几个陌生的API,这就是Netty提供给我们的核心组件!这些组件会在后面给大家详解介绍,也是本文的核心所在!这些组件,分别是:EventLoopGroupBootstrap(ServerBootstrap)NioServerSocketChannel(NioSocketChannel)ChannelHandlerChannelPipeline(这个需要点进去才能看到)、ByteBuf

这些API组件有什么特别吗?
同学们还记得我们说【Netty是什么吗】?【Netty 是一个基于NIO的客户、服务器端的编程通信框架】啊!那同学们还记得Java NIO 3个核心组件吗?Channel通道Selector多路复用器ByteBuffer缓冲区嘛(其实还要考虑一个多线程)。
好了,就算我不说你们通过英文翻译也稍微能一一对应上了,既然Netty是基于NIO的,那NIO的这些细节,肯定也会被包含在Netty的组件中!比如:

  • EventLoopGroup:直译【事件循环组】。NIO代码里面很多while(true),然后不断循环检测事件发生,像不像?对的,EventLoopGroup可以看成是一个【线程池】
  • Channel:通道嘛,这个大家最容易理解了。可能有些朋友还不明白Channel通道是什么,其实就是BIO演变到NIO之后,Socket被封装到了Channel里面

OK,更多详细介绍我会在【三、Netty核心组件详解】中讲到。

2.3 Netty的特性

我们在最开始介绍Netty的时候,有这么描述过:它是一个【异步事件驱动】的网络应用程序框架。并且向大家抛出了这么个问题:如何理解【异步事件驱动】?
如果你们看了我上一篇文章其实不难理解。【异步事件驱动】=【异步】+【事件驱动】。

  • 异步:跟同步相对。异步在编程中的体现就是,新开一条线程去处理任务
  • 事件驱动:其实就是Reactor模式在Netty中的体现。Netty是基于Reactor模式设计的

只不过稍微有些不同的是:Netty对于事件的定义。

2.3.1 Netty的事件

Netty 使用不同的事件来通知我们状态的改变或者是操作的状态。这使得我们能够基于已经发生的事件来触发适当的动作。Netty 事件是按照它们与入站或出站数据流的相关性进行分类的。

  1. 可能由入站数据或者相关的状态更改而触发的事件包括:
    • 连接已被激活或者连接失活;
    • 数据读取;
    • 用户事件;
    • 错误事件
  2. 出站事件是未来将会触发的某个动作的操作结果,这些动作包括:
    • 打开或者关闭到远程节点的连接;
    • 将数据写到或者冲刷到套接字

每个事件都可以被分发给 ChannelHandler 类中的某个用户实现的方法,既然事件分为入站和出站,用来处理事件的 ChannelHandler 也被分为可以处理入站事件的 Handler 和出站事件的 Handler,当然有些 Handler 既可以处理入站也可以处理出站。
Netty 提供了大量预定义的可以开箱即用的 ChannelHandler 实现,包括用于各种协议(如 HTTP 和 SSL/TLS)的 ChannelHandler。

基于 Netty 的网络应用程序中根据业务需求会使用 Netty 已经提供的 ChannelHandler 或者自行开发 ChannelHandler,这些 ChannelHandler 都放在 ChannelPipeline 中统一管理,事件就会在 ChannelPipeline 中流动,并被其中一个或者多个 ChannelHandler 处理。

它的原理图如下:
在这里插入图片描述

2.4 Netty线程模型

在这里插入图片描述

模型解读:
1) Netty 抽象出两组线程池BossGroup和WorkerGroup,BossGroup专门负责接收客户端的连接, WorkerGroup专门负责网络的读写

不就是Reactor模型的主从架构吗

2)BossGroup和WorkerGroup类型都是NioEventLoopGroup

NIO是一种IO方式,epoll,BIO都是。所以,其实还有EpollEventLoopGroup,以此类推

3)NioEventLoopGroup 相当于一个事件循环线程组, 这个组中含有多个事件循环线程 ,每一个事件循环线程是NioEventLoop
4)每个NioEventLoop都有一个selector , 用于监听注册在其上的socketChannel的网络通讯
5)每个Boss NioEventLoop线程内部循环执行的步骤有 3 步

  1. 处理accept事件 , 与client 建立连接 , 生成 NioSocketChannel
  2. 将NioSocketChannel注册到某个worker NIOEventLoop上的selector
  3. 继续处理任务队列的任务 , 即runAllTasks

6)每个worker NIOEventLoop线程循环执行的步骤

  1. 轮询注册到自己selector上的所有NioSocketChannel 的read, write事件
  2. 处理 I/O 事件, 即read , write 事件, 在对应NioSocketChannel 处理业务
  3. runAllTasks处理任务队列TaskQueue的任务 ,一些耗时的业务处理一般可以放入线程池中慢慢处理,这样不影响数据在 pipeline 中的流动处理

7)每个worker NIOEventLoop处理NioSocketChannel业务时,会使用 pipeline (管道),管道中维护了很多 handler 处理器用来处理 channel 中的数据

三、Netty核心组件详解(未完待续)

我们在【2.2 代码解读】中除了简单分析了Netty服务端客户端使用流程外,还给大家挖掘出了一些Netty的核心组件。分别是:EventLoopGroupBootstrap(ServerBootstrap)NioServerSocketChannel(NioSocketChannel)ChannelHandlerChannelPipeline(这个需要点进去才能看到)、ByteBuf。接下来我们就开始着手研究研究。
这些组件出现的位置大概就是代码流程【自上而下】、【自内而外】吧。

3.1 EventLoopGroup和EventLoop

EventLoop直译:事件循环;EventLoopGroup直译:事件循环组。

虽然我不知道这是啥,但是基本上可以猜测:后者是对前者做管理的类。所以我们还是要了解一下EventLoop先。
回想一下我们在 NIO 中是如何处理我们关心的事件的?很简单,就是在一个 while 循环中 select 出事件,然后依次处理每种事件,这不就是【事件循环】嘛。

3.1.1 EventLoop:事件循环

EventLoop是Netty 的核心接口,用于处理网络连接的生命周期中所发生的事件。它的类结构如下:
在这里插入图片描述
再来看看接口定义:
在这里插入图片描述
再看看对应的实现类:
在这里插入图片描述
看,我们用到的NioEventLoop就在里面了。如果大家翻开里面的源码,会发现,NioEventLoop里面有几个重要的属性,我这边用伪代码写一下:(有一些属性是在父类中的)

class NioEventLoop {Selector selector;Thread thread;Queue<Runnable> taskQueue;SelectedSelectionKeySet selectedKeys;
}

由上面的伪代码可以看到,NioEventLoop 中:

  1. 维护了一条线程任务队列是否似曾相识?线程池呀!),支持异步提交执行任务,线程启动时会调用 NioEventLoop 的 run 方法,执行 I/O 任务和非 I/O 任务:
    • I/O 任务,即 selectionKey 中 ready 的事件,如 accept、connect、read、write 等,由 processSelectedKeys 方法触发
    • 非 IO 任务,添加到 taskQueue 中的任务,如 register0、bind0 等任务,由 runAllTasks 方法触发
  2. 维护了一个Selector选择器(多路复用器)。这个在NIO里面,就是循环遍历注册在Selector上的所有Socket,然后响应对应事件嘛。所以从这个东西一定能看出一个东西,那就是:【一个EventLoop里面肯定管理了多个Channel
  3. 维护了一个SelectionKeySet。这个不知道大家有没有印象,在NIO模型中我说:向Selector注册了Channel和感兴趣事件后就会被包装成一个SelectionKey
3.1.2 NioEventLoopGroup:事件循环组

NioEventLoopGroup:事件循环组。它是做什么的呢?其实,它主要干下面几件事情:

  1. 管理 EventLoop 的生命周期,可以理解为一个线程池,内部维护了一组线程
  2. 负责为每个新建的Channel分配一个 EventLoop

EventLoop的分配
异步传输实现只使用了少量的 EventLoop(以及和它们相关联的 Thread),而且在当前的线程模型中,它们可能会被多个 Channel 所共享。这使得可以通过尽可能少量的 Thread 来支撑大量的 Channel,而不是每个 Channel 分配一个Thread(EventLoop)。
EventLoopGroup 负责为每个新创建的 Channel 分配一个 EventLoop。怎么实现的呢?顺序分配。

在当前实现中,使用顺序循环(round-robin)的方式进行分配以获取一个均衡的分布,并且相同的 EventLoop 可能会被分配给多个 Channel。一旦一个 Channel 被分配给一个 EventLoop,它将在它的整个生命周期中都使用这个EventLoop(以及相关联的 Thread)。
在这里插入图片描述

3.1.3 所谓异步的体现

我们在一开始说Netty的时候提到过:Netty是一个【异步事件驱动】的网络应用程序框架。
事件驱动跟Reactor模型有关嘛,看了上面的EventLoop估计有一点感觉了。那【异步】呢?其实这里已经有体现了。怎么说?

首先,我们说了,NioEventLoop它是有自己线程的,所以,我们服务端绑定ServerSocketChannel的时候肯定会给他分配一个EventLoop
那么,我们点开来看,服务端绑定的时候是怎样的,可以稍微窥见一点Netty的异步所在,以及EventLoop是如何工作的。
在这里插入图片描述
但是追踪链比较深,我只能告诉大家,在服务端调用这个bind的时候,会新建一条Channel,并且把真正的bind操作投递给Channel里面的QueueTask任务队列里面。

3.2 Channel:通道

我们在之前的NIO里面,包括上文中也提到过,NIO以后使用了Channel来封装Socket。但是

3.2.1 Channel接口

Channel即Java NIO编程里面的Socket。Netty 的 Channel 接口所提供的 API,被用于所有的 I/O 操作(bindconnectreadwrite)。大大地降低了直接使用 Socket 类的复杂性。此外,Channel也是拥有许多预定义的、专门化实现的广泛类层次结构的根。
由于 Channel 是独一无二的,所以为了保证顺序将 Channel 声明为 java.lang.Comparable的一个子接口。因此,如果两个不同的 Channel 实例都返回了相同的散列码,那么AbstractChannel 中的 compareTo()方法的实现将会抛出一个 Error。

说到Channel,如果大伙有手敲过上面的示例代码的话,你会有点印象:诶,我们自己手写ChannelInboudHandler的时候,貌似看到过不少如下的方法哦:
在这里插入图片描述
我想有过自己思考的朋友,估计通过英文多少能猜到了,这些肯定涉及【生命周期】、【事件】之类的说法。下面就是想给大家介绍一下,【Channel的生命周期】

Channel 的生命周期状态

  • ChannelUnregistered :Channel 已经被创建,但还未注册到 EventLoop
  • ChannelRegistered :Channel 已经被注册到了 EventLoop
  • ChannelActive :Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
  • ChannelInactive :Channel 没有连接到远程节点

当这些状态发生改变时,将会生成对应的事件。这些事件将会被转发给 ChannelPipeline中的 ChannelHandler,其可以随后对它们做出响应。在我们的编程中,关注 ChannelActive 和ChannelInactive 会更多一些。

重要 Channel 的方法
在这里插入图片描述

  • eventLoop:返回分配给 Channel 的 EventLoop
  • pipeline:返回 Channel 的 ChannelPipeline,也就是说每个 Channel 都有自己的ChannelPipeline
  • isActive:如果 Channel 是活动的,则返回 true。活动的定义依赖于底层的传输协议。例如,一个 Socket 传输一旦连接到了远程节点便是活动的,而一个 Datagram 传输一旦被打开便是活动的
  • localAddress:返回本地的 SokcetAddress
  • remoteAddress:返回远程的 SocketAddress
  • write:将数据写到远程节点,注意,这个写只是写往 Netty 内部的缓存,还没有真正写往 socket
  • flush:将之前已写的数据冲刷到底层 socket 进行传输
  • writeAndFlush:一个简便的方法,等同于调用 write()并接着调用 flush()

3.3 ChannelPipeline 和 ChannelHandlerContext

3.3.1 ChannelPipeline 接口

当 Channel 被创建时,它将会被自动地分配一个新的 ChannelPipeline,每个 Channel 都有自己的 ChannelPipeline。这项关联是永久性的。在 Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。
ChannelPipeline 提供了 ChannelHandler 链的容器,并定义了用于在该链上传播入站(也就是从网络到业务处理)和 出站(也就是从业务处理到网络),各种事件流的 API,我们代码中的 ChannelHandler 都是放在 ChannelPipeline 中的。
使得事件流经 ChannelPipeline 是 ChannelHandler 的工作,它们是在应用程序的初始化或者引导阶段被安装的。这些 ChannelHandler 对象接收事件、执行它们所实现的处理逻辑,并将数据传递给链中的下一个 ChannelHandler,而且 ChannelHandler 对象也完全可以拦截事件不让事件继续传递。它们的执行顺序是由它们被添加的顺序所决定的。
在这里插入图片描述
说到ChannelPipeline 有一个关于ChannelHandler的生命周期得跟大家伙说一说:ChannelHandler 的生命周期。

学习总结

感谢


http://www.ppmy.cn/news/1170034.html

相关文章

sql高级教程-索引

文章目录 架构简介1.连接层2.服务层3.引擎层4.存储层 索引优化背景目的劣势分类基本语法索引结构和适用场景 性能分析MySq| Query Optimizerexplain 索引优化单表优化两表优化三表优化 索引失效原因 架构简介 1.连接层 最上层是一些客户端和连接服务&#xff0c;包含本地sock通…

人大金仓与哪吒科技达成战略合作,加快推动智慧港口建设

近日&#xff0c;人大金仓与哪吒港航智慧科技&#xff08;上海&#xff09;有限公司&#xff08;以下简称“哪吒科技”&#xff09;达成战略合作。双方旨在共享优势资源&#xff0c;联合为港口企业转型升级提供完备的技术支撑与行业解决方案。人大金仓总裁杜胜、哪吒科技总经理…

一天吃透Java面试题

给大家分享我整理的Java高频面试题&#xff0c;有小伙伴靠他拿到字节offer了。 Java基础面试题 Java的特点Java 与 C 的区别JDK/JRE/JVM三者的关系Java程序是编译执行还是解释执行&#xff1f;面向对象和面向过程的区别&#xff1f;面向对象有哪些特性&#xff1f;数组到底是…

域名解析与记录

域名解析是将域名转换为IP的过程&#xff0c;使得人们能够直接通过域名访问网站&#xff0c;而不用记繁琐的IP地址信息。而在域名解析中&#xff0c;CNAME记录和A记录是两个不同的记录类型。 A记录&#xff08;Address Record&#xff0c;地址记录&#xff09;是指将一个域名解…

记调试SMBUS的心得

为什么电池电压读的不对 仔细一看是I2C读取数据的时候少了一个CLK I2C是非常严密的 读数据之后&#xff0c;发送 ACK&#xff0c;让从机准备数据 发送NACK&#xff0c;告诉从机别准备了 ACK和NACK的区别是啥&#xff0c;告诉你&#xff0c;就是NACK先拉高SDA&#xff0c;再…

学习人工智能

在线课程 优达学城 当斯坦福大学讲师 Sebastian Thrun 和 Peter Norvig 将他们的课程“人工智能概论”免费放到网上时&#xff0c;Udacity 开始了在线学习的实验。从那时起&#xff0c;它就受到了巨大的欢迎&#xff08;来自 190 多个国家的 160,000 名学生&#xff09;&#x…

JavaScript基础知识16——分支语句

哈喽&#xff0c;大家好&#xff0c;我是雷工。 今天学习JavaScript基础知识的分支语句&#xff0c;以下为学习笔记。 1、程序三大流程控制语句 ○写几句就从上往下执行几句&#xff0c;这种叫做顺序结构&#xff1b; ○有时要根据条件选择执行代码&#xff0c;这种叫分支结构…

基于卷积优化优化的BP神经网络(分类应用) - 附代码

基于卷积优化优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码 文章目录 基于卷积优化优化的BP神经网络&#xff08;分类应用&#xff09; - 附代码1.鸢尾花iris数据介绍2.数据集整理3.卷积优化优化BP神经网络3.1 BP神经网络参数设置3.2 卷积优化算法应用 4.测试结果…