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

Netty实现通信框架

一、LengthFieldBasedFrameDecoder的参数解释

1、LengthFieldBasedFrameDecoder的构造方法参数

看下最多参数的构造方法

/*** Creates a new instance.** @param byteOrder*        the {@link ByteOrder} of the length field* @param maxFrameLength*        the maximum length of the frame.  If the length of the frame is*        greater than this value, {@link TooLongFrameException} will be*        thrown.* @param lengthFieldOffset*        the offset of the length field* @param lengthFieldLength*        the length of the length field* @param lengthAdjustment*        the compensation value to add to the value of the length field* @param initialBytesToStrip*        the number of first bytes to strip out from the decoded frame* @param failFast*        If <tt>true</tt>, a {@link TooLongFrameException} is thrown as*        soon as the decoder notices the length of the frame will exceed*        <tt>maxFrameLength</tt> regardless of whether the entire frame*        has been read.  If <tt>false</tt>, a {@link TooLongFrameException}*        is thrown after the entire frame that exceeds <tt>maxFrameLength</tt>*        has been read.*/public LengthFieldBasedFrameDecoder(ByteOrder byteOrder, int maxFrameLength, int lengthFieldOffset, int lengthFieldLength,int lengthAdjustment, int initialBytesToStrip, boolean failFast) {this.byteOrder = checkNotNull(byteOrder, "byteOrder");checkPositive(maxFrameLength, "maxFrameLength");checkPositiveOrZero(lengthFieldOffset, "lengthFieldOffset");checkPositiveOrZero(initialBytesToStrip, "initialBytesToStrip");if (lengthFieldOffset > maxFrameLength - lengthFieldLength) {throw new IllegalArgumentException("maxFrameLength (" + maxFrameLength + ") " +"must be equal to or greater than " +"lengthFieldOffset (" + lengthFieldOffset + ") + " +"lengthFieldLength (" + lengthFieldLength + ").");}this.maxFrameLength = maxFrameLength;this.lengthFieldOffset = lengthFieldOffset;this.lengthFieldLength = lengthFieldLength;this.lengthAdjustment = lengthAdjustment;this.lengthFieldEndOffset = lengthFieldOffset + lengthFieldLength;this.initialBytesToStrip = initialBytesToStrip;this.failFast = failFast;}
  1. byteOrder:表示字节顺序,有两个常量分别是BIG_ENDIAN(多字节值的字节从最高有效到最低有效排序)和LITTLE_ENDIAN(多字节值的字节从最低有效到最高有效排序)
  2. maxFrameLength:包的最大长度,字面意思是最大帧的长度
  3. lengthFieldOffset:指的是长度域的偏移量,表示跳过指定个数字节之后的才是长度域
  4. lengthFieldLength:记录该帧数据长度的字段,也就是长度域本身的长度
  5. lengthAdjustment:长度的一个修正值,可正可负,Netty在读取到数据包的长度值N后, 认为接下来的N个字节都是需要读取的,但是根据实际情况,有可能需要增加N的值,也有可能需要减少N的值,具体增加多少,减少多少,写在这个参数里
  6. initialBytesToStrip:从数据帧中跳过的字节数,表示得到一个完整的数据包之后,扔掉这个数据包中多少字节数,才是后续业务实际需要的业务数据
  7. failFast:如果为 true,则表示读取到长度域,TA 的值的超过 maxFrameLength,就抛出 一个TooLongFrameException,而为false表示只有当真正读取完长度域的值表示的字节之后,才会抛出TooLongFrameException,默认情况下设置为 true,建议不要修改,否则可能会造成内存溢出

2、LengthFieldBasedFrameDecoder的构造方法参数对应数据包

在LengthFieldBasedFrameDecoder上也有对构造方法主要参数的解释,下面表示从解码前到解码后的字节参数分别对应

lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 0 (= do not strip header)BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000C | "HELLO, WORLD" |      | 0x000C | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+
// lengthFieldOffset长度存在位置的偏移量是0
// lengthFieldLength,一个十六进制4位,000C是16位,所以是占两个字节是2
// lengthAdjustment,000C就是12,"HELLO, WORLD"正好是12个字节,所以是0
// initialBytesToStrip没有丢数据,所以是0
lengthFieldOffset   = 0
lengthFieldLength   = 2
lengthAdjustment    = 0
initialBytesToStrip = 2 (= the length of the Length field)BEFORE DECODE (14 bytes)         AFTER DECODE (12 bytes)
+--------+----------------+      +----------------+
| Length | Actual Content |----->| Actual Content |
| 0x000C | "HELLO, WORLD" |      | "HELLO, WORLD" |
+--------+----------------+      +----------------+
// lengthFieldOffset长度存在位置的偏移量是0
// lengthFieldLength,一个十六进制4位,000C是16位,所以是占两个字节是2
// lengthAdjustment,000C就是12,"HELLO, WORLD"正好是12个字节,所以是0
// initialBytesToStrip丢弃了长度两个字节,所以是2
lengthFieldOffset   =  0
lengthFieldLength   =  2
lengthAdjustment    = -2 (= the length of the Length field)
initialBytesToStrip =  0BEFORE DECODE (14 bytes)         AFTER DECODE (14 bytes)
+--------+----------------+      +--------+----------------+
| Length | Actual Content |----->| Length | Actual Content |
| 0x000E | "HELLO, WORLD" |      | 0x000E | "HELLO, WORLD" |
+--------+----------------+      +--------+----------------+
// lengthFieldOffset长度存在位置的偏移量是0
// lengthFieldLength,一个十六进制4位,000C是16位,所以是占两个字节是2
// lengthAdjustment,000E就是14,而"HELLO, WORLD"是12个字节,少了两个字节所以是-2
// initialBytesToStrip丢弃了长度两个字节,所以是2
lengthFieldOffset   = 2 (= the length of Header 1)
lengthFieldLength   = 3
lengthAdjustment    = 0
initialBytesToStrip = 0BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
| Header 1 |  Length  | Actual Content |----->| Header 1 |  Length  | Actual Content |
|  0xCAFE  | 0x00000C | "HELLO, WORLD" |      |  0xCAFE  | 0x00000C | "HELLO, WORLD" |
+----------+----------+----------------+      +----------+----------+----------------+
// lengthFieldOffset,Header占用了两个字节,所以长度存在位置的偏移量是2
// lengthFieldLength,一个十六进制4位,00000C是24位,所以是占两个字节是3
// lengthAdjustment,00000C就是12,"HELLO, WORLD"正好是12个字节,所以是0
// initialBytesToStrip没有丢数据,所以是0
lengthFieldOffset   = 0
lengthFieldLength   = 3
lengthAdjustment    = 2 (= the length of Header 1)
initialBytesToStrip = 0BEFORE DECODE (17 bytes)                      AFTER DECODE (17 bytes)
+----------+----------+----------------+      +----------+----------+----------------+
|  Length  | Header 1 | Actual Content |----->|  Length  | Header 1 | Actual Content |
| 0x00000C |  0xCAFE  | "HELLO, WORLD" |      | 0x00000C |  0xCAFE  | "HELLO, WORLD" |
+----------+----------+----------------+      +----------+----------+----------------+
// lengthFieldOffset长度存在位置的偏移量是0
// lengthFieldLength,一个十六进制4位,00000C是24位,所以是占两个字节是3
// lengthAdjustment,00000C就是12,"HELLO, WORLD"正好是12个字节,而Header多占用了两个字节,所以是2
// initialBytesToStrip没有丢数据,所以是0
lengthFieldOffset   = 1 (= the length of HDR1)
lengthFieldLength   = 2
lengthAdjustment    = 1 (= the length of HDR2)
initialBytesToStrip = 3 (= the length of HDR1 + LEN)BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
+------+--------+------+----------------+      +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x000C | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+      +------+----------------+
// lengthFieldOffset,HDR1占用了一个字节,所以长度存在位置的偏移量是1
// lengthFieldLength,一个十六进制4位,000C是16位,所以是占两个字节是2
// lengthAdjustment,000C就是12,"HELLO, WORLD"正好是12个字节,而HDR2多占用了一个字节,所以是1
// initialBytesToStrip丢弃了HDR1和Length共三个字节,所以是3
lengthFieldOffset   =  1
lengthFieldLength   =  2
lengthAdjustment    = -3 (= the length of HDR1 + LEN, negative)
initialBytesToStrip =  3BEFORE DECODE (16 bytes)                       AFTER DECODE (13 bytes)
+------+--------+------+----------------+      +------+----------------+
| HDR1 | Length | HDR2 | Actual Content |----->| HDR2 | Actual Content |
| 0xCA | 0x0010 | 0xFE | "HELLO, WORLD" |      | 0xFE | "HELLO, WORLD" |
+------+--------+------+----------------+      +------+----------------+
// lengthFieldOffset,HDR1占用了一个字节,所以长度存在位置的偏移量是1
// lengthFieldLength,一个十六进制4位,0010是16位,所以是占两个字节是2
// lengthAdjustment,0010就是16,"HELLO, WORLD"加上HDR2是13个字节,所以是-3
// initialBytesToStrip丢弃了HDR1和Length共三个字节,所以是3

EmbeddedChannel的单元测试暂时略过,后面有空再看

三、手写Netty大体结构

1、功能描述

基于 Netty 的 NIO 通信框架

提供消息的编解码框架,可以实现 POJO 的序列化和反序列化(【编解码】与【序列化】一块)

消息内容防篡改机制(就跟我们web开发的鉴权一样,在处理之前先校验一下内容合法性)

提供基于 IP 地址的白名单接入认证机制

断线重连机制

2、通信模型

(1)客户端发送应用握手请求消息,携带节点ID等有效身份认证信息;

(2)服务端对应用握手请求消息进行合法性校验,包括节点ID有效性校验、节点重复 登录校验和IP地址合法性校验,校验通过后,返回登录成功的应用握手应答消息;

(3)链路建立成功之后,客户端发送业务消息;

(4)链路成功之后,服务端发送心跳消息;

(5)链路建立成功之后,客户端发送心跳消息;

(6)链路建立成功之后,服务端发送业务消息;

(7)服务端退出时,服务端关闭连接,客户端感知对方关闭连接后,被动关闭客户端连接。

协议通信双方链路建立成功之后,双方可以进行全双工通信,无 论客户端还是服务端,都可以主动发送请求消息给对方,通信方式可以是 TWO WAY 或者 ONE WAY。双方之间的心跳采用 Ping-Pong 机制,当链路处于空闲状态时,客户端主动发送 Ping 消息给服务端,服务端接收到 Ping 消息后发送应答消息 Pong 给客户端,如果客户端连 续发送 N 条 Ping 消息都没有接收到服务端返回的 Pong 消息,说明链路已经挂死或者对方处 于异常状态,客户端主动关闭连接,间隔周期 T 后发起重连操作,直到重连成功。

3、消息体定义

消息定义包含两部分:

消息头;消息体。

在消息的定义上,因为是同步处理模式,不考虑应答消息需要填入请求消息 ID,所以 消息头中只有一个消息的 ID。如果要支持异步模式,则请求消息头和应答消息头最好分开 设计,应答消息头中除了包括本消息的 ID 外,还应该包括请求消息 ID,以方便请求消息的 发送方根据请求消息 ID 做对应的业务处理。

消息体则支持 Java 对象类型的消息内容。

Netty 消息定义表

名称

类型

长度

描述

header

Header

变长

消息头定义

body

Object

变长

消息的内容

消息头定义(Header)

名称

类型

长度

描述

md5

String

变长

消息体摘要,缺省 MD5 摘要

msgId

Long

64

消息的ID

Type

Byte

8

0:业务请求消息 1:业务响应消息 2:业务one way消息

3:握手请求消息 4:握手应答消息 5:心跳请求消息 6:心跳应答消息

Priority

Byte

8

消息优先级:0~255

Attachment

Map<String, Object>

变长

可选字段,用于扩展消息头

4、链路的建立

客户端的说明如下:如果 A 节点需要调用 B 节点的服务,但是 A 和 B 之间还没有建立 物理链路,则有调用方主动发起连接,此时,调用方为客户端,被调用方为服务端。 考虑到安全,链路建立需要通过基于 Ip 地址或者号段的黑白名单安全认证机制,作为 样例,本协议使用基于 IP 地址的安全认证,如果有多个 Ip,通过逗号进行分割。在实际的 商用项目中,安全认证机制会更加严格,例如通过密钥对用户名和密码进行安全认证。

客户端与服务端链路建立成功之后,由客户端发送业务握手请求的认证消息,服务端接 收到客户端的握手请求消息之后,如果 IP 校验通过,返回握手成功应答消息给客户端,应 用层链路建立成功。握手应答消息中消息体为 byte 类型的结果,0:认证成功;-1 认证失败; 服务端关闭连接。

链路建立成功之后,客户端和服务端就可以互相发送业务消息了,在客户端和服务端的 消息通信过程中,业务消息体的内容需要通过 MD5 进行摘要防篡改。

5、可靠性设计

1)、心跳机制

在凌晨等业务低谷时段,如果发生网络闪断、连接被 Hang 住等问题时,由于没有业务 消息,应用程序很难发现。到了白天业务高峰期时,会发生大量的网络通信失败,严重的会 导致一段时间进程内无法处理业务消息。为了解决这个问题,在网络空闲时采用心跳机制来 检测链路的互通性,一旦发现网络故障,立即关闭链路,主动重连。

当读或者写心跳消息发生 I/O 异常的时候,说明已经中断,此时需要立即关闭连接,如 果是客户端,需要重新发起连接。如果是服务端,需要清空缓存的半包信息,等到客户端重连。

空闲的连接和超时

检测空闲连接以及超时对于及时释放资源来说是至关重要的。由于这是一项常见的任务, Netty 特地为它提供了几个 ChannelHandler 实现。

IdleStateHandler 当连接空闲时间太长时,将会触发一个 IdleStateEvent 事件。然后,可 以通过在 ChannelInboundHandler 中重写 userEventTriggered()方法来处理该 IdleStateEvent 事件。

ReadTimeoutHandler 如果在指定的时间间隔内没有收到任何的入站数据,则抛出一个 ReadTimeoutException 并关闭对应的 Channel。可以通过重写你的 ChannelHandler 中的 exceptionCaught()方法来检测该 Read-TimeoutException。

2)、重连机制

如果链路中断,等到 INTEVAL 时间后,由客户端发起重连操作,如果重连失败,间隔周 期 INTERVAL 后再次发起重连,直到重连成功。

为了保持服务端能够有充足的时间释放句柄资源,在首次断连时客户端需要等待 INTERVAL 时间之后再发起重连,而不是失败后立即重连。

为了保证句柄资源能够及时释放,无论什么场景下重连失败,客户端必须保证自身的资 源被及时释放,包括但不现居 SocketChannel、Socket 等。

重连失败后,可以打印异常堆栈信息,方便后续的问题定位。

3)、重复登录保护

当客户端握手成功之后,在链路处于正常状态下,不允许客户端重复登录,以防止客户 端在异常状态下反复重连导致句柄资源被耗尽。

服务端接收到客户端的握手请求消息之后,对 IP 地址进行合法性校验,如果校验成功, 在缓存的地址表中查看客户端是否已经登录,如果登录,则拒绝重复登录,同时关闭 TCP 链路,并在服务端的日志中打印握手失败的原因。

客户端接收到握手失败的应答消息之后,关闭客户端的 TCP 连接,等待 INTERVAL 时间 之后,再次发起 TCP 连接,直到认证成功。

6、实现

Handler示意图如下:

其中认证申请和认证检查可以在完成后移除。

7、前期准备

定义了消息有关的实体类,为了防篡改,消息体需要进行摘要, vo 包下提供了 EncryptUtils 类,可以对消息体进行摘要,目前支持 MD5、SHA-1 和 SHA-256 这 三种,缺省为 MD5,其中 MD5 额外提供了加盐摘要。 同时定义了有关序列化和反序列化的工具类和Handler, 本项目中序列化使用了 Kryo 序列化框架。

8、服务端

服务端中 NettyServe 类是服务端的主入口,内部使用了 ServerInit 类进行 Handler 的安装。

最先安装的当然是解决粘包和半包问题的 Handler,很自然,这里应该用 LengthFieldBasedFrameDecoder 进行解码,为了实现方便,我们也没有在消息报文中附带消 息的长度,由 Netty 帮我们在消息报文的最开始增加长度,所以编码器选择了 LengthFieldPrepender。

接下来,自然就是序列化和反序列化,直接使用我们在 kryocodec 下已经准备好的 KryoDecoder 和 KryoEncoder 即可。

服务端需要进行登录检查、心跳应答、业务处理,对应着三个 handler,于是我们分别 安装了 LoginAuthRespHandler、HeartBeatRespHandler、ServerBusiHandler。

为了节约网络和服务器资源,如果客户端长久没有发送业务和心跳报文,我们认为客户 端出现了问题,需要关闭这个连接,我们引入 Netty 的 ReadTimeoutHandler,当一定周期内 (默认值 50s,我们设定为 15s)没有读取到对方任何消息时,会触发一个 ReadTimeouttException,这时我们检测到这个异常,需要主动关闭链路,并清除客户端登录 缓存信息,等待客户端重连。

9、客户端

客户端的主类是 NettyClient,并对外提供一个方法 send,供业务使用内部使用了 ClientInit 类进行 Handler 的安装。

最先安装的当然是解决粘包和半包问题的 Handler,同样这里应该用 LengthFieldBasedFrameDecoder 进行解码,编码器选择了 LengthFieldPrepender。

接下来,自然就是序列化和反序列化,依然使用 KryoDecoder 和 KryoEncoder 即可。

客户端需要主动发出认证请求和心跳请求。

在 TCP 三次握手,链路建立后,客户端需要进行应用层的握手认证,才能使用服务,这 个功能由 LoginAuthReqHandler 负责,而这个 Handler 在认证通过后,其实就没用了,所以 在认证通过后,可以将这个 LoginAuthReqHandler 移除(其实服务端的认证应答 LoginAuthRespHandler 同样也可以移除)。

对于发出心跳请求有两种实现方式,一是定时发出,本框架的第一个版本就是这种实现 方式,但是这种方式其实有浪费的情况,因为如果客户端和服务器正在正常业务通信,其实 是没有必要发送心跳的;所以第二种方式就是,当链路写空闲时,为了维持通道,避免服务 器关闭链接,发出心跳请求。为了实现这一点,我们首先在整个 pipeline 的最前面安装一个 CheckWriteIdleHandler进行写空闲检测,空闲时间定位8S,取服务器读空闲时间15S的一半, 然后再安装一个 HearBeatReqHandler,因为写空闲会触发一个 FIRST_WRITER_IDLE_STATE_EVENT 入站事件,我们在 HearBeatReqHandler 的 userEventTriggered 方法中捕捉这个事件,并发出心跳请求报文。

考虑到在我们的实现中并没有双向心跳(即是客户端向服务器发送心跳请求,是服务器 也向客户端发送心跳请求),客户端这边同样需要检测服务器是否存活,所以我们客户端这 边安装了一个 ReadTimeoutHandler,捕捉 ReadTimeoutException 后提示调用者,并关闭通信 链路,触发重连机制。

为了测试,单独建立一个 BusiClient,模拟业务方的调用。因为客户端的网络通信代 码是在一个线程中单独启动的,为了协调主线程和通信线程的工作,我们引入了线程中的等 待通知机制。

Netty对于我来说过于复杂,后面再深究细节吧

相关文章:

Netty实现通信框架

一、LengthFieldBasedFrameDecoder的参数解释 1、LengthFieldBasedFrameDecoder的构造方法参数 看下最多参数的构造方法 /*** Creates a new instance.** param byteOrder* the {link ByteOrder} of the length field* param maxFrameLength* the maximum len…...

【OpenCV实现图像:用OpenCV图像处理技巧之白平衡算法】

文章目录 概要加载样例图像统计数据分析White Patch Algorithm小结 概要 白平衡技术在摄影和图像处理中扮演着至关重要的角色。在不同的光照条件下&#xff0c;相机可能无法准确地捕捉到物体的真实颜色&#xff0c;导致图像呈现出暗淡、色调不自然或者褪色的效果。为了解决这个…...

文件包含 [ZJCTF 2019]NiZhuanSiWei1

打开题目 代码审计 if(isset($text)&&(file_get_contents($text,r)"welcome to the zjctf")){ 首先isset函数检查text参数是否存在且不为空 用file_get_contents函数读取text制定的文件内容并与welcome to the zjctf进行强比较 echo "<br><h…...

Java网络编程基础内容

IP地址 域名解析&#xff1a; 本机访问域名时&#xff0c;会从本地的DNS上解析数据&#xff08;每个电脑都有&#xff09;&#xff0c;如果有&#xff0c;获取其对应的IP&#xff0c;通过IP访问服务器。如果本地没有&#xff0c;会去网络提供商的DNS找域名对应的IP&#xff0…...

DevChat:开发者专属的基于IDE插件化编程协助工具

DevChat&#xff1a;开发者专属的基于IDE插件化编程协助工具 一、DevChat 的介绍1.1 DevChat 简介1.2 DevChat 优势 二、DevChat 在 VSCode 上的使用2.1 安装 DevChat2.2 注册 DevChat2.3 使用 DevChat 三、DevChat 的实战四、总结 一、DevChat 的介绍 在AI浪潮的席卷下&#x…...

Python数据容器之[列表]

Python数据容器 Python中的数据容器&#xff1a; 一种可以容纳多份数据的数据类型&#xff0c;容纳的每一份数据称之为1个元素 每一个元素&#xff0c;可以是任意类型的数据&#xff0c;如字符串、数字、布尔等。 数据容器根据特点的不同&#xff0c;如&#xff1a; 是否支…...

大咖直播间”系列直播课第一期——如何抓住HarmonyOS带来的机遇?

想了解#HarmonyOS#背后隐藏着怎样的商业机遇&#xff1f; 想成功搭上万物互联快车&#xff0c;与HarmonyOS一起发展壮大&#xff1f; 想知道开发者应该怎样把握时代机遇&#xff0c;实现高质高效就业&#xff1f; 答案尽在#华为开发者学堂#《大咖直播间》第一期课程&#xff0c…...

跨域:利用JSONP、WebSocket实现跨域访问

跨域基础知识点&#xff1a;跨域知识点 iframe实现跨域的四种方式&#xff1a;iframe实现跨域的四种方式 注&#xff1a;本篇中使用到的虚拟主机也是上面iframe中配置的 目录 JSONP跨域 JSONP介绍 跨域实验&#xff1a; WebSocket跨域 websocket介绍 跨域实验 JSONP跨域 …...

java项目之戒烟网站(ssm+vue)

项目简介 戒烟网站实现了以下功能&#xff1a; 用户可以对首页&#xff0c;用户分享&#xff0c;论坛交流&#xff0c;公告文章&#xff0c;个人中心&#xff0c;后台管理等功能进行操作。 管理员可以对网站所有功能进行管理&#xff0c;包括管理用户的基本信息。 &#x1f4…...

Redis集群,你真的学会了吗?

目录 1、为什么引入集群 1.1、先来了解集群是什么 1.2、哨兵模式的缺陷 引入集群解决了什么问题 1.3、使用集群&#xff0c;如何存储数据 2、三种主流的分片方式【经典面试题】 2.1、哈希求余算法 2.1.1、哈希求余算法的介绍 2.1.2、哈希求余算法如何扩容 2.2、一致性…...

手机地磁传感器与常见问题

在手机中&#xff0c;存在不少传感器&#xff0c;例如光距感&#xff0c;陀螺仪&#xff0c;重力加速度&#xff0c;地磁等。关于各传感器&#xff0c;虽功能作用大家都有所了解&#xff0c;但是在研发设计debug过程中&#xff0c;却总是会遇到很多头疼的问题。关于传感器&…...

EF Core 数据库映射成实体类

首先在 NuGet 包管理器中安装三个包 Microsoft.EntityFrameworkCore.SqlServer 是一个用于与 SQL Server 数据库进行交互的实体框架核心包。这个包提供了方便的方法和工具&#xff0c;用于在 .NET Core 应用程序中操作 SQL Server 数据库。 Microsoft.EntityFrameworkCore.Too…...

【算法优选】 动态规划之斐波那契数列模型

文章目录 &#x1f38b;前言&#x1f340;[第 N 个泰波那契数](https://leetcode.cn/problems/n-th-tribonacci-number/)&#x1f6a9;题目描述&#x1f6a9;算法流程&#x1f6a9;代码实现 &#x1f384;[使用最小花费爬楼梯](https://leetcode.cn/problems/min-cost-climbing…...

FreeRTOS知识梳理

一、RTOS:Real time operating system,中文意思为 实时操作系统&#xff0c;它是一类操作系统&#xff0c;比如uc/OS、FreeRTOS、RTX、RT-Thread 这些都是实时操作系统。 二、移植FreeRTOS到STM32F103C8T6上 interface选择CMSIS_V1,RCC选择Crystal Ceramic Resonator 。 …...

冒泡排序算法(C++版)

1、什么是冒泡排序&#xff1f; 冒泡排序&#xff08;Bubble Sort&#xff09;是一种简单的排序算法&#xff0c;其基本思想是多次遍历待排序的元素序列&#xff0c;每次比较相邻两个元素&#xff0c;如果它们的顺序不正确就交换它们&#xff0c;直到整个序列有序。在每一轮遍…...

第22章_数据库的设计规范

文章目录 范式的概念三范式范式一范式二范式三 反范式总结 范式的概念 为了建立冗余较小、结构合理的数据库&#xff0c;设计数据库时必须遵循一定的规则。在关系型数据库中这种规则就称为范式。范式是符合某一种设计要求的总结。要想设计一个结构合理的关系型数据库&#xff…...

5. 深度学习——正则化

机器学习面试题汇总与解析——正则化 本章讲解知识点 什么是正则化为什么要使用正则化?详细讲解正则化本专栏适合于Python已经入门的学生或人士,有一定的编程基础。本专栏适合于算法工程师、机器学习、图像处理求职的学生或人士。本专栏针对面试题答案进行了优化,尽量做到好…...

【链表和顺序表的优缺点】

...

iOS移动应用安全加固:保护您的App免受恶意攻击的重要步骤

目录 iOS移动应用安全加固&#xff1a;保护您的App免受恶意攻击的重要步骤 摘要 引言 一、APP加固的概念 二、APP加固方案的比较 三、保护iOS应用的安全 四、总结 参考资料 摘要 本文介绍了移动应用程序&#xff08;App&#xff09;加固的概念和流程&#xff0c;以及市…...

C# .NET Core API 注入Swagger

C# .NET Core API 注入Swagger 环境 Windows 10Visual Studio 2019(2017就有可以集中发布到publish目录的功能了吧)C#.NET Core 可跨平台发布代码,超级奈斯NuGet 套件管理dll将方法封装(据说可以提高效率,就像是我们用的dll那种感觉)Swagger 让接口可视化编写时间2020-12-09 …...

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…...

CTF show Web 红包题第六弹

提示 1.不是SQL注入 2.需要找关键源码 思路 进入页面发现是一个登录框&#xff0c;很难让人不联想到SQL注入&#xff0c;但提示都说了不是SQL注入&#xff0c;所以就不往这方面想了 ​ 先查看一下网页源码&#xff0c;发现一段JavaScript代码&#xff0c;有一个关键类ctfs…...

ffmpeg(四):滤镜命令

FFmpeg 的滤镜命令是用于音视频处理中的强大工具&#xff0c;可以完成剪裁、缩放、加水印、调色、合成、旋转、模糊、叠加字幕等复杂的操作。其核心语法格式一般如下&#xff1a; ffmpeg -i input.mp4 -vf "滤镜参数" output.mp4或者带音频滤镜&#xff1a; ffmpeg…...

企业如何增强终端安全?

在数字化转型加速的今天&#xff0c;企业的业务运行越来越依赖于终端设备。从员工的笔记本电脑、智能手机&#xff0c;到工厂里的物联网设备、智能传感器&#xff0c;这些终端构成了企业与外部世界连接的 “神经末梢”。然而&#xff0c;随着远程办公的常态化和设备接入的爆炸式…...

Spring是如何解决Bean的循环依赖:三级缓存机制

1、什么是 Bean 的循环依赖 在 Spring框架中,Bean 的循环依赖是指多个 Bean 之间‌互相持有对方引用‌,形成闭环依赖关系的现象。 多个 Bean 的依赖关系构成环形链路,例如: 双向依赖:Bean A 依赖 Bean B,同时 Bean B 也依赖 Bean A(A↔B)。链条循环: Bean A → Bean…...

R语言速释制剂QBD解决方案之三

本文是《Quality by Design for ANDAs: An Example for Immediate-Release Dosage Forms》第一个处方的R语言解决方案。 第一个处方研究评估原料药粒径分布、MCC/Lactose比例、崩解剂用量对制剂CQAs的影响。 第二处方研究用于理解颗粒外加硬脂酸镁和滑石粉对片剂质量和可生产…...

JavaScript 数据类型详解

JavaScript 数据类型详解 JavaScript 数据类型分为 原始类型&#xff08;Primitive&#xff09; 和 对象类型&#xff08;Object&#xff09; 两大类&#xff0c;共 8 种&#xff08;ES11&#xff09;&#xff1a; 一、原始类型&#xff08;7种&#xff09; 1. undefined 定…...

【 java 虚拟机知识 第一篇 】

目录 1.内存模型 1.1.JVM内存模型的介绍 1.2.堆和栈的区别 1.3.栈的存储细节 1.4.堆的部分 1.5.程序计数器的作用 1.6.方法区的内容 1.7.字符串池 1.8.引用类型 1.9.内存泄漏与内存溢出 1.10.会出现内存溢出的结构 1.内存模型 1.1.JVM内存模型的介绍 内存模型主要分…...

tomcat入门

1 tomcat 是什么 apache开发的web服务器可以为java web程序提供运行环境tomcat是一款高效&#xff0c;稳定&#xff0c;易于使用的web服务器tomcathttp服务器Servlet服务器 2 tomcat 目录介绍 -bin #存放tomcat的脚本 -conf #存放tomcat的配置文件 ---catalina.policy #to…...

Vue ③-生命周期 || 脚手架

生命周期 思考&#xff1a;什么时候可以发送初始化渲染请求&#xff1f;&#xff08;越早越好&#xff09; 什么时候可以开始操作dom&#xff1f;&#xff08;至少dom得渲染出来&#xff09; Vue生命周期&#xff1a; 一个Vue实例从 创建 到 销毁 的整个过程。 生命周期四个…...