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

Read book Netty in action(Chapter VII)--ChannelHandler和ChannelPipeline

序言

我们曾经学过了ByteBuf – netty的数据容器,还有ChannelHandler和ChannelPipeline,这一把将他们组合起来,这些组件的交互正是Netty的灵魂所在!

ChannelHanlder家族

详细地学习ChannelHanlder之前,我们将在Netty的组件模型的这部门基础上花费一点时间,有可能我会去啃一下源码。

Channel的生命周期

Channel接口提供了一组和ChannelInBoundHandler API相关的但是功能强大的组件模型。Channel有如下4个状态:
1.ChannelUnregistered Channel已经被创建,但是还没有注册到EventLoop上。
2.ChannelRegistered已经成功注册在EventLoop上。
3,.ChannelActive Channel 处于活动状态(已经连接到它的远程节点)。它现在可以接收和发送数据了
4.ChannelInactive Channel 没有连接到远程节点

生命周期是:ChannelRegistered -> ChannelActive -> ChannelInactive -> ChannelUnregistered 当这些状态发生变化时,这些事件会被转化给ChannelPipeline中的ChannelHandler,其可以随后对它们做出
响应。

ChannelHandler 的生命周期

ChannelHandler作为ChannelPipeline的子链,将其添加或者删除都应该可以插入事件,正如监听一个集合,向集合中插入或者删除元素而触发事件一样。这些
方法中的每一个都接受一个ChannelHandlerContext 参数。正如集合在Context环境下,做一些操作一下。当然了,插入错误或者删除的时候出现了线程问题,都应该有一个兜底的事件,而这些事件都应该由调用者决定。

handlerAdded 当把ChannelHandler 添加到ChannelPipeline 中时被调用
handlerRemoved 当从ChannelPipeline 中移除ChannelHandler 时被调用
exceptionCaught 当处理过程中在ChannelPipeline 中有错误产生时被调用

Netty 定义了下面两个重要的ChannelHandler 子接口:
ChannelInboundHandler——处理入站数据以及各种状态变化;
ChannelOutboundHandler——处理出站数据并且允许拦截所有的操作。

ChannelInboundHandler 接口

这些方法将会在数据被接收时或者与其对应的Channel 状态发生改变时被调用。
这些方法与Channel的生命周期密切相关。

channelRegistered 当channel注册到EventLoop上的时候调用。
channelUnregistered 当channel从EvntLoop上注销的时候调用。
channelActive 当channel处于活跃状态的时候调用。
channelInactive 当Channel 离开活动状态并且不再连接它的远程节点时被调用。
channelReadComplete 当Channel上的一个读操作完成时被调用
channelRead 当Channel读取数据时调用
ChannelWritabilityChanged 当Channel 的可写状态发生改变时被调用。用户可以确保写操作不会完成得太快(以避免发生OutOfMemoryError)或者可以在Channel 变为再次可写时恢复写入。可以通过调用Channel 的isWritable()方法来检测Channel 的可写性。与可写性相关的阈值可以通过Channel.config().setWriteHighWaterMark()和Channel.config().setWriteLowWaterMark()方法来设置
userEventTriggered 当ChannelnboundHandler.fireUserEventTriggered()方法被调
用时被调用,因为一个POJO 被传经了ChannelPipeline

当某个ChannelInboundHandler 的实现重写channelRead()方法时,它将负责显式地
释放与池化的ByteBuf 实例相关的内存。Netty 为此提供了一个实用方法ReferenceCountUtil.release()。

public class DiscardHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// discard the msgReferenceCountUtil.release(msg);}
}

但是如果你继承了SimpleChannelInboundHandler,并不需要手动释放,因为他会自动的去释放内存。

ChannelOutboundHandler 接口

出站操作和数据将由ChannelOutboundHandler 处理。它的方法将被Channel、ChannelPipeline 以及ChannelHandlerContext 调用。

ChannelOutboundHandler 的一个强大的功能是可以按需推迟操作或者事件,这使得可以通过一些复杂的方法来处理请求。例如,如果到远程节点的写入被暂停了,那么你可以推迟冲刷操作并在稍后继续。

ChannelHandler 适配器

无论是ChannelInboundHandlerAdapter还是ChannelOutboundHandlerAdapter,他们都有一个超类,就是ChannelHandler(通过扩展抽象ChannelHandlerAdapter)。

ChannelHandlerAdapter 还提供了实用方法isSharable()。如果其对应的实现被标注为Sharable,那么这个方法将返回true,表示它可以被添加到多个ChannelPipeline中。

在ChannelInboundHandlerAdapter 和ChannelOutboundHandlerAdapter 中所提供的方法体调用了其相关联的ChannelHandlerContext 上的等效方法,从而将事件转发到了ChannelPipeline 中的下一个ChannelHandler 中。

你要想在自己的ChannelHandler 中使用这些适配器类,只需要简单地扩展它们,并且重写那些你想要自定义的方法。

资源管理

每当通过调用ChannelInboundHandler.channelRead()或者ChannelOutboundHandler.write()方法来处理数据时,你都需要确保没有任何的资源泄漏。
之前曾经说过,我们的ByteBuf是由计数器的,当我们完全的使用了ByteBuf之后,我们是有必要去调整其引用技术的。
为了帮助你诊断潜在的(资源泄漏)问题,Netty提供了ResourceLeakDetector。它将对你应用程序的缓冲区分配做大约1%的采样来检测内存泄露。相关的开销是非常小的。
Netty 目前定义了4 种泄漏检测级别:
DISABLED 禁用泄漏检测。只有在详尽的测试之后才应设置为这个值
SIMPLE 使用1%的默认采样率检测并报告任何发现的泄露。这是默认级别,适合绝大部分的情况
ADVANCED 使用默认的采样率,报告所发现的任何的泄露以及对应的消息被访问的位置
PARANOID 类似于ADVANCED,但是其将会对每次(对消息的)访问都进行采样。这对性能将会有很大的影响,应该只在调试阶段使用。

实现ChannelInboundHandler.channelRead()和ChannelOutboundHandler.write()方法时,应该如何使用这个诊断工具来防止泄露呢?让我们看看你的channelRead()操作直接消费入站消息的情况;也就是说,它不会通过调用ChannelHandlerContext.fireChannelRead()方法将入站消息转发给下一个ChannelInboundHandler

重要的是,不仅要释放资源,还要通知ChannelPromise。否则可能会出现Channel-
FutureListener 收不到某个消息已经被处理了的通知的情况。总之,如果一个消息被消费或者丢弃了,并且没有传递给ChannelPipeline 中的下一个ChannelOutboundHandler,那么用户就有责任调用ReferenceCountUtil.release()。
如果消息到达了实际的传输层,那么当它被写入时或者Channel 关闭时,都将被自动释放。

ChannelPipeline 接口

每一个新创建的Channel 都将会被分配一个新的ChannelPipeline。这项关联是永久性的;Channel 既不能附加另外一个ChannelPipeline,也不能分离其当前的。在Netty 组件的生命周期中,这是一项固定的操作,不需要开发人员的任何干预。很容易理解,同理一个元素绑定到一个链上。根据事件的起源,事件将会被ChannelInboundHandler 或者ChannelOutboundHandler处理,随后,将会到下一站ChannelHandler上处理。。如果一个入站事件被触发,它将被从ChannelPipeline 的头部开始一直被传播到Channel Pipeline 的尾端。Netty 总是将ChannelPipeline 的入站口作为头部,而将出站口作为尾端。 在ChannelPipeline 传播事件时,它会测试ChannelPipeline 中的下一个ChannelHandler 的类型是否和事件的运动方向相匹配。如果不匹配,ChannelPipeline 将跳过该ChannelHandler 并前进到下一个,直到它找到和该事件所期望的方向相匹配的为止。

修改ChannelPipeline

ChannelHandler 可以通过添加、删除或者替换其他的ChannelHandler 来实时地修改ChannelPipeline 的布局。(它也可以将它自己从ChannelPipeline 中移除。)这是ChannelHandler 最重要的能力之一,所以我们将仔细地来看看它是如何做到的。
addXXX 将一个ChannelHandler 添加到ChannelPipeline 中
remove 将一个ChannelHandler 从ChannelPipeline 中移除
replace 将ChannelPipeline 中的一个ChannelHandler 替换为另一个ChannelHandler
是不是和链表操作很像。其实ChannelPipeline就是ChannelHandler 构成的,这个已经强调很多遍了。

ChannelHandler 的执行和阻塞

通常ChannelPipeline 中的每一个ChannelHandler 都是通过它的EventLoop(I/O 线程)来处理传递给它的事件的。所以至关重要的是不要阻塞这个线程,因为这会对整体的I/O 处理产生负面的影响。但有时可能需要与那些使用阻塞API 的遗留代码进行交互。对于这种情况,ChannelPipeline 有一些接受一个EventExecutorGroup 的add()方法。如果一个事件被传递给一个自定义的EventExecutorGroup。它将被包含在这个EventExecutorGroup 中的某个EventExecutor 所处理,从而被从该
Channel 本身的EventLoop 中移除。对于这种用例,Netty 提供了一个叫DefaultEventExecutorGroup 的默认实现。

触发事件

ChannelPipeline 的API 公开了用于调用入站和出站操作的附加方法。下面是入站操作,用于通知ChannelInboundHandler 在ChannelPipeline 中所发生的事件。可能这些有的会用过。

fireChannelRegistered 调用ChannelPipeline 中下一个ChannelInboundHandler 的
channelRegistered(ChannelHandlerContext)方法
fireChannelUnregistered 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelUnRegistered(ChannelHandlerContext)方法
fireChannelActive 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelActive(ChannelHandlerContext)方法
fireChannelInactive 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelInactive(ChannelHandlerContext)方法
fireExceptionCaught 调用ChannelPipeline 中下一个ChannelInboundHandler 的exceptionCaught(ChannelHandlerContext,Throwable)方法
fireUserEventTriggered 调用ChannelPipeline 中下一个ChannelInboundHandler 的userEventTriggered(ChannelHandlerContext,Object)方法
fireChannelRead 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelRead(ChannelHandlerContext, Object msg)方法
fireChannelReadComplete 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelReadComplete(ChannelHandlerContext)方法
fireChannelWritabilityChanged 调用ChannelPipeline 中下一个ChannelInboundHandler 的channelWritabilityChanged(ChannelHandlerContext)方法
其实不难发现,firexxx指的是调用链结构中的下一个ChannelHandler方法,而xxx就是方法名。这个其实比较简单,一样的,以集合类比,就是 触发下一个元素的事件。

在出站这边,处理事件将会导致底层的套接字上发生一系列的动作。下面是出站操作。

bind 将Channel 绑定到一个本地地址,这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的bind(ChannelHandlerContext, SocketAddress, ChannelPromise)方法

connect 将Channel 连接到一个远程地址,这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的connect(ChannelHandlerContext, SocketAddress, ChannelPromise)方法

disconnect 将Channel 断开连接。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的disconnect(ChannelHandlerContext, Channel Promise)方法

close 将Channel 关闭。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的close(ChannelHandlerContext, ChannelPromise)方法

deregister 将Channel 从它先前所分配的EventExecutor(即EventLoop)中注销。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的deregister
(ChannelHandlerContext, ChannelPromise)方法

flush 冲刷Channel所有挂起的写入。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的flush(ChannelHandlerContext)方法

write 将消息写入Channel。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler的write(ChannelHandlerContext, Object msg, ChannelPromise)方法。注意:这并不会将消息写入底层的Socket,而只会将它放入队列中。要将它写入Socket,需要调用flush()或者writeAndFlush()方法

writeAndFlush 这是一个先调用write()方法再接着调用flush()方法的便利方法

read 请求从Channel 中读取更多的数据。这将调用ChannelPipeline 中的下一个ChannelOutboundHandler 的read(ChannelHandlerContext)方法

总结一下:
ChannelPipeline 保存了与Channel 相关联的ChannelHandler;
ChannelPipeline 可以根据需要,通过添加或者删除ChannelHandler 来动态地修改;
ChannelPipeline 有着丰富的API 用以被调用,以响应入站和出站事件。

ChannelHandlerContext

ChannelHandlerContext 代表了ChannelHandler 和ChannelPipeline 之间的关联,每当有ChannelHandler 添加到ChannelPipeline 中时,都会创ChannelHandlerContext。ChannelHandlerContext 的主要功能是管理它所关联的ChannelHandler 和在同一个ChannelPipeline 中的其他ChannelHandler 之间的交互。
ChannelHandlerContext 有很多的方法,其中一些方法也存在于Channel 和ChannelPipeline 本身上,但是有一点重要的不同。如果调用Channel 或者ChannelPipeline 上的这些方法,它们将沿着整个ChannelPipeline 进行传播。而调用位于ChannelHandlerContext上的相同方法,则将从当前所关联ChannelHandler 开始,并且只会传播给位于该ChannelPipeline 中的下一个能够处理该事件的ChannelHandler。
下面是ChannelHandlerContext的方法
alloc 返回和这个实例相关联的Channel 所配置的ByteBufAllocator
bind 绑定到给定的SocketAddress,并返回ChannelFuture
channel 返回绑定到这个实例的Channel
close 关闭这个实例,并返回ChannelFuture
connect 连接给定的SocketAddress,并返回ChannelFuture
deregister 从之前分配的EventExecutor 注销,并返回ChannelFuture
disconnect 从远程节点断开,并返回ChannelFuture
executor 返回调度事件的EventExecutor
fireChannelActive 触发对下一个ChannelInboundHandler 上的channelActive()方法(已连接)的调用
fireChannelInactive 触发对下一个ChannelInboundHandler 上的channelInactive()方法(已关闭)的调用
fireChannelRead 触发对下一个ChannelInboundHandler 上的channelRead()方法(已接收的消息)的调用
fireChannelReadComplete 触发对下一个ChannelInboundHandler 上的channelReadComplete()方法的调用
fireChannelRegistered 触发对下一个ChannelInboundHandler 上的fireChannelRegistered()方法的调用
fireChannelUnregistered 触发对下一个ChannelInboundHandler 上的fireChannelUnregistered()方法的调用
fireChannelWritabilityChanged 触发对下一个ChannelInboundHandler 上的fireChannelWritabilityChanged()方法的调用
fireExceptionCaught 触发对下一个ChannelInboundHandler 上的fireExceptionCaught(Throwable)方法的调用
handler 返回绑定到这个实例的ChannelHandler
isRemoved 如果所关联的ChannelHandler 已经被从ChannelPipeline中移除则返回true
name 返回这个实例的唯一名称、
pipeline 返回这个实例所关联的ChannelPipeline
read 将数据从Channel读取到第一个入站缓冲区;如果读取成功则触发一个channelRead事件,并(在最后一个消息被读取完成后)通知ChannelInboundHandler 的channelReadComplete(ChannelHandlerContext)方法
write 通过这个实例写入消息并经过ChannelPipeline
writeAndFlush 通过这个实例写入并冲刷消息并经过ChannelPipeline

当使用ChannelHandlerContext 的API 的时候,请牢记以下两点:
ChannelHandlerContext 和ChannelHandler 之间的关联(绑定)是永远不会改变的,所以缓存对它的引用是安全的;
相对于其他类的同名方法,ChannelHandler Context的方法将产生更短的事件流,应该尽可能地利用这个特性来获得最大的性能。

使用ChannelHandlerContext

当每一个ChannelHandler进入到ChannelPipeline的时候,此时就会有一个ChannelHandlerContext被创建。

@Overridepublic void read(ChannelHandlerContext ctx) throws Exception {Channel channel = ctx.channel();channel.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));super.read(ctx);}

通过channel方法获取到Channel,然后利用write方法写入数据。这是pipeline方法写入

        ChannelPipeline pipeline = ctx.pipeline();pipeline.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));

虽然被调用的Channel 或ChannelPipeline 上的write()方法将一直传播事件通
过整个ChannelPipeline,但是在ChannelHandler 的级别上,事件从一个ChannelHandler到下一个ChannelHandler 的移动是由ChannelHandlerContext 上的调用完成的。

但是我们应该尽量减少传递,至少不应该将本不该这个hanlder处理的事件传递给这个hanlder。
要想调用从某个特定的ChannelHandler 开始的处理过程,必须获取到在(ChannelPipeline)该ChannelHandler 之前的ChannelHandler 所关联的ChannelHandlerContext。这个ChannelHandlerContext 将调用和它所关联的ChannelHandler 之后的ChannelHandler。

     ctx.write(Unpooled.copiedBuffer("Netty in Action",CharsetUtil.UTF_8));

消息将从下一个ChannelHandler 开始流经ChannelPipeline,绕过了所有前面的ChannelHandler。

ChannelHandler 和ChannelHandlerContext 的高级用法

你可以通过调用ChannelHandlerContext 上的pipeline()方法来获得被封闭的ChannelPipeline 的引用。这使得运行时得以操作ChannelPipeline 的ChannelHandler,我们可以利用这一点来实现一些复杂的设计。另一种高级的用法是缓存到ChannelHandlerContext 的引用以供稍后使用,这可能会发生在任何的ChannelHandler 方法之外,甚至来自于不同的线程。

   @Overridepublic void handlerAdded(ChannelHandlerContext ctx) throws Exception {this.ctx = ctx;super.handlerAdded(ctx);}public void send(String msg){ctx.writeAndFlush(msg);}

先缓存我们的ChannelHandlerContext ctx,然后可以利用它发生消息。

因为一个ChannelHandler 可以从属于多个ChannelPipeline,所以它也可以绑定到多个ChannelHandlerContext 实例。

对于这种用法指在多个ChannelPipeline 中共享同一个ChannelHandler,对应的ChannelHandler 必须要使用@Sharable 注解标注;否则,试图将它添加到多个ChannelPipeline 时将会触发异常。显而易见,为了安全地被用于多个并发的Channel(即连接),这样的ChannelHandler 必须是线程安全的。

@ChannelHandler.Sharable
public class SharableHandler extends ChannelInboundHandlerAdapter{@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("Channel read message: " + msg);ctx.fireChannelRead(msg);}
}

前面的ChannelHandler 实现符合所有的将其加入到多个ChannelPipeline 的需求,
即它使用了注解@Sharable 标注,并且也不持有任何的状态。相反,书上也有错误的案例。

@ChannelHandler.Sharable
public class UnSharableHandler extends ChannelInboundHandlerAdapter {private int count;@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {count++;System.out.println("channelRead(...) called the "+ count + " time");ctx.fireChannelRead(msg);}
}

这段代码的问题在于它拥有状态,即用于跟踪方法调用次数的实例变量count。将这个类的一个实例添加到ChannelPipeline将极有可能在它被多个并发的Channel访问时导致问题。(当然,这个简单的问题可以通过使channelRead()方法变为同步方法来修正。)总之,只应该在确定了你的ChannelHandler 是线程安全的时才使用@Sharable 注解。

为何要共享同一个ChannelHandler 在多个ChannelPipeline中安装同一个ChannelHandler的一个常见的原因是用于收集跨越多个Channel 的统计信息。

异常处理

异常处理是任何真实应用程序的重要组成部分,它也可以通过多种方式来实现。因此,Netty提供了几种方式用于处理入站或者出站处理过程中所抛出的异常。

处理入站异常

其实处理异常非常简单,之前曾经写过一个方法,叫做exceptionCaught,先看默认实现。

 /*** Calls {@link ChannelHandlerContext#fireExceptionCaught(Throwable)} to forward* to the next {@link ChannelHandler} in the {@link ChannelPipeline}.** Sub-classes may override this method to change behavior.*/@Skip@Override@SuppressWarnings("deprecation")public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)throws Exception {ctx.fireExceptionCaught(cause);}

这个看起来很简单,就是firexxx,就是往下传递嘛,那么意思就是向下传递这个异常,但是我可以重写,例如:关闭channel,捕获等操作。当然了如果他传输到了尾端还没有处理,则会被标记为未处理异常。

处理出站异常

每个出站操作都将返回一个ChannelFuture。注册到ChannelFuture 的ChannelFutureListener 将在操作完成时被通知该操作是成功了还是出错了。
几乎所有的ChannelOutboundHandler 上的方法都会传入一个ChannelPromise的实例。作为ChannelFuture 的子类,ChannelPromise 也可以被分配用于异步通知的监听器。但是,ChannelPromise 还具有提供立即通知的可写方法:

ChannelPromise setSuccess();
Chan nelPromise setFailure(Throwable cause);

添加ChannelFutureListener 只需要调用ChannelFuture 实例上的addListener
(ChannelFutureListener)方法,并且有两种不同的方式可以做到这一点。其中最常用的方式是,调用出站操作(如write()方法)所返回的ChannelFuture 上的addListener()方法。

  @Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {promise.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}});}

第二种方式是将ChannelFutureListener 添加到即将作为参数传递给ChannelOutboundHandler的方法的ChannelPromise。

    @Overridepublic void write(ChannelHandlerContext ctx, Object msg,ChannelPromise promise) {promise.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture f) {if (!f.isSuccess()) {f.cause().printStackTrace();f.channel().close();}}});}

ChannelPromise 的可写方法
通过调用ChannelPromise 上的setSuccess()和setFailure()方法,可以使一个操作的状态在ChannelHandler 的方法返回给其调用者时便即刻被感知到。

结束语

这一次我们仔细地研究了Netty 的数据处理组件—ChannelHandler。我们讨论了
ChannelHandler 是如何链接在一起,以及它们是如何作为ChannelInboundHandler 和ChannelOutboundHandler 与ChannelPipeline 进行交互的。

相关文章:

Read book Netty in action(Chapter VII)--ChannelHandler和ChannelPipeline

序言 我们曾经学过了ByteBuf – netty的数据容器,还有ChannelHandler和ChannelPipeline,这一把将他们组合起来,这些组件的交互正是Netty的灵魂所在! ChannelHanlder家族 在详细地学习ChannelHanlder之前,我们将在Ne…...

react的严格模式 和 解决react useEffect执行两次

useEffect执行两次 这个问题,主要是刚接触react的时候发的问题,当时也没总结。现在回过头来再总结一次!!! 文章目录useEffect执行两次前言一、为什么useEffect执行两次1.React的严格模式(模版创建项目&…...

C++中的STL

一、概念 STL,英文全称 standard template library,中文可译为标准模板库或者泛型库,其包含有大量的模板类和模板函数,是 C 提供的一个基础模板的集合,用于完成诸如输入/输出、数学计算等功能。 STL 最初由惠普实验室…...

【沐风老师】3dmax一键窗户生成器插件使用方法详解

3dmax一键窗户生成器插件教程 3dMax一键窗户生成器是一个在3dMax中自动创建3D窗户模型的脚本。它有28种风格的窗户样式,可以在Archviz项目中灵活应用,同时为3D艺术家节省大量时间。 【适用版本】 适用3dMax 2018.2及更高版本 【安装方法】 1.解压缩包&…...

【图像处理】数字图像处理基础(分辨率,像素,显示...)

Table of Contents1.数字图像处理基础1.1 图像表示1.1.1 图像成像模型1.1.2 数字图像的表示a.图像采样b.图像灰度的量化c.算比特数1.2 分辨率1.2.1 空间分辨率1.2.2 灰度分辨率1.3 像素间的关系1.3.1 像素邻域a.4邻域b.4对角邻域c.8邻域1.3.2 像素邻接1.3.3 像素连通1.3.4 像素…...

UE实现相机飞行效果CesiumForUnreal之DynamicPawn飞行原理浅析

文章目录 1.实现目标2.实现过程2.1 FlyTo实现原理与代码2.2 DynamicPawn飞行原理3.参考资料1.实现目标 基于CesiumForUnreal的Dynamic Pawn实现飞行效果GIF动图: 2.实现过程 实现原理较为简单,基于CesiumForUnreal插件中DynamicPawn中的Camera实现相关功能。其中FlyTo直接通…...

AIGC被ChatGPT带火!底层基础算力有望爆发式增长

ChatGPT火爆全球的背后,可以窥见伴随人工智能技术的发展,数字内容的生产方式向着更加高效迈进。ChatGPT属于AIGC的具体应用,而AIGC是技术驱动的数字内容新生产方式。AIGC类产品未来有望成为5G时代新的流量入口,率先受益的有望是AI…...

【链表OJ题(一)】移除链表元素

​ ​📝个人主页:Sherry的成长之路 🏠学习社区:Sherry的成长之路(个人社区) 📖专栏链接:数据结构 🎯长路漫漫浩浩,万事皆有期待 文章目录链表OJ题(一)1. 移除…...

【解锁技能】学会Python条件语句的终极指南!

文章目录前言一. python条件语句的介绍1.1 什么是条件语句1.2 条件语句的语法1.3 关于内置函数bool()二. 分支语句之单分支三. 多分支语句3.1 二分支语句3.2 多分支语句3.3 嵌套循环总结前言 🏠个人主页:欢迎访问 沐风晓月的博客 🧑个人简介&…...

如何通过rem实现移动端的适配?

一、rem、em、vw\vh的区别&#xff1a; rem&#xff1a;参照HTML根元素的font-size em&#xff1a;参照自己的font-size vw/vh&#xff1a;将视口宽高平分100等份&#xff0c;数值就是所占比例 <!DOCTYPE html> <html lang"en"><head><meta…...

【论文阅读】-姿态识别

记录论文阅读&#xff0c;希望能了解我方向的邻域前沿吧 粗读 第一篇 ATTEND TO WHO YOU ARE: SUPERVISING SELF-ATTENTION FOR KEYPOINT DETECTION AND INSTANCE-AWARE ASSOCIATION 翻译&#xff1a;https://editor.csdn.net/md?not_checkout1&spm1001.2014.3001.5352…...

3.1 模拟栈+表达式求值

模拟栈 题目链接 栈的数组模拟非常简单&#xff0c;不详细描述 设置一个指针指向栈顶第一个元素即可 STL中stack实现已经更新在STL_Stack #include<iostream> #include<string>using namespace std;const int N1e51; int m; string s; int stack[N]; int p;//指针…...

【Python语言基础】——Python 创建表

Python语言基础——Python 创建表 文章目录 Python语言基础——Python 创建表一、Python 创建表一、Python 创建表 创建表 如需在 MySQL 中创建表,请使用 “CREATE TABLE” 语句。 请确保在创建连接时定义数据库的名称。 实例 创建表 “customers”: import mysql.connector…...

外贸建站,为什么别人的询盘更多更精准?

大多企业进行外贸建站的目的就是想要获得更多的精准询盘&#xff0c;但是具体该如何做&#xff0c;大多企业都没有方向&#xff0c;要么就是在网上看各种不系统的文章学着操作&#xff0c;要么就找个建站公司做好网站就不管了&#xff0c;而最终结果都不甚理想。那么怎样才能让…...

Gateway集成Netty服务

Gateway和Netty都有盲区的感觉&#xff1b; 一、Netty简介 Netty是一个异步的&#xff0c;事件驱动的网络应用框架&#xff0c;用以快速开发高可靠、高性能的网络应用程序。 传输服务&#xff1a;提供网络传输能力的管理&#xff1b; 协议支持&#xff1a;支持常见的数据传输…...

SpringMVC控制层private方法中出现注入的service对象空指针异常

一、现象 SpringMVC中controller里的private接口中注入的service层的bean为null&#xff0c;而同一个controller中访问修饰符为public和protected的方法不会出现这样的问题。 controller中的方法被AOP进行了代理&#xff0c;普通Controller如果没有AOP&#xff0c;private方法…...

【Unity】P4 脚本文件(基础)

Unity脚本文件&#xff08;基础&#xff09;适配的C#代码编辑器如何添加一个脚本文件获取蘑菇当前位置基础代码改变物体位置帧与帧更新前言 上一篇博文主要围绕Unity Inspector部分&#xff0c;围绕组件&#xff0c;资源文件&#xff0c;父子节点部分做介绍。 链接&#xff1a;…...

(2023版)零基础入门网络安全/Web安全,收藏这一篇就够了

由于我之前写了不少网络安全技术相关的文章和回答&#xff0c;不少读者朋友知道我是从事网络安全相关的工作&#xff0c;于是经常有人私信问我&#xff1a; 我刚入门网络安全&#xff0c;该怎么学&#xff1f; 要学哪些东西&#xff1f; 有哪些方向&#xff1f; 怎么选&#x…...

Vue3电商项目实战-登录模块2【05-登录-表单校验、06-登录-消息提示组件封装、07-登录-账户登录、08-登录-手机号登录、09-退出登录】

文章目录05-登录-表单校验06-登录-消息提示组件封装07-登录-账户登录08-登录-手机号登录09-退出登录05-登录-表单校验 文档&#xff1a;https://vee-validate.logaretm.com/v4/ 支持vue3.0 第一步&#xff1a;安装 执行命令 npm i vee-validate4.0.3 第二步&#xff1a;导入 …...

Python 中都有哪些常见的错误和异常?

本文首发自「慕课网」&#xff0c;想了解更多IT干货内容&#xff0c;程序员圈内热闻&#xff0c;欢迎关注&#xff01; 作者| 慕课网精英讲师 朱广蔚 Python 程序的执行过程中&#xff0c;当发生错误时会引起一个事件&#xff0c;该事件被称为异常。例如&#xff1a; 如果程…...

51单片机-1

1&#xff0c;单片机内部集成了CPU&#xff0c;RAM&#xff0c;ROM&#xff0c;定时器&#xff0c;中断系统&#xff0c;通讯接口等一系列电脑的常用硬件功能。单片机和计算机相比&#xff0c;单片机是一个袖珍版计算机 2&#xff0c;单片机里有中央处理器&#xff08;CPU&…...

【Azure 架构师学习笔记】-Azure Data Factory (4)-触发器详解-事件触发器

本文属于【Azure 架构师学习笔记】系列。 本文属于【Azure Data Factory】系列。 接上文【Azure 架构师学习笔记】-Azure Data Factory (3)-触发器详解-翻转窗口 前言 事件触发指的是存储事件&#xff0c;所以在新版的ADF 中&#xff0c;已经明确了是“存储事件”&#xff0c;…...

【项目设计】高并发内存池(三)[CentralCache的实现]

&#x1f387;C学习历程&#xff1a;入门 博客主页&#xff1a;一起去看日落吗持续分享博主的C学习历程博主的能力有限&#xff0c;出现错误希望大家不吝赐教分享给大家一句我很喜欢的话&#xff1a; 也许你现在做的事情&#xff0c;暂时看不到成果&#xff0c;但不要忘记&…...

2023年,35岁测试工程师只能被“优化裁员”吗?肯定不是····

国内的互联网行业发展较快&#xff0c;所以造成了技术研发类员工工作强度比较大&#xff0c;同时技术的快速更新又需要员工不断的学习新的技术。因此淘汰率也比较高&#xff0c;超过35岁的基层研发类员工&#xff0c;往往因为家庭原因、身体原因&#xff0c;比较难以跟得上工作…...

gitlab部署使用,jenkins部署使用

gitlab部署使用&#xff0c;jenkins部署使用在线安装gitlab下载gitlab安装gitlab使用gitlab设置中文修改管理员密码创建组,创建项目,创建用户jenkins下载jenkins安装jenkin使用jenkins更改管理员密码配置拉取代码配置登录gitlab拉取代码的账号密码配置项目配置gitlab仓库配置构…...

从零开始的机械臂yolov5抓取gazebo仿真(环境搭建篇下)

sunday功能包使用介绍以及开源 sunday我给自己机械臂的命名&#xff0c;原型是innfos的gluon机械臂。通过sw模型文件转urdf。Sunday项目主要由六个功能包sunday_description、sunday_gazebo、sunday_moveit_config、yolov5_ros、vacuum_plugin、realsense_ros_gazebo组成&…...

GCC编译器 MinGW的下载安装使用教程

哎 总所周知 gcc可以用来编译C 和C。在linux广泛应用&#xff0c;那么window怎么使用gcc呢。就要用到gcc的window工具----MInGW&#xff0c;安装好之后&#xff0c;直接可以在windows的dos界面编译。下面讲解安装使用过程。1.官网下载MinGW - Minimalist GNU for Windows downl…...

【项目实战】SpringMVC配置全局属性,是实现WebMvcConfigurer接口,还是直接继承WebMvcConfigurationSupport类?

一、说明 官方推荐以下两种方式来配置全局的SpringMVC的相关属性 方式一:实现WebMvcConfigurer接口(推荐)方式二:直接继承WebMvcConfigurationSupport类。介绍一下两者区别吧。 二、 WebMvcConfigurer介绍 WebMvcConfigurer是一个接口,用于配置全局的SpringMVC的相关属…...

房产营销、地产中介如何高效低成本获客?

数字化对企业而言&#xff0c;机遇和挑战并存。房产企业可借助数字化加强日益扩大的业务规模和业务领域管理&#xff0c;以提升管理效率&#xff0c;降低管理难度&#xff1b;基于数字化技术加强客户的服务体验&#xff0c;进而收集多业态客户和场景数据&#xff0c;拓展创新业…...

Kotlin-作用域函数

在对象的上下文中执行代码块。当您在提供lambda表达式的对象上调用这样的函数时&#xff0c;它会形成一个临时作用域。在此范围内&#xff0c;可以不带名称地访问对象。这样的函数称为作用域函数。 let run with apply also 作用域函数不会引入任何新的技术功能&#xff0c;但它…...

QNX7.1 交叉编译开源库

1.下载QNX7.1 SDK并解压 ITL:~/work/tiqnx710$ ls -l 总用量 16 drwxrwxr-x 4 xxx4096 1月 28 13:38 host -rwxrwxr-x 1 xxx 972 1月 28 13:38 qnxsdp-env.bat -rwxrwxr-x 1 xxx 1676 1月 28 13:38 qnxsdp-env.sh drwxrwxr-x 3 xxx 4096 1月 28 13:38 target xxxITL:~/work/ti…...

论文投稿指南——中文核心期刊推荐(外国语言)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…...

Fabric系列 - 链码-内部链码的特性

(1)Fabric repo下的案例 Chaincode(1.4的目录结构) fabric/examples/chaincode/go ├── example02 #一个简单的转账合约 ├── eventsender #发送事件通知 ├── passthru #调用其他链码(或者其他channel的链码)example02 (转账) 一个简单的转账合约。该链码简单实…...

NetApp SnapCenter 备份管理 ——借助应用程序一致的数据备份管理,简化混合云操作

NetApp SnapCenter 简单、可扩展、赋权&#xff1a;跨 Data Fabric 的企业级数据保护和克隆管理 主要优势 • 利用与应用程序集成的工作流和预定义策略简化备份、恢复和克隆管理。 • 借助基于存储的数据管理功能提高性能和可用性&#xff0c;并缩短测试和开发用时。 • 提供基…...

Java内置队列和高性能队列Disruptor

一、队列简介 队列是一种特殊的线性表&#xff0c;遵循先入先出、后入后出&#xff08;FIFO&#xff09;的基本原则&#xff0c;一般来说&#xff0c;它只允许在表的前端进行删除操作&#xff0c;而在表的后端进行插入操作&#xff0c;但是java的某些队列运行在任何地方插入删…...

比特数据结构与算法(第四章_下)二叉树的遍历

本章将会详细讲解二叉树遍历的四种方式&#xff0c;分别为前序遍历、中序遍历、后续遍历和层序遍历。在学习遍历之前&#xff0c;会先带大家回顾一下二叉树的基本概念。学习二叉树的基本操作前&#xff0c;需要先创建一颗二叉树&#xff0c;然后才能学习其相关的基本操作&#…...

chatGPT是什么

2022年11月&#xff0c;人工智能公司OpenAI推出了一款聊天机器人&#xff1a;ChatGPT。它能够通过学习和理解人类语言来进行对话&#xff0c;还能与聊天对象进行有逻辑的互动。除了聊天&#xff0c;ChatGPT还能够根据聊天对象提出的要求&#xff0c;进行文字翻译、文案撰写、代…...

jenkins漏洞集合

目录 CVE-2015-8103 反序列化远程代码执行 CVE-2016-0788 Jenkins CI和LTS 远程代码执行漏洞 CVE-2016-0792 低权限用户命令执行 CVE-2016-9299 代码执行 CVE-2017-1000353 Jenkins-CI 远程代码执行 CVE-2018-1000110 用户枚举 CVE-2018-1000861 远程命令执行 CVE-2018…...

用canvas画一个炫酷的粒子动画倒计时

前言 &#x1f606; 这是一篇踩在活动尾声的文章&#xff0c;主要是之前在摸鱼社群里有人发了个粒子动画的特效视频&#xff0c;想着研究研究写一篇文章出来看看&#xff0c;结果这一下子就研究了半个多月。 &#x1f602; 下面就把研究成果通过文字的形式展现出来吧&#xf…...

Java技术学习——Maven相关知识

一、什么是Maven&#xff1f; Maven是Apache软件基金会组织维护的一款专门为Java项目提供构建和依赖管理支持的工具。 1.1 构建 构建过程包含的主要环节如下&#xff1a; 清理&#xff1a;删除上一次构建的结果&#xff0c;为下一次构建做好准备编译&#xff1a;Java源程序…...

C++ 认识和了解C++

1.在使用C语言写代码的时候开头要用到的是&#xff1a; #include<iostream> using namespace std;不可以写成这样&#xff1a; #include iostream.h&#xff08;1&#xff09;iostream是输入输出流类&#xff0c; istream输入流类 cin >> ostream输出流类 cout &…...

u盘误删的文件怎么找回

u盘误删的文件怎么找回?u盘的特点之一就是极其便携&#xff0c;可以容纳各种格式的数据和文件&#xff0c;需要时可以直接使用。每次使用都会或多或少的存放一些文件&#xff0c;但有使用就会有删除&#xff0c;为了不影响使用性&#xff0c;清理存储空间是必要的。清理中如果…...

二分查找由浅入深--算法--java

二分查找写在开头算法前提&#xff1a;算法逻辑算法实现简单实现leftright可能超过int表示的最大限度代码分析和变换更多需求&#xff1a;求索引最小的值java二分API应用基础题思考难度方法写在开头 二分查找应该是算比较简单的这种算法了&#xff0c;我本以为还可以。但有时候…...

【学习】笔记本电脑重新安装系统win10

安装系统有很多方法: 软件安装制作启动u盘本文使用的方法就是启动盘安装: 1.首先下载iso镜像文件: msdn我告诉你:MSDN, 我告诉你 - 做一个安静的工具站 (itellyou.cn) 2.下载启动盘制作工具: 制作启动盘rufus:Rufus - 轻松创建 USB 启动盘 3.官网下载: https://do…...

RocketMQ的一些使用理解

1.RocketMQ的生产者生产负载策略&#xff08;3种&#xff09; (1)SelectMessageQueueByHash &#xff08;一致性hash&#xff09; (2)SelectMessageQueueByMachineRoom &#xff08;机器随机&#xff09; (3)SelectMessageQueueByRandom &#xff08;随机&#xff09; 第1种一…...

Java枚举详解

一.枚举 1.为什么有枚举&#xff1f; 如果我们的程序需要表示固定的几个值&#xff1a; 比如季节&#xff1a;spring (春)&#xff0c;summer(夏)&#xff0c;autumn(秋)&#xff0c;winter(冬) 用常量表示&#xff1a; public static final int SEASON_SPRING 1;public st…...

虚拟机上安装openKylin详细步骤总结

一、创建虚拟机 首先获取操作系统安装镜像文件&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1tSuXmDk2ZILR4ieee6iImw?pwdcy47 提取码&#xff1a;cy47 &#xff08;1-1&#xff09;进入新虚拟机创建向导&#xff0c;选择“自定义”&#xff1a; &#xff08;1-…...

夜天之书 #74 企业开源的软件协议模型实践(Part 2)

在上一篇文章中&#xff0c;我介绍了企业开源的完全开放源码策略及其风险。完全开放源码&#xff0c;即以符合开源定义的软件协议发布企业自研软件的情形。本文介绍应对完全开放源码后的风险的第一种策略&#xff1a;提高市场占有率与开放标准。与其说是策略&#xff0c;不如说…...

2.webpack实时打包

简介 上一章已经实现了使用 webpack 构建了一个简单的项目&#xff1b;但是我们发现&#xff0c;每次修改了 index.js 需要重新执行 cnpm run dev 命令重新构建 main.js&#xff1b;这在开发阶段是无法忍受的&#xff0c;因为这样调式将浪费大量的时间&#xff1b;还好 webpac…...

KingbaseES V8R3 表加密

前言 透明加密是指将数据库page加密后写入磁盘&#xff0c;当需要读取对应page时进行加密读取。此过程对于用户是透明&#xff0c; 用户无需干预。 该文档进行数据库V8R3版本测试透明加密功能&#xff0c;需要说明&#xff0c;该版本发布时间早于V8R6&#xff0c;所以只能进行表…...