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

Netty系列-7 Netty编解码器

背景

netty框架中,自定义解码器的起点是ByteBuf类型的消息, 自定义编码器的终点是ByteBuf类型。

1.解码器

业务解码器的起点是ByteBuf类型

netty中可以通过继承MessageToMessageEncoder类自定义解码器类。MessageToMessageEncoder继承自ChannelInboundHandlerAdapter,ChannelInboundHandlerAdapter使用默认方式(不处理,向下传递事件)实现了所有的Inbound接口。因此,MessageToMessageEncoder只需要重写channelRead方法,并在该方法中提取消息、转换消息、通过ChannelInvoker将转换后的消息以channelRead事件发向pipeline即可。
MessageToMessageEncoder抽象类的实现如下:

public abstract class MessageToMessageDecoder<I> extends ChannelInboundHandlerAdapter {private final TypeParameterMatcher matcher;protected MessageToMessageDecoder() {matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");}protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {matcher = TypeParameterMatcher.get(inboundMessageType);}public boolean acceptInboundMessage(Object msg) throws Exception {return matcher.match(msg);}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {CodecOutputList out = CodecOutputList.newInstance();try {if (acceptInboundMessage(msg)) {I cast = (I) msg;try {decode(ctx, cast, out);} finally {ReferenceCountUtil.release(cast);}} else {out.add(msg);}} catch (DecoderException e) {throw e;} catch (Exception e) {throw new DecoderException(e);} finally {try {int size = out.size();for (int i = 0; i < size; i++) {ctx.fireChannelRead(out.getUnsafe(i));}} finally {out.recycle();}}}protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}

1.1 类型的匹配器

MessageToMessageDecoder内部维护了一个TypeParameterMatcher类型的匹配器对象matcher,用于指定解码器可以处理的消息类型。可通过构造函数为其设置类型,也可通过泛型指定:

// 使用泛型类型
protected MessageToMessageDecoder() {matcher = TypeParameterMatcher.find(this, MessageToMessageDecoder.class, "I");
}// 子类调用MessageToMessageDecoder构造器时,传入类型
protected MessageToMessageDecoder(Class<? extends I> inboundMessageType) {matcher = TypeParameterMatcher.get(inboundMessageType);
}

一般,通过泛型指定解码器处理的消息对象,即使用MessageToMessageDecoder的无参构造函数。
acceptInboundMessage方法封装matcher的实现,返回布尔值,表示是否支持处理msg消息类型:

public boolean acceptInboundMessage(Object msg) throws Exception {return matcher.match(msg);
}

根据matcher的match方法:

private static final class ReflectiveMatcher extends TypeParameterMatcher {private final Class<?> type;//...@Overridepublic boolean match(Object msg) {// msg消息是否为type类型或者其子类型return type.isInstance(msg);}
}

1.2 channelRead方法

public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {// 构造List列表对象,存储解码后的对象CodecOutputList out = CodecOutputList.newInstance();try {// 判断是否支持处理消息if (acceptInboundMessage(msg)) {I cast = (I) msg;try {// 处理消息,将cast对象解码后的结果存放到out列表中decode(ctx, cast, out);} finally {ReferenceCountUtil.release(cast);}} else {// 不处理消息,以原样保存out.add(msg);}} catch (DecoderException e) {throw e;} catch (Exception e) {throw new DecoderException(e);} finally {try {int size = out.size();// 遍历列表,依次向pipeline触发解码后的对象for (int i = 0; i < size; i++) {ctx.fireChannelRead(out.getUnsafe(i));}} finally {out.recycle();}}
}

逻辑较为清晰:
[1] 构造列表对象out,用于临时存放解码后的消息;
[2] 判断当前解码器是否可以处理该消息,不可以处理,直接添加到out中;可以处理,调用decode方法解码消息,解码结果都添加到out中;
[3] 遍历out列表,将消息以ChannelRead事件传递给向pipeline;
[4] out清理、回收再利用;

1.3 decode方法

decode方法是实际进行消息转换的逻辑,由子类根据业务具体实现:

protected abstract void decode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;

将msg解码,解码后的对象存放在out中;由于out是数组,因此可以从msg中解码出一个对象,也可以解码出多个。如下所示:

protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {out.add(msg.toString(charset));
}

将ByteBuf类型的msg消息转为一个String类型的对象;

protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {String[] decodedMsgs = msg.toString(charset).split(";");for (String decodedMsg: decodedMsgs) {out.add(decodedMsg);}
}

将ByteBuf转为String,并按照;分隔符进行拆分,每个字符串作为一个消息对象。

2.解码器案例

案例的结构图如下所示,消息流入解码器和流出时的消息类型会发生变化:
在这里插入图片描述
引入三个解码器和一个业务Handler:
[1] 编码器1实现ByteBuf->String类型的转换;
[2] 编码器2实现String->Message1类型的转换;
[3] 编码器3实现Message1->Message2类型的转换;
[4] 业务Handler打印消息类型和消息;
实现类依次为:

// MyMessageDecoder1
public class MyMessageDecoder1 extends MessageToMessageDecoder<ByteBuf> {@Overrideprotected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {out.add(msg.toString(Charset.defaultCharset()));}
}// MyMessageDecoder2
class MyMessageDecoder2 extends MessageToMessageDecoder<String> {@Overrideprotected void decode(ChannelHandlerContext ctx, String msg, List<Object> out) {String[] decodedMsgs = msg.split(";");for (String decodedMsg : decodedMsgs) {out.add(new Message1(decodedMsg));}}
}// MyMessageDecoder3
class MyMessageDecoder3 extends MessageToMessageDecoder<Message1> {@Overrideprotected void decode(ChannelHandlerContext ctx, Message1 msg, List<Object> out) {out.add(new Message2(msg.getContent()));}
}

业务Handler定义如下:

private static class MyHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println(msg);}
}

Message1和Message2消息定义如下:

@Data
@RequiredArgsConstructor
pulic class Message1 {private final String content;
}@Data
@RequiredArgsConstructor
pulic class Message2 {private final String content;
}

客户端发送消息:"test1;test2;test3"时:

Microsoft Telnet> send test1;test2;test3
发送字符串 test1;test2;test3
Microsoft Telnet>

服务器日志如下所示:

Message2(content=test1)
Message2(content=test2)
Message2(content=test3)

注意:解码的顺序沿着pipeline进行,因此需要注意调整netty解码器在pipeline中的位置。

如果将3和解码器2的顺序调整一下:

protected void initChannel(NioSocketChannel channel) {channel.pipeline().addLast(new MyMessageDecoder1());channel.pipeline().addLast(new MyMessageDecoder3());channel.pipeline().addLast(new MyMessageDecoder2());channel.pipeline().addLast(new MyHandler());
}

重复上述操作,服务器日志如下:

Message1(content=test1)
Message1(content=test2)
Message1(content=test3)

此时,解码器1流出的数据为String类型,流入解码器2时-类型校验不通过直接以流入的String类型流出,流入解码器3时,将String类型转为Message1类型,流入业务Handler进行打印。

3.编码器

业务编码器的终点是ByteBuf类型

netty中可以通过继承MessageToMessageEncoder类自定义解码器类。MessageToMessageEncoder继承自ChannelOutboundHandlerAdapter,ChannelOutboundHandlerAdapter使用默认方式实现(不处理,向前传递事件)了所有的Outbound接口。因此,MessageToMessageEncoder只需要重写write方法,并在该方法中编码消息、并通过ChannelInvoker将编码后的消息发送到pipeline即可。
MessageToMessageEncoder抽象类的实现如下:

public abstract class MessageToMessageEncoder<I> extends ChannelOutboundHandlerAdapter {private final TypeParameterMatcher matcher;protected MessageToMessageEncoder() {matcher = TypeParameterMatcher.find(this, MessageToMessageEncoder.class, "I");}protected MessageToMessageEncoder(Class<? extends I> outboundMessageType) {matcher = TypeParameterMatcher.get(outboundMessageType);}public boolean acceptOutboundMessage(Object msg) throws Exception {return matcher.match(msg);}@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {CodecOutputList out = null;try {if (acceptOutboundMessage(msg)) {out = CodecOutputList.newInstance();@SuppressWarnings("unchecked")I cast = (I) msg;try {encode(ctx, cast, out);} finally {ReferenceCountUtil.release(cast);}if (out.isEmpty()) {throw new EncoderException(StringUtil.simpleClassName(this) + " must produce at least one message.");}} else {ctx.write(msg, promise);}} catch (EncoderException e) {throw e;} catch (Throwable t) {throw new EncoderException(t);} finally {if (out != null) {try {final int sizeMinusOne = out.size() - 1;if (sizeMinusOne == 0) {ctx.write(out.getUnsafe(0), promise);} else if (sizeMinusOne > 0) {if (promise == ctx.voidPromise()) {writeVoidPromise(ctx, out);} else {writePromiseCombiner(ctx, out, promise);}}} finally {out.recycle();}}}}private static void writeVoidPromise(ChannelHandlerContext ctx, CodecOutputList out) {final ChannelPromise voidPromise = ctx.voidPromise();for (int i = 0; i < out.size(); i++) {ctx.write(out.getUnsafe(i), voidPromise);}}private static void writePromiseCombiner(ChannelHandlerContext ctx, CodecOutputList out, ChannelPromise promise) {final PromiseCombiner combiner = new PromiseCombiner(ctx.executor());for (int i = 0; i < out.size(); i++) {combiner.add(ctx.write(out.getUnsafe(i)));}combiner.finish(promise);}protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;
}

3.1 类型的匹配器

MessageToMessageEncoder内部维护了一个TypeParameterMatcher类型的匹配器对象matcher,用于指定该编码器器可以处理的消息类型,与解码器中的matcher作用完全相同,不再赘述。

3.2 write方法

public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {CodecOutputList out = null;try {// 判断当前编码器是否可以编码消息if (acceptOutboundMessage(msg)) {out = CodecOutputList.newInstance();@SuppressWarnings("unchecked")I cast = (I) msg;try {// 编码消息,并将编码后的消息保存到out列表中encode(ctx, cast, out);} finally {ReferenceCountUtil.release(cast);}if (out.isEmpty()) {throw new EncoderException(StringUtil.simpleClassName(this) + " must produce at least one message.");}} else {// 不能编码的消息不处理,直接沿着pipeline向前传递ctx.write(msg, promise);}} catch (EncoderException e) {throw e;} catch (Throwable t) {throw new EncoderException(t);} finally {// 遍历out,依次调用ctx.write,沿着pipeline向前传递if (out != null) {try {final int sizeMinusOne = out.size() - 1;if (sizeMinusOne == 0) {ctx.write(out.getUnsafe(0), promise);} else if (sizeMinusOne > 0) {if (promise == ctx.voidPromise()) {writeVoidPromise(ctx, out);} else {writePromiseCombiner(ctx, out, promise);}}} finally {// 清理out列表,回收再利用out.recycle();}}}
}

逻辑较为清晰:
[1] 构造列表对象out,用于临时存放编码后的消息;
[2] 判断当前编码器是否可以处理该消息,不可以处理,直接通过ctx.write沿着pipeline向前传递;可以处理,调用encode方法编码消息,编码结果添加到out中;
[3] 遍历out列表,将消息以write事件传递给向pipeline;
[4] out清理、回收再利用;

3.2 encode方法

encode方法是实际进行消息转换的逻辑,由子类根据业务具体实现:

protected abstract void encode(ChannelHandlerContext ctx, I msg, List<Object> out) throws Exception;

将msg消息进行编码,编码后的对象存放在out中;由于out是数组,因此可以从msg中编码出一个对象,也可以编码出多个,与解码器逻辑相同。

protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {out.add(msg.toString(charset));
}

将ByteBuf类型的msg消息转为一个String类型的对象;

protected void decode(ChannelHandlerContext ctx, ByteBuf msg, List<Object> out) {String[] decodedMsgs = msg.toString(charset).split(";");for (String decodedMsg: decodedMsgs) {out.add(decodedMsg);}
}

将ByteBuf转为String,并按照;分隔符进行拆分,每个字符串作为一个消息对象。
netty向外发送数据时,一般经过业务Handler->编码器->HeadContext的流程。
向客户端发送消息的底层实现在HeadContext的unsafe对象(NioSocketChannel的unsafe对象)中,而发送前有消息类型判断:

final class HeadContext extends AbstractChannelHandlerContext implements ChannelOutboundHandler, ChannelInboundHandler{ public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {unsafe.write(msg, promise);}
}

unsafe对象的write方法如下:

public final void write(Object msg, ChannelPromise promise) {//...msg = filterOutboundMessage(msg);//...
}

在真实写操作前,通过filterOutboundMessage进行消息类型的判断:

@Override
protected final Object filterOutboundMessage(Object msg) {// 要求消息必须时ByteBuf或者FileRegion类型或其子类型if (msg instanceof ByteBuf) {ByteBuf buf = (ByteBuf) msg;if (buf.isDirect()) {return msg;}return newDirectBuffer(buf);}if (msg instanceof FileRegion) {return msg;}throw new UnsupportedOperationException("unsupported message type: " + StringUtil.simpleClassName(msg) + EXPECTED_TYPES);
}

由此,编码器将消息传递给HeadContext前,需要将消息最终编码为ByteBuf类型。

4.解码器案例

案例结构图如下所示:
在这里插入图片描述

在章节2中的案例基础上新增两个编码器,并修改业务Handler:
[1] 业务Handler,接收客户端消息后,响应相同消息;
[2] 编码器1:将Message2类型的消息转为String类型;
[3] 编码器2: 将String类型消息转为ByteBuf类型;
代码实现如下:
修改业务Handler:

private static class MyHandler extends ChannelInboundHandlerAdapter {@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {System.out.println(msg);// 新增逻辑“将消息对象发送给客户端ctx.write(msg);}
}

添加编码器:

// 将Message2消息转为String消息
public class MyEncoder1 extends MessageToMessageEncoder<Message2> {@Overrideprotected void encode(ChannelHandlerContext ctx, Message2 msg, List<Object> out) throws Exception {out.add(msg.getContent());}
}// 将String消息转为ByteBuf消息
public class MyEncoder2 extends MessageToMessageEncoder<String> {@Overrideprotected void encode(ChannelHandlerContext ctx, String msg, List<Object> out) {out.add(ByteBufUtil.encodeString(ctx.alloc(), CharBuffer.wrap(msg), Charset.defaultCharset()));}
}

在MyHandler前依次添加解码器MyEncoder2和MyEncoder1:

protected void initChannel(NioSocketChannel channel) {channel.pipeline().addLast(new MyMessageDecoder1());channel.pipeline().addLast(new MyMessageDecoder2());channel.pipeline().addLast(new MyMessageDecoder3());channel.pipeline().addLast(new MyEncoder2());channel.pipeline().addLast(new MyEncoder1());channel.pipeline().addLast(new MyHandler());
}

可以使用Netty写一个客户端, 也可用客户端工具模拟,这里为了方便,使用SocketTool.exe,控制台日志如下:

14:36:15 发送数据:test1;test2;test3[1次]
14:36:15 收到数据:test1test2test3

注意:客户端收到了test1test2test3消息,在客户端开来是一个消息,但在服务器看来是连续发送的3个消息,消息内容分别为test1和test2和test3。这是TCP的流传输模式导致,可在业务层添加额外处理解决这个问题。将在下一篇文件介绍Netty如何处理粘包和分包问题。

相关文章:

Netty系列-7 Netty编解码器

背景 netty框架中&#xff0c;自定义解码器的起点是ByteBuf类型的消息, 自定义编码器的终点是ByteBuf类型。 1.解码器 业务解码器的起点是ByteBuf类型 netty中可以通过继承MessageToMessageEncoder类自定义解码器类。MessageToMessageEncoder继承自ChannelInboundHandlerAdap…...

OpenHarmony标准系统上实现对rk系列芯片NPU的支持(npu使用)

在上篇文章中&#xff0c;我们学习了移植rk的npu驱动到OpenHarmony提供的内核。本文我们来学习如何在OpenHarmony标准系统rk系列芯片如何使用npu OpenHarmony RK系列芯片运行npu测试用例 在移植npu驱动到OpenHarmony之后&#xff0c;来运行npu样例进行简单测试 1.O 测试准备…...

大表性能优化的关键技术

1 引言 在现代企业应用中,随着数据量的不断增长,大表的性能优化成为数据库管理的重要环节。本文将探讨大表性能优化的关键技术,包括索引优化、查询优化、分区分表、读写分离以及缓存策略等方面。通过综合运用这些技术,可以显著提升大表的处理效率和响应速度,确保系统的稳…...

广联达 Linkworks办公OA Service.asmx接口存在信息泄露漏洞

漏洞描述 广联达科技股份有限公司以建设工程领域专业应用为核心基础支撑&#xff0c;提供一百余款基于“端云大数据”产品/服务&#xff0c;提供产业大数据、产业新金融等增值服务的数字建筑平台服务商。广联达OA存在信息泄露漏洞&#xff0c;由于某些接口没有鉴权&#xff0c…...

如何成为成功的AI产品经理:经验与策略分享

引言 随着人工智能(AI)技术的迅猛发展,AI产品经理(AI PM)的角色变得越来越重要。Google AI产品负责人Marily Nika在最近的一次播客中分享了她在AI产品管理领域的宝贵经验和见解。本文将整理并总结她的核心内容,帮助有志于进入AI PM领域的人士了解如何准备、所需的核心技…...

spring loCDI 详解

文章目录 一、IoC & DI 基本知识1.1 IoC 的基本概念&#xff1a;1.2 IoC 的优势&#xff1a;1.3 DI 介绍&#xff1a; 二、IoC 详解2.1 Spring 容器&#xff1a;2.2 被存储 Bean 的命名约定&#xff1a;2.3 Bean 的存储方式&#xff1a;2.3.1 五大类注解&#xff1a;2.3.1.…...

遇到 Docker 镜像拉取失败的问题时该如何解决

遇到 Docker 镜像拉取失败的问题时&#xff0c;可以按照以下步骤进行排查和解决&#xff1a; 1. 检查网络连接 确保你的计算机可以访问互联网。尝试 ping 通 Docker Hub 或其他镜像仓库的域名&#xff1a; ping hub.docker.com2. 检查 Docker 服务状态 确保 Docker 服务正在…...

【C/C++】错题记录(三)

题目一 题目二 题目三 题目四 题目五 题目六 题目七&#xff1f;&#xff1f;&#xff1f; 题目八 这道题主要考查对数据类型和位运算的理解与运用。 分析选项 A&#xff1a; *((unsigned char *)(&number) 1)0xcd; 这里将 number 的地址强制转换为 unsigned char* 类型&a…...

深入理解Web浏览器与服务器的连接过程

目录 1. 域名解析&#xff1a;找到地址 2. TCP连接&#xff1a;建立通信 3. HTTP请求&#xff1a;点菜 4. 服务器处理请求&#xff1a;厨房做菜 5. HTTP响应&#xff1a;上菜 6. 客户端接收响应&#xff1a;品尝美食 7. 关闭TCP连接&#xff1a;吃完离开 8. 持久连接&a…...

深入解析 https

我的主页&#xff1a;2的n次方_ 1. 背景介绍 在使用 http 协议的时候是不安全的&#xff0c;可能会出现运营商劫持等安全问题&#xff0c;运营商通过劫持 http 流量&#xff0c;篡改返回的网页内容&#xff0c;例如广告业务&#xff0c;可能会通过 Referer 字段 来统计是…...

NP-hard问题

一、前置知识 1.多项式 多项式是由变量&#xff08;如x、y等&#xff09;和系数通过有限次的加、减、乘运算得到的表达式。例如3x^22x 1就是一个关于(x)的多项式 2.时间复杂度 时间复杂度是用来衡量算法运行效率的一个指标。它描述了算法运行时间随着输入规模增长而增长的量…...

【Nacos架构 原理】内核设计之Nacos通信通道

文章目录 Nacos通信通道 &#xff08;长链接&#xff09;现状背景场景分析配置服务 长链接核心诉求功能性诉求负载均衡连接生命周期 Nacos通信通道 &#xff08;长链接&#xff09; 现状背景 Nacos 1.X 版本 Config/Naming 模块各自的推送通道都是按照自己的设计模型来实现的…...

【单片机】单片机map表详细解析

1、RO Size、RW Size、ROM Size分别是什么 首先将map文件翻到最下面&#xff0c;可以看到 1.1 RO Size&#xff1a;只读段 Code&#xff1a;程序的代码部分&#xff08;也就是 .text 段&#xff09;&#xff0c;它存放了程序的指令和可执行代码。 RO Data&#xff1a;只读…...

考研笔记之操作系统(三)- 存储管理

操作系统&#xff08;三&#xff09;- 存储管理 1. 内存的基础知识1.1 存储单元与内存地址1.2 按字节编址和按字编址1.3 指令1.4 物理地址和逻辑地址1.5 从写程序到程序运行1.6 链接1.6.1 静态链接1.6.2 装入时动态链接1.6.3 运行时动态链接 1.7 装入1.7.1 概念1.7.2 绝对装入1…...

vim/vi常用命令大全

启动和退出Vim 命令/操作作用vim启动Vimvim filename直接打开指定的文件命令模式下&#xff0c;输入 :q退出&#xff0c;q!强制退出:wq保存并退出:wq!保存并强制退出vim中按下a进入编辑模式Esc退出编辑模式进入命令模式new创建新窗口close关闭窗口 光标移动 命令/操作作用h、…...

什么是大语言模型,一句话解释

定义 先说语言模型&#xff08;Language Model&#xff09;旨在建模词汇序列的生成概率&#xff0c;提升机器的语言智能水平&#xff0c;使机 器能够模拟人类说话、写作的模式进行自动文本输出。 白话&#xff1a;语言模式是一种解决机器与人类交流的手段&#xff0c;机器人与…...

【数据库】 MongoDB 撤销用户的角色和权限

在 MongoDB 中&#xff0c;撤销用户的角色和权限是一项重要的管理任务&#xff0c;确保用户仅能访问和操作他们需要的数据。以下是如何撤销用户的角色和权限的详细步骤。 1. 使用 MongoDB Shell 撤销角色 1.1 修改用户角色 要撤销用户的角色&#xff0c;可以使用 updateUser…...

vue2接入高德地图实现折线绘制、起始点标记和轨迹打点的完整功能(提供Gitee源码)

目录 一、申请密钥 二、安装element-ui 三、安装高德地图依赖 四、完整代码 五、运行截图 六、官方文档 七、Gitee源码 一、申请密钥 登录高德开放平台&#xff0c;点击我的应用&#xff0c;先添加新应用&#xff0c;然后再添加Key。 ​ 如图所示填写对应的信息&…...

【重学 MySQL】四十六、创建表的方式

【重学 MySQL】四十六、创建表的方式 使用CREATE TABLE语句创建表使用CREATE TABLE LIKE语句创建表使用CREATE TABLE AS SELECT语句创建表使用CREATE TABLE SELECT语句创建表并从另一个表中选取数据&#xff08;与CREATE TABLE AS SELECT类似&#xff09;使用CREATE TEMPORARY …...

WPS在表格中填写材料时,内容过多导致表格不换页,其余内容无法正常显示 以及 内容过多,导致表格换页——解决方法

一、现象 1&#xff0c;内容过多导致表格不换页&#xff0c;其余内容无法正常显示 2&#xff0c;内容过多&#xff0c;导致表格换页 二、解决方法 在表格内右击&#xff0c;选择表格属性 在菜单栏选择行&#xff0c;勾选允许跨页断行&#xff0c;点击确定即可 1&#xff0…...

挑战杯推荐项目

“人工智能”创意赛 - 智能艺术创作助手&#xff1a;借助大模型技术&#xff0c;开发能根据用户输入的主题、风格等要求&#xff0c;生成绘画、音乐、文学作品等多种形式艺术创作灵感或初稿的应用&#xff0c;帮助艺术家和创意爱好者激发创意、提高创作效率。 ​ - 个性化梦境…...

MODBUS TCP转CANopen 技术赋能高效协同作业

在现代工业自动化领域&#xff0c;MODBUS TCP和CANopen两种通讯协议因其稳定性和高效性被广泛应用于各种设备和系统中。而随着科技的不断进步&#xff0c;这两种通讯协议也正在被逐步融合&#xff0c;形成了一种新型的通讯方式——开疆智能MODBUS TCP转CANopen网关KJ-TCPC-CANP…...

基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解

JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用&#xff0c;结合SQLite数据库实现联系人管理功能&#xff0c;并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能&#xff0c;同时可以最小化到系统…...

安全突围:重塑内生安全体系:齐向东在2025年BCS大会的演讲

文章目录 前言第一部分&#xff1a;体系力量是突围之钥第一重困境是体系思想落地不畅。第二重困境是大小体系融合瓶颈。第三重困境是“小体系”运营梗阻。 第二部分&#xff1a;体系矛盾是突围之障一是数据孤岛的障碍。二是投入不足的障碍。三是新旧兼容难的障碍。 第三部分&am…...

现有的 Redis 分布式锁库(如 Redisson)提供了哪些便利?

现有的 Redis 分布式锁库&#xff08;如 Redisson&#xff09;相比于开发者自己基于 Redis 命令&#xff08;如 SETNX, EXPIRE, DEL&#xff09;手动实现分布式锁&#xff0c;提供了巨大的便利性和健壮性。主要体现在以下几个方面&#xff1a; 原子性保证 (Atomicity)&#xff…...

Caliper 负载(Workload)详细解析

Caliper 负载(Workload)详细解析 负载(Workload)是 Caliper 性能测试的核心部分,它定义了测试期间要执行的具体合约调用行为和交易模式。下面我将全面深入地讲解负载的各个方面。 一、负载模块基本结构 一个典型的负载模块(如 workload.js)包含以下基本结构: use strict;/…...

PHP 8.5 即将发布:管道操作符、强力调试

前不久&#xff0c;PHP宣布了即将在 2025 年 11 月 20 日 正式发布的 PHP 8.5&#xff01;作为 PHP 语言的又一次重要迭代&#xff0c;PHP 8.5 承诺带来一系列旨在提升代码可读性、健壮性以及开发者效率的改进。而更令人兴奋的是&#xff0c;借助强大的本地开发环境 ServBay&am…...

​​企业大模型服务合规指南:深度解析备案与登记制度​​

伴随AI技术的爆炸式发展&#xff0c;尤其是大模型&#xff08;LLM&#xff09;在各行各业的深度应用和整合&#xff0c;企业利用AI技术提升效率、创新服务的步伐不断加快。无论是像DeepSeek这样的前沿技术提供者&#xff0c;还是积极拥抱AI转型的传统企业&#xff0c;在面向公众…...

Matlab实现任意伪彩色图像可视化显示

Matlab实现任意伪彩色图像可视化显示 1、灰度原始图像2、RGB彩色原始图像 在科研研究中&#xff0c;如何展示好看的实验结果图像非常重要&#xff01;&#xff01;&#xff01; 1、灰度原始图像 灰度图像每个像素点只有一个数值&#xff0c;代表该点的​​亮度&#xff08;或…...

大数据治理的常见方式

大数据治理的常见方式 大数据治理是确保数据质量、安全性和可用性的系统性方法&#xff0c;以下是几种常见的治理方式&#xff1a; 1. 数据质量管理 核心方法&#xff1a; 数据校验&#xff1a;建立数据校验规则&#xff08;格式、范围、一致性等&#xff09;数据清洗&…...