深入了解Netty,这一篇就够了

news2024/11/17 6:55:42

一、Netty简介

  Netty是由JBOSS提供的一个java开源框架,现为 Github上的独立项目。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

  也就是说,Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。  

  “快速”和“简单”并不用产生维护性或性能上的问题。Netty 是一个吸收了多种协议(包括FTP、SMTP、HTTP等各种二进制文本协议)的实现经验,并经过相当精心设计的项目。最终,Netty 成功的找到了一种方式,在保证易于开发的同时还保证了其应用的性能,稳定性和伸缩性

1.1 Netty的特点

  • 设计优雅
    适用于各种传输类型的统一API - 阻塞和非阻塞Socket
    基于灵活且可扩展的事件模型,可以清晰地分离关注点
    高度可定制的线程模型 - 单线程,一个或多个线程池
    真正的无连接数据报套接字支持(自3.1起)
  • 使用方便
    详细记录的Javadoc,用户指南和示例
    没有其他依赖项,JDK 5(Netty 3.x)或6(Netty 4.x)就足够了
  • 高性能
    吞吐量更高,延迟更低
    减少资源消耗
    最小化不必要的内存复制
  • 安全
    完整的SSL / TLS和StartTLS支持
  • 社区活跃,不断更新
    社区活跃,版本迭代周期短,发现的BUG可以被及时修复,同时,更多的新功能会被加入

1.2 Netty常见使用场景

  • 互联网行业
    在分布式系统中,各个节点之间需要远程服务调用,高性能的RPC框架必不可少,Netty作为异步高新能的通信框架,往往作为基础通信组件被这些RPC框架使用。
    典型的应用有:阿里分布式服务框架Dubbo的RPC框架使用Dubbo协议进行节点间通信,Dubbo协议默认使用Netty作为基础通信组件,用于实现各进程节点之间的内部通信。
  • 游戏行业
    无论是手游服务端还是大型的网络游戏,Java语言得到了越来越广泛的应用。Netty作为高性能的基础通信组件,它本身提供了TCP/UDP和HTTP协议栈。
    非常方便定制和开发私有协议栈,账号登录服务器,地图服务器之间可以方便的通过Netty进行高性能的通信
  • 大数据领域
    经典的Hadoop的高性能通信和序列化组件Avro的RPC框架,默认采用Netty进行跨界点通信,它的Netty Service基于Netty框架二次封装实现

二、Netty高性能设计

2.1 Netty线程模型

  Netty主要基于主从Reactors多线程模型(参考:【Linux】IO的线程模型)(如下图)做了一定的修改,其中主从Reactor多线程模型有多个Reactor:MainReactor和SubReactor:

  • MainReactor负责客户端的连接请求,并将请求转交给SubReactor
  • SubReactor负责相应通道的IO读写请求
  • 非IO请求(具体逻辑处理)的任务则会直接写入队列,等待worker threads进行处理

  这里引用Doug Lee大神的Reactor介绍:Scalable IO in Java里面关于主从Reactor多线程模型的图

  

  特别说明的是:
  虽然Netty的线程模型基于主从Reactor多线程,借用了MainReactor和SubReactor的结构,但是实际实现上,SubReactor和Worker线程在同一个线程池中:

1 EventLoopGroup bossGroup = new NioEventLoopGroup();
2 EventLoopGroup workerGroup = new NioEventLoopGroup();
3 ServerBootstrap server = new ServerBootstrap();
4 server.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class)

  上面代码中的bossGroup 和workerGroup是Bootstrap构造方法中传入的两个对象,这两个group均是线程池

  • bossGroup线程池则只是在bind某个端口后,获得其中一个线程作为MainReactor,专门处理端口的accept事件,每个端口对应一个boss线程
  • workerGroup线程池会被各个SubReactor和worker线程充分利用

2.2 异步处理

  异步的概念和同步相对。当一个异步过程调用发出后,调用者不能立刻得到结果。实际处理这个调用的部件在完成后,通过状态、通知和回调来通知调用者。

  Netty中的I/O操作是异步的,包括bind、write、connect等操作会简单的返回一个ChannelFuture,调用者并不能立刻获得结果,通过Future-Listener机制,用户可以方便的主动获取或者通过通知机制获得IO操作结果。

  当future对象刚刚创建时,处于非完成状态,调用者可以通过返回的ChannelFuture来获取操作执行的状态,注册监听函数来执行完成后的操作,常见有如下:

  • 通过isDone方法来判断当前操作是否完成
  • 通过isSuccess方法来判断已完成的当前操作是否成功
  • 通过getCause方法来获取已完成的当前操作失败的原因
  • 通过isCancelled方法来判断已完成的当前操作是否被取消
  • 通过addListener方法来注册监听器,当操作已完成(isDone方法返回完成),将会通知指定的监听器;如果future对象已完成,则理解通知指定的监听器

  例如下面的代码中绑定端口是异步操作,当绑定操作处理完,将会调用相应的监听器处理逻辑

1 serverBootstrap.bind(port).addListener(future -> {
2     if (future.isSuccess()) {
3         System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
4     } else {
5         System.err.println("端口[" + port + "]绑定失败!");
6     }
7 });

  相比传统阻塞I/O,执行I/O操作后线程会被阻塞住, 直到操作完成;异步处理的好处是不会造成线程阻塞,线程在I/O操作期间可以执行别的程序,在高并发情形下会更稳定和更高的吞吐量。

三、Netty架构设计

  前面介绍完Netty相关一些理论介绍,下面从功能特性、模块组件、运作过程来介绍Netty的架构设计

3.1 功能特性

  

  • 传输服务
    支持BIO和NIO
  • 容器集成
    支持OSGI、JBossMC、Spring、Guice容器
  • 协议支持
    HTTP、Protobuf、二进制、文本、WebSocket等一系列常见协议都支持。
    还支持通过实行编码解码逻辑来实现自定义协议
  • Core核心
    可扩展事件模型、通用通信API、支持零拷贝的ByteBuf缓冲对象

3.2 模块组件

Bootstrap、ServerBootstrap

  Bootstrap意思是引导,一个Netty应用通常由一个Bootstrap开始,主要作用是配置整个Netty程序,串联各个组件,Netty中Bootstrap类是客户端程序的启动引导类,ServerBootstrap是服务端启动引导类。

Future、ChannelFuture

  正如前面介绍,在Netty中所有的IO操作都是异步的,不能立刻得知消息是否被正确处理,但是可以过一会等它执行完成或者直接注册一个监听,具体的实现就是通过Future和ChannelFutures,他们可以注册一个监听,当操作执行成功或失败时监听会自动触发注册的监听事件。

Channel

  Netty网络通信的组件,能够用于执行网络I/O操作。
  Channel为用户提供:

  • 当前网络连接的通道的状态(例如是否打开?是否已连接?)
  • 网络连接的配置参数 (例如接收缓冲区大小)
  • 提供异步的网络I/O操作(如建立连接,读写,绑定端口),异步调用意味着任何I/O调用都将立即返回,并且不保证在调用结束时所请求的I/O操作已完成。调用立即返回一个ChannelFuture实例,通过注册监听器到ChannelFuture上,可以I/O操作成功、失败或取消时回调通知调用方。
  • 支持关联I/O操作与对应的处理程序

不同协议、不同的阻塞类型的连接都有不同的 Channel 类型与之对应,下面是一些常用的 Channel 类型

  • NioSocketChannel,异步的客户端 TCP Socket 连接
  • NioServerSocketChannel,异步的服务器端 TCP Socket 连接
  • NioDatagramChannel,异步的 UDP 连接
  • NioSctpChannel,异步的客户端 Sctp 连接
  • NioSctpServerChannel,异步的 Sctp 服务器端连接
    这些通道涵盖了 UDP 和 TCP网络 IO以及文件 IO.

Selector

  Netty基于Selector对象实现I/O多路复用,通过 Selector, 一个线程可以监听多个连接的Channel事件, 当向一个Selector中注册Channel 后,Selector 内部的机制就可以自动不断地查询(select) 这些注册的Channel是否有已就绪的I/O事件(例如可读, 可写, 网络连接完成等),这样程序就可以很简单地使用一个线程高效地管理多个 Channel 。

NioEventLoop

  NioEventLoop中维护了一个线程和任务队列,支持异步提交执行任务,线程启动时会调用NioEventLoop的run方法,执行I/O任务和非I/O任务:

  • I/O任务
    即selectionKey中ready的事件,如accept、connect、read、write等,由processSelectedKeys方法触发。
  • 非IO任务
    添加到taskQueue中的任务,如register0、bind0等任务,由runAllTasks方法触发。

  两种任务的执行时间比由变量ioRatio控制,默认为50,则表示允许非IO任务执行的时间与IO任务的执行时间相等。

NioEventLoopGroup

  NioEventLoopGroup,主要管理eventLoop的生命周期,可以理解为一个线程池,内部维护了一组线程,每个线程(NioEventLoop)负责处理多个Channel上的事件,而一个Channel只对应于一个线程。

ChannelHandler

  ChannelHandler是一个接口,处理I/O事件或拦截I/O操作,并将其转发到其ChannelPipeline(业务处理链)中的下一个处理程序。

  ChannelHandler本身并没有提供很多方法,因为这个接口有许多的方法需要实现,方便使用期间,可以继承它的子类:

  • ChannelInboundHandler用于处理入站I/O事件
  • ChannelOutboundHandler用于处理出站I/O操作

  或者使用以下适配器类:

  • ChannelInboundHandlerAdapter用于处理入站I/O事件
  • ChannelOutboundHandlerAdapter用于处理出站I/O操作
  • ChannelDuplexHandler用于处理入站和出站事件

ChannelHandlerContext

  保存Channel相关的所有上下文信息,同时关联一个ChannelHandler对象

ChannelPipline

  保存ChannelHandler的List,用于处理或拦截Channel的入站事件和出站操作。 ChannelPipeline实现了一种高级形式的拦截过滤器模式,使用户可以完全控制事件的处理方式,以及Channel中各个的ChannelHandler如何相互交互。

  下图引用Netty的Javadoc4.1中ChannelPipline的说明,描述了ChannelPipeline中ChannelHandler通常如何处理I/O事件。 I/O事件由ChannelInboundHandler或ChannelOutboundHandler处理,并通过调用ChannelHandlerContext中定义的事件传播方法(例如ChannelHandlerContext.fireChannelRead(Object)和ChannelOutboundInvoker.write(Object))转发到其最近的处理程序。

1                                                  I/O Request
 2                                             via Channel or
 3                                         ChannelHandlerContext
 4                                                       |
 5   +---------------------------------------------------+---------------+
 6   |                           ChannelPipeline         |               |
 7   |                                                  \|/              |
 8   |    +---------------------+            +-----------+----------+    |
 9   |    | Inbound Handler  N  |            | Outbound Handler  1  |    |
10   |    +----------+----------+            +-----------+----------+    |
11   |              /|\                                  |               |
12   |               |                                  \|/              |
13   |    +----------+----------+            +-----------+----------+    |
14   |    | Inbound Handler N-1 |            | Outbound Handler  2  |    |
15   |    +----------+----------+            +-----------+----------+    |
16   |              /|\                                  .               |
17   |               .                                   .               |
18   | ChannelHandlerContext.fireIN_EVT() ChannelHandlerContext.OUT_EVT()|
19   |        [ method call]                       [method call]         |
20   |               .                                   .               |
21   |               .                                  \|/              |
22   |    +----------+----------+            +-----------+----------+    |
23   |    | Inbound Handler  2  |            | Outbound Handler M-1 |    |
24   |    +----------+----------+            +-----------+----------+    |
25   |              /|\                                  |               |
26   |               |                                  \|/              |
27   |    +----------+----------+            +-----------+----------+    |
28   |    | Inbound Handler  1  |            | Outbound Handler  M  |    |
29   |    +----------+----------+            +-----------+----------+    |
30   |              /|\                                  |               |
31   +---------------+-----------------------------------+---------------+
32                   |                                  \|/
33   +---------------+-----------------------------------+---------------+
34   |               |                                   |               |
35   |       [ Socket.read() ]                    [ Socket.write() ]     |
36   |                                                                   |
37   |  Netty Internal I/O Threads (Transport Implementation)            |
38   +-------------------------------------------------------------------+

  入站事件由自下而上方向的入站处理程序处理,如图左侧所示。 入站Handler处理程序通常处理由图底部的I/O线程生成的入站数据。 通常通过实际输入操作(例如SocketChannel.read(ByteBuffer))从远程读取入站数据。

  出站事件由上下方向处理,如图右侧所示。 出站Handler处理程序通常会生成或转换出站传输,例如write请求。 I/O线程通常执行实际的输出操作,例如SocketChannel.write(ByteBuffer)。

  在 Netty 中每个 Channel 都有且仅有一个 ChannelPipeline 与之对应, 它们的组成关系如下:

  

  一个 Channel 包含了一个 ChannelPipeline, 而 ChannelPipeline 中又维护了一个由 ChannelHandlerContext 组成的双向链表, 并且每个 ChannelHandlerContext 中又关联着一个 ChannelHandler。入站事件和出站事件在一个双向链表中,入站事件会从链表head往后传递到最后一个入站的handler,出站事件会从链表tail往前传递到最前一个出站的handler,两种类型的handler互不干扰。

四、工作原理架构

  初始化并启动Netty服务端过程如下:

1     public static void main(String[] args) {
 2         // 创建mainReactor
 3         NioEventLoopGroup boosGroup = new NioEventLoopGroup();
 4         // 创建工作线程组
 5         NioEventLoopGroup workerGroup = new NioEventLoopGroup();
 6 
 7         final ServerBootstrap serverBootstrap = new ServerBootstrap();
 8         serverBootstrap 
 9                  // 组装NioEventLoopGroup 
10                 .group(boosGroup, workerGroup)
11                  // 设置channel类型为NIO类型
12                 .channel(NioServerSocketChannel.class)
13                 // 设置连接配置参数
14                 .option(ChannelOption.SO_BACKLOG, 1024)
15                 .childOption(ChannelOption.SO_KEEPALIVE, true)
16                 .childOption(ChannelOption.TCP_NODELAY, true)
17                 // 配置入站、出站事件handler
18                 .childHandler(new ChannelInitializer<NioSocketChannel>() {
19                     @Override
20                     protected void initChannel(NioSocketChannel ch) {
21                         // 配置入站、出站事件channel
22                         ch.pipeline().addLast(...);
23                         ch.pipeline().addLast(...);
24                     }
25     });
26 
27         // 绑定端口
28         int port = 8080;
29         serverBootstrap.bind(port).addListener(future -> {
30             if (future.isSuccess()) {
31                 System.out.println(new Date() + ": 端口[" + port + "]绑定成功!");
32             } else {
33                 System.err.println("端口[" + port + "]绑定失败!");
34             }
35         });
36 }

  • 基本过程如下:
  • 1 初始化创建2个NioEventLoopGroup,其中boosGroup用于Accetpt连接建立事件并分发请求, workerGroup用于处理I/O读写事件和业务逻辑
  • 2 基于ServerBootstrap(服务端启动引导类),配置EventLoopGroup、Channel类型,连接参数、配置入站、出站事件handler
  • 3 绑定端口,开始工作

  结合上面的介绍的Netty Reactor模型,介绍服务端Netty的工作架构图:

   

  server端包含1个Boss NioEventLoopGroup和1个Worker NioEventLoopGroup,NioEventLoopGroup相当于1个事件循环组,这个组里包含多个事件循环NioEventLoop,每个NioEventLoop包含1个selector和1个事件循环线程。

  每个Boss NioEventLoop循环执行的任务包含3步:

  • 1 轮询accept事件
  • 2 处理accept I/O事件,与Client建立连接,生成NioSocketChannel,并将NioSocketChannel注册到某个Worker NioEventLoop的Selector上
  • 3 处理任务队列中的任务,runAllTasks。任务队列中的任务包括用户调用eventloop.execute或schedule执行的任务,或者其它线程提交到该eventloop的任务

  每个Worker NioEventLoop循环执行的任务包含3步:

  • 1 轮询read、write事件
  • 2 处理I/O事件,即read、write事件,在NioSocketChannel可读、可写事件发生时进行处理
  • 3 处理任务队列中的任务,runAllTasks

  其中任务队列中的task有3种典型使用场景

  • 1 用户程序自定义的普通任务
1 ctx.channel().eventLoop().execute(new Runnable() {
2     @Override
3     public void run() {
4         //...
5     }
6 });
  • 2 非当前reactor线程调用channel的各种方法
    例如在推送系统的业务线程里面,根据用户的标识,找到对应的channel引用,然后调用write类方法向该用户推送消息,就会进入到这种场景。最终的write会提交到任务队列中后被异步消费。

  • 3 用户自定义定时任务

1 ctx.channel().eventLoop().schedule(new Runnable() {
2     @Override
3     public void run() {
4 
5     }
6 }, 60, TimeUnit.SECONDS);

一、服务端Netty的工作架构

  

二、Netty服务端

  代码如下:

  1、NettyServer.java 服务端主类

1 public class NettyServer {
 2 
 3     public static void main(String[] args) throws InterruptedException {
 4 
 5         // 创建EventLoopGroup
 6         NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
 7         NioEventLoopGroup workerGroup = new NioEventLoopGroup();
 8 
 9         try {
10             // 创建ServerBootstrap
11             ServerBootstrap bootstrap = new ServerBootstrap();
12 
13             // 配置bootstrap
14             bootstrap.group(bossGroup, workerGroup)
15                     // 指定使用的 NIO传输Channel
16                     .channel(NioServerSocketChannel.class)
17                     // 指定端口,设置套接字地址
18                     .localAddress(new InetSocketAddress(9000))
19                     // 添加处理器
20                     .childHandler(new ChannelInitializer<SocketChannel>() {
21                         @Override
22                         protected void initChannel(SocketChannel ch) throws Exception {
23                             // 初始化channel时,添加处理器
24                             ch.pipeline().addLast(new NettyServerHandler());
25                         }
26                     });
27 
28             // 绑定端口
29             // sync():调用sync()方法阻塞等待知道绑定完成
30             ChannelFuture channelFuture = bootstrap.bind().sync();
31             // 获取Channel的CloseFuture,并阻塞知道它完成
32             channelFuture.channel().closeFuture().sync();
33         } finally {
34             // 关闭释放资源
35             bossGroup.shutdownGracefully().sync();
36             workerGroup.shutdownGracefully().sync();
37         }
38 
39     }
40 }

  2、NettyServerHandler.java 服务端处理类

1 public class NettyServerHandler extends ChannelInboundHandlerAdapter {
 2 
 3     // 对于每个传入的消息都要调用
 4     @Override
 5     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
 6         // 记录已接收的消息
 7         ByteBuf byteBuf = (ByteBuf) msg;
 8         System.out.println("Server received: " + byteBuf.toString(CharsetUtil.UTF_8));
 9     }
10 
11     // 通知 ChannelInboundHandlerAdapter 最后一次 channelRead()的调用
12     // 是当前批量读取中的最后一条消息
13     @Override
14     public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
15         // 将未决消息冲刷到远程节点,并且关闭该Channel;
16         ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
17                 .addListener(ChannelFutureListener.CLOSE);
18         System.out.println("读取完成");
19     }
20 
21     // 在读取操作期间,有异常抛出时调用
22     @Override
23     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
24         // 打印异常
25         cause.printStackTrace();
26         // 关闭Channel
27         ctx.channel();
28     }
29 }

三、Netty客户端

  代码如下:

  1、NettyClient.java 客户端主类

 1 public class NettyClient {
 2     public static void main(String[] args) throws InterruptedException {
 3         // 事件循环组
 4         NioEventLoopGroup group = new NioEventLoopGroup();
 5         try {
 6             // 启动对象
 7             Bootstrap bootstrap = new Bootstrap();
 8             // 配置
 9             bootstrap.group(group)
10                     .channel(NioSocketChannel.class)
11                     .remoteAddress(new InetSocketAddress("127.0.0.1",9000))
12                     .handler(new ChannelInitializer<SocketChannel>() {
13                         @Override
14                         protected void initChannel(SocketChannel ch) throws Exception {
15                             ch.pipeline().addLast(new NettyClientHandler());
16                         }
17                     });
18 
19             // 连接远程地址,阻塞至连接完成
20             ChannelFuture channelFuture = bootstrap.connect().sync();
21             // 阻塞直到channel关闭
22             channelFuture.channel().closeFuture().sync();
23         } finally {
24             // 关闭释放资源
25             group.shutdownGracefully().sync();
26         }
27     }
28 }
  2、NettyClientHandler.java 客户端处理类

1 public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
 2 
 3     // 当从服务器接收到一条消息时被调用
 4     @Override
 5     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception {
 6         // 记录已接收的消息
 7         System.out.println("Client received: " + msg.toString(CharsetUtil.UTF_8));
 8     }
 9 
10     // 在到服务器的连接已建立之后将被调用
11     @Override
12     public void channelActive(ChannelHandlerContext ctx) throws Exception {
13         // 当被通知Channel是活跃的时侯,发送一条消息
14         ctx.writeAndFlush(Unpooled.copiedBuffer("Netty rocks!", CharsetUtil.UTF_8));
15     }
16 
17     // 在处理过程中引发异常时调用
18     @Override
19     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
20         // 打印异常
21         cause.printStackTrace();
22         // 关闭Channel
23         ctx.channel();
24     }
25 }

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

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

相关文章

微分方程(人口预测+传染病模型)

一、定义 微分方程&#xff1a;含导数或微分的方程 微分方程的阶数&#xff1a;所含导数或微分的最高阶数&#xff0c;如y’’’2y’’-2x0是三阶微分方程 微分方程的解&#xff1a;使得微分方程成立的函数 例如y’-2x0的解可以为x或者x1 微分方程的通解和特解&#xff1a;特…

【pat】分而治之【图】

分而治之&#xff0c;各个击破是兵家常用的策略之一。在战争中&#xff0c;我们希望首先攻下敌方的部分城市&#xff0c;使其剩余的城市变成孤立无援&#xff0c;然后再分头各个击破。为此参谋部提供了若干打击方案。本题就请你编写程序&#xff0c;判断每个方案的可行性。输入…

MySQL触发器相关知识

1、什么是触发器 触发器&#xff08;trigger&#xff09;是mysql的数据库对象之一&#xff0c;是一种与表操作有关的数据库对象&#xff0c;当触发器所在表上出现指定事件时&#xff08;这些事件包括insert、update、delete三种&#xff09;&#xff0c;将调用该对象&#xff0…

2023年安装Flutter开发环境_在C盘空间占用空间

2023年安装Flutter开发环境&#xff0c;C盘空间还剩多少&#xff1f; 1&#xff1a;Flutter开发对磁盘空间的要求 2&#xff1a;其余日常辅助软件安装D盘&#xff08;占用8GB&#xff09; 3&#xff1a;消耗时间&#xff08;3天–网络有时会中断&#xff09;–【劝退提示】 安…

Hudi(12):Hudi集成Flink之sql-client方式

目录 0. 相关文章链接 1. 启动sql-client 1.1. 修改flink-conf.yaml配置 1.2. local模式 1.3. yarn-session模式 2. 插入数据 3. 查询数据 4. 更新数据 5. 流式插入 5.1. 创建测试表 5.2. 执行插入 5.3. 查看job 5.4. 查看job 5.5. 查看HDFS目录 5.6. 查询结果 …

行为型模式 - 解释器模式Interpreter

学习而来&#xff0c;代码是自己敲的。也有些自己的理解在里边&#xff0c;有问题希望大家指出。 模式的定义与特点 解释器模式&#xff08;Interperter Pattern&#xff09;&#xff0c;给定一个语言&#xff0c;定义它的文法表示&#xff0c;并定义一个解释器&#xff0c;这个…

智引未来,深兰科技机器人家族首次亮相TechG

12月31日&#xff0c;首届上海国际消费电子技术展(简称TechG)在南京国际博览中心圆满落下帷幕。作为全球消费电子技术领域的顶级行业盛会&#xff0c;本届展会共吸引了来自全球的300余家企业出席&#xff0c;共计逾2万名专业人士到场参观。阿里巴巴、蚂蚁科技、海尔、科大讯飞、…

PyQt6快速入门-菜单与工具栏

菜单与工具栏 接下来我们将了解一些常见的用户界面元素,您可能在许多其他应用程序中都见过它们——工具栏和菜单。 我们还将介绍Qt 提供的用于最小化不同 UI 区域之间的重复的整洁系统 — QAction。 1、Toolbars 最常见的用户界面元素之一是工具栏。 工具栏是用于在应用程序…

【微服务】Nacos 账号权限体系

目录 一、背景 1、账号体系 2、账号实体映射 二、方案 1、Nacos 资源模型 2、Nacos 授权 resource 2.1、授权 resource 组成 2.2、不同级别授权资源组成 3、Nacos 授权 Opers 4、Nacos 具体权限定义 4.1、Opers 组成 4.2、具体实例 4.3、工程实现 三、RBAC 设计实…

IDEA使用Spring initializr 创建SpringBoot项目超时问题解决办法

1.问题描述 IDEA使用Spring initializr 创建SpringBoot项目时经常会出现连接超时的问题&#xff0c;报错提示如下 还有一个提示非常简短就是 connect timed out 总之问题都是一样&#xff0c;可能因为是外网所以有时候会出现连接问题&#xff0c;多试几次会成功&#xff0c;…

AutoScraper——爬虫神器

AutoScraper是一个自动化的爬虫工具&#xff0c;非常智能&#xff0c;而且使用简单便捷。AutoScraper 是使用 Python 实现的 Web 爬虫&#xff0c;兼容 Python 3&#xff0c;能快速且智能获取指定网站上的数据&#xff0c;在github上具有4.8K⭐️。github链接&#xff1a;https…

有哪些你看了以后大呼过瘾的计算机书籍?

推荐几本让程序员们爱不释手的经典书。 1、代码整洁之道 鲍勃大叔作品&#xff0c;程序员&#xff0c;汇聚编程数十年编程生涯的心得体会&#xff0c;阐释如何解决软件开发人员、项目经理及软件项目领导们所面临的棘手的问题。 本书提出一种观点&#xff1a;代码质量与其整洁…

Qt+C/C++文章小说人物关系分析

程序示例精选 QtC文章小说人物关系分析 如需安装运行环境或远程调试&#xff0c;见文章底部微信名片&#xff0c;由专业技术人员远程协助&#xff01; 前言 这篇博客针对<<QtC/C文章小说人物关系分析>>编写代码&#xff0c;代码整洁&#xff0c;规则&#xff0c;易…

浅谈未来跨境电商发展的新趋势?

从21世纪初&#xff0c;互联网在我国应运而生&#xff0c;并且在国家政策的倾斜和互联网的渗透下&#xff0c;结合互联网商业巨头对全球互联网产业的优化布局&#xff0c;互联网技术逐渐得到完善&#xff0c;伴随着近年来直播带货以及互联网电商的加持&#xff0c;我国的线上购…

Crack:dhtmlx JavaScript UI Libraries 商业企业版

dhtmlx JavaScript UI Libraries 企业版 包含Ω578867473&#xff1a; JS Gantt Chart JS Scheduler JS UI Widgets Library JS Diagram Library JS Kanban Board JS To Do List JS Event Calendar JS Spreadsheet JS Pivot Table JS File Uploader JS Rich Text Editor Gantt…

家庭库存管理系统Homebox

本文完成于 2022 年的 10 月&#xff0c; 最近正好有网友在找这方面的软件&#xff0c;就给翻出来了&#xff1b;Homebox 通过存储位置和标签两个维度来管理物品&#xff0c;非常简单易用&#xff0c;希望能满足要求&#xff1b; 什么是 Homebox &#xff1f; Homebox 是一个自…

TensorFlow和PyTorch的实际应用比较

TensorFlow和PyTorch是两个最受欢迎的开源深度学习框架&#xff0c;这两个框架都为构建和训练深度学习模型提供了广泛的功能&#xff0c;并已被研发社区广泛采用。但是作为用户&#xff0c;我们一直想知道哪种框架最适合我们自己特定项目&#xff0c;所以在本文与其他文章的特性…

我阳了,一针疫苗未打

12月31日凌晨两点&#xff0c;我被热醒&#xff0c;浑身湿透。身体发出强烈信号&#xff0c;本能地催促我赶紧喝水&#xff0c;再不喝&#xff0c;要炸。毫不犹豫地&#xff0c;我走进厨房&#xff0c;摁下电水壶&#xff0c;1.5L 水&#xff0c;90度。几杯水下肚&#xff0c;身…

2020年MathorCup高校数学建模挑战赛—大数据竞赛B题遥感图像地块分割与提取求解全过程文档及程序

2020年MathorCup高校数学建模挑战赛—大数据竞赛 B题 遥感图像地块分割与提取 原题再现&#xff1a; 耕地的数量和质量是保持农业可持续发展的关键&#xff0c;利用卫星遥感影像可以识别并提取耕地&#xff0c;并对耕地进行遥感制图&#xff0c;准确的耕地分布能够为国家决策…

聚观早报 | 蚂蚁集团调整股东结构;「快看点」2 月 6 日终止运营

点击蓝字 / 关注我们 今日要闻&#xff1a;蚂蚁集团调整股东结构&#xff1b;「快看点」2 月 6 日终止运营&#xff1b;Google 同意为隐私问题进行赔偿&#xff1b;苹果已取消 iPhone SE 4 发布&#xff1b;Lightyear 2 太阳能汽车发布 蚂蚁集团调整股东结构1 月 7 日&#xf…