当前位置: 首页 > news >正文

Netty应用(五) 之 Netty引入 EventLoop

目录

第三章 Netty

1.什么是Netty?

2.为什么需要使用Netty?

3.Netty的发展历程

4.谁在使用Netty?

5.为什么上述这些分布式产品都使用Netty?

6.第一个Netty应用

7.如何理解Netty是NIO的封装

8.logback日志使用的加强

9.EventLoop(NioEventLoop)

9.1 作用

9.2 类结构图

9.3 如何使用EventLoop?

10.EventLoopGroup(NioEventLoopGroup)

10.1 测试EventLoopGroup

11.NioEventLoop VS DefaultEventLoopGroup

11.1 注意点1

11.2 注意点2

11.3 注意点3

11.4 Netty中Reactor模型总结


第三章 Netty

1.什么是Netty?

Netty: Home

Netty is an asynchronous event-driven network application frameworkfor rapid development of maintainable high performance protocol servers & clients.

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

Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.

Netty是一个NIO客户服务器框架,它能够快速和容易地开发网络应用,如协议服务器和客户端。它大大简化和精简了网络编程,如TCP和UDP套接字服务器。

'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.

快速和简单 "并不意味着开发出来的应用程序会出现可维护性或性能问题。Netty的设计是经过精心设计的,其经验来自于许多协议的实施,如FTP、SMTP、HTTP以及各种基于二进制和文本的遗留协议。因此,Netty成功地找到了一种方法来实现开发的简易性、性能、稳定性和灵活性,而没有任何妥协。

  • 异步事件驱动的概念

异步事件驱动的概念:

Netty是一个异步事件驱动的网络应用框架。事件驱动是指:服务器监控不同的事件,如ACCEPT,READ,WRITE等事件。异步是指:Netty其实沿用了Reactor响应式编程,开启一个或多个Boss线程专门处理所有客户端的连接操作,当连接操作处理完毕后。该客户端后续对应的读写操作会交给worker线程去做,当然对于很多个客户端而言,我们是需要多个worker线程的。但是worker线程处理客户端时的操作决定了是同步还是异步,先说同步:如果worker线程处理某个客户端读写操作,worker线程一直等待到该客户端读或写操作完成后才返回给客户端,这个过程中,该worker线程是不可以处理其他客户端的!这就是同步!再说异步:worker线程在处理客户端读或写操作时,当接收到这一读或写操作的请求后,立即返回一个确认操作,然后异步开启一个新线程去处理此次对应的读或写的操作!等这个异步线程处理完毕后,再返回结果给到对应的客户端。异步线程处理的过程中,不会阻塞当前worker线程的,worker线程可以处理其他客户端的读或写的操作!这是十分高性能的处理,对于客户端而言也很优化。

回调这里存在疑惑:!!!后续学netty的时候结合案例分析一下!!!

这个异步肯定是基于回调机制的:在worker线程交付读或写的任务给异步线程的时候,会把特定的客户端信息注册给异步线程,等到异步线程处理完后再通过回调函数给该特定的客户端。异步线程能返回任务结果 是通过调用客户端提供的回调函数

2.为什么需要使用Netty?

1.Netty是完成网络通信的框架,底层封装了NIO

2.NIO存在很多问题,比如:

API复杂难用,尤其是ByteBuffer的指针切换来切换去的(时刻切换读模式或写模式),在Netty中使用ByteBuf这一核心结构来封装解决!

需要掌握丰富的知识,比如多线程和网络编程。Netty也都进行了封装,比如:EventLoopGroup

可靠性无法保证,断线重连,半包粘包,网络拥塞问题等问题都需要我们自己去考虑,但是在Netty中这些问题都得到了很好的解决和封装!而且Netty处理的非常优秀

空轮询 bug,若Selector的轮询结果为空,也没有调用wakeup或新消息处理,Selector.select()被唤醒而发生空轮询,CPU使用率100%。在Netty中也对该问题进行了解决。

空轮询bug参考下面文章:

Netty笔记-NIO空轮询Bug的分析与解决代码 - 掘金

3.Netty的发展历程

1. Trustin Lee 2004年开发了Netty,成功入职了Arreo通信公司

2. 2008年,Trustin Lee,加入JBoss,发布了Netty3

3. 2012年,Trustin Lee,单干,发布了Netty4

4. 2013年,发布了Netty5。 引入JDK的新特性,比如 ForkJoinPool等

使用 ForkJoinPool 提升了复杂性

没有带来明显的性能提升

同时维护太多分支太耗费精力

4.谁在使用Netty?

1. 框架,gRPC、Dubbo、Spring WebFlux、Spring Cloud Gateway

2. 大数据,Spark、Hadoop、Flink

3. 消息队列,RocketMQ、ActiveMQ

4. 搜索引擎,Elasticsearch

5. 分布式协调器,Zookeeper

6. 数据库,Cassandra、Neo4j

7. 负载均衡,Ribbon

5.为什么上述这些分布式产品都使用Netty?

为什么这些技术都使用Netty?

因为这些技术都是分布式技术,对于分布式技术而言都逃不开一个话题,那就是多节点集群的搭建,像最常说的就是主从集群,主节点是一个单独的JVM进程,从节点也是一个单独的JVM进程。主节点和从节点之前需要维护数据,传输数据时需要跨进程通信,一旦涉及到跨进程通信,无论是位于一台机器上的不同进程,还是位于多台机器上的不同进程,有一个统一的解决方案就是:RPC网络通信。RPC有很多,比如说:Dubbo,gRPC,但是它们无非都是Netty的套壳,使得我们使用Dubbo,grpc做网络通信时更加方便,性能更高了。

netty是基于传输层TCP/UDP协议的快速开发框架,Dubbo基于netty开发了一套专属的协议,Dubbo协议,也有内部的Triple协议-新一代网络通信协议,Dubbo甚至还兼容了Grpc协议做了新版协议(后续会总结)。Grpc也有基于Netty的自己一套协议,这里称之为Grpc协议。

像我们平常开发,javaEE走的其实是应用层协议,http协议。

但是对于分布式框架底层都是要基于TCP/UDP这种操作系统内核提供的传输层协议做定制封装成自己的专属协议的,但是自己去封装做网络通信太麻烦,需要考虑网络的问题太多,所以都使用netty作为自己封装协议的基础,因为netty就是基于TCP/UDP的呀。

所以你java开发一个分布式产品,网络通信你只能用netty,也只有netty成熟,稳定,牛逼,高性能。

6.第一个Netty应用

  • 依赖
<dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.8.0</version>
</dependency><dependency><groupId>io.netty</groupId><artifactId>netty-all</artifactId><version>4.1.45.Final</version>
</dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.13.2</version><scope>test</scope>
</dependency><dependency><groupId>org.slf4j</groupId><artifactId>slf4j-api</artifactId><version>1.7.32</version>
</dependency><dependency><groupId>ch.qos.logback</groupId><artifactId>logback-classic</artifactId><version>1.2.9</version>
</dependency><dependency><groupId>com.fasterxml.jackson.core</groupId><artifactId>jackson-databind</artifactId><version>2.9.9</version>
</dependency><dependency><groupId>javax.servlet</groupId><artifactId>servlet-api</artifactId><version>2.5</version>
</dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><version>1.18.22</version>
</dependency>
  • 引入logback日志---》参考之前NIO笔记总结的步骤即可
  • 服务端
package com.messi.netty_core_02.netty01;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyServer {private static final Logger log1 = LoggerFactory.getLogger(MyNettyServer.class);private static final Logger log = LoggerFactory.getLogger(MyNettyServer.class);public static void main(String[] args) {//构建一个服务端的处理器ServerBootstrap serverBootstrap = new ServerBootstrap() ;//处理器绑定一个服务端的SocketChannel,这里Netty就是开启一个NioServerSocketChannel通道serverBootstrap.channel(NioServerSocketChannel.class);//启动一组线程(NioEventLoopGroup),可以理解成线程池,就是管理EventLoop的。//实际上就是我们的NIO版本中实现的Reactor模型中:Boss线程通过死循环监控Accept事件。Worker线程通过死循环监控read或write事件//每个EventLoop都是一个线程,每个线程都有自己的Selector监控器serverBootstrap.group(new NioEventLoopGroup());//上面使用EventLoop监控到了各个事件的发生,下面就需要使用Handler接口处理器类来处理接收到的事件//这里就开始处理接收到的IO事件(read或write)// 注释:Accept连接事件Netty已经内置帮你做好了(因为连接代码是可复用的),你只需要手动处理IO事件// 所以这里要进行初始化NioSocketChannel,也就是通过Handler,通过Pipline来流水线处理serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {//添加一个解码器,把通过网络接收到客户端发送的所有ByteBuffer解码成字符串,客户端通过网络传输给我们服务端的所有数据都是ByteBuffer//网络传输的过程中不能传输字符串//这里可以添加很多个Handler,不可以按照你的需求在后面不断的追加,完成你的业务。//new StringDecoder()这种是内置的处理器,你也可以自定义Handler然后添加进去ch.pipeline().addLast(new StringDecoder()) ;//添加一个InboundHandler解析器,所有我们从外部读入的数据这里都是Inbound。就是输入数据。//因为我们是addLast,所以前面要加一个字符串解码器,这里又追加了一个输出处理器// 按照流水线的先后处理规则:先执行字符串解码器,然后再执行这个输出处理器ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){//Adapter是适配器,我们不需要全部实现所有的方法,只需要执行我们自己想要的方法,比如:我们就实现一个读操作对应的方法channelRead()//上面执行完解码器,把ByteBuffer数据处理转为字符串,那么到了这里,我们Inbound就是输入到这里,所以这里应该对应的就是读操作,我们实现读操作channel就可以//这个方法的参数:msg。如果第一个处理器把ByteBuffer解码成json,那么该msg就是json//我们后续就可以处理json转变成java对象,就会把该java对象接着传递下去了,后续可能还会添加新的处理器处理该java对象,你如果没有第一个解码处理器,这里msg拿到的就是ByteBuffer //如果拿到的是ByteBuffer,那么你就需要自己去进行解码操作,你自己看着办@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log1.info("服务端读取到的数据为:{}",msg);}});}});serverBootstrap.bind(8000);}}
  • 客户端
package com.messi.netty_core_02.netty01;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;import java.net.InetSocketAddress;public class MyNettyClient {public static void main(String[] args) throws InterruptedException {//初始化一个客户端启动器Bootstrap bootstrap = new Bootstrap();//客户端也要开启一个NioSocketChannel通道,它是做IO和连接的bootstrap.channel(NioSocketChannel.class);//客户端也做成多线程组了,为什么?我在细节中有总结bootstrap.group(new NioEventLoopGroup());bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {//客户端发数据给服务端是要进行编码的 同理服务端ch.pipeline().addLast(new StringEncoder());}});//客户端请求连接服务端,是一个异步操作。底层会开启一个新的线程处理这个阻塞式的连接操作,当该异步新线程处理完毕后,会回调把结果传输给main线程//使用ChannelFuture类型的connect进行接收到异步新线程处理的结果ChannelFuture connect = bootstrap.connect("localhost",8000);//但是要注意一点就是:一切IO操作都是建立在连接完成的前提下的,所以这里要进行阻塞式等待连接完成后才能进行下面的操作connect.sync();//连接成功后,通过这个连接获取到Channel通道,也就是当初NIO版本对应的SocketChannel,此时这里肯定是main线程处理Channel channel = connect.channel();//往服务端写数据,写数据写的是经过pipline流水线进行编码Handler处理后的"梅西",即ByteBuffer类型的字节,底层再转换成二进制字节流channel.writeAndFlush("梅西");}
}
  • 测试

启动服务端和客户端,成功

  • 细节

1.

你可以把流水线连接的Handler设为一个Controller,在某一个Handler中可以调用Service层的代码,但是Service层的业务代码不会在这里写:

2.

为什么客户端也是多线程?

通常客户端与服务端做通信,发数据是要经过很多步骤的:1.与服务端建立连接 2.通信:read或write数据

我们要把步骤1做成一个或多个线程,步骤2也做成一个或多个线程。并且是异步的,所谓异步就是是啥前面也解释了。

解释一下为什么:建立连接时开启一个线程。当调用read方法后,执行这个读IO,我们也开启一个线程。当调用write方法时,执行这个写IO,同样也开启一个线程。为什么?因为当read这个IO时间很久的话,如果是单线程,对于这个客户端而言,是不是就阻塞了write这个IO呢?对吧!Netty把客户端也做成了多线程,然后EventLoopGroup事件循环组监控每一个事件的触发。

每开启一个客户端,客户端所在的计算机服务器都这样做。是不是大大的提升了客户端这边的性能和效率?对吧。

3.

这不是建造者设计模式:

建造者设计模式也是点点点添加,如果少添加哪一个点的时候,该对象就意味着创建不成功,建造者设计模式创建的对象对应的类一般是不提供无参构造器的,需要一个个点点点。每一个点都是建造者设计模式创建对象的必须步骤。

但是对于这里的ServerBootstrap对象并不是这样,它是有无参构造器的,当使用无参构造器new出来的时候已经创建完成。只不过后续点点点是对该对象的一系列属性值进行赋值操作(而并不是创建对象的必须步骤)。所以这并不是建造者设计模式。

7.如何理解Netty是NIO的封装

我们前面说netty就是NIO的封装,他把nio的那些难用的操作封装起来变得简单易用了。那么既然是封

装,我们就说,不管你咋操作,咋封装,那些该有的东西还是得有,因为你不是重新创造。那我们看看

netty中哪些东西是源自于NIO的,或者说NIO有哪些东西在netty中体现着。他们的对应关系是啥。

再或者说就是netty的什么地方封装了NIO的什么东西。我们先来一段NIO的代码,前面真的写烂了。

  • NIO版本伪代码
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.configureBlocking(false);
ssc.bind(new InetSocketAddress(8000));Selector selector = Selector.open();
ssc.register(selector,SelectionKey.OP_ACCEPT):while(true) {selector.select();Iterator<SelectionKey> iterator = selector.selectionKeys().iterator();while(iterator.hasNext()) {SelectionKey sscKey = iterator.next();iterator.remove();if(sscKey.isAcceptable()) {//处理连接操作 } else if(sscKey.isWritable()) {//这里省略获取sc这个SocketChannel的过程ByteBuffer byteBuffer = Charset.forName("UTF-8").encode("xxx");int write = sc.write(byteBuffer);} }
  • 一张图胜过千言万语

8.logback日志使用的加强

只要你配置了日志相关的pom依赖,那么即使你不配置logback.xml这一配置文件,netty内部也会自动识别到这个pom依赖,然后会进行netty内部默认的日志输出

  • 在很多情况下,我们引入logback.xml日志是为了做定制化:

情况1:比如下面这个:当出现Error错误异常时才会打印输出

输出:

情况2:

输出:

总结如下:

<?xml version="1.0" encoding="UTF-8"?>
<!--整个文件的根元素 <configuration-->
<configuration><!-- name:定义了一个名为"STDOUT"的日志附加器(appender) ,用于控制台的输出class:指定了附加器的实现类,表示将日志消息输出到控制台--><appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender"><!--定义了附加器的编码器,用于格式化输出的日志消息--><encoder><!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符--><pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern></encoder></appender><!--自定义日志输出器:输出com.messi包及其子包下所有DEBUG日志级别及其以上的日志信息。并且不向上传递,意思就是不进行输出<root相关的日志输出--><logger name="com.messi" level="DEBUG" additivity="false"><!--控制台输出--><appender-ref ref="STDOUT"/></logger><!--root日志输出器:输出除com.messi包及其子包之外所有包下的日志输出,并且日志输出级别是DEBUG及其以上--><root level="DEBUG"><!--控制台输出--><appender-ref ref="STDOUT"/></root></configuration>
<!--
使用logback日志的步骤
1.引入logback依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.12</version>
</dependency>
2.引入该logback.xml配置文件3.安装idea相关插件:log support4.logback相关快捷键:logd logi 等等-->

9.EventLoop(NioEventLoop)

9.1 作用

EventLoop翻译过来就是事件循环。

事件就是我们之前说的那些NIO监听的事件,比如ACCEPT以及IO事件:READ或WRITE

循环就是我们之前在NIO的时候的那个死循环,while循环里面不断的监听事件的触发

NioEventLoop由NioEventLoopGroup管理,它对应到后续Reactor模型中的worker线程或boss线程

NioEventLoop就是死循环监控事件的触发,真正的处理交给Handler去做。如何监控的?肯定是由每一个NioEventLoop专属的Selector监控器监管呀,把Channel注册到Selector上,然后Selector监控Channel上可能触发的事件。

9.2 类结构图

我们可以看到NioEventLoop这个类继承了两个类,一个是SingleThreadEventExecutor。另外一个是EventLoop。

SingleThreadEventExecutor:

这个是单线程的线程池,实际上这里面就是new了一个Thread,框架里面喜欢封装一下,顺便提供了很多内置功能,做了增强。

这其实就是我们之前在worker或Boss里面new的那个线程。可见框架也是这么做的。

EventLoop:

这里面就是处理所有的IO操作,也就是我们之前那个while死循环,然后去处理IO操作

总结:

可见这个NioEventLoop就是我们之前的worker或boss线程,当然了每一个NioEventLoop对应一种角色,但是我们这个NioEventLoopGroup分组,里面有多个NioEventLoop,由NioEventLoopGroup去管理NioEventLoop。这多个NioEventLoop,他们逮到哪个事件操作就处理哪一个,就充当什么角色,比如说:当逮到accept时,该处理accept的NioEventLoop就充当boss线程的角色。当逮到IO事件(read或write)时,对应处理的NioEventLoop就充当worker线程的角色。

9.3 如何使用EventLoop?

NioEventLoop的构造方法:

常见的创建对象的方式就是new,也就是使用构造函数创建,但是NioEventLoop不可以new创建。因为它的构造方法不是通过public修饰的,它的修饰符为default。

如果一个方法没有public修饰符而是default,那么只能在声明该方法的包中去访问它。这意味着其他包的类无法访问该方法。你发现没,这里说的是"方法",构造方法也是一种方法,所以同理即可。所以这个限制导致我们无法在NioEventLoop这个类以外的包去创建这个类,那么我们开发的时候就很容易就不在同一个包,所以人家Netty本来就不想让我们去创建。

那么应该如何创建呢?

创建的方法使用的是NioEventLoopGroup去创建。于是就引出了后续研究NioEventLoopGroup。

10.EventLoopGroup(NioEventLoopGroup)

1.EventLoopGroup就是编程角度开放出来的编程接口,其实就是一个NioEventLoopGroup

2.我们上面看到的EventLoop是一个只有一个线程的线程池,也就是单线程,但是EventLoopGroup是一个组,也就是里面有多个线程。你可以理解为EventLoopGroup是EventLoop的池化,也就是工厂。EventLoopGroup来创建并且管理多个EventLoop。

  • NioEventLoopGroup类图

  • EventLoopGroup这个类的构造方法

我们看到它其实是一个能继承的方法,也就是不断给子类继承最后成为public,不管怎么说,它是能new出来的。而我们看到new的时候可以通过参数指定这个组里面有几个线程,而内部逻辑是一个三目运算:如果你没指定具体线程数,那么就是0,newThreads == 0为true,那么值就是DEFAULT_EVENT_LOOP_THREADS。如果你要是指定了,newThreads==0为false,那么就按照你的参数来。

DEFAULT_EVENT_LOOP_THREADS初始化的值如下:

DEFAULT_EVENT_LOOP_THREADS初始化值就是取1和SystemPropertyUtil.getInt("io.netty.eventLoopThreads",NettyRuntime.availables()*2)的最大值,也就是说你要是在启动的时候设置了jvm参数,指定了io.netty.eventLoopThreads他就会读取你这个指定,要是你没指定,那么就是当前你电脑CPU处理器核数*2,这里的核数是指逻辑核数,并不是你真的有8个或16个CPU!为什么虚拟化出多个核?其实就是为了线程并行能力的提升,并且也进一步提升并发的性能。

10.1 测试EventLoopGroup

  • 通过上述分析,我们来对比之前的代码

但是有两个问题:

1.可能Netty获取到的CPU核数不是所在计算机的精确核数,可能你有8个核,实际上Netty按照二倍去开启线2程数

2.为什么有时候我们通过指定JVM参数去初始化线程数呢?

因为现在服务都docker容器化,一旦容器化上云,一切变得不确定,可能获取到的核数极其不精确,所以在这些场景下,我们通过JVM参数进行指定。

11.NioEventLoop VS DefaultEventLoopGroup

这两个都是EventLoop,到底有啥不同呢?我们说EventLoop本质是一个单线程的线程池,那么他们的区别自然要去看这个单线程的里面的run方法的实现逻辑。我们去看下DefaultEventLoop和NioEventLoop的run方法,如下:

DefaultEventLoop:

NioEventLoop:

所以区别为:

1.NioEventLoop是一个线程,处理IO事件操作。处理write,read事件监控的

2.DefaultEventLoop就是一个简单普通的线程,就是执行你提交的任务的

后续如果在netty环境中进行多线程开发的时候,要优先使用DefaultEventLoop而不是自己去开线程,线程池,因为DefaultEventLoop是netty体系中的一员,使用它更好的和netty融合。但是本质上它就是线程池,你使用JDK的也没事,只不过和netty更好的整合了。

  • 测试

11.1 注意点1

EventLoop会绑定Channel,并且同一EventLoop支持多个Channel访问

1.EventLoop会绑定Channel

EventLoop实际上就是一个线程,每个线程里面封装了一个Selector,所以它能监控到channel,如果客户端1理解过去,加入是EventLoop1处理的,那么以后客户端1的IO事件操作,就都是由这个EventLoop1来进行处理

2.EventLoop可以支持多个Channel访问

假如我们的eventLoopGroup里面只有两个eventLoop,但是客户端却有三个连接,那么一个EventLoop。实际上是可以处理多个channel的,也就是他还是能处理够的。

debug检验:

当一个客户端与服务端建立连接后,进行第一次交互(读或写)后,服务端会开启一个EventLoop处理这次交互!此时该EventLoop就与该交互数据的通道SocketChannel绑定了,当下一次相同的客户端使用该相同的SocketChannel与服务端交互数据时,服务端还是该EventLoop进行处理。ofcourse,当客户端数量过多,导致服务端EventLoop不够用的时候,同一个EventLoop可以复用处理多个客户端的操作。

细节:

11.2 注意点2

我们前面说的R模型是一个主从模型,我们说主也就是boss,它只处理一个连接事件,所以就很简单,消耗少,所以只需要少量的线程,一般一个就行了,一个线程当主Boss

而从worker是用来处理IO事件的,这个相对是耗时的,所以他会有多个,用来处理多个客户端连接后对应的IO事件

但是我们上面目前serverBootstrap.group(new NioEventLoopGroup());就这一句话,里面创建了16个线程,但是并没有区分boss还是worker,好像内部线程谁都能干boss或worker的。

但是从业务角度区分,netty也是提供这种设计的,所以我们可以如下进行设计代码:

package com.messi.netty_core_02.netty02;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyReactorServer {private static final Logger log = LoggerFactory.getLogger(MyNettyReactorServer.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);//创建boss线程对应的EventLoopGroup,用来处理accept事件,线程数一个就够了//如果你不显示指定线程数,那么NioEventLoopGroup默认创建的线程数为:JVM参数指定值 或 Netty计算出本机CPU核心数*2NioEventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(1);//创建worker线程对应的EventLoopGroup,用来处理客户端连接后对应的所有IO事件(read或write)NioEventLoopGroup workerEventLoopGroup = new NioEventLoopGroup(8);/*** 提供一个两个参数的重载方法,* EventLoopGroup parentGroup,EventLoopGroup childGroup* parentGroup就是boss线程的,用来处理连接* childGroup就是worker线程的,用来处理IO事件*/serverBootstrap.group(bossEventLoopGroup,workerEventLoopGroup);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("读取到的数据为:{}",msg);}});}});//服务端监听一个端口serverBootstrap.bind(8000);}}
package com.messi.netty_core_02.netty02;import io.netty.bootstrap.Bootstrap;
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.StringEncoder;import java.net.InetSocketAddress;public class MyNettyReactorClient {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(new NioEventLoopGroup());bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());}});//异步开启一个新线程ChannelFuture future = bootstrap.connect(new InetSocketAddress(8000));//阻塞到该异步新线程执行任务完毕future.sync();Channel channel = future.channel();channel.writeAndFlush("hello,netty!");System.out.println("MyNettyReactorClient.main");}}

11.3 注意点3

worker线程的阻塞问题

存在一个问题:假如我们现在有一个Boss线程,两个worker线程。在这个设计中,boss用来负责处理连接的事件。而worker负责处理IO事件(read或write事件)。但是在实际开发的过程中,我们还有另外一种场景需要线程处理,即核心业务功能,这是非IO事件,是极其耗时 阻塞的一种业务处理。你如果这个时候把该操作交给worker线程去处理,那么当一个worker线程处理该耗时的非IO事件(业务处理),该worker线程就阻塞了,它无法再去处理新的客户端连接后的IO事件!所以这不太好,此时我们就搞一种新的设计:

当一个客户端请求连接服务端时,首先使用boss线程处理客户端连接事件,然后使用worker线程处理IO事件(read或write),当客户端还需要进行核心耗时的业务操作时,worker线程接收到后,会异步开启一个新线程DefaultEventLoop去处理该耗时的非IO非连接操作。为了提升客户端的并发性能,worker线程可以直接返回一个确认给客户端,客户端收到后会接着执行客户端自己下面的逻辑而无需等待该业务处理完。此时我们提升了两个点:1.服务端不会阻塞worker线程处理新的IO事件 2.客户端先接收到服务端的确认,不等待,继续执行下面的业务逻辑。当服务端的DefaultEventLoop执行完后通过回到函数机制把结果传递给客户端。ofcourse,DefaultEventLoop一定会保留客户端的一些信息的。

DefaultEventLoop具体是怎么操作的?

经过前面boss线程(NioEventLoop)处理连接,worker线程(NioEventLoop)处理IO事件,最后通过Handler处理器获取到待处理的数据,然后使用DefaultEventLoop进行处理这非IO操作(非IO操作对应的数据就是Handler获取到的待处理数据)。这样该非IO操作的处理就逃离了NioEventLoop的处理。

为什么使用DefaultEventLoop去充当一个新线程,而不是使用JDK的线程池?

1.简单好用,无需自己手动创建。

2.与netty体系完美融合

  • 代码
package com.messi.netty_core_02.netty02;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.DefaultEventLoopGroup;
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 org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyReactorServer2 {private static final Logger log = LoggerFactory.getLogger(MyNettyReactorServer2.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.channel(NioServerSocketChannel.class);NioEventLoopGroup bossEventLoopGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerEventLoopGroup = new NioEventLoopGroup(8);//这个和NioEventLoopGroup底层调用的是同一个构造方法DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.group(bossEventLoopGroup,workerEventLoopGroup) ;serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringDecoder());ch.pipeline().addLast(defaultEventLoopGroup,new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("服务端读取到的结果为:{}",msg);}});}});serverBootstrap.bind(8000);}}
package com.messi.netty_core_02.netty02;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
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.StringEncoder;import java.net.InetSocketAddress;public class MyNettyReactorClient2 {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap() ;bootstrap.channel(NioSocketChannel.class);bootstrap.group(new NioEventLoopGroup());bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());}});ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));connect.sync();Channel channel = connect.channel();channel.writeAndFlush("hello,netty");}}
  • 测试

服务端输出:

我们看到一共出现三种线程:

1.main线程

这是初始化ServerBootstrap还有那些group以及添加Handler处理器等主流程的执行

2.nioEventLoopGroup-3-1线程

这个就是处理的事件。包括像连接事件,IO事件的处理。

3,defaultEventLoopGroup-4-1线程

上述的nioEventLoopGroup-3-1线程(worker线程)只是接入了业务操作事件,而真正的业务操作(handler里面的操作),交给了defaultEventLoopGroup线程去异步处理,此时NioEventLoop只是做了一个接管,,但是拿到I之后的事情将会交给异步线程DefaultEventLoop去做。

defaultEventLoopGroup-4-1线程这个就是defaultEventLoop用来做异步处理的,处理了处理器里面的业务,输出了hello,netty。这就是netty整合进去帮你绑定DefaultEventLoopGroup了,在源码中封装了。

11.4 Netty中Reactor模型总结

当有一个客户端与服务端连接,然后交互。具体过程如下:服务端使用一个NioEventLoop(Boss线程)接收一个客户端的连接并且处理,处理完连接后,Boss线程把该客户端后续的操作全盘交出,另外一个NioEventLoop(Worker线程)进行处理该客户端发生的IO事件(读或写的操作)。但是如果该客户端想要执行一些业务操作(非IO操作),那么NioEventLoop(worker线程)会通过Handler处理器把该执行该业务所需要的业务数据交给一个DefaultEventLoop(异步开启的一个新线程),该新线程负责处理该客户端的非IO事件(业务操作)。这样异步开启一个新线程处理耗时的业务操作(非IO操作),可以完美的把NioEventLoop(worker线程)解放,NioEventLoop只需要专注于处理IO事件,而无需处理耗时的业务操作,是不是可以有更多的时间去处理新客户端的IO事件,提高了并发访问性能,提高了系统的吞吐性能。

还有一个问题:客户端请求服务端一个业务操作(非IO),NioEventLoop(worker线程)接收到后会立即返回一个确认,然后异步开启一个新线程(DefaultEventLoop)去处理该业务操作,当异步处理完后,通过回调机制再把结果传递给客户端。如何把结果回调传给客户端的?其实就是保留了客户端的一些信息。

相关文章:

Netty应用(五) 之 Netty引入 EventLoop

目录 第三章 Netty 1.什么是Netty&#xff1f; 2.为什么需要使用Netty&#xff1f; 3.Netty的发展历程 4.谁在使用Netty&#xff1f; 5.为什么上述这些分布式产品都使用Netty&#xff1f; 6.第一个Netty应用 7.如何理解Netty是NIO的封装 8.logback日志使用的加强 9.Ev…...

【c++基础】国王的魔镜

说明 国王有一个魔镜&#xff0c;可以把任何接触镜面的东西变成原来的两倍——只是&#xff0c;因为是镜子嘛&#xff0c;增加的那部分是反的。 比如一条项链&#xff0c;我们用AB来表示&#xff0c;不同的字母表示不同颜色的珍珠。如果把B端接触镜面的话&#xff0c;魔镜会把…...

配置DNS正反向解析服务!!!!

一.准备工作 #关闭防火墙和selinux,或者允许服务通过 [rootnode ~]# nmcli c mod ens32 ipv4.method manual ipv4.address 192.168.32.133/24 ipv4.gateway 192.168.32.2 ipv4.dns 192.168.32.132 [rootnode ~]# nmcli c reload [rootnode ~]# nmcli c up ens32[rootnode ~]# …...

大模型2024规模化场景涌现,加速云计算走出第二增长曲线

导读&#xff1a;2024&#xff0c;大模型第一批规模化应用场景已出现。 如果说“百模大战”是2023年国内AI产业的关键词&#xff0c;那么2024年我们将正式迈进“应用为王”的新阶段。 不少业内观点认为&#xff0c;2024年“百模大战”将逐渐收敛甚至洗牌&#xff0c;而大模型在…...

Gitlab和Jenkins集成 实现CI (三)

Gitlab和Jenkins集成 实现CI (一) Gitlab和Jenkins集成 实现CI (二) Gitlab和Jenkins集成 实现CI (三) 自动部署 配置免密ssh 进入http服务器 生成ssh密钥 ssh-keygen -t rsa进入jenkins(容器) 拷贝公钥 ssh-copy-id http服务器用户名http服务器ip #输入http服务器密码配…...

随机过程及应用学习笔记(二)随机过程的基本概念

随机过程论就是研究随时间变化的动态系统中随机现象的统计规律的一门数学学科。 目录 前言 一、随机过程的定义及分类 1、定义 2、分类 二、随机过程的分布及其数字特征 1、分布函数 2、数字特征 均值函数和方差函数 协方差函数和相关函数 3、互协方差函数与互相关函…...

【机器学习】Kmeans如何选择k值

确定 K 值是 K-means 聚类分析的一个重要步骤。不同的 K 值可能会产生不同的聚类结果,因此选择合适的 K 值非常重要。 以下是一些常见的方法来选择 K 值: 手肘法:该方法基于绘制聚类内误差平方和(SSE)与 K 值之间的关系图。随着 K 值的增加,SSE会逐渐降低,但降低幅度逐…...

LeetCode 热题 100 | 链表(下)

目录 1 148. 排序链表 2 23. 合并 K 个升序链表 3 146. LRU 缓存 3.1 解题思路 3.2 详细过程 3.3 完整代码 菜鸟做题第三周&#xff0c;语言是 C 1 148. 排序链表 解题思路&#xff1a; 遍历链表&#xff0c;把每个节点的 val 都存入数组中用 sort 函数对数组进…...

Ubuntu搭建计算集群

计算机硬件和技术的发展使得高性能模拟和计算在生活和工作中的作用逐渐显现出来&#xff0c;无论是计算化学&#xff0c;计算物理和当下的人工智能都离不开高性能计算。笔者工作主要围绕计算化学和物理开展&#xff0c;亦受限于自身知识和技术所限&#xff0c;文中只是浅显地尝…...

数据结构~~树(2024/2/8)

目录 树 1、定义&#xff1a; 2、树的基本术语&#xff1a; 3、树的表示 树 1、定义&#xff1a; 树是一种非线性的数据结构&#xff0c;它是由n&#xff08;n>0&#xff09;个有限结点组成一个具有层次关系的集合。把它叫做树是因为它看起来像一棵倒挂的树&…...

【教学类-48-03】202402011“闰年”(每4年一次 2月有29日)世纪年必须整除400才是闰年)

2000-2099年之间的闰年有25次&#xff0c; 背景需求&#xff1a; 已经制作了对称年月的数字提取&#xff0c;和年月日相等的年份提取 【教学类-48-01】20240205对称的“年”和“月日”&#xff08;如2030 0302&#xff09;-CSDN博客文章浏览阅读84次。【教学类-48-01】202402…...

如何开发一个属于自己的人工智能语言大模型?

要开发一个属于自己的人工智能语言模型&#xff0c;你需要遵循以下步骤&#xff1a; 数据收集&#xff1a;首先你需要大量的文本数据来训练你的模型。这些数据可以来自于各种来源&#xff0c;例如书籍、网站、新闻文章等。你需要确保这些数据足够多样化&#xff0c;以便模型能学…...

【HTTP】localhost和127.0.0.1的区别是什么?

目录 localhost是什么呢&#xff1f; 从域名到程序 localhost和127.0.0.1的区别是什么&#xff1f; 域名的等级划分 多网站共用一个IP和端口 私有IP地址 IPv6 今天在网上逛的时候看到一个问题&#xff0c;没想到大家讨论的很热烈&#xff0c;就是标题中这个&#xff1a; …...

Edge浏览器-常用快捷键

按键组合作用Ctrl Shift I开发人员工具Ctrl E定位到 空地址栏Ctrl L定位到 地址栏Ctrl Shift B显示或隐藏 收藏夹栏Ctrl Shift O打开收藏夹(搜索)Ctrl T打开一个新标签页Ctrl W关闭当前标签页Ctrl Shift T重新打开刚才关闭的标签页Ctrl Tab切换到下一个标签页Ctrl…...

C++:Vector动态数组的copy深入理解

动态数组分配的大小默认为2的n次方1&#xff0c;2&#xff0c;4&#xff0c;8... 在main中创建的vertices&#xff0c;push需要放到Vertex中&#xff08;copy&#xff09;&#xff0c;下一次copy是因为要调整vertices的大小 vertices.push_back(Vertex(1,2,3));//拷贝 第一次&a…...

【PyTorch】PyTorch中张量(Tensor)切片操作

PyTorch深度学习总结 第三章 PyTorch中张量(Tensor)切片操作 文章目录 PyTorch深度学习总结一、前言二、获取张量中的元素1、切片&#xff08;行、列数&#xff09;方法2、torch.where()函数3、使元素置零的操作 一、前言 上文介绍了PyTorch中改变张量(Tensor)形状的操作&…...

GeoServer 2.11.1升级解决Eclipse Jetty 的一系列安全漏洞问题

Eclipse Jetty 资源管理错误漏洞(CVE-2021-28165) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7656) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7657) Eclipse Jetty HTTP请求走私漏洞(CVE-2017-7658) Jetty 信息泄露漏洞(CVE-2017-9735) Eclipse Jetty 安全漏洞(CVE-2022-20…...

【蓝桥杯选拔赛真题34】C++最大值 第十三届蓝桥杯青少年创意编程大赛C++编程选拔赛真题解析

目录 C/C++最大值 一、题目要求 1、编程实现 2、输入输出...

STM32之USART

概述 串口通信&#xff0c;通用异步收发传输器&#xff08;Universal Asynchronous Receiver/Transmitter &#xff09;&#xff0c;简称UART&#xff1b;而USART&#xff08;Universal Synchronous/Asynchronous Receiver/Transmitter&#xff09;通用同步收发传输器。 USAR…...

unity 点击事件

目录 点击按钮&#xff0c;显示图片功能教程 第1步添加ui button&#xff0c;添加ui RawImage 第2步 添加脚本&#xff1a; 第3步&#xff0c;把脚本拖拽到button&#xff0c;点击button&#xff0c;设置脚本的变量&#xff0c; GameObject添加 Component组件 点击按钮&am…...

idea自带的HttpClient使用

1. 全局变量配置 {"local":{"baseUrl": "http://localhost:9001/"},"test": {"baseUrl": "http://localhost:9002/"} }2. 登录并将结果设置到全局变量 PostMapping("/login")public JSONObject login(H…...

vue3-应用规模化-路由和状态

客户端 vs. 服务端路由 服务端路由指的是服务器根据用户访问的 URL 路径返回不同的响应结果。当我们在一个传统的服务端渲染的 web 应用中点击一个链接时&#xff0c;浏览器会从服务端获得全新的 HTML&#xff0c;然后重新加载整个页面。 然而&#xff0c;在单页面应用中&…...

网络安全检查表

《网络攻击检查表》 1.应用安全漏洞 2.弱口令&#xff0c;默认口令 3.服务器互联网暴露 4.操作系统&#xff0c;中间件安全漏洞 5.研发服务器&#xff0c;邮件服务器等安全检查...

SSM框架,Maven的学习(下)

依赖传递和依赖冲突 依赖传递指的是当一个模块或库 A 依赖于另一个模块或库 B&#xff0c;而 B 又依赖于模块或库 C&#xff0c;那么 A 会间接依赖于 C。这种依赖传递结构可以形成一个依赖树。当我们引入一个库或框架时&#xff0c;构建工具&#xff08;如 Maven、Gradle&…...

Vivado开发FPGA使用流程、教程 verilog(建立工程、编译文件到最终烧录的全流程)

目录 一、概述 二、工程创建 三、添加设计文件并编译 四、线上仿真 五、布局布线 六、生成比特流文件 七、烧录 一、概述 vivado开发FPGA流程分为创建工程、添加设计文件、编译、线上仿真、布局布线&#xff08;添加约束文件&#xff09;、生成比特流文件、烧录等步骤&a…...

C语言之动态内存管理

目录 1. 为什么要有动态内存分配2. malloc和freemallocfree 3. calloc和realloccallocrealloc 4. 常见的动态内存的错误对NULL直接的解引用操作对动态开辟空间的越界访问对非动态开辟内存使用free释放使用free释放一块动态开辟内存的一部分对同一块动态内存多次释放动态开辟内存…...

【AIGC风格prompt深度指南】掌握绘画风格关键词,实现艺术模仿的革新实践

[小提琴家]ASCII风格&#xff0c;点&#xff0c;爆炸&#xff0c;光&#xff0c;射线&#xff0c;计算机代码 由冰和水制成的和平标志]非常详细&#xff0c;寒冷&#xff0c;冰冻&#xff0c;大气&#xff0c;照片逼真&#xff0c;流动&#xff0c;16K 胡迪尼模拟火和水&#x…...

Qt安装配置教程windows版(包括:Qt5.8.0版本,Qt5.12,Qt5.14版本下载安装教程)(亲测可行)

目录 Qt5.8.0版本安装教程Qt5.8.0版本下载安装 Qt5.12.2版本安装教程下载安装 Qt 5.14.2安装教程下载安装和创建项目 参考视频 QT为嵌入式系统提供了大量的库和可重用组件。 WPS Office&#xff0c;咪咕音乐&#xff0c;Linux桌面环境等都是QT开发的。 Qt5.8.0版本安装教程 Q…...

SpringCloud-Ribbon实现负载均衡

在微服务架构中&#xff0c;负载均衡是一项关键的技术&#xff0c;它可以确保各个服务节点间的负载分布均匀&#xff0c;提高整个系统的稳定性和性能。Spring Cloud 中的 Ribbon 就是一种负载均衡的解决方案&#xff0c;本文将深入探讨 Ribbon 的原理和在微服务中的应用。 一、…...

Qt网络编程-TCP与UDP

网络基础 TCP与UDP基础 关于TCP与UDP的基础这里就不过多介绍了&#xff0c;具体可以查看对应百度百科介绍&#xff1a; TCP&#xff08;传输控制协议&#xff09;_百度百科 (baidu.com) UDP_百度百科 (baidu.com) 需要知道这两者的区别&#xff1a; 可靠性&#xff1a; TC…...

Promise 常见题目

微信搜索“好朋友乐平”关注公众号。 1. Promise 对象池 请你编写一个异步函数 promisePool &#xff0c;它接收一个异步函数数组 functions 和 池限制 n。它应该返回一个 promise 对象&#xff0c;当所有输入函数都执行完毕后&#xff0c;promise 对象就执行完毕。 池限制 定…...

五大架构风格之五:仓库架构风格

仓库架构风格&#xff1a; 仓库风格架构&#xff08;Repository Architecture Style&#xff09;是一种软件架构模式&#xff0c;它主要用于处理系统中的持久化数据存储和检索。在这一风格中&#xff0c;仓库&#xff08;Repository&#xff09;作为应用程序与数据库或其他持久…...

探索设计模式的魅力:外观模式简化术-隐藏复杂性,提供简洁接口的设计秘密

设计模式专栏&#xff1a;http://t.csdnimg.cn/U54zu 目录 引言&#xff1a;探索简化之路 一、起源和演变 二、场景案例分析 2.1 不用模式实现&#xff1a;用一坨坨代码实现 2.2 问题 2.3 外观模式重构代码 定义 界面 接口 利用外观模式解决问题步骤 外观模式结构和说明 重构…...

java之Maven

1. maven Maven是管理和构建java项目的工具 项目依赖资源(jar包)的管理,避免版本冲突统一项目结构项目构建&#xff0c;标准跨平台(Linux,window,MacOS)的自动化项目管理 2.maven依赖仓库 2.maven安装 maven安装视频教程 3. IDEA集成Maven 4. maven的依赖范围 5. maven生命…...

Elasticsearch(四)

是这样的前面的几篇笔记&#xff0c;感觉对我没有形成知识体系&#xff0c;感觉乱糟糟的&#xff0c;只是大概的了解了一些基础知识&#xff0c;仅此而已&#xff0c;而且对于这技术栈的学习也是为了在后面的java开发使用&#xff0c;但是这里的API学的感觉有点乱&#xff01;然…...

蓝桥杯-X图形

问题描述 给定一个字母矩阵。一个 X 图形由中心点和由中心点向四个 45度斜线方向引出的直线段组成&#xff0c;四条线段的长度相同&#xff0c;而且四条线段上的字母和中心点的字母相同。 一个 X 图形可以使用三个整数 r,c,L 来描述&#xff0c;其中 r,c 表示中心点位于第 r 行…...

2. Maven 继承与聚合

目录 2. 2.1 继承 2.2继承关系 2.2.1 思路分析 2.2.2 实现 2.1.2 版本锁定 2.1.2.1 场景 2.1.2.2 介绍 2.1.2.3 实现 2.1.2.4 属性配置 2.2 聚合 2.2.1 介绍 2.2.2 实现 2.3 继承与聚合对比 maven1&#xff1a;分模块设计开发 2. 在项目分模块开发之后啊&#x…...

如何把手机平板变为电脑的屏幕

文章目录 安装软件运行效果结尾 本文首发地址 https://h89.cn/archives/181.html 最新更新地址 https://gitee.com/chenjim/chenjimblog 闲置的手机平板、触屏音箱等&#xff0c;均可作为电脑的扩展屏&#xff0c;为电脑增加一块显示屏&#xff0c;本文介绍如何使用免费的软件s…...

Amazon Dynamo学习总结

目录 一、Amazon Dynamo的问世 二、Amazon Dynamo主要技术概要 三、数据划分算法 四、数据复制 五、版本控制 六、故障处理 七、成员和故障检测 一、Amazon Dynamo的问世 Amazon Dynamo是由亚马逊在2007年开发的一种高度可扩展和分布式的键值存储系统&#xff0c;旨在解…...

appium抓包总结

appium抓包总结 背景&#xff1a;有些app通过抓包工具charles等抓不到接口数据&#xff0c;应为这一类抓包工具只能抓到应用层的数据包&#xff0c;而某些app的接口是走的传输层协议&#xff0c;所以此时只能通过AppIUM工具来进行抓包。 1、Appium 是什么&#xff1f; Appium…...

arcgis各种版本下载

arcgic 下载&#xff01;&#xff01;&#xff01; ArcGIS是一款地理信息系统软件&#xff0c;由美国Esri公司开发。它提供了一系列完整的GIS功能&#xff0c;包括地图制作、空间数据管理、空间分析、空间信息整合、发布与共享等。ArcGIS是一个可扩展的GIS平台&#xff0c;提供…...

第五篇:MySQL常见数据类型

MySQL中的数据类型有很多&#xff0c;主要分为三类:数值类型、字符串类型、日期时间类型 三个表格都在此网盘中&#xff0c;需要者可移步自取&#xff0c;如果觉得有帮助希望点个赞~ MySQL常见数据类型表 数值类型 &#xff08;注&#xff1a;decimal类型举例&#xff0c;如1…...

Oracle用BETWEEN AND查某年的数据可能会丢失条数

随便找一张有日期&#xff08;字段类型为DATE&#xff09;的表即可测试。 假设存在这样一张表HOLIDAY&#xff0c;里面存储的是某些国家(表字段为COUNTRY_CODE)某些年的法定假日日期(表字段为HOLIDAY_DATE)。 我想查中国在2023年和2024年的法定假日日期。 BETWEEN AND 首先想…...

Nuscenes数据集点云数据如何转换到图像上

零、概要 注意&#xff1a;该文章是手写ai自动驾驶&#xff0c;Nuscenes数据集的笔记。 首先&#xff0c;学习需要使用到 nuScenes 数据集。python 工具需要使用到 nuscenes-devkit、pyquaternion from nuscenes.nuscenes import NuScenes from pyquaternion import Quatern…...

【C语言期末】商品管理系统

本文资源&#xff1a;https://download.csdn.net/download/weixin_47040861/88820155 1.题目要求 商品管理系统 商品信息包括&#xff1a;包括编号、类别、名称、价格、折扣比例、生产时间 、存货数量等要求&#xff1a;1、信息首先保存在文件中&#xff0c;然后打开文件进行…...

单片机学习笔记---串口通信(2)

目录 串口内部结构 串口相关寄存器 串口控制寄存器SCON SM0和SM1 SM2 REN TB8和RB8 TI和RI 电源控制寄存器PCON SMOD 串口工作方式 方式0 方式0输出&#xff1a; 方式0输入 方式1 方式1输出。 方式1输入 方式2和方式3 方式2和方式3输出&#xff1a; 方式2和…...

【Java】乐观锁有哪些常见实现方式?

Java中的乐观锁主要有两种常见的实现方式&#xff1a; CAS&#xff08;Compare and Swap&#xff09;&#xff1a;这是实现乐观锁的核心算法。CAS操作包含三个参数&#xff1a;内存地址V、旧的预期值A和要修改的新值B。执行CAS操作时&#xff0c;会先比较内存地址V中的值是否等…...

Javaweb之SpringBootWeb案例之登录校验功能的详细解析

2. 登录校验 2.1 问题分析 我们已经完成了基础登录功能的开发与测试&#xff0c;在我们登录成功后就可以进入到后台管理系统中进行数据的操作。 但是当我们在浏览器中新的页面上输入地址&#xff1a;http://localhost:9528/#/system/dept&#xff0c;发现没有登录仍然可以进…...

CSS之盒模型

盒模型概念 浏览器盒模型&#xff08;Box Model&#xff09;是CSS中的基本概念&#xff0c;它描述了元素在布局过程中如何占据空间。盒模型由内容&#xff08;content&#xff09;、内边距&#xff08;padding&#xff09;、边框&#xff08;border&#xff09;、和外边距&…...

博客系统-SpringBoot版本

相比于之前使用Servlet来完成的博客系统&#xff0c;SpringBoot版本的博客系统功能更完善&#xff0c;使用到的技术更接近企业级&#xff0c;快来看看吧~ 目录 1.项目介绍 2.数据库准备 3.实体化类 4.返回格式 5.登录和注册功能 6.登出&#xff08;注销&#xff09;功能…...