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

Netty应用(七) 之 Handler Netty服务端编程总结

目录

15.Handler

15.1 handler的分类

15.1.1 按照方向划分

15.1.2 handler的结构

15.2 输入方向ChannelInboundHandlerAdapter

15.2.1 输出方向Handler的顺序

15.2.2 多个输入方向Handler之间的数据传递

15.2.2.1 handler消失了

15.2.2.2 手动编写netty提供的new StringDecoder();这一Handler

15.2.2.3 责任链设计模式

15.2.2.4 ctx上下文对象

15.3 输出方向ChannelOutboundHandlerAdapter

15.3.1 输出方向Handler的顺序

15.3.2 总结 【自己多测试测试,真正底层细节看源码,现在只能测试,然后瞎猜】

15.3.3 ctx和ch的writeAndFlush()

15.4 关于head和tail节点

16.netty服务端编程总结

16.1 服务端关于handler和childHandler

16.2 为什么叫孩子处理器?childHandler

16.3 客户端关于bootstrap.handler

17.作图总结 [橘子哥的图]

18.EmbeddedChannel


15.Handler

Handler是程序员接触最多的地方。最重要的编码环节。

为何说重要,因为我们前面可以知道serverBootstrap.group(new NioEventLoopGroup());服务端在结束了该操作后,实际上就开启了一个线程池在处理连接ACCEPT事件了,等到连接建立后,后续的IO操作会把数据发送过来这个数据实际上就是我们业务中要处理的对象,那么这个处理就是在Handler里面处理的,这个处理就是你业务逻辑的所在地,至于你怎么实现,是发mq还是写库,还是做什么处理,这个是另外一件事。但是这里的Handler就是拿到网络传输数据的地方,也就是以前所说的SocketChannel的地方,而这个Handler通常都是一组,它有很多实现,许多个Handler组成了一个pipeline流水线,每个Handler各司其职,每一种Handler会完成功能中的一件事。通过pipeline流水线来组合各种Handler,实现一系列的功能

15.1 handler的分类

15.1.1 按照方向划分

我们说的handler是有方向的,可以按照读入数据和写出数据的方向划分

读入方向

也就是站在一个角度,数据是流入,举个例子。

我在看服务端的时候,接收客户端过来的数据,对于服务端来说这属于数据流入,也就是读入数据。这就是读入方向。而此时对于客户端来说,数据就是写出方向的。

对于读入数据来说,都属于ChannelInboundHandlerAdapter,我们接收数据的一系列Handler都是这个ChannelInboundHandlerAdapter的子类实现。

写出方向

如果服务端此时要给客户端发数据。这就属于服务端的写出方向,这都属于

ChannelOutboundHandlerAdapter。

这个方向是相对的,你服务端写出数据,对于客户端就是写入。不管怎么看,你如果属于读入(吃数据),就是在拿到数据之后做ChannelInboundHandlerAdapter的处理,如果你是往出写数据,也就是吐数据,写出去之前,那就是要做ChannelOutboundHandlerAdapter的处理。

15.1.2 handler的结构

pipeline中的各个handler是用双向链表组成的,这个链表中间是你所有的配置的handler,实际上一头一尾还有一个head Handler和一个tail Handler,这两个Handler是Netty自带的Handler,负责来管理这个双向链表

1.Handler作用:用于处理接收数据后 或 发送数据前这两个时间点的数据,是程序员使用netty最重要的战斗场地

2.通过Pipeline把多个handler有机的整合成了一个整体

读取接收数据:ChannelInboundHandler子类

写出数据:ChannelOutboundHandler子类

3.Pipeline中,执行相同种类的Handler有固定顺序,不同种类的Handler不讲究先后顺序

4.Handler传递数据:

super.channelRead(ctx,msg);

super.channelRead(ctx,msg);底层为ctx.fireChannelRead(s)

最后一个Handler不需要传递数据,所以最后一个Handler无需调用该方法

5.pipeline中的各个Handler是使用双向链表组成的,这个链表中间就是你所有的配置的Handler,实际上一头一尾: Head Handler,tail Handler这两个Handler来进行管理整个双向链表

15.2 输入方向ChannelInboundHandlerAdapter

15.2.1 输出方向Handler的顺序

我们说handler的处理是被pipeline流水线管理的。当你把handler一个个的添加到pipeline之后。就是按照你添加的顺序执行的。因为他的添加方法就是addLast,不断的往后面追加,所以就是先来先执行的。

我们来看一下这个顺序性。

  • 服务端
package com.messi.netty_core_02.netty04;import com.sun.security.ntlm.Server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class MyNettyServer2 {private static final Logger log = LoggerFactory.getLogger(MyNettyServer2.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(defaultEventLoopGroup,"handler1",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler1_____________________________");log.info("msg:{}",msg);super.channelRead(ctx,msg) ;}});pipeline.addLast(defaultEventLoopGroup,"handler2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler2_____________________________");super.channelRead(ctx,msg) ;}});}});serverBootstrap.bind(8000);}}
  • 客户端
package com.messi.netty_core_02.netty04;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;import java.net.InetSocketAddress;public class MyNettyClient2 {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group);bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LoggingHandler());pipeline.addLast(new StringEncoder());}});Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();channel.writeAndFlush("leomessi");System.out.println("MyNettyClient2.main");group.shutdownGracefully();}}
  • debug客户端进行测试

1.先给客户端设置断点:

2.启动服务端

3.debug启动客户端

4.看控制台打印输出:

5.debug客户端,让客户端多次发数据给服务端

见下图:

你可以观察到一个输出现象就是:无论你客户端发送多少次数据给服务端,处理你这个客户端发送数据的线程都是同一个DefaultEventLoop(处理该NioSocketChannel对应的业务逻辑)或同一个NioEventLoop(处理该NioSocketChannel对应的IO事件逻辑)

所以你可以得出一个结论:客户端与服务端建立连接后,NioServerSocketChannel会分配一个NioSocketChannel给该客户端与服务端作为交互通道。之后,同一个SocketChannel的任务(IO事件任务或业务逻辑任务)会让同一个线程去做,比如说:同一个NioEventLoop处理读写事件,同一个DefaultEventLoop处理业务Handler

  • 细节

为什么要引入DefaultEventLoopGroup来处理业务逻辑?

其实很简单,就是为了提高吞吐。因为NioEventLoopGroup通过Reactor模型划分为Boss和Worker分别去处理连接,read,write等IO事件。再复习下Reactor模型的设计吧。当一个客户端请求连接服务端时,服务端的Boss线程进行处理这一次连接,一旦这一次的连接建立后,在程序层面,NioServerSocketChannel就会生成一个NioSocketChannel,Boss线程就会把该连接所对应的NioSocketChannel中IO事件,业务逻辑的交互操作全部交给worker线程去做,我们知道连接只需要建立一次,所以boss线程压力较小,所以boss线程一般只有一个或两个。worker线程压力过大,所以一般根据计算机CPU核数去具体设置,但是呢当客户端过多时,一个worker线程是要进行处理多个客户端连接后的所有操作的,如果一个worker线程在处理完某一个客户端的写出数据的操作后,又得接着去处理该客户端触发的业务逻辑,假设说这个业务逻辑很复杂很耗时,你这个worker线程是不是就阻塞了。。。我们之前就说过worker线程数量是有限制的,所以为了提高系统吞吐量,worker线程只处理IO事件,对于业务处理耗时操作,会异步新开启一个新线程去处理。worker线程会直接返回一个确认告诉客户端,客户端也可以继续向下执行它的业务逻辑,对于客户端而言这也很高效。当异步线程处理完这一业务操作后,需要返回业务处理结果,此时会拿到worker线程给的客户端信息进行回调客户端的回调方法,然后把业务处理结果返回给客户端。可见,异步线程是要开启的,那么这个异步线程怎么做呢?其实就是DefaultEventLoopGroup这一线程池去做啦,为什么呢?还是没说为什么,其实很好理解,使用DefaultEventLoopGroup可以简化开发,最重要的就是更好的和netty体系进行融合!

但是注意:只有显示指定使用DefaultEventLoopGroup的Handler才可以使用defaultEventLoop线程去处理对应的Handler业务,否则还是使用NioEventLoop线程去处理,如下图所示:

ofcourse,当然,假设客户端多次进行发数据给服务端,服务端同样使用相同的defaultEventLoop线程或NioEventLoop线程去处理对应的Handler,不会改变的。

如下这个例子:

15.2.2 多个输入方向Handler之间的数据传递
15.2.2.1 handler消失了

15.2.2.2 手动编写netty提供的new StringDecoder();这一Handler

15.2.2.3 责任链设计模式

多个Handler组成最终的处理链路,这就是责任链设计模式,把各个工作分到每个部分里面,放到链路中挨个处理,你不往下传(不调用super.channelRead(ctx,msg)),就类似于filter过滤器中不往下写(return true)。后面就不会再执行了,链路就断开了。

而且你如果是处于最后一个handler比如我的handler2,他处于最后一个handler了,其实他不往下传了,也就可以不写这个了。写了也没事。反正后面没了。

pipeline中的handler是个双向链表,因为有读入读出,这个后面看看。

总结:

把一个工作做成一个链条,则这一个工作分成若干个步骤,并且每一个步骤都会对数据进行不断的加工处理,会把数据不断的传递给下一个步骤,直到最后一个环节步骤为止。

15.2.2.4 ctx上下文对象

ctx:上下文环境(ChannelHandlerContext类型)

ctx对象管理的是所有Handler,它是Handler运行的环境。ctx管理着数据的传递,也管理着ByteBuf

15.3 输出方向ChannelOutboundHandlerAdapter

15.3.1 输出方向Handler的顺序

前面我们一直说的是输入方向的Hnadler的处理,也就是ChannelInboundHandlerAdapter这个处理。 现在我们再来看一下关于写出数据的操作,也就是ChannelOutboundHandlerAdapter这个处理器操作。我们来看一下代码。

  • 客户端代码
package com.messi.netty_core_02.netty04;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LoggingHandler;import java.net.InetSocketAddress;public class MyNettyClient2 {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group);bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new LoggingHandler());pipeline.addLast(new StringEncoder());}});Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();channel.writeAndFlush("leomessi");System.out.println("MyNettyClient2.main");group.shutdownGracefully();}}
  • 服务端代码
package com.messi.netty_core_02.netty04;import com.sun.security.ntlm.Server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
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;import java.nio.CharBuffer;
import java.nio.charset.Charset;public class MyNettyServer2 {private static final Logger log = LoggerFactory.getLogger(MyNettyServer2.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//                pipeline.addLast(new StringDecoder());pipeline.addLast(defaultEventLoopGroup,"handler1",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler1_____________________________");ByteBuf byteBuf = (ByteBuf) msg;CharBuffer decode = Charset.forName("UTF-8").decode(byteBuf.nioBuffer());log.info("decode:{}",decode);super.channelRead(ctx,decode);}});pipeline.addLast(defaultEventLoopGroup,"handler2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler2_____________________________");log.info("decode:{}",msg);super.channelRead(ctx,msg) ;}});pipeline.addLast(defaultEventLoopGroup,"handler3",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler3___________________________");super.channelRead(ctx,msg);}});pipeline.addLast(defaultEventLoopGroup,"handler4",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler4_______________进行write写出,输出处理,其实就是向客户端写数据");ch.writeAndFlush("服务端向客户端发送数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler5",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler5_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler6",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler6_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});}});serverBootstrap.bind(8000);}}
  • 测试

我们把服务端写出的操作放在handler4中,结果服务端写出对应的Handler没有调用:

修改一下:

我们把服务端写出数据的操作放置到最后一个读取的Handler中:

结果显示可以成功调用写出Handler!

分析:

我们看到执行了输出处理器的操作信息,因为我们接收数据的也就是h3那里写出了数据,就能往下走了,每个写handler里面用了super。write继续往下传递写出数据,但是问题来了。我们add的handler顺序是4,5,6但是执行的顺序却是6,5,4这样的倒序。

实际上我们来看个图。这个图是所有的handler的一个结构,我们说数据从外部进来的时候,数据会从head接收到,然后顺序执行h1 h2 h3这个顺序。

但是输出的操作处理器是从tail开始的,也就是h6 h5 h4这样的顺序。而且当我们先处理接收数据,在处

理写出数据,是按照这样的h1 h2 h3执行完了,看有没有下一个输入,如果没有就直接走到tail了,然后

从tail往前执行输出,执行输出的时候,也就是从tail开始的。然后倒序执行。

注释:head和tail这两个Handler是netty自定义自带的Handler处理器类,负责进行管理整个pipeline流水线,管理所有的Handler处理器类

  • 再修改一下

把服务端写出数据的操作放到第一个Handler:

输出:

  • 再修改一下

把handler1的向后传递给删除:

把handler5的向后传递给删除:

输出:

由于handler1和handler5的向前传递都断了,所以:读取Handler只输出handler1,输出Handler只输出handler6和handler5

  • 修改:基于最原始开头给出的代码,只断开handler4的向前传递

结果表明:对读取Handler无影响

  • 修改:基于最原始开头给出的代码,任意修改handler4的位置

根据输出结果可以得出一个结论,可以自己测试一下:

顺序只在同种handler里面产生,不同种类的handler不受顺序影响。你可以这样想,当你此时执行输入Handler时,你把输出Handler都掩盖住,看输入Handler之间的相对位置就是真正的执行顺序!当你执行输出Handler时,同理可得。

15.3.2 总结 【自己多测试测试,真正底层细节看源码,现在只能测试,然后瞎猜】

# 其逻辑一定是从接收到的数据head开始往后走,挨个走过所有的InBound处理器。然后处理完了,从tail走,倒序往前走执行所有的outBound处理器。

# 基于第一条规则,不管你怎么变顺序,哪怕是输出和输入的各种交错add。也是这么个逻辑,输出的处理器顺序不会影响输入的处理器逻辑。而且每个输入的处理器都要super.channelRead(ctx,msg);才能往下一个发。不管123这样的顺序,而是看你add的顺序。而且哪怕你是h1 h4 h2 h3这样,他也是先处理读的h1 h2 h3然后才是h4,因为h4是输出Handler,因为读写处理器是互不影响的。

# 基于前两条规则,顺序只在同种handler里面产生,不同种类的handler不影响

15.3.3 ctx和ch的writeAndFlush()

我们刚才写出数据的时候用的是ch.writeAndFlush("服务端给客户端写的信息...");这个操作,其中ch是NioSocketChannel ch,这是客户端和服务端建立的连接。其实我们的参数里面ctx也有这个写出方法。我们来看一下:

  • 服务端代码
package com.messi.netty_core_02.netty04;import com.sun.security.ntlm.Server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
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;import java.nio.CharBuffer;
import java.nio.charset.Charset;public class MyNettyServer2 {private static final Logger log = LoggerFactory.getLogger(MyNettyServer2.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//                pipeline.addLast(new StringDecoder());pipeline.addLast(defaultEventLoopGroup,"handler1",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler1_____________________________");ByteBuf byteBuf = (ByteBuf) msg;CharBuffer decode = Charset.forName("UTF-8").decode(byteBuf.nioBuffer());log.info("decode:{}",decode);super.channelRead(ctx,decode);}});pipeline.addLast(defaultEventLoopGroup,"handler2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler2_____________________________");log.info("decode:{}",msg);super.channelRead(ctx,msg) ;}});pipeline.addLast(defaultEventLoopGroup,"handler3",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler3___________________________");
//                        ch.writeAndFlush("服务端向客户端发送数据");ctx.writeAndFlush("服务端向客户端写信息....")super.channelRead(ctx,msg);}});pipeline.addLast(defaultEventLoopGroup,"handler4",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler4_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler5",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler5_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler6",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler6_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});}});serverBootstrap.bind(8000);}}
  • 测试

我们发现h4 h5 h6又没了,原因还是那个图:

之前我们使用ch这一SocketChannel去写出数据,这个ch是本次连接的对象,所以他能感知到所有本次连接的handler,也就是全局的,他是从tail节点往前遍历一直到head节点,但是ctx只是当前h3的上下文对象,他无法感知到其余handler的信息。

ctx代表的是当前这个handler处理器的上下文,也就是h3的上下文,其余的handler不知道h3他的上下文。当h3使用ctx发起写出操作时,他的流程是从当前上下文的handler节点往前走,一直到head节点,所有他会去执行h3前面所有的写出处理器handler。但是此时h3前面没有写出处理器handler,所以就不执行了。那么我们现在修改一下代码,在h3之前注册几个写出handler处理器,比如h5这个handler:

package com.messi.netty_core_02.netty04;import com.sun.security.ntlm.Server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
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;import java.nio.CharBuffer;
import java.nio.charset.Charset;public class MyNettyServer2 {private static final Logger log = LoggerFactory.getLogger(MyNettyServer2.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();//                pipeline.addLast(new StringDecoder());pipeline.addLast(defaultEventLoopGroup,"handler1",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler1_____________________________");ByteBuf byteBuf = (ByteBuf) msg;CharBuffer decode = Charset.forName("UTF-8").decode(byteBuf.nioBuffer());log.info("decode:{}",decode);super.channelRead(ctx,decode);}});pipeline.addLast(defaultEventLoopGroup,"handler5",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler5_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler2_____________________________");log.info("decode:{}",msg);super.channelRead(ctx,msg) ;}});pipeline.addLast(defaultEventLoopGroup,"handler3",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler3___________________________");
//                        ch.writeAndFlush("服务端向客户端发送数据");ctx.writeAndFlush("服务端向客户端写信息....");super.channelRead(ctx,msg);}});pipeline.addLast(defaultEventLoopGroup,"handler4",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler4_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler6",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler6_______________进行write写出,输出处理,其实就是向客户端写数据");super.write(ctx, msg, promise);}});}});serverBootstrap.bind(8000);}}

我们看到如期输出了,来分析一下原因,我们在代码里面依次注册了h1,h5,h2,h3,h4,h6。结构变为如下所示:

当h3这一handler执行完ctx的写出之后,会从当前h3开始执行h3前面的写出处理器,直到head,只会执行h5。因为ctx只具有当前handler3的上下文。

但是把handler3中的ctx.writeXXX修改成ch.writeAndFlush的话,由于ch是整个客户端连接的NioSocketChannel,所以他是面向全局的,也就是他拿到的是tail,然后从tail往前走,去找出全部的输出handler,执行顺序为:h6->h4->h5,前后顺序是相对的。找输出handler时只看输出handler,不看输入!

PS:tail和head是辅助节点,你在代码里面看不到,得去看源码。

当启动服务端的时候,此时就是NioEventLoop里面的线程在做select监听。进入死循环,等客户端连上来才走后面的发送,然后交给handler的流水线做处理

15.4 关于head和tail节点

我们上面说的pipline中有两个handler是netty内置的,叫做head和tail,我们已经知道了,当服务端输入的时候,会按照添加顺序执行inboundHandler,当服务端往出写的时候,会按照添加顺序的反方向执行outBoundHandler。那么问题来了,对于内置handler的head和tail在输入输出的时候到底执行不执行呢?

# 答案是输入的时候执行head->h1->...->tail

# 输出的时候执行的是h6->h4->h5->head

看一下原因,这里的顺序后面源码再看,目前为止只能根据测试结果去总结结论。

我们看一下head节点的源码:

final class HeadContext extends AbstractChannelHandlerContext implements

ChannelOutboundHandler, ChannelInboundHandler

Head的类实现了in和outbound,可见其本质就是OutboundHandler和InboundHandler,所以他其实在输入输出都会和其他的in out一起执行。

我们看一下tail节点的源码:

final class TailContext extends AbstractChannelHandlerContext implements

ChannelInboundHandler

可见tail是一个inbound,所以他只会在输入的时候执行。

补充:

而且这两个节点的类都是内部类,都在DefaultChannelPipeline类中,这就是一个高内聚的体现,如果一个类只在这个类中进行使用,那就在这个类里面定义即可,不对外暴露。

16.netty服务端编程总结

package com.messi.netty_core_02.netty05;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.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class NettyServer {private static final Logger log = LoggerFactory.getLogger(NettyServer.class);public static void main(String[] args) {ServerBootstrap serverBootstrap = new ServerBootstrap();serverBootstrap.group(new NioEventLoopGroup(1),new NioEventLoopGroup(8));DefaultEventLoopGroup defaultEventLoopGroup = new DefaultEventLoopGroup();serverBootstrap.channel(NioServerSocketChannel.class);serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {@Overrideprotected void initChannel(NioServerSocketChannel ch) throws Exception {}});serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ChannelPipeline pipeline = ch.pipeline();pipeline.addLast(new StringDecoder());pipeline.addLast(new LoggingHandler());pipeline.addLast(defaultEventLoopGroup,"handler1",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler1");super.channelRead(ctx, msg);}});pipeline.addLast(defaultEventLoopGroup,"handler2",new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("handler2");ch.writeAndFlush("llll");super.channelRead(ctx, msg);}});pipeline.addLast(defaultEventLoopGroup,"handler3",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler3");super.write(ctx, msg, promise);}});pipeline.addLast(defaultEventLoopGroup,"handler4",new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("handler4");super.write(ctx, msg, promise);}});}});serverBootstrap.bind(8000);}}
package com.messi.netty_core_02.netty05;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
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 io.netty.handler.logging.LoggingHandler;import java.net.InetSocketAddress;public class NettyClient {public static void main(String[] args) throws InterruptedException {Bootstrap bootstrap = new Bootstrap() ;NioEventLoopGroup group = new NioEventLoopGroup();bootstrap.group(group);bootstrap.channel(NioSocketChannel.class);bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new StringEncoder());ch.pipeline().addLast(new LoggingHandler());}});Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();channel.writeAndFlush("netty hello");System.out.println("NettyClient.main");group.shutdownGracefully();}}

16.1 服务端关于handler和childHandler

  • 我们来看一段代码:
serverBootstrap.handler(new ChannelInitializer<NioServerSocketChannel>() {@Overrideprotected void initChannel(NioServerSocketChannel ch) throws Exception {}
});
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {}
});

我们看到这段代码就是我们在服务端启动的时候的设置的东西,但是我们在上面编程的时候只设置了serverBootstrap.childHandler。那么关于handler和childHandler有啥区别呢,下面为了方便我直接简称为h和ch。

我们前面说过serverBootstrap.childHandler也就是ch中实现的是数据的IO处理,也就是对应在NIO中是

socketChannel的功能。其实从他的参数中的new ChannelInitializer()是NioSocketChannel泛型就能知道,他是对应的SC做IO处理的。

而对于我们的服务端,他其实又两个功能,一个是处理IO,一个是accept处理连接的。那么childHandler处理了IO,serverBootstrap.handler就是处理连接的。

其实你看他的参数泛型是NioServerSocketChannel也能知道他对应的是NioServerSocketChannel也就是SSC,他是处理连接的。你不能乱写泛型启动会报错。

其次你也不能不写serverBootstrap.childHandler这一部分,因为服务端你不能不处理IO,你是实际建立IO连接的socketChannel的,所以不写也会报错。

而不写serverBootstrap.handler这个处理连接的代码是不会报错,不影响运行的,他是为了 ServerSocketChannel服务的,你可以写里面也能用pipline,但是他只是为了做连接,没有太多的操作就是accept,源码已经给你封装了,所以你可以不写,但是你要是做一些复杂开发,你要监控SSC的状态,就可以增加pipline在这里,就能监控连接accept的状态和信息。

所以我们就可以知道每一个childHandler就是一个sc,都是一个连接,一个连接就对应一个childHandler,然后他里面的一组pipline的一组handler是每一个childHandler独立拥有一份的,他们不能混着来。其实也好理解,一个连接肯定是自己一组pipline的一组handler。不然就混乱了数据。

16.2 为什么叫孩子处理器?childHandler

每一个客户端过来与ServerSocketChannel进行建立accept连接,ServerSocketChannel会给每一个客户端都建立一个SocketChannel与之对应。所以站在服务端的角度,SocketChannel就是孩子。childHandler()方法是针对服务端-客户端之间建立SocketChannel之后进行的读写事件操作),所以childHandler必须是要建立编码的。你想想你与客户端后续是不是主要进行读写?对吧。然后对于每一个SocketChannel,childHandler都会建立一条pipeline流水线进行处理建立SocketChannel后的读写操作以及其他业务操作,pipeline流水线是由多个Handler处理器类构成(可以为netty自带的Handler也可以为自定义Handler)。

那么与之对应的就是handler()方法,handler方法是处理ServerSocketChannel进行accept()建立连接操作(通常没什么特别的,所以handler()方法可以省略不写)。注:由于accept()建立连接为公共可封装的代码,netty会把ServerSocketChannel.accept()这种代码都给你封装好。

补充:

对于handler()可以省略不写,childHandler()不可以省略不写这一结论,还可以这样理解:

handler()对应的是ServerSocketChannel进行accept建立连接的操作,连接操作是固定的,你想想:客户端与服务端交互,那不必须连接吗?对吧。所以这部分代码可封装。handler()主要处理ServerSocketChannel建立连接时这一时间段的处理,能有啥处理,不就重复建立连接操作的过程吗,直接封装不就完了。在特别复杂的情况下可能会使用handler()做处理,所以可以省略handler()不写。

childHandler()对应的是SocketChannel建立后的读写,读写是不确定的,谁知道你建立连接后是读还是写,还是只读还是只写,所以一般需要自定义,所以childHanlder不可以省略。并且当你读取到数据后,你会在Handler中配置一些处理器类来进行读取数据后的业务处理,写出前同样要进行Handler处理。

每一个SocketChannel都对应一份pipeline流水线(pipeline流水线是许多个handler构成的)

16.3 客户端关于bootstrap.handler

在客户端的代码是这样的:

Bootstrap bootstrap = new Bootstrap();
bootstrap.channel(NioSocketChannel.class);
NioEventLoopGroup nioEventLoopGroup = new NioEventLoopGroup();
bootstrap.group(nioEventLoopGroup);
bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {@Overrideprotected void initChannel(NioSocketChannel ch) throws Exception {ch.pipeline().addLast(new LoggingHandler());ch.pipeline().addLast(new StringEncoder());}
});

我们看到只有bootstrap.handler,客户端只有这个东西,设计层面上,因为他本身就是一个发起连接的,读写数据的,没有什么接收处理连接的操作(因为客户端是请求服务端建立连接的,而不是服务端请求客户端建立连接!!)。所以他一个就行了。

为什么在客户端代码中编写的handler()方法中实现的泛型是NioSocketChannel而不是服务端中那种NioServerSocketChannel呢????

因为客户端代码是Bootstrap类构建的代码,Bootstrap是ServerBootstrap的父类,ServerBootstrap扩展重写了Bootstrap这个类。所以泛型肯定不一样呀。

17.作图总结 [橘子哥的图]

我理解的就是:

serverBootstrap.handler(多个handler):其中多个handler是进行客户端-服务端连接操作后,后续的一系列的业务逻辑处理Handler。但是你对于连接后,能有什么操作?没啥操作,所以一般handler方法省略不写。注:连接事件这一网络操作netty都已经帮你封装好了。。。你表层看不见的

serverBootstrap.childhandler(多个handler):这其中也有多个handler,是客户端-服务端进行IO事件操作后,后续一系列的业务逻辑处理Handler,因为你对于读写事件(读IO后,写IO前这个事件段)一定有其他的业务逻辑可以处理,比如:你读取到的数据可以用来存储MQ还是把它存储起来等,这都属于业务逻辑,你可以把这一系列业务逻辑写在一个个的Handler中。

注:IO事件(read或write)这一网络操作netty都已经帮你封装好了。。。你表层看不见的

对于连接,IO事件,netty都帮你封装好了,你看不见的,你能进行自定义处理的只有Handler操作。所以Handler对于程序员而言,是多么的重要。

18.EmbeddedChannel

前面我们都是启动一个客户端,启动一个服务端然后客户端发消息,服务端或者Inbound接收,或者outbound输出。这样的操作模式,那么我们可以看到我们每次写一个服务端代码都要写一遍handler。属实麻烦,我们可以写成这样。

package com.messi.netty_core_02.netty05;import io.netty.buffer.ByteBufAllocator;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise;
import io.netty.channel.embedded.EmbeddedChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;public class TestEmbededHandler {private static final Logger log = LoggerFactory.getLogger(TestEmbededHandler.class);public static void main(String[] args) {ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h1 {}",msg) ;super.channelRead(ctx, msg);}};ChannelInboundHandlerAdapter h2 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h2 {}",msg) ;super.channelRead(ctx, msg);}};ChannelInboundHandlerAdapter h3 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h3 {}",msg) ;super.channelRead(ctx, msg);}};ChannelOutboundHandlerAdapter h4 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h4 {}",msg) ;super.write(ctx, msg, promise);}};ChannelOutboundHandlerAdapter h5 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h5 {}",msg) ;super.write(ctx, msg, promise);}};ChannelOutboundHandlerAdapter h6 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h6 {}",msg) ;super.write(ctx, msg, promise);}};//把handler都绑定到Channel上面EmbeddedChannel channel = new EmbeddedChannel(h1,h2,h3,h4,h5,h6);//读入操作channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("llll".getBytes()));//写出操作channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("llll".getBytes()));}}
  • 测试

  • 分析
 ChannelInboundHandlerAdapter h1 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h1 {}",msg) ;super.channelRead(ctx, msg);}};ChannelInboundHandlerAdapter h2 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h2 {}",msg) ;super.channelRead(ctx, msg);}};ChannelInboundHandlerAdapter h3 = new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {log.info("h3 {}",msg) ;super.channelRead(ctx, msg);}};ChannelOutboundHandlerAdapter h4 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h4 {}",msg) ;super.write(ctx, msg, promise);}};ChannelOutboundHandlerAdapter h5 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h5 {}",msg) ;super.write(ctx, msg, promise);}};ChannelOutboundHandlerAdapter h6 = new ChannelOutboundHandlerAdapter(){@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {log.info("h6 {}",msg) ;super.write(ctx, msg, promise);}};

我们可以把上面这段代码分解写成6个类 ,如下所示:

public class InboundHandlerAdapter1 extends ChannelInboundHandlerAdapter {
static Logger logger = LoggerFactory.getLogger(InboundHandlerAdapter1.class);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
logger.info("HandlerAdapter1 *************msg is {}",msg);
super.channelRead(ctx, msg);
}
}
public class InboundHandlerAdapter2 extends ChannelInboundHandlerAdapter {
static Logger logger = LoggerFactory.getLogger(InboundHandlerAdapter2.class);
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
logger.info("HandlerAdapter2 **************msg is {}",msg);
super.channelRead(ctx, msg);
}
}
public class InboundHandlerAdapter3 extends ChannelInboundHandlerAdapter {
static Logger logger = LoggerFactory.getLogger(InboundHandlerAdapter3.class);
@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws
Exception {
logger.info("HandlerAdapter3 *************msg is {}",msg);
super.channelRead(ctx, msg);
}
}
public class OutboundHandlerAdapter4 extends ChannelOutboundHandlerAdapter {
static Logger logger =
LoggerFactory.getLogger(OutboundHandlerAdapter4.class);
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise
promise) throws Exception {
logger.info("HandlerAdapter4 *************msg is {}",msg);
super.write(ctx, msg, promise);
}
}
public class OutboundHandlerAdapter5 extends ChannelOutboundHandlerAdapter {
static Logger logger =
LoggerFactory.getLogger(OutboundHandlerAdapter5.class);
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise
promise) throws Exception {
logger.info("HandlerAdapter5 ************msg is {}",msg);
super.write(ctx, msg, promise);
}
}
public class OutboundHandlerAdapter6 extends ChannelOutboundHandlerAdapter {
static Logger logger =
LoggerFactory.getLogger(OutboundHandlerAdapter6.class);
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise
promise) throws Exception {
logger.info("HandlerAdapter6 *************msg is {}",msg);
super.write(ctx, msg, promise);
}
}

然后原始代码只需要把这六个类的对象创建出来然后封装给EmbeddedChannel对象即可。和我们最开始编写的代码是一样的

  • 分析2:如果编码改变一下,如下:
// 把handler绑定到Channel上面
EmbeddedChannel embeddedChannel = new EmbeddedChannel(h1,h2,h3,h4,h5,h6);
// 读入操作,和之前的inbound一样
embeddedChannel.writeInbound("inbound netty");
// 写出操作,和之前的outbound一样
embeddedChannel.writeOutbound("outbound netty");

我们执行writeInbound就是类似以前的执行多个inboundHandler,按照EmbeddedChannel添加的顺序执行。

而执行writeOutbound就是类似以前的执行多个outboundHandler,按照outBoundHandler的添加反顺序执行。

还有一个注意点:

我们在embeddedChannel.writeInbound("inbound netty");这个操作类似于以前的接收客户端的数据。以前我们客户端是经过编码成为bytebuf发给服务端接收的,然后服务端在走解码器,成为字符串,现在我们就直接写了一个"inbound netty"的字符串,和以前的不太真实一样了,所以需要我们发送的时候编码为bytebuff才能更加真实。

也就是这样像之前那样:

channel.writeInbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("llll".getBytes()));

channel.writeOutbound(ByteBufAllocator.DEFAULT.buffer().writeBytes("llll".getBytes()));

修改后并运行程序,此时则Handler输出的msg类型为:ByteBuf。这是Netty对NIO-ByteBuffer类型的封装

相关文章:

Netty应用(七) 之 Handler Netty服务端编程总结

目录 15.Handler 15.1 handler的分类 15.1.1 按照方向划分 15.1.2 handler的结构 15.2 输入方向ChannelInboundHandlerAdapter 15.2.1 输出方向Handler的顺序 15.2.2 多个输入方向Handler之间的数据传递 15.2.2.1 handler消失了 15.2.2.2 手动编写netty提供的new Strin…...

LeetCode、1268. 搜索推荐系统【中等,前缀树+优先队列、排序+前缀匹配】

文章目录 前言LeetCode、1268. 搜索推荐系统【中等&#xff0c;前缀树优先队列、排序前缀匹配】题目类型及分类思路API调用&#xff08;排序前缀匹配&#xff09;前缀树优先队列 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创…...

计算机视觉基础:矩阵运算

矩阵及其表示方式 一个矩阵是由行(row)和列(column)组成的一个矩形数组&#xff0c;通常包含数字。我们可以用大写字母&#xff08;如 A、B&#xff09;来表示一个矩阵。例如&#xff0c;矩阵 A 可能看起来像这样&#xff1a; A [ a11 a12 a13 ][ a21 a22 a23 ][ a31 a32 a3…...

Gateway中Spring Security6统一处理CORS

文章目录 一、起因二、解决方法 一、起因 使用了gateway微服务作为整体的网关&#xff0c;并且整合了Spring Security6&#xff1b;还有一个system微服务&#xff0c;作为被请求的资源&#xff0c;当浏览器向gateway发送请求&#xff0c;请求system资源时&#xff0c;遇到CORS…...

突破编程_C++_基础教程(输入、输出与文件)

1 流和缓冲区 C中&#xff0c;流&#xff08; stream &#xff09;和缓冲区&#xff08; buffer &#xff09;是两个紧密相关的概念&#xff0c;它们在处理输入和输出时起着重要的作用。 流&#xff08; Stream &#xff09; 流是一种抽象的概念&#xff0c;用于表示数据的流动…...

UE的 HUD 类中的必备方法和属性

在屏幕上绘制的方法 1. DrawText() DrawText() 方法允许开发者在屏幕上渲染文本。参数包括文本内容、位置、颜色、字体、缩放等。 void DrawText(const FString& Text, const FLinearColor& TextColor, float ScreenX, float ScreenY, UFont* Font, float Scale 1.…...

单片机的认识

单片机的定义 先简单理解为&#xff1a; 在一片集成电路芯片上集成了微处理器&#xff08;CPU &#xff09;存储器&#xff08;ROM和RAM&#xff09;、I/O 接口电路&#xff0c;构成单芯片微型计算机&#xff0c;即为单片机。 把组成微型计算机的控制器、运算器、存储器、输…...

转发:udig安装 用来为geoserver上shp地图配置显示样式 颜色

下载udig&#xff0c;解压缩 这东东是基于eclipse的&#xff0c;需要Java JRE 把 JDK 1.8 里面的jre目录拷贝到 udig目录下面 udig下载、安装及汉化&#xff0c;简单生成geoserver图层样式sld-CSDN博客...

Linux--常用命令(详解)

详细目录 一、终端命令格式二、显示文件列表命令-ls2.1作用2.2格式2.3 ls常用选项2.3.1 ls -a2.3.2 ls -l(等价于 ll)2.3.2 ls -h 三、相对路径与绝对路径3.1绝对路径3.2相对路径 四、目录操作命令 -cd4.1作用4.2格式4.3案例4.3.1 cd -&#xff1a; 返回上一次所在目录4.3.2 cd…...

SouthLeetCode-打卡24年02月第1周

SouthLeetCode-打卡24年02月第1周 // Date : 2024/02/01 ~ 2024/02/04 034.合并两个有序链表 (1) 题目描述 034#LeetCode.21.#北岸计划2024/02/01 将两个升序链表合并为一个新的 升序 链表并返回。 新链表是通过拼接给定的两个链表的所有节点组成的。 (2) 题解代码 cla…...

vscode的cmake工具小三角符号旁边没有目标的解决方法

vscode里面写了个项目&#xff0c;找了半天没办法用cmake调试&#xff0c;最后发现是cmake里面的set(CMAKE_BUILD_TYPE Release)导致的&#xff0c;都是release模式了当然不能调试了&#xff1b;改成Debug就行了 参考&#xff1a;https://stackoverflow.com/questions/7549672…...

Servlet JSP-Eclipse安装配置Maven插件

Maven 是一款比较常用的 Java 开发拓展包&#xff0c;它相当于一个全自动 jar 包管理器&#xff0c;会导入用户开发时需要使用的相应 jar 包。使用 Maven 开发 Java 程序&#xff0c;可以极大提升开发者的开发效率。下面我就跟大家介绍一下如何在 Eclipse 里安装和配置 Maven 插…...

os模块

os 模块是 Python 中用于与操作系统进行交互的标准库之一。它提供了许多函数来执行文件和目录操作&#xff0c;管理进程以及与操作系统交互的其他功能。 下面是一些 os 模块中常用的函数和功能&#xff1a; 文件和目录操作&#xff1a; os.getcwd(): 返回当前工作目录的路径。…...

【C语言进阶】深度剖析数据在内存中的存储--上

1. C语言中的数据类型的简单介绍 注&#xff1a;C99标准里面&#xff0c;定义了bool类型变量。这时&#xff0c;只要引入头文件stdbool.h &#xff0c;就能在C语言里面正常使用bool类型。 1.1 在C语言中各类型所占内存空间的大小如下 char类型的数据类型大小为1字节即8比特位。…...

【doghead】VS2022 win11 安装配置WSL2 以编译linux端的cmake项目并运行2

【bifrost】VS2022 win11 安装配置WSL2 以编译linux端的cmake项目并运行1 完成了WSL2的安装。13900K 的电脑安装了ubuntu22.04构建中出现了一些问题,fix了。发现libuv 似乎不识别,认为是libuv.so ,无法让worker识别到uv 从而没构建。干脆单独构建好了,官方的脚本如此:而且…...

【教程】C++语言基础学习笔记(七)——Array数组

写在前面&#xff1a; 如果文章对你有帮助&#xff0c;记得点赞关注加收藏一波&#xff0c;利于以后需要的时候复习&#xff0c;多谢支持&#xff01; 【C语言基础学习】系列文章 第一章 《项目与程序结构》 第二章 《数据类型》 第三章 《运算符》 第四章 《流程控制》 第五章…...

BUGKU-WEB GET

题目描述 没有提示&#xff0c;就一个get&#xff0c;启动场景看看&#xff1a; 解题思路 显然是PHP语言解读分析代码吧写出你的payload 相关工具 略 解题步骤 进入场景分析代码 $what$_GET[what]; echo $what; if($whatflag) echo flag{****};前两句&#xff1a;使用get…...

蓝桥杯每日一题----唯一分解定理

唯一分解定理 1.内容 任何一个大于1的整数n都可以分解成若干个质数的连乘积&#xff0c;如果不计各个质数的顺序&#xff0c;那么这种分解是惟一的&#xff0c;即若n>1&#xff0c;则有 n ∏ p i j n\prod{p^j_i} n∏pij​ 这里的 p i p_i pi​是质数。可以进行简单证明…...

openssl3.2 - osslsigncode工程的学习

文章目录 openssl3.2 - osslsigncode工程的学习概述笔记工程库地址工程的编译osslsigncodeM工程文件列表osslsigncodeM工程搭建细节原始工程实现的改动自己封装的包含openssl和curl的实现osslsigncodeM工程命令行的用法备注 - VS2019调试环境备注 - 如果要单步openssl的API学学…...

HTML 超文本标记语言

超文本标记语言 HTML 在一个客户程序主窗口上显示出的万维网文档称为页面 (page)。 页面制作的标准语言&#xff1a;HTML。 超文本标记语言 HTML (HyperText Markup Language) 是一种制作万维网页面的标准语言&#xff0c;它消除了不同计算机之间信息交流的障碍&#xff0c…...

sklearn:机器学习 分类特征编码category_encoders

文章目录 category_encoders简介OrdinalEncoder 序列编码OneHotEncoder 独热编码TargetEncoder 目标编码Binary Encoder 二进制编码BaseNEncoder 贝叶斯编码LeaveOneOutEncoder 留一法HashingEncoder 哈希编码CatBoostEncoder catboost目标编码CountEncoder 频率编码WOEEncoder…...

C++错误[错误] call of overloaded ‘min(int, int)‘ is ambiguous

错误代码&#xff1a; #include<iostream> using namespace std;template <class T> T min(T x,T y){if(x<y){return x;}else return y; }int main(){int n12,n210;double d11.5,d25.6;cout<<min(n1,n2)<<endl;cout<<min(d1,d2)<<endl…...

2024全栈元年-thinkphp-数据操作

thinkphp 数据相关操作 1.单数据查询 1、单数据查询 ,Db::table(‘tp_stu’) 必须加前缀 2、如果只是查询符合条件的使用where find,如果没有符合条件的返回null 3、使用 findOrFail 没有数据会抛出异常 4、使用 findOrEmpty 没有数据会返回【】 5、得到最近一个原生SQL …...

HTML世界之第二重天

目录 一、HTML 格式化 1.HTML 文本格式化标签 2.HTML "计算机输出" 标签 3.HTML 引文, 引用, 及标签定义 二、HTML 链接 1.HTML 链接 2.HTML 超链接 3.HTML 链接语法 4.文本链接 5.图像链接 6.锚点链接 7.下载链接 8.Target 属性 9.Id 属性 三、HTML …...

社区经营的好处与优势:为何越来越多的人选择社区店?

社区店&#xff0c;这个曾经被视为小型、局限的商业模式&#xff0c;如今正逐渐崭露头角&#xff0c;成为众多创业者和消费者的首选。 特别是在鲜奶吧这样的细分市场中&#xff0c;社区店更是展现出了其独特的魅力和优势。作为一名拥有五年鲜奶吧经营经验的创业者&#xff0c;…...

C语言系列1——详解C语言:变量、常量与数据类型

目录 写在开始1. 变量与常量的概念1.1. 变量1.2. 常量1.3. 变量与常量的比较1.4. 选择变量还是常量 2. C语言中的基本数据类型2.1. 整型&#xff08;Integer Types&#xff09;2.2. 浮点型&#xff08;Floating-Point Types&#xff09;2.3. 字符型&#xff08;Character Type&…...

WordPress修改所有用户名并发送邮件通知的插件Easy Username Updater

前面跟大家介绍了『如何修改WordPress后台管理员用户名&#xff1f;推荐2种简单方法』一文&#xff0c;但是对于有很多用户的站长来说&#xff0c;操作有点复杂&#xff0c;而且无法发邮件通知对方&#xff0c;所以今天boke112百科向大家推荐一款可以直接在WordPress后台修改所…...

C语言中的数据类型-强转

强制类型转换 概念&#xff1a;将某种类型的数据转化我们需要的数据类型&#xff0c;注意强制类型转化是临时强转&#xff0c;不会改变本身的数据类型。 强转又分为显式强转和隐式转化 显示强转是按照我们的要求进行转化 格式&#xff1a;(需要转化数据类型)变量名 #inclu…...

大数据可视化BI分析工具Apache Superset结合内网穿透实现远程访问

文章目录 前言1. 使用Docker部署Apache Superset1.1 第一步安装docker 、docker compose1.2 克隆superset代码到本地并使用docker compose启动 2. 安装cpolar内网穿透&#xff0c;实现公网访问3. 设置固定连接公网地址 前言 Superset是一款由中国知名科技公司开源的“现代化的…...

C# 线程与线程池的使用方法、注意事项

在C#中&#xff0c;线程和线程池是两种用于实现多线程编程的方式。线程用于执行并发任务&#xff0c;而线程池提供了一种更有效率的方式来管理和复用线程资源。 C# 线程&#xff08;System.Threading.Thread&#xff09; 创建和启动线程&#xff1a; Thread thread new Thre…...

2024年华为OD机试真题-按身高和体重排队-Python-OD统一考试(C卷)

题目描述: :某学校举行运动会,学生们按编号(1、2、3…n)进行标识,现需要按照身高由低到高排列,对身高相同的人,按体重由轻到重排列;对于身高体重都相同的人,维持原有的编号顺序关系。请输出排列后的学生编号。 输入描述:两个序列,每个序列由n个正整数组成(0 < n …...

openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O

文章目录 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O218.1 查看I/O状况218.2 性能参数分析 openGauss学习笔记-218 openGauss性能调优-确定性能调优范围-硬件瓶颈点分析-I/O 获取openGauss节点的CPU、内存、I/O和网络资源使用情况&#xf…...

去除vue自带的边距

使用vue时发现总有去不掉的外边距&#xff0c;在index.vue里面怎样设置样式都不管用 查阅资料后发现要在vue项目自带的index.html文件内添加下面的样式代码才行 <style>*{margin: 0;padding: 0;}body,html{margin: 0;padding: 0;} </style>...

ElasticSearch级查询Query DSL上

目录 ES高级查询Query DSL match_all 返回源数据_source 返回指定条数size 分页查询from&size 指定字段排序sort 术语级别查询 Term query术语查询 Terms Query多术语查询 exists query ids query range query范围查询 prefix query前缀查询 wildcard query通…...

120.乐理基础-五线谱-五线谱的多声部与指法问题

内容参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;119.乐理基础-五线谱-五线谱的标记-CSDN博客 五线谱多声部与简谱的多声部一样&#xff1a;简谱的多声部 五线谱多声部例子&#xff1a;钢琴谱 另一个例子&#xff1a;在纵向上有多个音符 然后放大之后&#xff0c…...

YOLOv5独家改进:上采样算子 | 超轻量高效动态上采样DySample,效果秒杀CAFFE,助力小目标检测

💡💡💡本文独家改进:一种超轻量高效动态上采样DySample, 具有更少的参数、FLOPs,效果秒杀CAFFE和YOLOv5网络中的nn.Upsample 💡💡💡在多个数据集下验证能够涨点,尤其在小目标检测领域涨点显著。 收录 YOLOv5原创自研 https://blog.csdn.net/m0_63774211/cate…...

洛谷 P1102 A-B 数对 (Java)

洛谷 P1102 A-B 数对 (Java) 传送门&#xff1a;P1102 A-B 数对 题目&#xff1a; A-B 数对 题目背景 出题是一件痛苦的事情&#xff01; 相同的题目看多了也会有审美疲劳&#xff0c;于是我舍弃了大家所熟悉的 AB Problem&#xff0c;改用 A-B 了哈哈&#xff01; 题目描…...

情人节到了,写一份爱心程序(python)

前言 情人节到了&#xff0c;写一份爱心代码给喜欢的人呀 公式 首先我们介绍下爱心的公式的参数方程&#xff1a; x 16 s i n 3 ( t ) x 16sin^3(t) x16sin3(t) y 13 c o s ( t ) − 5 c o s ( 2 t ) − 2 c o s ( 3 t ) − c o s ( 4 t ) y 13cos(t) - 5cos(2t) - 2co…...

Java图形化界面编程—— 基本组件和对话框 笔记

2.5 AWT中常用组件 2.5.1 基本组件 组件名功能ButtonButtonCanvas用于绘图的画布Checkbox复选框组件&#xff08;也可当做单选框组件使用&#xff09;CheckboxGroup选项组&#xff0c;用于将多个Checkbox 组件组合成一组&#xff0c; 一组 Checkbox 组件将只有一个可以 被选中…...

使用Docker,拉取Nginx镜像,创建并运行Nginx容器

查看DockerHub&#xff0c;拉取Nginx镜像&#xff0c;创建并运行Nginx容器。 在DockerHub中搜索Ningx镜像&#xff0c;查看镜像名称 拉取Nginx镜像 [rootservice ~]# systemctl start docker [rootservice ~]# mkdir -p /etc/docker [rootservice ~]# tee /etc/docker/daemo…...

InstantBox:开箱即用的临时 Linux 环境

在云计算和虚拟化技术日益成熟的今天&#xff0c;我们有时需要一个快速、简单、临时的 Linux 环境来进行各种任务。这就是 InstantBox 的用武之地。 什么是 InstantBox&#xff1f; InstantBox 是一个开源项目&#xff0c;它可以快速启动临时的 Linux 系统&#xff0c;并提供…...

【面试】国家公务员考试复试,面试内容准备方向(非技术面试考察点)

【面试】国家公务员考试复试&#xff0c;面试内容准备方向&#xff08;非技术面试考察点&#xff09; 说明&#xff1a; csdn无法发部分考试相关例题&#xff08;提示涉z&#xff09;&#xff0c;所以本文主要还是针对评分标准和仪表等相关因素。 文章目录 1、面试试卷与评分1.…...

点餐|外卖订餐小程序|基于微信小程序的外卖订餐系统设计与实现(源码+数据库+文档)

点餐|外卖订餐小程序目录 目录 基于微信小程序的外卖订餐系统设计与实现 一、前言 二、系统功能设计 三、系统实现 1、用户微信端功能模块 2、管理员服务端功能模块 3、商家务端功能模块 四、数据库设计 1、实体ER图 五、核心代码 六、论文参考 七、最新计算机毕设…...

使用Cargo创建、编译与运行Rust项目

在 Rust 开发中&#xff0c;Cargo 是一个非常重要的工具&#xff0c;它负责项目的构建、管理和依赖管理。以下是如何使用 Cargo 创建、编译和运行 Rust 项目的详细步骤。 1. 创建新项目 首先确保你已经在计算机上安装了 Rust 和 Cargo。然后&#xff0c;在命令行中输入以下命…...

Js中toFixed(2)精度问题的原因及解决办法

toFixed() 方法可把 Number 四舍五入为指定小数位数的数字。例如将数据Num保留2位小数&#xff0c;则表示为&#xff1a;toFixed(Num)&#xff1b;但是其四舍五入的规则与数学中的规则不同&#xff0c;使用的是银行家舍入规则&#xff0c;银行家舍入&#xff1a;所谓银行家舍入…...

【医学大模型 知识增强】SMedBERT:结构化语义知识 + 医学大模型 = 显著提升大模型医学文本挖掘性能

SMedBERT&#xff1a;结构化语义知识 医学大模型 显著提升医学文本挖掘任务性能 名词解释结构化语义知识预训练语言模型医学文本挖掘任务 提出背景具体步骤提及-邻居混合注意力机制实体嵌入增强实体描述增强三元组句子增强 提及-邻居上下文建模域内词汇权重学习领域自监督任务…...

Python爬虫:安全与会话管理

源码分享 ​​https://docs.qq.com/sheet/DUHNQdlRUVUp5Vll2?tabBB08J2​​ 在进行网站数据抓取时&#xff0c;会话管理是保持与目标网站通信连续性的一种机制。这对于模拟登录、保持用户状态、维护cookie等场景至关重要。同时&#xff0c;安全性也是我们不可忽视的一个方面…...

[Python进阶] 识别验证码

11.3 识别验证码 我们再开发某些项目的时候&#xff0c;如果遇到要登录某些网页&#xff0c;那么会经常遇到输入验证码的情况&#xff0c;而每次人工输入验证码的话&#xff0c;比较浪费时间。于是&#xff0c;可以通过调用某些接口进行识别。 11.3.1 调用百度文字识别接口 …...

华为问界M9:全方位自动驾驶技术解决方案

华为问界M9的自动驾驶技术采用了多种方法来提高驾驶的便利性和安全性。以下是一些关键技术&#xff1a; 智能感知系统&#xff1a;问界M9配备了先进的传感器&#xff0c;包括高清摄像头、毫米波雷达、超声波雷达等&#xff0c;这些传感器可以实时监测车辆周围的环境&#xff0…...

Java 与 JavaScript 的区别与联系

Java 和 JavaScript 两种编程语言在软件开发中扮演着重要的角色。尽管它们都以“Java”命名&#xff0c;但实际上它们是完全不同的语言&#xff0c;各有其独特的特点和用途。本文将深入探讨 Java 和 JavaScript 的区别与联系&#xff0c;帮助大家更好地理解它们在编程世界中的作…...