从零开始学习Netty - 学习笔记 -Netty入门【协议设计和解析】

2.协议设计和解析

协议
在计算机中,协议是指一组规则和约定,用于在不同的计算机系统之间进行通信和数据交换。计算机协议定义了数据传输的格式、顺序、错误检测和纠正方法,以及参与通信的各个实体的角色和责任。计算机协议可以在各种不同的层次上操作,包括物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。

以下是一些常见的计算机协议:

  1. 传输层协议:例如TCP (Transmission Control Protocol) 和UDP (User Datagram Protocol),用于在网络上可靠地传输数据。
  2. 网络层协议:例如IP (Internet Protocol),负责在网络上寻址和路由数据包。
  3. 应用层协议:例如HTTP (Hypertext Transfer Protocol)、FTP (File Transfer Protocol)、SMTP (Simple Mail Transfer Protocol) 等,用于支持特定的应用程序和服务。
  4. 数据链路层协议:例如Ethernet、PPP (Point-to-Point Protocol) 等,用于在物理网络之间传输数据帧。

2.1.redis协议

Redis 使用一种简单而有效的文本协议进行通信,这种协议被称为 RESP(REdis Serialization Protocol)。RESP 是一种二进制安全的协议,它可以将多种类型的数据结构序列化为字节流进行传输,并且允许客户端和服务器之间进行高效的通信。

下面是 RESP 协议的一些基本规则:

  1. 简单字符串(Simple Strings):以 “+” 开头,后面跟着字符串内容和回车换行符 “\r\n”。例如:+OK\r\n 表示一个成功的响应。
  2. 错误消息(Errors):以 “-” 开头,后面跟着错误消息内容和回车换行符 “\r\n”。例如:-ERR unknown command 'foobar'\r\n 表示一个错误的响应。
  3. 整数(Integers):以 “:” 开头,后面跟着整数内容和回车换行符 “\r\n”。例如::1000\r\n 表示整数 1000。
  4. 批量字符串(Bulk Strings):以 “$” 开头,后面跟着字符串的长度(以字节为单位)、字符串内容和回车换行符 “\r\n”。例如:$6\r\nfoobar\r\n 表示一个长度为 6 的字符串 “foobar”。
  5. 数组(Arrays):以 “*” 开头,后面跟着数组的长度和数组的元素,每个元素都可以是任意 RESP 类型。例如:*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n 表示一个包含两个元素的数组,分别是字符串 “foo” 和字符串 “bar”。

在实际的通信中,客户端发送命令给 Redis 服务器,并等待服务器的响应。客户端发送的命令遵循 RESP 协议的格式,而服务器返回的响应也是 RESP 格式的。

这种简单而灵活的 RESP 协议使得 Redis 能够高效地处理各种数据类型和命令,并在性能和易用性之间找到了平衡。

代码

package com.hrfan.java_se_base.netty.protocol;import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;import java.lang.invoke.MethodHandles;
import java.nio.charset.Charset;/*** @author 13723* @version 1.0* 2024/3/3 0:03*/
public class TestRedisProtocol {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());public static void main(String[] args) {// 测试redis协议NioEventLoopGroup worker = new NioEventLoopGroup();try {Bootstrap bootstrap = new Bootstrap();bootstrap.channel(NioSocketChannel.class);bootstrap.group(worker);bootstrap.handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel channel) throws Exception {channel.pipeline().addLast(new LoggingHandler());channel.pipeline().addLast(new ChannelInboundHandlerAdapter() {/*** 连接一旦建立就发送命令* @param ctx* @throws Exception*/@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {// 如果redis 有密码 需要先发送一个auth命令//// 发送 AUTH 命令进行认证// String authCommand = "*2\r\n$4\r\nauth\r\n$5\r\n12345\r\n";// ByteBuf authBuffer = ctx.alloc().buffer();// authBuffer.writeBytes(authCommand.getBytes());// ctx.writeAndFlush(authBuffer);// 发送一个连接建立的命令// redis 协议是一种文本协议 以 \r\n 作为结束符 以$开头的是长度  以*开头的是数组// 例如 *3\r\n$3\r\nset\r\n$4\r\nname\r\n$8\r\nhrfan\r\n// 表示一个数组 有三个元素  第一个元素是set 第二个元素是name 第三个元素是hrfan// 也就是执行 set name hrfanString command = "*3\r\n$3\r\nset\r\n$4\r\nname\r\n$5\r\nhrfan\r\n";ByteBuf buffer = ctx.alloc().buffer();buffer.writeBytes(command.getBytes());ctx.writeAndFlush(buffer);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);// redis 接收到结果  肯定会返回信息 +OK\r\nByteBuf byteBuf = (ByteBuf) msg;String string = byteBuf.toString(Charset.defaultCharset());logger.info("redis 返回的结果是:{}", string);}});}});// 和redis建立连接ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6379).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {logger.error("client error !",e);}finally {worker.shutdownGracefully();}}
}

image-20240303011539551

image-20240303011603698

2.2.HTTP协议

HTTP(Hypertext Transfer Protocol,超文本传输协议)是一种用于传输超媒体文档(例如 HTML)的应用层协议,是互联网上数据传输的基础。

HTTP的特点:

  1. 无连接
    • HTTP 协议是无连接的,即每个请求都是独立的,服务器处理完请求后即断开连接,因此每个请求需要单独建立连接和断开连接,无法复用连接,导致了额外的开销。
  2. 无状态
    • HTTP 协议是无状态的,即服务器不会保存客户端的请求信息,每个请求之间没有关联,服务器无法知道当前请求与之前的请求是否相关。
    • 为了实现状态保持,引入了 Cookie 和 Session 机制。
  3. 简单快速
    • HTTP 协议基于请求-响应模型,简单易懂,通信速度较快。
    • 由于 HTTP 协议的简单性,使得它被广泛应用于 Web 数据传输。
  4. 灵活性
    • HTTP 协议允许传输任意类型的数据对象,不限于文本数据,也可以传输图片、视频、音频等多媒体数据。
  5. 无安全性
    • HTTP 协议是明文传输的,数据传输过程中不对数据进行加密处理,容易被窃听、篡改或伪造,因此不适合传输敏感数据。

HTTP请求/响应的基本结构:

  1. 请求结构
    • 请求行:包括请求方法(GET、POST 等)、请求 URI 和 HTTP 版本号。
    • 请求头部:包括客户端信息、请求资源信息、支持的压缩方法等。
    • 请求正文:传输请求相关的数据。
  2. 响应结构
    • 状态行:包括 HTTP 版本号、状态码和状态描述。
    • 响应头部:包括服务器信息、响应时间、响应内容类型等。
    • 响应正文:包含响应的实际数据。

HTTP的方法(请求方式):

  1. GET:用于请求指定的资源。
  2. POST:用于提交数据,常用于提交表单数据。
  3. PUT:用于上传指定的 URI 表示的内容。
  4. DELETE:用于删除指定的资源。
  5. HEAD:与 GET 类似,但服务器只返回响应头部,不返回实际内容。
  6. OPTIONS:用于请求目标资源所支持的通信选项。
  7. TRACE:用于测试目的,回显服务器收到的请求,主要用于诊断。

HTTP状态码:

  1. 1xx(信息):请求已接收,继续处理。
  2. 2xx(成功):请求已成功被服务器接收、理解、并接受。
  3. 3xx(重定向):需要客户端采取进一步的操作才能完成请求。
  4. 4xx(客户端错误):请求包含语法错误或无法完成请求。
  5. 5xx(服务器错误):服务器在处理请求的过程中发生了错误。

HTTP持久连接:

HTTP/1.1 引入了持久连接(Persistent Connection)机制,使得可以在同一连接上发送和接收多个 HTTP 请求和响应,减少了连接建立和断开的开销,提高了性能。

/*** @author 13723* @version 1.0* 2024/3/3 0:03*/
public class TestHttpProtocol {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());public static void main(String[] args) {// 测试HTTP协议NioEventLoopGroup boss = new NioEventLoopGroup();NioEventLoopGroup worker = new NioEventLoopGroup();try {ServerBootstrap bootstrap = new ServerBootstrap();bootstrap.channel(NioServerSocketChannel.class);bootstrap.group(boss,worker);bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel channel) throws Exception {channel.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));channel.pipeline().addLast(new HttpServerCodec());// 对编解码的请求结果进行处理channel.pipeline().addLast(new ChannelInboundHandlerAdapter(){@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);// 此时打开浏览器 输入localhost:8080 会看到请求的信息logger.error("获取的信息:{}",msg);}});}});// 建立和http之间的连接ChannelFuture channelFuture = bootstrap.bind(9999).sync();channelFuture.channel().closeFuture().sync();} catch (InterruptedException e) {logger.error("client error !",e);}finally {worker.shutdownGracefully();boss.shutdownGracefully();}}
}

image-20240304212730612

但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体
会默认发送两次请求 一次是请求头 一次是请求体
所以我们需要对请求头和请求体进行处理
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {super.channelRead(ctx, msg);// 此时打开浏览器 输入localhost:8080 会看到请求的信息logger.error("获取的信息:{}",msg);// 但是这个信息是一个http请求的信息 但是http请求 会分为请求头和请求体// 会默认发送两次请求 一次是请求头 一次是请求体// 所以我们需要对请求头和请求体进行处理if (msg instanceof HttpRequest){HttpRequest request = (HttpRequest) msg;logger.error("请求头:{}",request.headers());}else if (msg instanceof HttpContent){HttpContent content = (HttpContent) msg;ByteBuf buf = content.content();logger.error("请求体:{}",buf.toString(Charset.defaultCharset()));}
}

image-20240304213137183

还可以使用 添加指定处理器 处理特定的内容

SimpleChannelInboundHandler 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息,Netty会自动帮你进行转换 你不需要进行类型转换

// 对请求头和请求体进行处理 我们还可以使用SimpleChannelInboundHandler
// 它可根据消息的类型进行选择处理,例如我们只关心HttpRequest类型的消息
// 他会自动帮你进行转换 你不需要进行类型转换
channel.pipeline().addLast(new SimpleChannelInboundHandler<HttpRequest>() {@Overrideprotected void channelRead0(ChannelHandlerContext channelHandlerContext, HttpRequest httpRequest) throws Exception {logger.error("请求信息:{}",httpRequest);// 向浏览器返回响应// netty提供一个响应对象// 符合http协议的响应对象 第一个参数 时http协议的版本 第二个参数是响应的状态码DefaultFullHttpResponse response = new DefaultFullHttpResponse(httpRequest.protocolVersion(), HttpResponseStatus.OK);// 向浏览器写入一些内容byte[] bytes = "<h1>hello world</h1>".getBytes();response.content().writeBytes(bytes);// 设置响应头 否则浏览器会一直等待 告知箱体response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH,bytes.length);// 设置响应头的类型response.headers().set(HttpHeaderNames.CONTENT_TYPE,"text/html;charset=utf-8");// 写入响应channelHandlerContext.writeAndFlush(response);}
});

image-20240304214047954

image-20240304214058259

2.3.自定义协议

自定义协议要素

  • 魔改 (Magic Number)
    • 用于第一时间判定是否是有效数据包,通常是一个固定的字节序列或者数字,用来标识该数据包是符合自定义协议的。
    • 例如,可以是一个特定的字节序列,如 0x7E 0x7E。
  • 版本号 (Protocol Version)
    • 用于支持协议的升级,可以在协议中包含一个字段来表示协议的版本号。
    • 这样可以在协议升级时识别和处理不同版本的协议。
  • 序列化算法 (Serialization Algorithm)
    • 用于消息正文的序列化和反序列化,可以支持多种序列化算法,如 JSON、Protobuf、Hessian、JDK 自带的序列化等。
    • 这样可以根据需求选择最适合的序列化算法来进行数据的编码和解码。
  • 指令类型 (Instruction Type)
    • 用来表示消息的类型,与业务相关,包括登录、注册、单聊、群聊等操作。
    • 可以使用一个字段来标识不同的指令类型,以便在接收方根据指令类型进行相应的业务处理。
  • 请求序号 (Request Sequence Number)
    • 用于实现双工通信和提供异步能力,每个请求都有一个唯一的序号。
    • 接收方在处理请求后,可以通过该序号将响应与请求进行关联。
  • 消息正文长度和消息正文
    • 消息正文长度字段用于表示消息正文的长度,以便在解析消息时可以正确地读取到消息的内容。
    • 消息正文则是实际的数据内容,根据指令类型和业务需求可以是不同格式的数据,例如文本、二进制、结构化数据等。

定义一个简单的自定义协议,协议由两部分组成:消息类型和消息内容。消息类型用一个字节表示,消息内容是一个字符串。

消息格式:消息类型字节消息类型字节 消息内容长度字节消息内容长度字节 消息内容消息内容

  1. MessageType (消息类型):一个字节,0表示心跳消息,1表示业务消息。
  2. MessageContentLength (消息内容长度):4个字节,表示消息内容的长度。
  3. MessageContent (消息内容):消息内容的字节数组。

编码器

1.自定义消息信息的枚举类型
  • MessageType 枚举定义了两种消息类型:心跳消息和业务消息,分别用 0 和 1 表示。
public enum MessageType {HEARTBEAT((byte) 0),BUSINESS((byte) 1);private final byte value;MessageType(byte value) {this.value = value;}public byte getValue() {return value;}public static MessageType valueOf(byte value) {for (MessageType type : values()) {if (type.value == value) {return type;}}throw new IllegalArgumentException("Invalid MessageType value: " + value);}
}
2.自定义协议消息类
  • MyProtocolMessage 类表示一个自定义协议消息,包括消息类型和消息内容。
public class MyProtocolMessage {private MessageType type;private String content;public MyProtocolMessage(MessageType type, String content) {this.type = type;this.content = content;}public MessageType getType() {return type;}public String getContent() {return content;}
}
3.自定义协议的编码器
  • 继承自 Netty 的 MessageToByteEncoder 类,负责将 MyProtocolMessage 编码成字节流。
  • 将消息类型、消息内容长度和消息内容依次写入 ByteBuf 中。
public class MyProtocolEncoder extends MessageToByteEncoder<MyProtocolMessage> {@Overrideprotected void encode(ChannelHandlerContext ctx, MyProtocolMessage msg, ByteBuf out) throws Exception {// 写入消息类型out.writeByte(msg.getType().getValue());// 获取消息内容的字节数组byte[] contentBytes = msg.getContent().getBytes(StandardCharsets.UTF_8);// 写入消息内容长度out.writeInt(contentBytes.length);// 写入消息内容out.writeBytes(contentBytes);}
}
4.自定义协议的解码器
  • 继承自 Netty 的 ByteToMessageDecoder 类,负责将字节流解码成 MyProtocolMessage 对象。
  • 读取字节流中的消息类型和消息内容长度,然后读取对应长度的字节流作为消息内容。
  • 构造 MyProtocolMessage 对象并加入到解码器的输出列表中。
public class MyProtocolDecoder extends ByteToMessageDecoder {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {// 至少需要5个字节来解码if (in.readableBytes() < 5) {return;}// 标记当前读取位置in.markReaderIndex();// 读取消息类型byte messageType = in.readByte();// 读取消息内容长度int contentLength = in.readInt();// 如果可读字节数小于消息内容长度,说明消息不完整,重置读取位置if (in.readableBytes() < contentLength) {in.resetReaderIndex();return;}// 读取消息内容byte[] contentBytes = new byte[contentLength];in.readBytes(contentBytes);String content = new String(contentBytes, StandardCharsets.UTF_8);// 构造消息对象MyProtocolMessage message = new MyProtocolMessage(MessageType.valueOf(messageType), content);out.add(message);}
}
5.测试
  • 使用 Netty 的 EmbeddedChannel 类模拟了一个通道来进行测试。
  • 测试了编码器和解码器的正确性,包括编码后解码得到的消息与原消息是否相同。
public class MyProtocolTest {private static final Logger logger = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());public static void main(String[] args) {// 创建一个嵌入式通道,并添加编码器和解码器// 指定日志级别为DEBUG,可以看到编码后的字节EmbeddedChannel channel = new EmbeddedChannel(new MyProtocolEncoder(), new MyProtocolDecoder(),new LoggingHandler(LogLevel.DEBUG));// 构造一个业务消息MyProtocolMessage message = new MyProtocolMessage(MessageType.BUSINESS, "Hello, Netty!");// 写入消息到通道channel.writeOutbound(message);// 读取通道中的字节ByteBuf encoded = channel.readOutbound();// 打印编码后的字节logger.error("编码后的字节:Encoded Message: {}",encoded);// 写入编码后的字节到通道channel.writeInbound(encoded.retain());// 读取通道中的解码后的消息MyProtocolMessage decodedMessage = channel.readInbound();// 打印解码后的消息logger.error("解码后的字节:Decoded Message: {}",decodedMessage.getContent());// 关闭通道channel.finish();}
}

image-20240304221744643

自定义协议的优点:

  1. 灵活性
    • 自定义协议可以根据实际业务需求进行设计,灵活地定义消息格式和通信规则,使得通信双方能够更好地适应特定的业务场景。
  2. 性能优化
    • 自定义协议可以针对特定的业务需求进行优化,可以选择合适的数据格式和编码方式,减少通信数据量,提高通信效率。
  3. 安全性
    • 自定义协议可以设计加密和校验机制,确保通信数据的安全性和完整性,防止数据被篡改或窃取。
  4. 版本控制
    • 自定义协议可以包含版本号,便于协议的升级和兼容,能够保证通信双方在协议更新后仍能正常通信。
  5. 易于调试和维护
    • 自定义协议通常具有明确的结构和语义,易于调试和排查问题,同时也方便日后的维护和扩展。

自定义协议的缺点和注意事项:

  1. 复杂性增加
    • 自定义协议的设计和实现需要对网络通信有深入的理解,不当的设计可能导致协议过于复杂,增加开发和维护的难度。
  2. 兼容性问题
    • 协议的升级和演化可能会导致与旧版本的不兼容,需要谨慎处理版本控制和协议演化的问题,以确保新旧版本的兼容性。
  3. 安全风险
    • 自定义协议的安全性需要开发者自行考虑和实现,不恰当的安全机制可能会导致数据泄露和安全漏洞。
  4. 性能折衷
    • 自定义协议的设计需要兼顾性能和灵活性,有时需要在性能和灵活性之间进行权衡和折衷,选择合适的方案。
  5. 协议文档和规范
    • 自定义协议需要有清晰的文档和规范,以确保通信双方都能正确理解和实现协议,避免因为误解或者实现不一致导致通信失败。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/720555.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索云原生世界:Spring Cloud全方位解读——构建微服务架构的利器

目录 一、微服务简介 二、微服务发展史 三、Spring Cloud 3.1 Spring Cloud 版本策略 3.2 Spring Cloud 发展历程 微服务是一种软件架构风格&#xff0c;将单一应用程序拆分成一组小型、独立的服务。每个服务运行在自己的进程中&#xff0c;服务之间采用轻量级通信机制进行交…

Open3D 进阶(21)无序点云平面检测的鲁棒统计方法

目录 一、算法原理1、算法过程2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫与GPT。 一、算法原理 1、算法过程 除了寻找具有最大支持的单个平面外,Open3D还包含一个算法,该算法使…

【Java EE初阶二十九】Linux 系统的学习

当前写的博客系统程序,只是部署在咱们自己的电脑上,其他用户是无法直接访问的.由于 NAT 机制的存在,导致了IP 地址就被分成了 内网 IP 和 外网 IP. 云服务器,包括公司中使用专用服务器,一般都是 Linux 系统&#xff0c;这个系统的使用和 Windows 差异很大.(通过命令行来操作的系…

Jupyter Notebook的安装和使用(windows环境)

一、jupyter notebook 安装 前提条件&#xff1a;安装python环境 安装python环境步骤&#xff1a; 1.下载官方python解释器 2.安装python 3.命令行窗口敲击命令pip install jupyter 4.安装jupyter之后&#xff0c;直接启动命令jupyter notebook,在默认浏览器中打开jupyte…

测试环境搭建整套大数据系统-问题篇(一:实时遇到的问题)

1. java.io.IOException: Failed to deserialize JSON ‘{“age”:867,“sex”:“fba8c074f9”,“t_insert_time”:“2024-03-04 14:12:24.821”}’ 解决方式 修改数据类型。将TIMESTAMP_LTZ改为TIMESTAMP。 2. java. lang,classNotFoundException: org,apache.flink,streami…

典中典之西电A测-气压测控仿真系统

兄弟,如果你看到这篇,只能说明你A测也挂了,没办法,哥们太菜了,抄的太假过不了你电有些老师的慧眼 这坨&#x1f415;⑩我先吃为敬 环境搭建可以参考这个兄弟的博客 一、题目要求 实现功能&#xff1a;使用 Arduino UNO 微控制器&#xff0c;搭建一个 PC 上位机远程气压检测控…

账号管理支持批量测试资产可连接性,资产管理支持通过标签方式选择资产,JumpServer堡垒机v3.10.4 LTS版本发布

2024年3月4日&#xff0c;JumpServer开源堡垒机正式发布v3.10.4 LTS版本。JumpServer开源项目组将对v3.10 LTS版本提供长期的支持和优化&#xff0c;并定期迭代发布小版本。欢迎广大社区用户升级至v3.10 LTS最新版本&#xff0c;以获得更佳的使用体验。 在v3.10.4 LTS版本中&a…

ChromeDriver全版本下载教程

确定自己的Chrome版本 step1. 打开Chrome浏览器右上角的三个点&#xff0c;再点击设置 step2. 在设置中点击“关于Chrome”&#xff0c;圈起来的红框即为当前Chrome版本&#xff0c;我的版本就是121.0.6167.185 在json中查找自己对应ChromeDriver版本下载链接 一般教程会让你…

【树】【异或】【深度优先】【DFS时间戳】2322. 从树中删除边的最小分数

作者推荐 【二分查找】【C算法】378. 有序矩阵中第 K 小的元素 涉及知识点 树 异或 DFS时间戳 LeetCode2322. 从树中删除边的最小分数 存在一棵无向连通树&#xff0c;树中有编号从 0 到 n - 1 的 n 个节点&#xff0c; 以及 n - 1 条边。 给你一个下标从 0 开始的整数数组…

C++惯用法之RAII思想: 资源管理

C编程技巧专栏&#xff1a;http://t.csdnimg.cn/eolY7 目录 1.概述 2.RAII的应用 2.1.智能指针 2.2.文件句柄管理 2.3.互斥锁 3.注意事项 3.1.禁止复制 3.2.对底层资源使用引用计数法 3.3.复制底部资源(深拷贝)或者转移资源管理权(移动语义) 4.RAII的优势和挑战 5.总…

MATLAB:Image Processing Toolbox工具箱入门实战

目录 1.基本图像导入、处理和导出 2.实战项目一&#xff1a;利用imfindcircles()函数检测和测量图像中的圆形目标 1.基本图像导入、处理和导出 Basic Image Import, Processing, and Export- MATLAB & SimulinkThis example shows how to read an image into the worksp…

Java集合框架-Collection和Map

文章目录 Collection-单列集合特点ListArrayListLinkedListVecter SetHashSetTreeSet Map-键值对集合特点Map常用APIput添加细节remove Map的三种遍历方式1.通过键找值2.通过"键值对"3.Lambda表达式foreach源码 HashMap需求 为什么要使用泛型 泛型的优点1.集合中存储…

#QT(智能家居界面-布局)

1.IDE&#xff1a;QTCreator 2.实验&#xff1a; 水平布局&#xff0c;垂直布局&#xff0c;栅格布局&#xff08;弹簧&#xff09; 界面自动调整 3.记录 注意弹簧不是拖拽拉长&#xff0c;而是使用栅格布局 运行发现窗口放大缩小可以自动调整 如果想要重新布局&#xff0c;需…

【PHP趣味技术】分分钟教会你轻松采集PDF文本内容 《重庆话真的太吃皮老!》

&#x1f680; 个人主页 极客小俊 ✍&#x1f3fb; 作者简介&#xff1a;web开发者、设计师、技术分享博主 &#x1f40b; 希望大家多多支持一下, 我们一起学习和进步&#xff01;&#x1f604; &#x1f3c5; 如果文章对你有帮助的话&#xff0c;欢迎评论 &#x1f4ac;点赞&a…

备忘 clang diagnostic 类的应用示例 ubuntu 22.04

系统的ncurses环境有些问题 通过源码安装了ncurses6.3后&#xff0c;才可以在 llvmort-18.1.rc4中编译通过示例&#xff1a; 1&#xff0c;折腾环境 ncurses-6.3$ ./configure ncurses-6.3$ make -j ncurses-6.3$ sudo make install sudo apt install libtinfo5 sudo…

使用Visual Studio 2022 创建lib和dll并使用

概述&#xff1a;对于一个经常写javaWeb的人来说,使用Visual Studio似乎没什么必要&#xff0c;但是对于使用ffi的人来说&#xff0c;使用c或c编译器&#xff0c;似乎是必不可少的&#xff0c;下面我将讲述如何用Visual Studio 2022 来创建lib和dll&#xff0c;并使用。 静态库…

ABAP - SALV教程12 显示图标和提示信息

ALV要求字段的值为图标的需求并不多见&#xff0c;一般都用于红黄绿灯&#xff0c;来表示单据的执行状态&#xff0c;添加图标的方式也可以实现红黄绿灯的功能&#xff0c;也可以参考SALV实现红黄绿灯这篇文章&#xff1a;http://t.csdnimg.cn/Dzx7x效果图SAVL列设置为图标图标…

434G数据失窃!亚信安全发布《勒索家族和勒索事件监控报告》

最新态势快速感知 最新一周全球共监测到勒索事件90起&#xff0c;与上周相比数量有所增加。 lockbit3.0仍然是影响最严重的勒索家族&#xff1b;alphv和cactus恶意家族也是两个活动频繁的恶意家族&#xff0c;需要注意防范。 Change Healthcare - Optum - UnitedHealth遭受了…

详细分析服务器自动重启原因(涉及Linux、Window)

目录 前言1. Linux2. Window 前言 对于服务器异常重启的问题&#xff0c;需要定位原因并解决&#xff0c;下次就不会重启 1. Linux 要查看Linux服务器自动重启的原因&#xff0c;可以执行以下步骤&#xff1a; 检查系统日志&#xff1a;Linux系统通常会记录系统事件和错误信…

vue3页面内容切换(类似登录、注册内容切换)

一、内容描述 页面有俩块内容&#xff0c;分别是验证码登录页面内容&#xff0c;账号密码登录页面内容。有俩种处理方式&#xff0c;一个是写俩个页面跳转使用&#xff0c;还有一种是一个页面俩个内容&#xff0c;切换的只是不同的内容&#xff0c;相同的内容保留。一般都是选择…