Netty核心技术七--Google Protobuf
1.编码和解码的基本介绍
-
编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码
-
codec(编解码器)
的组成部分有两个:decoder(解码器)
和encoder(编码器)
。encoder 负责把业务数据转换成字节码数据,decoder 负责把字节码数据转换成业务数据
2. Netty 本身的编码解码的机制和问题分析
- Netty 自身提供了一些
codec(编解码器)
- Netty 提供的编码器
StringEncoder
,对字符串数据进行编码ObjectEncoder
,对 Java 对象进行编码- …
- Netty 提供的解码器
StringDecoder
, 对字符串数据进行解码ObjectDecoder
,对 Java 对象进行解码- …
- Netty 本身自带的 ObjectDecoder 和 ObjectEncoder 可以用来实现POJO对象或各种业务对象的编码和解码,底层使用的仍是 Java 序列化技术 , 而Java 序列化技术本身效率就不高,存在如下问题
- 无法跨语言
- 序列化后的体积太大,是二进制编码的 5 倍多。
- 序列化性能太低
结论:所以我们使用Protobuf
3. Protobuf
3.1 Protobuf基本介绍和使用示意图
-
Protobuf 是 Google 发布的开源项目,全称 Google Protocol Buffers,是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或 RPC[远程过程调用 remote procedure call ] 数据交换格式。
- 目前很多公司 http+json 向 tcp+protobuf转型
-
Protobuf 是以 message 的方式来管理数据的.
-
支持跨平台、跨语言,即[客户端和服务器端可以是不同的语言编写的] (支持目前绝大多数语言,例如 C++、C#、Java、python 等)
-
高性能,高可靠性
- 在序列化和反序列化数据方面,Protobuf 比JSON 快得多。由于格式是二进制的,在Protobuf 中读写结构化数据所需的时间比在JSON 中要短。
-
使用 protobuf 编译器能自动生成代码,Protobuf 是将类的定义使用
.proto文件
进行描述。说明,在idea 中编写 .proto 文件时,会自动提示是否下载.ptotot 编写插件.可以让语法高亮。 -
然后通过 protoc.exe 编译器根据.proto 自动生成.java 文件
-
protobuf 使用示意图
- 客户端通过protoc.exe 编译器根据.proto 自动生成.java 文件
- 客户端再将生成的java文件对应的对象通过
ProtobufEncoder
编码为二进制字节码,通过二进制进行传输 - 服务端拿到二进制文件再通过
ProtobufDecoder
解码为业务数据对象即可使用
3.2 Protobuf快速入门实例1
需求:
- 客户端可以发送一个Student PoJo 对象到服 务器 (通过 Protobuf 编码)
- 服务端能接收Student PoJo 对象,并显示信 息(通过 Protobuf 解码)
protoc-3.6.1-win32.zip官方下载地址
-
将我们之前写的Netty核心技术五–Netty高性能架构设计的
Netty快速入门实例-TCP服务
案例直接复用 -
Protobuf的使用–Java
-
通过Maven的方式导入Protobuf相关依赖
<!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java --> <dependency><groupId>com.google.protobuf</groupId><artifactId>protobuf-java</artifactId><version>3.6.1</version> </dependency>
-
protobuf定义属性各语言对照表官方文档https://protobuf.dev/programming-guides/proto3/#scalar
.proto Type C++ Type Java/Kotlin Type[1] Python Type[3] Go Type Ruby Type C# Type PHP Type Dart Type double double double float float64 Float double float double float float float float float32 Float float float double int32 int32 int int int32 Fixnum or Bignum (as required) int integer int int64 int64 long int/long[4] int64 Bignum long integer/string[6] Int64 uint32 uint32 int[2] int/long[4] uint32 Fixnum or Bignum (as required) uint integer int uint64 uint64 long[2] int/long[4] uint64 Bignum ulong integer/string[6] Int64 sint32 int32 int int int32 Fixnum or Bignum (as required) int integer int sint64 int64 long int/long[4] int64 Bignum long integer/string[6] Int64 fixed32 uint32 int[2] int/long[4] uint32 Fixnum or Bignum (as required) uint integer int fixed64 uint64 long[2] int/long[4] uint64 Bignum ulong integer/string[6] Int64 sfixed32 int32 int int int32 Fixnum or Bignum (as required) int integer int sfixed64 int64 long int/long[4] int64 Bignum long integer/string[6] Int64 bool bool boolean bool bool TrueClass/FalseClass bool boolean bool string string String str/unicode[5] string String (UTF-8) string string String bytes string ByteString str (Python 2) bytes (Python 3) []byte String (ASCII-8BIT) ByteString string List -
新建一个.proto文件
Student.proto
syntax = "proto3"; //协议版本,因为我们的protobuf的版本是3.6.1 option java_outer_classname = "StudentPOJO";//生成的外部类名,同时也是文件名 //protobuf 使用message 管理数据 message Student { //会在 StudentPOJO 外部类生成一个内部类 Student, 他是真正发送的POJO对象int32 id = 1; // Student 类中有 一个属性 名字为 id 类型为int32(protobuf类型) 1表示属性序号,不是值string name = 2; }
-
客户端通过protoc.exe 编译器根据.proto 自动生成.java 文件
-
将拷贝到 protoc.exe 同级目录或者自己配置一个环境变量
-
使用protoc.exe编译
protoc.exe --java_out=. Student.proto
-
-
我们将 StudentPOJO.java拷贝回目录
StudentPOJO有一个内部类Student,这个类才是我们传输的对象
-
编写服务端和客户端(因为我们是复用的代码,所以只需要修改一下)
-
3.2.1 客户端NettyClient
较之前的代码在initChannel的最前面加上了
ProtoBufEncoder
handler
package site.zhourui.nioAndNetty.netty.codec;import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufEncoder;public class NettyClient {public static void main(String[] args) throws InterruptedException {//客户端需要一个事件循环组NioEventLoopGroup group = new NioEventLoopGroup();try {//创建客户端启动对象//注意客户端使用的不是 ServerBootstrap 而是 BootstrapBootstrap bootstrap = new Bootstrap();bootstrap.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {//在pipeline中加入 ProtoBufEncodersocketChannel.pipeline().addLast("encoder", new ProtobufEncoder());socketChannel.pipeline().addLast(new NettyClientHandler());}});System.out.println("客户端 ok..");//启动客户端去连接服务器端//关于 ChannelFuture 要分析,涉及到netty的异步模型ChannelFuture cf = bootstrap.connect("127.0.0.1", 6668).sync();//给关闭通道进行监听cf.channel().closeFuture().sync();}finally {group.shutdownGracefully();}}
}
3.2.2 客户端NettyClientHandler
- 将重写的channelActive方法修改为发送一个StudentPOJO.Student实例对象
package site.zhourui.nioAndNetty.netty.codec;import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;public class NettyClientHandler extends ChannelInboundHandlerAdapter {//当通道就绪就会触发该方法@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//发生一个Student 对象到服务器StudentPOJO.Student student = StudentPOJO.Student.newBuilder().setId(4).setName("智多星 吴用").build();ctx.pipeline().writeAndFlush(student);}//当通道有读取事件时,会触发@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());}//当通道有异常时触发@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}
3.2.3 服务端NettyServer
- 较之前的代码在initChannel的最前面加上了
ProtoBufDecoder
handler
package site.zhourui.nioAndNetty.netty.codec;import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.protobuf.ProtobufDecoder;public class NettyServer {public static void main(String[] args) throws InterruptedException {//创建BossGroup 和 WorkerGroup//说明//1. 创建两个线程组 bossGroup 和 workerGroup//2. bossGroup 只是处理连接请求 , 真正的和客户端业务处理,会交给 workerGroup完成//3. 两个都是无限循环//4. bossGroup 和 workerGroup 含有的子线程(NioEventLoop)的个数// 默认实际 cpu核数 * 2NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);NioEventLoopGroup workerGroup = new NioEventLoopGroup(8);try {//创建服务器端的启动对象,配置参数ServerBootstrap bootstrap = new ServerBootstrap();//使用链式编程来进行设置bootstrap.group(bossGroup,workerGroup)//设置两个线程组.channel(NioServerSocketChannel.class)//使用NioSocketChannel 作为服务器的通道实现.option(ChannelOption.SO_BACKLOG,128)// 设置线程队列得到连接个数.childOption(ChannelOption.SO_KEEPALIVE,true)//设置保持活动连接状态.childHandler(new ChannelInitializer<SocketChannel>() {//创建一个通道初始化对象(匿名对象)//给pipeline 设置处理器@Overrideprotected void initChannel(SocketChannel socketChannel) throws Exception {//可以使用一个集合管理 SocketChannel, 再推送消息时,可以将业务加入到各个channel 对应的 NIOEventLoop 的 taskQueue 或者 scheduleTaskQueueSystem.out.println("客户socketchannel hashcode=" + socketChannel.hashCode());//在pipeline加入ProtoBufDecoder//指定对哪种对象进行解码socketChannel.pipeline().addLast("decoder", new ProtobufDecoder(StudentPOJO.Student.getDefaultInstance()));socketChannel.pipeline().addLast(new NettyServerHandler());}});System.out.println(".....服务器 is ready...");//绑定一个端口并且同步, 生成了一个 ChannelFuture 对象//启动服务器(并绑定端口)ChannelFuture cf = bootstrap.bind(6668).sync();//给cf 注册监听器,监控我们关心的事件cf.addListener(new ChannelFutureListener() {@Overridepublic void operationComplete(ChannelFuture channelFuture) throws Exception {if (channelFuture.isSuccess()){System.out.println("监听端口 6668 成功");}else {System.out.println("监听端口 6668 失败");}}});//对关闭通道进行监听cf.channel().closeFuture().sync();}finally {bossGroup.shutdownGracefully();workerGroup.shutdownGracefully();}}
}
3.2.4 服务端NettyServerHandler
- 将
ChannelInboundHandlerAdapter
修改为SimpleChannelInboundHandler<StudentPOJO.Student>
,这样就需要我们手动将Object对象转为StudentPOJO.Student- 重新channelRead0方法
- 打印出接收到的消息
package site.zhourui.nioAndNetty.netty.codec;import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;import java.util.concurrent.TimeUnit;/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler*/
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<StudentPOJO.Student> {//读取数据实际(这里我们可以读取客户端发送的消息)/*1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址2. Object msg: 就是客户端发送的数据 默认Object*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, StudentPOJO.Student msg) throws Exception {//读取从客户端发送的StudentPojo.StudentSystem.out.println("客户端发送的数据 id=" + msg.getId() + " 名字=" + msg.getName());}//数据读取完毕@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));}//处理异常, 一般是需要关闭通道@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}
}
3.2.5 测试
-
启动服务端
-
启动客户端
服务端接收到了客户端发送的对象
3.3 Protobuf快速入门实例2
需求:
- 客户端可以随机发送Student PoJo/ Worker PoJo 对象到服务器 (通过 Protobuf 编码)
- 服务端能接收Student PoJo/ Worker PoJo 对象(需要判断是哪种类型),并显示信息(通过 Protobuf 解码)
个人理解如果是每个对象都像实例1一样都生成一个.java,那么就很麻烦,所以我们将需要发送的对象(类)封装在一个.proto文件中就只需要编译一次
3.3.1 复用案例1的代码
因为实现具体功能都差不多,我们直接复用案例1的代码
3.3.2 修改 Student.proto
较之案例1的差异:
- 加快解析
- 指定.java生成的位置
- protobuf 可以使用message 管理其他的message
- 定义三个message:
MyMessage
是用于管理Student
,Worker
MyMessage
中定义枚举:在proto3 要求enum的编号从0开始- 用data_type 来标识传的是哪一个枚举类型
oneof
:表示每次枚举类型最多只能出现其中的一个, 节省空间- 将编写的Student.proto再次编译为.java,放在codec2包下
syntax = "proto3";
option optimize_for = SPEED; // 加快解析
option java_package="site.zhourui.nioAndNetty.netty.codec2"; //指定生成到哪个包下
option java_outer_classname="MyDataInfo"; // 外部类名, 文件名//protobuf 可以使用message 管理其他的message
message MyMessage {//定义一个枚举类型enum DataType {StudentType = 0; //在proto3 要求enum的编号从0开始WorkerType = 1;}//用data_type 来标识传的是哪一个枚举类型DataType data_type = 1;//表示每次枚举类型最多只能出现其中的一个, 节省空间oneof dataBody {Student student = 2;Worker worker = 3;}}message Student {int32 id = 1;//Student类的属性string name = 2; //
}
message Worker {string name=1;int32 age=2;
}
3.3.3 客户端NettyClientHandler
- 客户端端在通道连接的时候随机发送Student或Worker对象
package site.zhourui.nioAndNetty.netty.codec2;import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;import java.util.Random;public class NettyClientHandler extends ChannelInboundHandlerAdapter {//当通道就绪就会触发该方法@Overridepublic void channelActive(ChannelHandlerContext ctx) throws Exception {//随机的发送Student 或者 Workder 对象int random = new Random().nextInt(3);MyDataInfo.MyMessage myMessage = null;if(0 == random) { //发送Student 对象myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.StudentType).setStudent(MyDataInfo.Student.newBuilder().setId(5).setName("玉麒麟 卢俊义").build()).build();} else { // 发送一个Worker 对象myMessage = MyDataInfo.MyMessage.newBuilder().setDataType(MyDataInfo.MyMessage.DataType.WorkerType).setWorker(MyDataInfo.Worker.newBuilder().setAge(20).setName("老李").build()).build();}ctx.writeAndFlush(myMessage);}//当通道有读取事件时,会触发@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {ByteBuf buf = (ByteBuf) msg;System.out.println("服务器回复的消息:" + buf.toString(CharsetUtil.UTF_8));System.out.println("服务器的地址: "+ ctx.channel().remoteAddress());}//当通道有异常时触发@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {cause.printStackTrace();ctx.close();}
}
3.3.4 服务端NettyServer
- 因为是发送消息是编码所以不需要修改
3.3.5 服务端NettyServerHandler
- 修改SimpleChannelInboundHandler泛型为MyDataInfo.MyMessage
- channelRead0中根据dataType 来显示不同的信息
package site.zhourui.nioAndNetty.netty.codec2;import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;/*
说明
1. 我们自定义一个Handler 需要继续netty 规定好的某个HandlerAdapter(规范)
2. 这时我们自定义一个Handler , 才能称为一个handler*/
//public class NettyServerHandler extends ChannelInboundHandlerAdapter {
public class NettyServerHandler extends SimpleChannelInboundHandler<MyDataInfo.MyMessage> {//读取数据实际(这里我们可以读取客户端发送的消息)/*1. ChannelHandlerContext ctx:上下文对象, 含有 管道pipeline , 通道channel, 地址2. Object msg: 就是客户端发送的数据 默认Object*/@Overrideprotected void channelRead0(ChannelHandlerContext ctx, MyDataInfo.MyMessage msg) throws Exception {//根据dataType 来显示不同的信息MyDataInfo.MyMessage.DataType dataType = msg.getDataType();if(dataType == MyDataInfo.MyMessage.DataType.StudentType) {MyDataInfo.Student student = msg.getStudent();System.out.println("学生id=" + student.getId() + " 学生名字=" + student.getName());} else if(dataType == MyDataInfo.MyMessage.DataType.WorkerType) {MyDataInfo.Worker worker = msg.getWorker();System.out.println("工人的名字=" + worker.getName() + " 年龄=" + worker.getAge());} else {System.out.println("传输的类型不正确");}}//数据读取完毕@Overridepublic void channelReadComplete(ChannelHandlerContext ctx) throws Exception {//writeAndFlush 是 write + flush//将数据写入到缓存,并刷新//一般讲,我们对这个发送的数据进行编码ctx.writeAndFlush(Unpooled.copiedBuffer("hello, 客户端~(>^ω^<)喵1", CharsetUtil.UTF_8));}//处理异常, 一般是需要关闭通道@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {ctx.close();}}
3.3.6 测试
开启一次服务端,多次重启服务端模拟发送不同类型的消息
相关文章:

Netty核心技术七--Google Protobuf
1.编码和解码的基本介绍 编写网络应用程序时,因为数据在网络中传输的都是二进制字节码数据,在发送数据时就需要编码,接收数据时就需要解码 codec(编解码器) 的组成部分有两个:decoder(解码器)和encoder(编码器)。encoder 负责把…...
【Docker】Docker常用命令总结
文章目录 一、帮助命令二、镜像命令三、容器命令四、常用的其他命令 在开发过程中,经常涉及到 docker 的相关操作,本文对常用的指令进行汇总。 一、帮助命令 docker version # 显示docker版本信息 docker info # 显示docker系统信息ÿ…...

React 对比class与Effect Hook优化响应式数据更新监听,感受useEffect真正的强大
还是之前写过的一个组件 import React from "react"export default class index extends React.Component{constructor(props){super(props);this.state {name: "小猫猫"}}componentDidMount ()>{document.title this.state.name;}componentDidUpda…...

AWS Lambda 介绍
计算服务的演进 EC2------Container-------Lambda 虚拟机---容器--------------serverless无服务器架构 什么是AWS Lambda? AWS lambda的核心是事件驱动,驱动可能来自,Alexa,SNS,DynamoDB,S3,Kinesis等&…...
linux之权限管理
目录 1.一.基本小语句 2.文件权限操作chmod 1.一.基本小语句 ls - a 查看此文件夹所有和隐藏内容 ls - l 查看此文件夹权限 chown 改变文所有者 2.文件权限操作chmod chmod 参数 文件名 文件的权限主要针对三类对象进行定义 owner 属主, u:针对前三个部分的权限修改 …...
【设计模式与范式:行为型】61 | 策略模式(下):如何实现一个支持给不同大小文件排序的小程序?
上一节课,我们主要介绍了策略模式的原理和实现,以及如何利用策略模式来移除 if-else 或者 switch-case 分支判断逻辑。今天,我们结合“给文件排序”这样一个具体的例子,来详细讲一讲策略模式的设计意图和应用场景。 除此之外&…...

【C++】auto_ptr为何被唾弃?以及其他智能指针的学习
搭配异常可以让异常的代码更简洁 文章目录 智能指针 内存泄漏的危害 1.auto_ptr(非常不建议使用) 2.unique_ptr 3.shared_ptr 4.weak_ptr总结 智能指针 C中为什么会需要智能指针呢?下面我们看一下样例: int div() {int a, b;cin >&g…...

数据结构练习题1:基本概念
练习题1:基本概念 1 抽象数据类型概念分析2. 逻辑结构与存储结构概念分析3.综合选择题4.综合判断题5.时间复杂度相关习题6 时间复杂度计算方法(一、二、三层循环) 1 抽象数据类型概念分析 1.可以用(抽象数据类型)定义…...

如何消除Msxml2.XMLHTTP组件的缓存
之前使用这个组件,是每隔十分钟取数据,没有遇到这个缓存问题, 这次使用它是频繁访问接口,就出现了一直不变的问题。觉得是缓存没有清除的问题。 网上搜了一些方案。最好的方案就是给url地址末尾给一个随机参数。用于让组件觉得是…...

深入理解Java虚拟机jvm-运行时数据区域(基于OpenJDK12)
运行时数据区域 运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途,以及创建和销毁的时间,有的…...
(OpenCV) 基础demo
文章目录 前言Demo图片录制播放人脸识别 END 前言 OpenCV - Open Computer Vision Library OpenCV的名声想必不用多说了。 本文介绍4个基础使用demo。分别为,显示图片,录制视频,播放视频和一个基于开源算法库的人脸识别小demo。 只要环境…...
using 的使用
作者: 苏丙榅 链接: https://subingwen.cn/cpp/using/ 在 C 中 using 用于声明命名空间,使用命名空间也可以防止命名冲突。在程序中声明了命名空间之后,就可以直接使用命名空间中的定义的类了。在 C11 中赋予了 using 新的功能,让C变得更年轻…...
Websocket、Socket、HTTP之间的关系
Websocket、Socket、HTTP之间的关系 ★ Websocket是什么?★ Websocket的原理★ websocket具有以下特点:★ webSocket可以用来做什么?★ websocket与socket区别:★ WebSocket与HTTP区别 ★ Websocket是什么? ● Websocket是HTML5下…...
hustoj LiveCD版系统在局域网虚拟机安装和配置
root权限 打开terminal命令行输入sudo su输入初始密码freeproblemsetmysql数据库的密码的位置,如何登陆数据库 数据库账号密码存放在两个配置文件中: /home/judge/etc/judge.conf/home/judge/src/web/include/db_info.inc.php 新版本中,快…...
读书-代码整洁之道10-14
类 类的三大特性:封装、继承、多态;类应该短小;单一权责原则认为,类或模块应有且只有一条加以修改的理由;当类丧失了内聚性,就拆分它;隔离修改 系统 构造和使用是非常不一样的过程。每个应用…...
UDP 广播/组播
广播UDP与单播UDP的区别就是IP地址不同,广播使用广播地址xxx.xxx.xxx.255,将消息发送到在同一广播网络上的每个主机,广播/组播只能用udp进行实现 函数:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_topt…...

高效创作助手:ChatGPT最新版实现批量撰写聚合文章的全新水平
随着人工智能技术的不断发展,ChatGPT最新版作为一款智能创作助手,实现了批量撰写聚合文章的全新水平。它能够在短时间内生成高质量的文章,极大地提高了创作效率。本文将从随机8-20个方面对ChatGPT最新版进行详细的阐述,让我们一起…...
Python中的包是什么,如何创建和使用包?
在Python中,包是一种将相关模块分组在一起的方式。它可以让我们更好地组织和重用代码。 一个Python包实际上是一个文件夹,其中包含该包的Python模块和其他资源文件(例如配置文件、数据文件等)。包的根目录通常包含一个名为__init…...

Spring Cloud Alibaba Seata(二)
目录 一、Seata 1、Seata-AT模式 1.1、具体案例 1.2、通过Seata的AT模式解决分布式事务 2、Seata-XA模式 3、Seata-TCC模式 4、Seata-SAGA模式 一、Seata 1、Seata-AT模式 概念:AT模式是一种无侵入的分布式事务解决方案,在 AT 模式下,…...
如何在 MySQL 中使用 COALESCE 函数
1. 简介 在 MySQL 中,COALESCE 函数可以用来返回参数列表中的第一个非空值。如果所有参数都为空,则返回 NULL。本文将介绍 COALESCE 函数的语法和用法,并通过示例演示其效果。 2. 语法 COALESCE 函数的语法如下所示: COALESCE(…...

华为云AI开发平台ModelArts
华为云ModelArts:重塑AI开发流程的“智能引擎”与“创新加速器”! 在人工智能浪潮席卷全球的2025年,企业拥抱AI的意愿空前高涨,但技术门槛高、流程复杂、资源投入巨大的现实,却让许多创新构想止步于实验室。数据科学家…...

业务系统对接大模型的基础方案:架构设计与关键步骤
业务系统对接大模型:架构设计与关键步骤 在当今数字化转型的浪潮中,大语言模型(LLM)已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中,不仅可以优化用户体验,还能为业务决策提供…...

使用VSCode开发Django指南
使用VSCode开发Django指南 一、概述 Django 是一个高级 Python 框架,专为快速、安全和可扩展的 Web 开发而设计。Django 包含对 URL 路由、页面模板和数据处理的丰富支持。 本文将创建一个简单的 Django 应用,其中包含三个使用通用基本模板的页面。在此…...
React Native 导航系统实战(React Navigation)
导航系统实战(React Navigation) React Navigation 是 React Native 应用中最常用的导航库之一,它提供了多种导航模式,如堆栈导航(Stack Navigator)、标签导航(Tab Navigator)和抽屉…...
React Native 开发环境搭建(全平台详解)
React Native 开发环境搭建(全平台详解) 在开始使用 React Native 开发移动应用之前,正确设置开发环境是至关重要的一步。本文将为你提供一份全面的指南,涵盖 macOS 和 Windows 平台的配置步骤,如何在 Android 和 iOS…...

工业安全零事故的智能守护者:一体化AI智能安防平台
前言: 通过AI视觉技术,为船厂提供全面的安全监控解决方案,涵盖交通违规检测、起重机轨道安全、非法入侵检测、盗窃防范、安全规范执行监控等多个方面,能够实现对应负责人反馈机制,并最终实现数据的统计报表。提升船厂…...

04-初识css
一、css样式引入 1.1.内部样式 <div style"width: 100px;"></div>1.2.外部样式 1.2.1.外部样式1 <style>.aa {width: 100px;} </style> <div class"aa"></div>1.2.2.外部样式2 <!-- rel内表面引入的是style样…...

【 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内存模型的介绍 内存模型主要分…...
MFE(微前端) Module Federation:Webpack.config.js文件中每个属性的含义解释
以Module Federation 插件详为例,Webpack.config.js它可能的配置和含义如下: 前言 Module Federation 的Webpack.config.js核心配置包括: name filename(定义应用标识) remotes(引用远程模块࿰…...

spring Security对RBAC及其ABAC的支持使用
RBAC (基于角色的访问控制) RBAC (Role-Based Access Control) 是 Spring Security 中最常用的权限模型,它将权限分配给角色,再将角色分配给用户。 RBAC 核心实现 1. 数据库设计 users roles permissions ------- ------…...