使用C++快速上手ProtoBuf (一)

文章目录

    • 课程目标
    • 一、初始ProtoBuf
      • 1. 序列化概念
      • 2.ProtoBuf是什么
      • 3.ProtoBuf的使⽤特点
    • 二、安装ProtoBuf
    • 三、教学思路
    • 四、快速上⼿
      • 步骤1:创建.proto文件
      • 步骤2:编译contacts.proto⽂件,⽣成C++⽂件
      • 步骤3:序列化与反序列化的使⽤
      • ⼩结ProtoBuf使⽤流程

课程目标

  • 认识ProtoBuf是什么,写demo了解ProtoBuf的使用流程。
  • 学习proto3语法,对最新的语法进行深度学习
  • 实战ProtoBuf,在网络传输过程中的使用。
  • 总结,对比多种序列化协议,分析ProtoBuf适合那些场景的使用。

一、初始ProtoBuf

在这里插入图片描述

1. 序列化概念

序列化和反序列化:

  • 序列化:把对象转换为字节序列的过程称为对象的序列化。
  • 反序列化:把字节序列恢复为对象的过程称为对象的反序列化。

什么情况下需要序列化:

  • 存储数据:当你想把的内存中的对象状态保存到⼀个⽂件中或者存到数据库中时。
  • ⽹络传输:⽹络直接传输数据,但是⽆法直接传输对象,所以要在传输前序列化,传输完成后反序列化成对象。例如我们之前学习过socket编程中发送与接收数据。

如何实现序列化:

  • xml、json、ProtoBuf。

2.ProtoBuf是什么

将结构化数据进行序列化的一种方式。

文档解释:

  • ProtocolBuffers是Google的⼀种语⾔⽆关、平台⽆关、可扩展的序列化结构数据的⽅法,它可⽤于(数据)通信协议、数据存储等。
  • ProtocolBuffers类⽐于XML,是⼀种灵活,⾼效,⾃动化机制的结构数据序列化⽅法,但是⽐XML更⼩、更快、更为简单。
  • 你可以定义数据的结构,然后使⽤特殊⽣成的源代码轻松的在各种数据流中使⽤各种语⾔进⾏编写和读取结构数据。你甚⾄可以更新数据结构,⽽不破坏由旧数据结构编译的已部署程序。

简单来讲,ProtoBuf(全称为ProtocolBuffer)是让结构数据序列化的⽅法,其具有以下特点

  • 语言⾔⽆关、平台⽆关:即ProtoBuf⽀持Java、C++、Python等多种语⾔,⽀持多个平台例如linux或者windows。
  • ⾼效:即⽐XML更⼩、更快、更为简单。-- 序列化的数据更小,数据小意味着传输更快,操作更简单。
  • 扩展性、兼容性好:你可以更新数据结构,⽽不影响和破坏原有的旧程序。-- 序列化组件的共同特点

3.ProtoBuf的使⽤特点

程序员手动编写网络协议,最原始的方式:

  • 网络通信就是端对端通信,由请求协议,响应协议,协议的反序列化和序列方法构成。
  • 请求协议和响应协议就是数据结构。
  • 网络传输的是二进制序列,序列化最原始的方法就是将数据结构以二进制的形式转换为序列。
  • 反序列化最原始的方法就是使用数据结构进行反序列化。
  • 最原始的方式的优点:
    • 操作简单
  • 最原始的方式的缺点:
    • 程序员需要手动编写序列化和反序列化方法,例如编写时需要解决粘包问题。
    • 没有扩展性,兼容性:一旦一端数据结构更新,影响和破坏旧程序。

ProtoBuf的使⽤特点:

  • 如下图,
  • 一开始,程序员定制协议时
  • 对于程序员来说,属性字段必须写的,后两项开发起来繁琐耗时。
    在这里插入图片描述
  • 如下图,
  • 有了ProtoBuf我们定义的是message而不是class。
  • 程序员只需要定义一系列的属性字段。
  • 处理字段的方法和处理类的方法由Protoc编译器生成。

在这里插入图片描述

  • 如下图,
  • 编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
  • 使⽤protoc编译器编译.proto⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
  • 依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对.proto⽂件中定义的字段进⾏设置和获取,和对message对象进⾏序列化和反序列化。
    在这里插入图片描述

总的来说:ProtoBuf是需要依赖通过编译⽣成的头⽂件和源⽂件来使⽤的。有了这种代码⽣成机制,开发⼈员再也不⽤吭哧吭哧地编写那些协议解析的代码了(⼲这种活是典型的吃⼒不讨好)。

二、安装ProtoBuf

https://editor.csdn.net/md/?articleId=130808175

三、教学思路

对ProtoBuf的完整学习,将使⽤项⽬推进的⽅式完成教学:即对于ProtoBuf知识内容的展开,会对⼀个项⽬进⾏⼀个版本⼀个版本的升级去讲解ProtoBuf对应的知识点。

在后续的内容中,将会实现⼀个通讯录项⽬。对通讯录⼤家应该都不陌⽣,⼀般,通讯录中包含了⼀批的联系⼈,每个联系⼈⼜会有很多的属性,例如姓名、电话等等。

随着对通讯录项⽬的升级,我们对ProtoBuf的学习与使⽤就越深⼊

四、快速上⼿

在快速上⼿中,会编写第⼀版本的通讯录1.0。在通讯录1.0版本中,将实现:

  • 对⼀个联系⼈的信息使⽤PB进⾏序列化,并将结果打印出来。
  • 对序列化后的内容使⽤PB进⾏反序列,解析出联系⼈信息并打印出来。
  • 联系⼈包含以下信息:姓名、年龄。
    通过通讯录1.0,我们便能了解使⽤ProtoBuf初步要掌握的内容,以及体验到ProtoBuf的完整使⽤流程。

步骤1:创建.proto文件

⽂件规范

  • 创建.proto⽂件时,⽂件命名应该使⽤全⼩写字⺟命名,多个字⺟之间⽤ _ 连接。例如:
    lower_snake_case.proto
  • 书写.proto⽂件代码时,应使⽤2个空格的缩进。

我们为通讯录1.0新建⽂件: contacts.proto

添加注释

  • 向⽂件添加注释,可使⽤ // 或者 /* ... */

指定proto3语法

  • ProtocolBuffers语⾔版本3,简称proto3,是.proto⽂件最新的语法版本。proto3简化了ProtocolBuffers语⾔,既易于使⽤,⼜可以在更⼴泛的编程语⾔中使⽤。它允许你使⽤Java,C++,Python等多种语⾔⽣成protocolbuffer代码。
  • 在.proto⽂件中,要使⽤ syntax = "proto3"; 来指定⽂件语法为proto3,并且必须写在除去注释内容的第⼀⾏。如果没有指定,编译器会使⽤proto2语法。在通讯录1.0的contacts.proto⽂件中,可以为⽂件指定proto3语法,内容如下:
syntax = "proto3";

package声明符

  • package是⼀个可选的声明符,能表⽰.proto⽂件的命名空间,在项⽬中要有唯⼀性。它的作⽤是为了避免我们定义的消息出现冲突。
  • 在通讯录1.0的contacts.proto⽂件中,可以声明其命名空间,内容如下:
syntax = "proto3";
package contacts;

定义消息(message)

消息(message):要定义的结构化对象,我们可以给这个结构化对象中定义其对应的属性内容。这⾥再提⼀下为什么要定义消息?

  • 在⽹络传输中,我们需要为传输双⽅定制协议。定制协议说⽩了就是定义结构体或者结构化数据,⽐如,tcp,udp报⽂就是结构化的。
  • 再⽐如将数据持久化存储到数据库时,会将⼀系列元数据统⼀⽤对象组织起来,再进⾏存储。

所以ProtoBuf就是以message的⽅式来⽀持我们定制协议字段,后期帮助我们形成类和⽅法来使⽤。在通讯录1.0中我们就需要为联系⼈定义⼀message。

.proto⽂件中定义⼀个消息类型的格式为:

message 消息类型名{

}

注意事项:`消息类型命名规范:使⽤驼峰命名法,⾸字⺟⼤写`

为contacts.proto(通讯录1.0)新增联系⼈message,内容如下:

syntax = "proto3";
package contacts;

// 定义联系人消息
message PeopleInfo{

}

定义消息字段

在message中我们可以定义其属性字段,字段定义格式为:字段类型字段名=字段唯⼀编号;

  • 字段名称命名规范:全⼩写字⺟,多个字⺟之间⽤_连接。
  • 字段类型分为:标量数据类型和特殊类型(包括枚举、其他消息类型等)。
  • 字段唯⼀编号:⽤来标识字段,⼀旦开始使⽤就不能够再改变。

该表格展⽰了定义于消息体中的标量数据类型,以及编译.proto⽂件之后⾃动⽣成的类中与之对应的字段类型。在这⾥展⽰了与C++语⾔对应的类型。

.proto TypeNotesC++ Type
doubledouble
floatfloat
int32使⽤变⻓编码[1]。负数的编码效率较低⸺——若字段可能为负值,应使⽤sint32代替。int32
int64使⽤变⻓编码[1]。负数的编码效率较低⸺——若字段可能为负值,应使⽤sint64代替。int64
uint32使⽤变⻓编码[1]。uint32
uint64使⽤变⻓编码[1]。uint64
sint32使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的int32类型。int32
sint64使⽤变⻓编码[1]。符号整型。负值的编码效率⾼于常规的int64类型。int64
fixed32定⻓4字节。若值常⼤于2^28则会⽐uint32更⾼效。uint32
fixed64定⻓8字节。若值常⼤于2^56则会⽐uint64更⾼效。uint64
sfixed32定⻓4字节。int32
sfixed64定⻓8字节。int64
boolbool
string包含UTF-8和ASCII编码的字符串,⻓度不能超过2^32。string
bytes可包含任意的字节序列但⻓度不能超过2^32。string

详细解释如上表格:

名字解释说明
使⽤变⻓编码[1]经过protobuf编码后,原本4字节或8字节的数可能会被变为其他字节数。

更新contacts.proto(通讯录1.0),新增姓名、年龄字段:

// 首行:语法指定行
syntax = "proto3";
package contacts;

// 定义联系人message
message PeopleInfo {
  string name = 1;  // 姓名
  int32 age = 2;    // 年龄  
}

在这⾥还要特别讲解⼀下字段唯⼀编号的范围:

1 ~ 536,870,911(2^29-1),其中 19000 ~ 19999不可⽤。

19000~19999不可⽤是因为:在Protobuf协议的实现中,对这些数进⾏了预留。如果⾮要在.proto⽂件中使⽤这些预留标识号,例如将name字段的编号设置为19000,编译时就会报警:

// 消息中定义了如下编号,代码会告警:
// Field numbers 19,000 through 19,999 are reserved for the protobuf implementation

string name = 19000;

值得⼀提的是,范围为1~15的字段编号需要⼀个字节进⾏编码,16 ~ 2047内的数字需要两个字节进⾏编码。编码后的字节不仅只包含了编号,还包含了字段类型。所以1~15要⽤来标记出现⾮常频繁的字段,要为将来有可能添加的、频繁出现的字段预留⼀些出来。

步骤2:编译contacts.proto⽂件,⽣成C++⽂件

编译命令
编译命令⾏格式为:

protoc [--proto_path=IMPORT_PATH] --cpp_out=DST_DIR path/to/file.proto
  • protoc :是 Protocol Buffer 提供的命令⾏编译⼯具。
  • --proto_path :指定 被编译的.proto⽂件所在⽬录,可多次指定。可简写成 -I IMPORT_PATH
  • 如不指定该参数,则在当前⽬录进⾏搜索。
  • 当某个.proto ⽂件 import 其他.proto ⽂件时,或需要编译的 .proto ⽂件不在当前⽬录下,这时就要⽤-I来指定搜索⽬录。 --cpp_out= 指编译后的⽂件为 C++ ⽂件。
  • OUT_DIR: 指编译后⽣成⽂件的⽬标路径。 path/to/file.proto 要编译的.proto⽂件

编译contacts.proto⽂件命令如下:

# 第一种方式,在当前contacts.proto文件目录下执行。
protoc --cpp_out=. contacts.proto
# 第二种方式,在contacts.proto文件父目录下执行。
protoc -I ./fast_start --cpp_out=./fast_start/ contacts.proto

编译contacts.proto⽂件后会⽣成什么

编译contacts.proto⽂件后,会⽣成所选择语⾔的代码,我们选择的是C++,所以编译后⽣成了两个⽂件: contacts.pb.h contacts.pb.cc

对于编译⽣成的C++代码,包含了以下内容:

  • 对于每个message,都会⽣成⼀个对应的消息类。
  • 在消息类中,编译器为每个字段提供了获取和设置⽅法,以及⼀下其他能够操作字段的⽅法。
  • 编辑器会针对于每个 .proto ⽂件⽣成 .h 和 .cc ⽂件,分别⽤来存放类的声明与类的实现。

contacts.pb.h部分代码展⽰

class PeopleInfo final : public ::PROTOBUF_NAMESPACE_ID::Message {
public:
	using ::PROTOBUF_NAMESPACE_ID::Message::CopyFrom;
	void CopyFrom(const PeopleInfo& from);
	using ::PROTOBUF_NAMESPACE_ID::Message::MergeFrom;
	void MergeFrom( const PeopleInfo& from) {
	PeopleInfo::MergeImpl(*this, from);
	}
	static ::PROTOBUF_NAMESPACE_ID::StringPiece FullMessageName() {
	return "PeopleInfo";
	}
	// string name = 1;
	void clear_name();
	const std::string& name() const;
	template <typename ArgT0 = const std::string&, typename... ArgT>
	void set_name(ArgT0&& arg0, ArgT... args);
	std::string* mutable_name();
	PROTOBUF_NODISCARD std::string* release_name();
	void set_allocated_name(std::string* name);
	// int32 age = 2;
	void clear_age();
	int32_t age() const;
	void set_age(int32_t value);
};

述的例⼦中:

  • 每个字段都有设置和获取的⽅法,getter的名称与⼩写字段完全相同,setter⽅法以set_开头。
  • 每个字段都有⼀个clear_⽅法,可以将字段重新设置回empty状态。

contacts.pb.cc中的代码就是对类声明⽅法的⼀些实现,在这⾥就不展开了。

到这⾥有同学可能就有疑惑了,那之前提到的序列化和反序列化⽅法在哪⾥呢?在消息类的⽗类MessageLite 中,提供了读写消息实例的⽅法,包括序列化⽅法和反序列化⽅法。

class MessageLite {
public:
	//序列化:
	bool SerializeToOstream(ostream* output) const; // 将序列化后数据写⼊⽂件流
	bool SerializeToArray(void *data, int size) const;
	bool SerializeToString(string* output) const;
	//反序列化:
	bool ParseFromIstream(istream* input); // 从流中读取数据,再进⾏反序列化动作
	bool ParseFromArray(const void* data, int size);
	bool ParseFromString(const string& data);
};

注意:

  • 序列化的结果为⼆进制字节序列,⽽⾮⽂本格式。
  • 以上三种序列化的⽅法没有本质上的区别,只是序列化后输出的格式不同,可供不同的应⽤场景使⽤。
  • 序列化的API函数均为const成员函数,因为序列化不会改变类对象的内容,⽽是将序列化的结果保存到函数⼊参指定的地址中。
  • 详细messageAPI可以参⻅完整列表。

步骤3:序列化与反序列化的使⽤

创建⼀个测试⽂件test.cc,⽅法中我们实现:

  • 对⼀个联系⼈的信息使⽤PB进⾏序列化,并将结果打印出来。
  • 对序列化后的内容使⽤PB进⾏反序列,解析出联系⼈信息并打印出来。

test.cc 文件

#include <iostream> 
#include "contacts.pb.h"
 

int main() { 
    std::string people_str; 

    {
        // 对⼀个联系⼈的信息使⽤ PB 进⾏序列化,并将结果打印出来。
        contacts::PeopleInfo people; 
        people.set_name("张珊"); 
        people.set_age(20); 
        if (!people.SerializeToString(&people_str)) { 
            std::cerr << "序列化联系⼈失败!" << std::endl; 
            return -1;
        }
        std::cout << "序列化成功,结果:" << people_str << std::endl; 
    }
    
    {
        // 对序列化后的内容使⽤ PB 进⾏反序列,解析出联系⼈信息并打印出来。
        contacts::PeopleInfo people; 
        if (!people.ParseFromString(people_str)) { 
            std::cerr << "反序列化联系⼈失败!" << std::endl; 
            return -1;
        } 
        std::cout << "反序列化成功!" << std::endl
                  << "姓名: " << people.name() << std::endl
                  << "年龄: " << people.age() << std::endl;
    }

    return 0;
} 

contacts.proto 文件

// 首行:语法指定行
syntax = "proto3";
package contacts;

// 定义联系人message
message PeopleInfo {
  string name = 1;  // 姓名
  int32 age = 2;    // 年龄  
}

2.执行一下语句


# 1. 编译 contacts文件 
g++ -o testPB test.cc contacts.pb.cc -lprotobuf -std=c++11
# 2. 编译 test.cc, 生成执行程序 testPB
protoc --cpp_out=. contacts.proto
  • -lprotobuf:依赖的库, 必加,不然会有链接错误。
  • std=c++11: 使用c++语法。

执⾏ TestPB ,可以看⻅people经过序列化和反序列化后的结果:

./testPB

# 打印如下说明成功了

[YYK@VM-8-7-centos fast_start]$ ./testPB
序列化成功,结果:
张珊
反序列化成功!
姓名: 张珊
年龄: 20

由于ProtoBuf是把联系⼈对象序列化成了⼆进制序列,这⾥⽤string来作为接收⼆进制序列的容器。
所以在终端打印的时候会有换⾏等⼀些乱码显⽰。
所以相对于xml和JSON来说,因为被编码成⼆进制,破解成本增⼤,ProtoBuf编码是相对安全的。

⼩结ProtoBuf使⽤流程

在这里插入图片描述

  • 1.编写.proto⽂件,⽬的是为了定义结构对象(message)及属性内容。
  • 2.使⽤protoc编译器编译.proto⽂件,⽣成⼀系列接⼝代码,存放在新⽣成头⽂件和源⽂件中。
  • 3.依赖⽣成的接⼝,将编译⽣成的头⽂件包含进我们的代码中,实现对.proto⽂件中定义的字段进⾏ 设置和获取,和对message对象进⾏序列化和反序列化。
  • 总的来说:ProtoBuf是需要依赖通过编译⽣成的头⽂件和源⽂件来使⽤的。有了这种代码⽣成机制,开发⼈员再也不⽤吭哧吭哧地编写那些协议解析的代码了(⼲这种活是典型的吃⼒不讨好)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

人员与叉车防撞预警方案

叉车是仓库重吨位运输设备&#xff0c;在工厂、港口、码头、物流企业等有着广泛的使用。然而&#xff0c;叉车事故频繁发生已经引起人们的广泛关注。多数叉车因为前方货物遮挡的视线盲区多&#xff0c;极容易发生事故&#xff0c;例如撞伤人或货架导致货物倒塌伤人&#xff0c;…

Promise面试题

Promise面试题&#xff0c;带你搞懂同步异步执行顺序 前置知识面试题面试题一面试题二面试题三面试题四 分析面试题一分析面试题二分析面试题三分析面试题四分析 前置知识 Promise中的then方法 then&#xff1a;指定用于得到成功value的成功回调和用于得到失败reason的失败回调…

C语言深度解析--数组

目录 一维数组的创建与初始化 一维数组的创建&#xff1a; 一维数组的初始化&#xff1a; 一维数组的使用&#xff1a; 一维数组在内存中的存储&#xff1a; 二维数组的创建与初始化 二维数组的创建&#xff1a; 二维数组的初始化&#xff1a; 二维数组的使用&#xf…

字节跳动开源其云原生数据仓库 ByConity

动手点关注 干货不迷路 ‍ ‍项目简介 ByConity 是字节跳动开源的云原生数据仓库&#xff0c;它采用计算-存储分离的架构&#xff0c;支持多个关键功能特性&#xff0c;如计算存储分离、弹性扩缩容、租户资源隔离和数据读写的强一致性等。通过利用主流的 OLAP 引擎优化&#xf…

Hive---拉链表设计与实现

1 数据同步问题 Hive在实际工作中主要用于构建离线数据仓库&#xff0c;定期的从各种数据源中同步采集数据到Hive中&#xff0c;经过分层转换提供数据应用。比如每天需要从MySQL中同步最新的订单信息、用户信息、店铺信息等到数据仓库中&#xff0c;进行订单分析、用户分析。 …

使用Gradle7.6.1 + SpringBoot3.0.2 + java17创建微服务项目(学习)

这是一个大胆的决定 这里是导航 技术栈开发工具一、创建gradle父子项目&#xff08;deity&#xff09;1.0 简单流程示意1.1、IDEA中主要图示1.1.1 项目结构图1.1.2 IDEA中 Gradle配置 1.2、deity父项目build.gradle文件1.3、deity父项目settings.gradle文件1.4、子项目build.g…

django ORM框架 第四章 聚合函数

上一章&#xff1a;django ORM框架 第三章 关联表的数据创建与查询_做测试的喵酱的博客-CSDN博客 一、聚合函数类型&#xff1a; from django.db.models import Q, Count, Avg, Max, Min 班级表&#xff1a; 学生信息表&#xff1a; 1.1 Count 模版&#xff1a; QuerySet.…

3 手工推导Neural Networ

线性模型假设的问题 如上图&#xff0c;对非线性类边界的数据进行分类 一个解决方案是将数据映射到更高维的空间&#xff0c;就变成线性可分的了。 ϕ \phi ϕ 是一个映射函数&#xff0c;将x从一个低维空间映射到高维空间。 ϕ \phi ϕ 可不可以是一个线性函数&#xff1f; …

音视频源码调试前准备vs2019+qt5.15.2搭建可调试环境

安装vs2019qt,并且在windows环境上安装ffmpeg&#xff0c;尝试使用qtcdb进行调试&#xff0c;尝试使用vs2019加载qt的程序。 安装VS20195.12.2qt环境&#xff0c;并进行测试。 1&#xff1a;安装Visual Studio 2019, a.从官网下载&#xff0c;或者vs2019社区版本下载地址 ht…

Cloud Studio 内核升级之触手可及

前言 Cloud Studio是基于浏览器的集成式开发环境&#xff08;IDE&#xff09;&#xff0c;为开发者提供了一个永不间断的云端工作站。用户在使用 Cloud Studio 时无需安装&#xff0c;随时随地打开浏览器就能使用。云端开发体验与本地几乎一样&#xff0c;上手门槛更低&#x…

前端axios fetch 解决接口请求响应数据返回快慢不均导致的数据错误问题

引言 搜索功能&#xff0c;我想很多业务都会涉及&#xff0c;这个功能的特点是&#xff1a; 用户可以在输入框中输入一个关键字&#xff0c;然后在一个列表中显示该关键字对应的数据&#xff1b;输入框是可以随时修改/删除全部或部分关键字的&#xff1b;如果是实时搜索&…

查看电脑的BIOS版本的五种方法

查看主板BIOS版本的五种方法 概述1. 在 BIOS 中查看2. 使用 DirectX 诊断工具3. 使用 CPU-Z 中查看4. 在 CMD 中查看&#xff08;一&#xff09;5. 在 CMD 中查看 &#xff08;二&#xff09;结束语 概述 BIOS是 Basic Input Output System 的缩略词&#xff0c;直译就是 **基…

【数据结构与算法】- 周测四

课程链接: 清华大学驭风计划 代码仓库&#xff1a;Victor94-king/MachineLearning: MachineLearning basic introduction (github.com) 驭风计划是由清华大学老师教授的&#xff0c;其分为四门课&#xff0c;包括: 机器学习(张敏教授) &#xff0c; 深度学习(胡晓林教授), 计算…

InnoDB数据页结构

什么是页&#xff1f;什么是数据页&#xff1f; 页是InnoDB管理存储空间的基本单元&#xff0c;一个页的大小一般是16k。 InnoDB有许多不同的页&#xff0c;有存放表空间头部信息的页&#xff0c;INODE信息的页&#xff0c;当然还有存放我们记录信息的页&#xff0c;这个页叫…

车载以太网 - SomeIP - 协议用例 - Messages_01

目录 Service Discovery Messages 1、验证Instance ID为0xFFFF时DUT需要返回该Serveice ID包含的所有Instance ID

「实验记录」MIT 6.824 Raft Lab2B Log Replication

#Lab2B - Log Replication I. SourceII. My CodeIII. MotivationIV. SolutionS1 - leader上任即初始化S2 - leader发送AppendEntriesS3 - follower接收AppendEntriesS4 - leader收到AppendEntries 回信S5 - candidate选举限制S6 - defs.go约定俗成和实现Start() V. Result I. S…

LeetCode 栈和队列OJ题目分享

目录 有效的括号&#xff08;括号匹配&#xff09;用栈实现队列用队列实现栈设计循环队列 有效的括号&#xff08;括号匹配&#xff09; 链接: link 题目描述&#xff1a; 题目思路&#xff1a; 1、如果是左括号“&#xff08; { [ ”就入栈 2、如果是右括号“&#xff09; }…

程序员:面试造火箭,入职拧螺丝?太难了···

刚开始工作的时候&#xff0c;我也想不通这个问题&#xff0c;甚至很鄙视这种现象。后面当了面试官&#xff0c;做到了公司中层管理&#xff0c;也会站在公司以及行业角度去重新思考这个问题。 为什么这种现象会越来越普遍呢&#xff1f;尤其在 IT 行业愈加明显。 面试看的是…

Packet Tracer – VLAN 实施故障排除场景 2

Packet Tracer – VLAN 实施故障排除场景 2 拓扑图 地址分配表 设备 接口 IPv4 地址 子网掩码 默认网关 S1 VLAN 56 192.168.56.11 255.255.255.0 不适用 S2 VLAN 56 192.168.56.12 255.255.255.0 不适用 S3 VLAN 56 192.168.56.13 255.255.255.0 不适用 P…

头歌计算机组成原理实验—运算器设计(11)第11关:MIPS运算器设计

第11关&#xff1a;MIPS运算器设计 实验目的 学生理解算术逻辑运算单元&#xff08;ALU&#xff09;的基本构成&#xff0c;掌握 Logisim 中各种运算组件的使用方法&#xff0c;熟悉多路选择器的使用&#xff0c;能利用前述实验完成的32位加法器、 Logisim 中的运算组件构造指…