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(…...
Python爬虫之Scrapy框架系列(22)——初识分布式爬虫scrapy_redis
目录: 分布式爬虫(Scrapy\_redis):1.简单介绍:2.Scrapy_redis的安装:分布式爬虫(Scrapy_redis): 官方文档:https://scrapy-redis.readthedocs.io/en/stable/1.简单介绍: scrapy_redis是一个基于Redis的Scrapy组件,用于scrapy项目的分布式部署和开发。 特点: 分布…...
ChatGPT的前世今生
原文首发于博客文章ChatGPT发展概览 ChatGPT 是OpenAI开发的人工智能聊天机器人程序,于2022年11月推出。该程序使用基于 GPT-3.5、GPT-4 架构的大语言模型并以强化学习训练。ChatGPT目前仍以文字方式交互,而除了可以用人类自然对话方式来交互,…...
WireShark常用协议抓包与原理分析
1.ARP协议(地址解析协议) nmap 发现网关nmap -sn 192.168.133.2wireshark 抓请求包和响应包 arp请求包内容 arp响应包内容 总结:请求包包含包类型(request),源IP地址,源MAC地址,目标IP地址,目标MAC地址(未知,此处为全0);响应包包含包类型(reply),源IP地址,源…...
Mysql数据库操作总结
文章目录 1. DDL(Data Definition Language - 数据定义语言)1.1 数据库1.2 数据表(创建查询删除)1.3 数据表(修改) 2. 数据类型2.1 数值2.2 字符2.3 日期 3. 字段约束3.1 约束3.2 主键约束修改3.3 主键自增 联合主键 4. DML(Data Manipulation Language - 数据操作语言)4.1 添…...
在 ZBrush、Substance 3D Painter 和 UE5 中创作警探角色(P2)
大家好,下篇分享咱们继续来说警探角色的重新拓扑、UV、材质贴图和渲染处理。 重新拓扑/UV 这是对我来说最不有趣的部分——重新拓扑。它显然是实时角色中非常重要的一部分,不容忽视,因为它会影响大量的 UV、绑定和后期渲染,这里…...
如何在大规模服务中迁移缓存
当您启动初始服务时,通常会过度设计以考虑大量流量。但是,当您的服务达到爆炸式增长阶段,或者如果您的服务请求和处理大量流量时,您将需要重新考虑您的架构以适应它。糟糕的系统设计导致难以扩展或无法满足处理大量流量的需求&…...
【GPT LLM】跟着论文学习gpt
GPT1开山之作:Improving language understanding by generative pre-training 本文提出了gpt1,即使用无标签的数据对模型先进行训练,让模型学习能够适应各个任务的通用表示;后使用小部分 task-aware的数据对模型进行微调ÿ…...
【玩转Docker小鲸鱼叭】Docker容器常用命令大全
在 Docker 核心概念理解 一文中,我们知道 Docker容器 其实就是一个轻量级的沙盒,应用运行在不同的容器中从而实现隔离效果。容器的创建和运行是以镜像为基础的,容器可以被创建、销毁、启动和停止等。本文将介绍下容器的这些常用操作命令。 1、…...
专项练习11
目录 一、选择题 1、执行下列选项的程序,输出结果不是Window对象的是() 2、以下哪些代码执行后 i 的值为10: 二、编程题 1、判断 val1 和 val2 是否完全等同 2、统计字符串中每个字符的出现频率,返回一个 Object&…...
ASP.NET+SQL通用作业批改系统设计(源代码+论文)
随着网络高速地融入当今现代人的生活,学校对网络技术的应用也在不断地提高。学校的教学任务十分复杂,工作也很繁琐,在教学任务中,作业的批改也是一个很重要的环节。为了提高老师工作效率,减轻教师的工作强度,提高作业批改的灵活性,《通用作业批改系统》的诞生可以说是事在…...
wordpress怎么把分类弄成导航/怎么给自己的网站设置关键词
每一个项目都有其需求,这也是做这个项目的目的和背景。否则便构成不了这个项目,这个项目也不会有意义。同样的我们所做的项目要有其市场价值、社会意义,这样我们的项目才具有完整性。 接下来的介绍我希望大家能够了解到《品优购》是怎样的一个…...
广告设计图网站/百度浏览器下载
那么不知道你 对于Spring支持的常用数据库事务传播属性和隔离级别 了解得怎么样呢?要不要一起复习复习了:grin: 很喜欢一句话:“八小时内谋生活,八小时外谋发展” 共勉 :woman::computer: 描述 :进来先看看风景啦,…...
上海专业做网站价格/做网站的网络公司
随着计算机化系统在制药企业的广泛应用,尤其是GMP附录《计算机化系统》的颁布和实施,制药企业的计算机化系统的验证被提上日程,日益受到重视。然而,制药企业的计算机化系统验证既需要制药和计算机专业知识,又需要IT知识…...
可以做动漫的网站有哪些/无锡优化网站排名
近日,北京大学信息科学技术学院软件所博士生导师、中国电子学会图论与系统优化专业委员会副主任许进教授在千佛山校区教学三楼3507教室作了题为“计算智能及探真计算机前沿”的学术报告。管理科学与工程学院院长刘希玉担任主持,学院全体领导、教师及研究…...
做的网站如何发布会/seo的理解
后面请求网络用的是免费的 Bmob ,这里面生成实体类推荐用 json_serializable 。 进阶失败了。 添加依赖: environment:sdk: ">2.15.0-116.0.dev <3.0.0"dependencies:flutter:sdk: fluttercupertino_icons: ^1.0.2json_annotation: ^4.…...
合肥营销网站建设联系方式/杭州网站优化搜索
题目链接:http://poj.org/problem?id3420 题目思路:状态压缩矩阵二分幂,一般解法是构造16*16的转移矩阵,也有一种是去掉无用状态,只剩下5个有用状态的5*5转移矩阵。还有一种方法是推组合学公式。 #include<stdio…...