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

Netty编码器和解码器

文章目录

  • 一、Decoder原理与实践
    • 1、ByteToMessageDecoder解码器
    • 2、自定义整数解码器
      • 1)常规方式
      • 2)ReplayingDecoder解码器
    • 3、分包解码器
    • 3、MessageToMessageDecoder解码器
  • 二、Netty内置的Decoder
    • 1、LineBasedFrameDecoder解码器
    • 2、DelimiterBasedFrameDecoder解码器
    • 3、LengthFieldBasedFrameDecoder解码器
  • 三、Encoder原理与实践
    • 1、MessageToByteEncoder编码器
    • 2、MessageToMessageEncoder编码器
  • 四、解码器和编码器的结合
    • 1、ByteToMessageCodec编解码器
    • 2、CombinedChannelDuplexHandler组合器

Netty从底层Java通道读到ByteBuf二进制数据,传入Netty通道的流水线,随后开始入站处理。在入站处理过程中,需要将ByteBuf二进制类型解码成Java POJO对象。这个解码过程可以通过Netty的Decoder解码器去完成。在出站处理过程中,业务处理后的结果需要从某个Java POJO对象编码为最终的ByteBuf二进制数据,然后通过底层 Java通道发送到对端。在编码过程中,需要用到Netty的Encoder编码器去完成数据的编码工作。

一、Decoder原理与实践

Netty的解码器Decoder本质上是一个InBound入站处理器,它将上一站InBound入站处理器传过来的输入数据进行数据的解码或者格式转换,然后输出到下一站InBound入站处理器。

一个标准的解码器将输入类型为ByteBuf缓冲区的数据进行解码,输出一个一个的Java POJO对象。Netty内置了这个解码器,叫做ByteToMessageDecoder。

Netty中的解码器都是Inbound入站处理器类型,都直接或间接地实现了ChannelInboundHandler接口。

1、ByteToMessageDecoder解码器

ByteToMessageDecoder是一个非常重要的解码器基类,它是一个抽象类,实现了解码的基础逻辑和流程。ByteToMessageDecoder继承自ChannelInboundHandlerAdapter适配器,是一个入站处理器,实现了从ByteBuf到Java POJO对象的解码功能。

ByteToMessageDecoder解码的流程大致是先将上一站传过来的输入到ByteBuf中的数据进行解码,解码出一个List对象列表,然后迭代这个列表,逐个将Java POJO对象传入下一站Inbound入站处理器。

ByteToMessageDecoder的解码方法名为decode,在该类中只是提供了一个抽象方法,具体的解码过程,即如何将ByteBuf数据变成Object数据需要子类去完成。ByteToMessageDecoder作为解码器的父类,只是提供了一个流程性质的框架,它仅仅将子类的decode方法解码后的Object结果放入自己内部的结果列表List中(这个过程也是子类的decode方法完成的),最终父类会负责将列表中的元素一个一个传递给下一个站。

如果要实现一个自己的解码器,首先继承ByteToMessageDecoder抽象类,然后实现其积累的decode抽象方法,将解码的逻辑写入此方法。总体来说,流程大致如下:

  1. 继承ByteToMessageDecoder抽象类
  2. 实现基类的decode抽象方法,将ByteBuf到POJO解码的逻辑写入此方法。将ByteBuf二进制数据解码成一个一个的Java POJO对象
  3. 在子类的decode方法中奖解码后的Java POJO对象放入decode的List市财政,这个实惨是父类传入的,也就是父类的结果收集列表
  4. 父类将List中的结果一个个分开地传递到下一站的Inbound入站处理器

ByteToMessageDecoder传递给下一站的是解码之后的Java POJO对象(会遍历list中的所有元素,将其作为参数调用fireChannelRead方法),不是ByteBuf缓冲区。那么ByteBuf缓冲区由谁负责进行引用计数和释放管理的呢?

起始积累的ByteToMessageDecoder负责解码器的ByteBuf缓冲区的释放工作,它会自动调用release方法将之前的ByteBuf缓冲区的引用数减1,这个工作是自动完成的。

如果这个ByteBuf在后面还需要用到,那么可以在decode方法中调用一次retain方法来增加一次引用计数。

2、自定义整数解码器

1)常规方式

public class Byte2IntegerDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List<Object> list) throws Exception {while (byteBuf.readableBytes() >= 4) {int i = byteBuf.readInt();System.out.println("解码出一个整数:" + i);list.add(i);}}
}public class IntegerProcessHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {Integer integer = (Integer) msg;System.out.println("打印出一个整数:" + integer);super.channelRead(ctx, msg);}
}public class Byte2IntegerDecoderTester {public static void main(String[] args) {EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInitializer<EmbeddedChannel>() {@Overrideprotected void initChannel(EmbeddedChannel ch) throws Exception {ch.pipeline().addLast(new Byte2IntegerDecoder()).addLast(new IntegerProcessHandler());}});ByteBuf buf = Unpooled.buffer();buf.writeInt(1);embeddedChannel.writeInbound(buf);}
}

使用上面的Byte2IngeterDecoder证书解码器需要先对ByteBuf的长度进行检查,如果有足够的字节,才进行整数的读取。这种长度的判断可以由Netty的ReplayingDecoder类来完成。

2)ReplayingDecoder解码器

ReplayingDecoder类是ByteToMessageDecoder的子类,其作用是:

  • 在读取ByteBuf缓冲区的数据之前,检查缓冲区是否有足够的字节
  • 若ByteBuf中有足够的字节则会正常读取,否则会停止解码

也就是说使用Replaying基类来编写整数解码器可以不用我们进行长度检测。

public class Byte2IntegerReplayDecoder extends ReplayingDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {int i = in.readInt();System.out.println("解码出一个整数:" + i);out.add(i);}
}

可以看出通过继承ReplayingDecoder类来实现一个解码器就不用编写长度判断的代码。Replaying内部定义了一个新的二进制缓冲区类对ByteBuf缓冲区进行了装饰,类名为ReplayingDecoderBuffer。这个装饰器会在缓冲区真正读数据之前首先进行长度的判断,如果长度合格则读取数据,否则抛出ReplayError。ReplayingDecoder捕获到ReplayError后会留着数据,等待下一次IO时间到来时再读取。

也就是说实际上ReplayingDecoder中decode方法所得到的实参in的值并不是原始的ByteBuf类型,而是ReplayingDecoderBuffer类型,它继承了ByteBuf类,包装了大部分的读取方法,在读取前进行长度判断。

当然ReplayingDecoder的作用远远不止进行长度判断,更重要的作用是分包传输。

3、分包解码器

底层通信协议是分包传输的,一份数据可能分几次达到对端,也就是说发送端出去的包在传输过程会进行多次的拆分和组装。接收端所收到的包和发送端所发送的包不是一模一样的。

在Java OIO流式传输中,不会出现这样的问题,因为他的策略是不读完完整的信息就一直阻塞程序,不向后执行。但是在Java的NIO中,由于NIO的非阻塞性,就会出现接受的数据包和发送端发送的包不是一模一样的情况。比如说发送方发送的是ABC和DEF,而接收方接受到的是ABCD和EF。

对于这种问题,还是可以使用ReplayingDecoder来解决,在进行数据解析时,如果发现当前ByteBuf中所有可读的数据不够,ReplayingDecoder会结束解析直到可读数据足够。这一切都是在ReplayingDecoder内部实现的,不需要用户程序操行。与整数分包传输不同的是,字符串的长度不向整数的长度是固定的,时可变长度的。因此一般来说在Netty中进行字符串的传输可以采用普遍的Header-Content内容传输协议:

  1. 在协议的Head部分放置字符串的字节长度,Head部分可以用一个整形int来描述
  2. 在协议的Content部分,放置的是字符串的字节数组。

那么在实际传输过程中,一个Header-Content内容包,在发送端会被编码成为一个ByteBuf内容发送包,当到达接收端后可能被分成很多ByteBuf接收包。对于这些参差不齐的接收包,如何解码成为最初的ByteBuf内容发送包呢?

在ReplayingDecoder中有一个很重要的state成员属性,该属性的作用是保存当前解码器在解码过程中的当前阶段。该属性的类型和ReplayingDecoder的泛型一致,并且ReplayingDecoder提供了有参的构造方法初始化这个值。此外还提供了checkpoint(status)方法将状态设置为新的status值并且设置读断点指针。

读断点指针是ReplayingDecoder类的另一个重要的成员,它保存着装饰器内部ReplayingDecoderBuffer成员的起始读指针,有点类似mark标记。当读数据时,一旦可读数据不够,ReplayingDecoderBuffer在抛出RelayError异常之前,会把读指针的值还原到之前checkpoint方法设置的读断点指针,因此下次读取时还会从之前设置的断点位置开始。

因此我们可以将读取分为两个阶段,第一个阶段获取长度,第二个阶段获取字符串。根据前面的ReplayingDecoder提供的state属性,我们只需要采用ReplayingDecoder解码器即可实现自定义的字符串分包解码器,代码如下:

public class StringReplayDecoder extends ReplayingDecoder<StringReplayDecoder.Status> {private int length;private byte[] inBytes;public StringReplayDecoder() {super(Status.PARSE_1);}@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {switch (state()) {case PARSE_1 -> {length = in.readInt();inBytes = new byte[length];checkpoint(Status.PARSE_2);}case PARSE_2 -> {in.readBytes(inBytes, 0, length);out.add(new String(inBytes, StandardCharsets.UTF_8));checkpoint(Status.PARSE_1);}}}enum Status {PARSE_1, PARSE_2}
}public class StringProcessHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {System.out.println("打印出一个字符串:" + msg);super.channelRead(ctx, msg);}
}public class StringReplayDecoderTester {public static void main(String[] args) {EmbeddedChannel embeddedChannel = new EmbeddedChannel(new ChannelInitializer<EmbeddedChannel>() {@Overrideprotected void initChannel(EmbeddedChannel ch) throws Exception {ch.pipeline().addLast(new StringReplayDecoder()).addLast(new StringProcessHandler());}});final String str = "你好世界!";for (int i = 0; i < 3; i++) {ByteBuf buf = Unpooled.buffer();buf.writeInt((i + 1) * str.getBytes().length);for (int j = 0; j < i + 1; j++) {buf.writeBytes(str.getBytes(StandardCharsets.UTF_8));}embeddedChannel.writeInbound(buf);}}
}
输出结果:
打印出一个字符串:你好世界!
打印出一个字符串:你好世界!你好世界!
打印出一个字符串:你好世界!你好世界!你好世界!

可以看到结果成功打印出了我们输入的数据,这是依赖于ReplayingDecoder的state属性实现的,创建这个解码器时默认为STATE1状态,此时会去尝试读取一个整形,读取出来的结果就是我们这次希望读取到的字符串(分包)的长度,接着改变状态到STATE2,并且decode方法结束。因为这里并没有将读出来的结果加入到out列表中,因此不会触发第二个处理器的逻辑。而当读取到字符串时,此时已经处于STATE2状态,因此会调用in.readBytes方法去进行读取指定的长度。如果因为传输过程的拆包原因,此次读取到的字符串并不完整,那么此时达不到目标读入长度,ReplayingDecoderBuffer在抛出RelayError异常之前,会把读指针的值还原到之前checkpoint方法设置的读断点指针,因此下次读取时还会从之前设置的断点位置开始。因此保证了读取到的分包的正确性。

虽然通过这种方式可以正确的解码分包后的ByteBuf数据包,但是在实际开发过程中不太建议继承这个类,原因是:

  1. 不是所有的ByteBuf操作都被ReplayingDecoderBuffer装饰类所支持,可能有些操作在decode方法中被使用时就会抛出ReplayError异常
  2. 在数据解析逻辑复杂的应用场景,ReplayingDecoder的解析速度相对较差(因为ByteBuf中长度不够时,ReplayingDecoder会捕获一个ReplayError异常,这时会把ByteBuf中的读指针还原到之前的读断点指针(checkpoint),然后解析这次解析操作等待下一次IO读事件,在网络条件比较糟糕时,一个数据包的解析逻辑会被反复执行多次,如果解析过程是一个消耗CPU的操作,那么对CPU是个大的负担)

因此ReplayingDecoder更多的是应用于数据解析逻辑简单的场景,复杂的场景建议使用ByteToMessageDecoder或其子类,如下:

public class StringIntegerHeaderDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {if (in.readableBytes() < 4) {return;}in.markReaderIndex();int length = in.readInt();if (in.readableBytes() < length) {// 重置为读取长度前in.resetReaderIndex();return;}byte[] inBytes = new byte[length];in.readBytes(inBytes, 0, length);out.add(new String(inBytes, StandardCharsets.UTF_8));}
}

表面上ByteToMessageDecoder基类是无状态的,不像ReplayingDecoder需要使用状态为来保存当前的读取阶段。但是实际上ByteToMessageDecoder内部有一个二进制字节的累积器cumulation,用来保存没有解析完的二进制内容。所以ByteToMessageDecoder及其子类是有状态的业务处理器,不能共享。因此每次初始化通道的流水线时,都需要重新创建一个ByteToMessageDecoder或者它的子类的实例。

3、MessageToMessageDecoder解码器

前面的解码器都是讲ByteBuf缓冲区的二进制数据解码成Java的普通POJO对象,而如果想要将一种POJO对象解析成另一种POJO对象,则需要继承一个新的Netty解码器基类——MessageToMessageDecoder,在继承它的时候需要明确泛型实参,它表示入站消息Java POJO类型。

二、Netty内置的Decoder

  1. 固定长度数据包解码器:FixedLengthFrameDecoder

    • 使用场景:每个接受到的数据包的长度都是固定的
    • 会把入站的ByteBuf拆分成一个个固定长度的数据包(ByteBuf)然后发往下一个channelHandler入站处理器
  2. 行分割数据包解码器:LineBasedFrameDecoder

    • 使用场景:每个ByteBuf数据包,使用换行符(或回车换行符)作为数据包的边界分隔符
  3. 自定义分隔符数据包解码器:DelimiterBasedFrameDecoder

  4. 自定义长度数据包解码器:LengthFieldBasedFramDecoder

    • 一种灵活长度的解码器。在ByteBuf数据包中加了一个长度字段,保存了原始数据报的长度。解码的时候会根据这个长度进行原始数据包的提取

1、LineBasedFrameDecoder解码器

前面字符串分包解码器中内容是按照Header-Content协议进行传输的。如果不使用这种协议而是在发送端通过换行符来(“\n"或者”\r\n")来分割每一次发送的字符串,那么久需要使用LinedBasedFrameDecoder解码器。

这个解码器的工作原理很简单,它一次遍历ByteBuf数据包中的可读字节,判断在二进制字节流中是否存在换行符"\n"或"\r\n"的字节码,如果有就以此位置为结束位置,把从可读索引到结束为止之间的字节作为解码成功后的ByteBuf数据包。

LineBasedFrameDecoder还支持配置一个最大长度值(构造函数传入),表示一行最大能包含的字节数。如果连续读取到最大长度后仍然没有发现换行符就会抛出异常。

2、DelimiterBasedFrameDecoder解码器

DelimiterBasedFrameDecoder解码器不仅可以使用换行符,还可以将其他的特殊字符作为数据包的分隔符,例如制表符“\t”。其构造方法如下:

public DelimiterBasedFrameDecoder(int maxFrameLength, 
boolean stripDelimiter, // 解码后数据包是否去掉分隔符
ByteBuf delimiter) // 分隔符

分隔符是ByteBuf类型的,也就是需要将分隔符对应的字节数组用ByteBuf包装起来

3、LengthFieldBasedFrameDecoder解码器

LengthFieldBasedFrameDecoder解码器可以翻译为长度字段数据包解码器,传输内容中的LengthField长度字段的值,是指存放在数据包中要传输内容的字节数。普通的基于Header-Content协议的内容传输,尽量用内置的LengthFieldBasedFrameDecoder来解码。

其具体的构造函数如下:

public LengthFieldBasedFrameDecoder(int maxFrameLength, // 发送的数据包最大长度int lengthFieldOffset, // 长度字段偏移值int lengthFieldLength, // 长度字段自己占用的字节数int lengthAdjustment, // 长度字段的偏移量矫正int initialBytesToStrip) // 丢弃的起始字节数
  1. maxFrameLength:发送的数据包的最大长度
  2. lengthFieldOffset:指的是长度字段位于整个数据包内部的字节数组中的下标志
  3. lengthFieldLength:长度字段所占的字节数,如果长度字段是一个int整数则为4
  4. lengthAdjustment:在传输协议比较复杂的情况下(例如包含了长度字段、协议版本号、魔数等等),解码时需要进行长度矫正。长度校正值的计算公式为:内容字段偏移量-长度字段偏移量-长度字段的字节数
  5. initialBytesToStrip:在有效数据字段Content前面,还有一些其他字段的字节,作为最终的解析结果,可以丢弃。

假设我们要传输的数据包ByteBuf(58个字节)包括以下三个部分:

  1. 长度字段(4个字节):52
  2. 版本字段(2个字节):10
  3. content字段(52个字节):xxxxx

那么此时运用LengthFieldBasedFrameDecoder解码器需要传入构造器的参数为:

  1. 最大长度可以设置为1024
  2. 长度字段偏移量为0
  3. 长度字段的长度为4
  4. 长度字段的偏移量矫正为2,也就是长度字段距离内容部分的字节数为2
  5. 获取最终Content内容的字节数组时,前面6个字节的内容可以抛弃

三、Encoder原理与实践

在Netty的业务处理完成后,业务处理的结果往往是某个Java POJO对象,需要编码成最终的ByteBuf二进制类型,通过流水线写入到底层的Java通道。

编码器和解码器相呼应,编码器是一个Outbound出站处理器,将上一站传过来的输入数据(一般是某种Java POJO对象)编码成二进制ByteBuf,或者编码成另一种Java POJO对象。

编码器是ChannelOutboundHandler出站处理器的实现类。一个编码器将出站对象编码后,数据将被传递到下一个ChannelOutboundHandler出站处理器,进行后面的出站处理。由于最后只有ByteBuf才能写入到通道中去,因此可以肯定通道流水线上装配的第一个编码器一定是把数据编码成了ByteBuf类型(出站处理的顺序是从后向前的)。

1、MessageToByteEncoder编码器

MessageToByteEncoder编码器是一个非常重要的编码器基类,它的功能是讲一个Java POJO对象编码成一个ByteBuf数据包。它是一个抽象类,仅仅实现了编码的基础流程,在编码过程中,通过调用encode抽象方法来完成,具体的encode逻辑需要由子类去实现。

如果需要实现一个自己的编码器,则需要继承自MessageToByteEncoder基类,实现它的encode抽象方法。继承MessageToByteEncoder时需要带上泛型实参,表示编码之前的Java POJO原类型。

2、MessageToMessageEncoder编码器

除了将POJO对象编码成ByteBuf二进制对象,也可以将POJO对象编码成另一种POJO对象。通过继承MessageToMessageEncoder编码器,并且实现它的encode抽象方法。在子类的encode方法实现中,完成原POJO类型到目标POJO类型的编码逻辑。在encode实现方法中,编码完成后,将对象加入到encode方法中的List实参列表中即可。

四、解码器和编码器的结合

在流水线处理时,数据的流动往往一进一出,进来时解码,出去时编码。所以在同一个流水线上,加了某种编码逻辑,往往需要加上一个相对应的解码逻辑。

前面讲到的编码器和解码器都是分开实现的,例如通过继承ByteToMessageDecoder基类或者它的子类完成ByteBuf到POJO的解码工作;通过继承MessageToByteEncoder基类或者它的子类完成POJO到ByteBuf数据包的编码工作。总之具有相反逻辑的编码器和解码器实现在两个不同的类中,导致相互配套的编码器和解码器在加入到通道的流水线时,需要分两次添加。

因此Netty提供了新的类型Codec类型,实现具有相互配套逻辑的编码器和解码器放在同一个类中。

1、ByteToMessageCodec编解码器

完成POJO到ByteBuf数据包的配套的编码器和解码器的基类,叫做ByteToMessageCodec,它是一个抽象类。从功能上说,继承它就等同于继承了ByteToMessageDecoder解码器和MessageToByteEncoder编码器这两个基类。

ByteToMessageCodec同时包含了编码encode和解码decode两个抽象方法,这两个方法都需要自己实现:

  1. 编码方法:encode(ChannelHandlerContext, I, ByteBuf)
  2. 解码方法:decode(ChannelHandlerContext, ByteBuf, List)

2、CombinedChannelDuplexHandler组合器

前面的编码器和解码器相结合是通过继承完成的。将编码器和解码器的逻辑强制性地放在同一个类中,在只需要编码或者解码单边操作的流水线上,逻辑上不太合适。

编码器和解码器如果要结合起来,除了继承的方法之外,还可以通过组合的方式实现。与继承相比,组合会带来更大的灵活性:编码器和解码器可以捆绑使用,也可以单独使用。

Netty提供了一个新的组合器——CombinedChannelDuplexHandler基类,继承该类不需要像ByteToMessageCodec那样将编码逻辑和解码逻辑都挤在同一个类中,还是复用原来的编码器和解码器,具体使用方式如下:

public class IntegerDuplexHandler extends CombinedChannelDuplexHandler<Byte2IntegerDecoder, Integer2ByteEncoder> {public IntegerDuplexHandler() {super(new Byte2IntegerDecoder(), new Integer2ByteEncoder());}
}

继承该类不需要像ByteToMessageCodec那样把编码解码两个逻辑放在一个类中,还是复用原来的编码器和解码器。总之使用这个类保证了相反逻辑关系的encoder编码器和decoder解码器既可以结合使用,又可以分开使用,十分方便。

相关文章:

Netty编码器和解码器

文章目录 一、Decoder原理与实践1、ByteToMessageDecoder解码器2、自定义整数解码器1&#xff09;常规方式2&#xff09;ReplayingDecoder解码器 3、分包解码器3、MessageToMessageDecoder解码器 二、Netty内置的Decoder1、LineBasedFrameDecoder解码器2、DelimiterBasedFrameD…...

大语言模型(LLM)综述(三):大语言模型预训练的进展

A Survey of Large Language Models 前言4. PRE-TRAINING4.1数据收集4.1.1 数据源4.1.2 数据预处理4.1.3 预训练数据对LLM的影响 4.2 模型架构4.2.1 典型架构4.2.2 详细配置4.2.3 预训练任务4.2.4 解码策略4.2.5 总结和讨论 4.3 模型训练4.3.1 优化设置4.3.2 可扩展的训练技术 …...

如何在Node.js中使用环境变量或命令行参数来设置HTTP爬虫ip?

首先&#xff0c;定义问题&#xff1a;在 Node.js 应用程序中&#xff0c;我们可以通过环境变量或命令行参数来设置HTTP爬虫ip&#xff0c;以便在发送请求时使用这些HTTP爬虫ip。 亲身经验&#xff1a;我曾经需要为一个项目设置HTTP爬虫ip&#xff0c;以便在发送请求时使用这些…...

VMware打开共享虚拟机后找不到/mnt/hgfs/文件夹,以及不能拖拽/复制粘贴等操作,ubuntu不能安装VMware tools

问题原因 我的问题出现原因是&#xff0c;安装ubuntn虚拟机的时候VMware tools没有安装好&#xff0c;需要重新安装&#xff0c;但安装选项是暗的&#xff0c;不能操作。 类似这种情况&#xff0c;虚拟机开启时也是&#xff0c;因为我虚拟机已经装好了&#xff0c;开启时是亮…...

pytorch 入门 (五)案例三:乳腺癌识别识别-VGG16实现

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】乳腺癌识别&#x1f356; 原作者&#xff1a;K同学啊 在本案例中&#xff0c;我将带大家探索一下深…...

【QT开发(14)】QT P2P chat 聊天

在【P2P学习&#xff08;2&#xff09;】P2P 通信&#xff0c;主要存在四种不同的网络模型的第一阶段&#xff1a;集中式P2P 模式 最简单的路由方式就是集中式&#xff0c;即存在一个中心节点保存了其他所有节点的索引信息&#xff0c;索引信息一般包括节点 IP 地址、端口、节…...

解决adb root命令时错误 adbd cannot run as root in production builds

我测试的手机是小米8&#xff0c;root权限已经刷过了&#xff0c;但是在pc端使用adb root命令的时候&#xff0c;会报错"adbd cannot run as root in production builds" 后来查资料发现是因为Magisk和安卓9版本的问题 https://www.cnblogs.com/jeason1997/p/124105…...

操作系统中套接字和设备独立性软件的关系

网络编程就是编写程序让两台联网的计算机相互交换数据。在我们不需要考虑物理连接的情况下&#xff0c;我们只需要考虑如何编写传输软件。操作系统提供了名为“套接字”&#xff0c;套接字是网络传输传输用的软件设备。 这是对软件设备的解释&#xff1a; 在操作系统中&#…...

C++ Qt/VTK装配体组成联动连接杆

效果 关键代码 #include "View3D.h" #include "Axis.h"#include <vtkActor.h> #include <vtkAppendPolyData.h > #include <vtkAreaPicker.h> #include <vtkAxesActor.h> #include <vtkBox.h> #include <vtkCamera.h>…...

File文件查找

用的是递归调用&#xff0c; &#xff08;递归死循环的结果是导致栈内存溢出错误&#xff09; 一.代码 package org.example;import java.io.File;public class day03 {public static void main(String[] args) {//文件查找&#xff0c;在d&#xff1a;temp下查找改名.mp4sea…...

小程序 wxml2canvas开发文档

wxml: <view class"share__canvas share__canvas1"><view class"share__canvas1-text draw_canvas" data-type"text" data-text"这是一段无边距文字">这是一段无边距文字</view> </view> <canvas canvas-…...

SpringCloud微服务 【实用篇】| 认识微服务

目录 一&#xff1a;认识微服务 1. 微服务框架介绍 2. 服务架构演变 3. 微服务技术对比 4. SpringCloud 图书推荐&#xff1a;《巧用ChatGPT快速提高职场晋升力》 一&#xff1a;认识微服务 本课程学习于黑马&#xff0c;会通过分层次学习&#xff0c;分为三部分去讲解微…...

Csdn文章编写参考案例

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…...

Jmeter性能测试:高并发分布式性能测试

​一、为什么要进行分布式性能测试 当进行高并发性能测试的时候&#xff0c;受限于Jmeter工具本身和电脑硬件的原因&#xff0c;无法满足我们对大并发性能测试的要求。 基于这种场景下&#xff0c;我们就需要采用分布式的方式来实现我们高并发的性能测试要求。 二、分布式性能…...

2015年亚太杯APMCM数学建模大赛B题城市公共交通服务水平动态评价模型求解全过程文档及程序

2015年亚太杯APMCM数学建模大赛 B题 城市公共交通服务水平动态评价模型 原题再现 城市公共交通服务评价是城市公共交通系统建设和提高公共交通运营效率的重要组成部分。对于公交企业&#xff0c;管理和规划部门&#xff0c;传统公交车站、线路和换乘枢纽的规划数据只是基于主…...

CCF CSP认证历年题目自练 Day40

题目 试题编号&#xff1a; 201412-3 试题名称&#xff1a; 集合竞价 时间限制&#xff1a; 1.0s 内存限制&#xff1a; 256.0MB 问题描述&#xff1a; 问题描述   某股票交易所请你编写一个程序&#xff0c;根据开盘前客户提交的订单来确定某特定股票的开盘价和开盘成交量…...

闲聊一下写技术博客的一些感想

大家好&#xff0c;我是阿赵。   在我的163博客关闭之后&#xff0c;我就把一部分的博文移到了CSDN这边。不过实际上我有好几年都没有写过博客&#xff0c;所以这个博客的浏览量和粉丝数一直都不高。直到今年2023年的2月底开始&#xff0c;打算总结一下3DsMax的MaxScript的用…...

单片机为什么一直用C语言,不用其他编程语言?

单片机为什么一直用C语言&#xff0c;不用其他编程语言&#xff1f; 51 单片机规模小得拮据&#xff0c;C 的优势几乎看不到。放个类型信息进去都费劲&#xff0c;你还想用虚函数&#xff1f;还想模板展开&#xff1f;程序轻松破 10k。最近很多小伙伴找我&#xff0c;说想要一些…...

利用HTTP2,新型DDoS攻击峰值破纪录

亚马逊、Cloudflare 和谷歌周二联合发布消息称&#xff0c;一种依赖于 HTTP/2 快速重置技术的攻击行为对它们造成了破纪录的分布式拒绝服务 (DDoS) 攻击。 根据披露的信息&#xff0c;该攻击自8月下旬以来便一直存在&#xff0c;所利用的漏洞被跟踪为CVE-2023-44487&#xff0c…...

android鼠标滚轮事件监听方法

Overridepublic boolean onGenericMotionEvent(MotionEvent event) { //The input source is a pointing device associated with a display. //输入源为可显示的指针设备&#xff0c;如&#xff1a;mouse pointing device(鼠标指针),stylus pointing device(尖笔设备)if (0 ! …...

【C语言|关键字】C语言32个关键字详解(4)——其他(typedef、sizeof)

&#x1f601;博客主页&#x1f601;&#xff1a;&#x1f680;https://blog.csdn.net/wkd_007&#x1f680; &#x1f911;博客内容&#x1f911;&#xff1a;&#x1f36d;嵌入式开发、Linux、C语言、C、数据结构、音视频&#x1f36d; &#x1f923;本文内容&#x1f923;&a…...

Hafnium简介和构建

安全之安全(security)博客目录导读 目录 一、Hafnium简介 二、Hafnium构建 2.1.1 先决条件 2.1.1.1 构建Host 2.1.1.2 工具链 2.1.1.3 依赖 2.1.1.4 获取源码 2.1.2 构建 一、Hafnium简介 可信固件为Armv8-A、Armv9-A和Armv8-M提供了安全软件的参考实现。它为SoC开发人…...

2023年香水行业数据分析:国人用香需求升级,高端香水高速增长

在人口结构变迁的背景下&#xff0c;“Z世代”作为当下我国的消费主力&#xff0c;正在将“悦己”消费推动成为新潮流。具备经济基础的“Z世代”倡导“高颜值”、“个性化”、“精致主义”&#xff0c;这和香水、香氛为代表的“嗅觉经济”的特性充分契合&#xff0c;因此&#…...

这可能是最简单的Page Object库

做过web自动化测试的同学&#xff0c;对Page object设计模式应该不陌生。 Page object库应该根据以下目标开发&#xff1a; Page object应该易于使用 清晰的结构 PageObjects 对于页面对象 PageModules对于页面内容 只写测试&#xff0c;而不是基础。 在可能的情况下防止…...

论文阅读——BERT

ArXiv&#xff1a;https://arxiv.org/abs/1810.04805 github&#xff1a;GitHub - google-research/bert: TensorFlow code and pre-trained models for BERT 一、模型及特点&#xff1a; 1、模型&#xff1a; 深层双向transformer encoder结构 BERT-BASE&#xff1a;(L12, H…...

竞赛 深度学习人体跌倒检测 -yolo 机器视觉 opencv python

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **基于深度学习的人体跌倒检测算法研究与实现 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满…...

Springboot创建多数据源

yml文件 spring:datasource:dynamic:# 设置默认的数据源或者数据源组,默认值即为 masterprimary: masterdatasource:# 主库数据源master:driver-class-name: com.mysql.cj.jdbc.Driverurl: jdbc:mysql://xxx.xxx.xxx.xxx:3306/test?useUnicodetrue&characterEncodingutf8…...

【Hello Algorithm】滑动窗口内最大值最小值

滑动窗口介绍 滑动窗口是一种我们想象中的数据结构 它是用来解决算法问题的 我们可以想象出一个数组 然后再在这个数组的起始位置想象出两个指针 L 和 R 我们对于这两个指针做出以下规定 L 和 R指针只能往右移动L指针不能走到R指针的右边我们只能看到L指针和R指针中间的数字 …...

HTML,CSS实现鼠标划过头像,头像突出变大(附源码)

话不多说&#xff0c;先上代码 先看原图&#xff1a; 再看 鼠标放上去后的图&#xff1a; 是不是明显感觉到 人物头像突出了一些&#xff0c;而且还增加了阴影部分的效果呢&#xff1f; 直接上代码&#xff01;&#xff01;&#xff01; <!--由于我的 img 标签放的是循环后…...

“爱知道”,你知道吗?

拥抱时代浪潮&#xff0c;加速科技变革。数字经济时代&#xff0c;杭州重点贯彻市委市政府数字经济创新提质“一号发展工程”&#xff0c;加快发展数字经济&#xff0c;推动全市数字经济往高攀升、向新进军、以融提效。基于政府对数字经济新活力的赋能、优化数字社会环节、构建…...