Protocol Buffer 序列化

Protobuf使用

目录

  • proto3的更新
  • 定义协议格式
  • 编译protobuf
  • protobuf_API
    • 枚举和嵌套类
    • 标准消息方法
    • 解析和序列化
  • 写一条消息
  • 阅读消息
  • 编译
  • Protobuf扩展
  • 优化
  • 高级用法

proto3的更新

  • 在第一行非空白非注释行,必须写:
syntax  = "proto3";
  • 字段规则移除了 required,并把 optional 改名为 singular;
    proto2required 也是不推荐使用的。proto3 直接从语法层面上移除了 required规则。其实可以做的更彻底,把所有字段规则描述都撤销,原来的repeated 改为在类型或字段名后加一对中括号。这样是不是更简洁?

  • repeated字段默认采用 packed 编码;
    proto2 中,需要明确使用 [packed=true] 来为字段指定比较紧凑的 packed 编码方式。

  • 移除了default 选项;
    proto2 中,可以使用 default 选项为某一字段指定默认值。在 proto3 中,字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。
    在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。
    但这样就无法区分某字段是根本没赋值,还是赋值了默认值。这在 proto3 中问题不大,但在 proto2 中会有问题。
    比如,在更新协议的时候使用 default 选项为某个字段指定了一个与原来不同的默认值,旧代码获取到的该字段的值会与新代码不一样。

  • 枚举类型的第一个字段必须为 0 ;

  • 移除了对分组的支持;
    分组的功能完全可以用消息嵌套的方式来实现,并且更清晰。在 proto2 中已经把分组语法标注为『过期』了。这次也算清理垃圾了。

  • 移除了对扩展的支持,新增了 Any 类型;
    Any 类型是用来替代 proto2 中的扩展的。目前还在开发中。
    proto2 中的扩展特性很像 Swift 语言中的扩展。理解起来有点困难,使用起来更是会带来不少混乱。
    相比之下,proto3 中新增的 Any 类型有点像 C/C++ 中的 void* ,好理解,使用起来逻辑也更清晰。

  • 增加了 JSON 映射特性;
    语言的活力来自于与时俱进。当前,JSON 的流行有其充分的理由。很多『现代化』的语言都内置了对 JSON 的支持,比如 GoPHP 等。而 C++ 这种看似包罗万象的学院派语言,因循守旧、故步自封,以致于现出了式微的苗头。

  • map支持

map<key_type, value_type> map_field = N;
example:
map<string, Project> projects = 3;

定义协议格式

.proto文件中的定义很简单:为要序列化的每个数据结构添加消息,然后为消息中的每个字段指定名称和类型。这是.proto定义您的消息的文件addressbook.proto

(好的.proto文件命名风格是:packagename.messagename.proto)

syntax = "proto3";package tutorial;message Person {string name = 1;int32 id = 2;string email = 3;enum PhoneType {MOBILE = 0;HOME = 1;WORK = 2;}message PhoneNumber {string number = 1;PhoneType type = 2;}repeated PhoneNumber phones = 4;
}message AddressBook {repeated Person people = 1;
}

.proto文件以包声明开头,这有助于防止不同项目之间的命名冲突。在C++中,生成的类将放在与包名匹配的命名空间中。

每个元素上的“= 1”,“= 2”标记标识该字段在二进制编码中使用的唯一“标记”。标签号1-15需要少于一个字节来编码而不是更高的数字,因此作为优化,您可以决定将这些标签用于常用或重复的元素,将标签16和更高版本留给不太常用的可选元素。重复字段中的每个元素都需要重新编码标记号,因此重复字段特别适合此优化。

必须使用以下修饰符之一注释每个字段:

  • required(proto3中移除):必须提供该字段的值,否则该消息将被视为“未初始化”。如果libprotobuf在调试模式下编译,则序列化未初始化的消息将导致断言失败。在优化的构建中,将跳过检查并始终写入消息。但是,解析未初始化的消息将始终失败(通过false从解析方法返回)。除此之外,必填字段的行为与可选字段完全相同。

  • optional(proto3中为singular):该字段可能已设置,也可能未设置。如果未设置可选字段值,则使用默认值。对于简单类型,您可以指定自己的默认值,就像我们type在示例中为电话号码所做的那样。否则,使用系统默认值:数字类型为0,字符串为空字符串,boolsfalse。对于嵌入式消息,默认值始终是消息的“默认实例”或“原型”,其中没有设置其字段。调用访问器以获取尚未显式设置的可选(或必需)字段的值始终返回该字段的默认值。

  • repeated(proto3默认采用 packed 编码):该字段可以重复任意次数(包括零)。重复值的顺序将保留在协议缓冲区中。将重复字段视为动态大小的数组。

  • proto3 中移除了default选项:字段的默认值只能根据字段类型由系统决定。也就是说,默认值全部是约定好的,而不再提供指定默认值的语法。在字段被设置为默认值的时候,该字段不会被序列化。这样可以节省空间,提高效率。

编译protobuf

现在运行编译器,指定源目录(应用程序的源代码所在的位置 - 如果不提​​供值,则使用当前目录),目标目录(您希望生成的代码在哪里;通常相同$SRC_DIR) ,以及你的道路.proto。:

protoc -I = $ SRC_DIR --cpp_out = $ DST_DIR $ SRC_DIR / addressbook.proto

这里都生成到当前目录,输入

protoc -I=. --cpp_out=. ./addressbook.proto
protoc --cpp_out=. addressbook.proto // 这种也可以

因为您需要C++类,所以使用该--cpp_out选项 - 为其他支持的语言提供了类似的选项。

这将在指定的目标目录中生成以下文件:

  • addressbook.pb.h,标头声明您生成的类。
  • addressbook.pb.cc,其中包含您的类的实现。

protobuf_API

addressbook.pb.h中,可以看到指定的每条消息都有一个类addressbook.proto。对于Person类,可以看到编译器已为每个字段生成了访问器。 例如,对于名称,ID,电子邮件和电话字段,有以下方法:

// name
inline bool has_name() const;
inline void clear_name();
inline const ::std::string& name() const;
inline void set_name(const ::std::string& value);
inline void set_name(const char* value);
inline ::std::string* mutable_name();// id
inline bool has_id() const;
inline void clear_id();
inline int32_t id() const;
inline void set_id(int32_t value);// email
inline bool has_email() const;
inline void clear_email();
inline const ::std::string& email() const;
inline void set_email(const ::std::string& value);
inline void set_email(const char* value);
inline ::std::string* mutable_email();// phones
inline int phones_size() const;
inline void clear_phones();
inline const ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >& phones() const;
inline ::google::protobuf::RepeatedPtrField< ::tutorial::Person_PhoneNumber >* mutable_phones();
inline const ::tutorial::Person_PhoneNumber& phones(int index) const;
inline ::tutorial::Person_PhoneNumber* mutable_phones(int index);
inline ::tutorial::Person_PhoneNumber* add_phones();

对于字符串 : 一个mutable_让你获得指向字符串的直接指针的getter,以及一个额外的setter。请注意,mutable_email()即使email尚未设置,您也可以进行呼叫; 它将自动初始化为空字符串。如果你在这个例子中有一个单数的消息字段,它也有一个mutable_方法但不是一个set_方法。

重复的字段也有一些特殊的方法 - 如果你看一下repeated phones字段的方法,你会发现你可以

  • 检查重复的字段的_size (换句话说,有多少电话号码与此相关联的 Person).

  • 使用索引获取指定的电话号码.

  • 更新指定索引处的现有电话号码.

  • 在邮件中添加另一个电话号码然后可以编辑(重复的标量类型add_只允许您传入新值).

有关协议编译器为任何特定字段定义生成的确切成员的详细信息,请参阅C ++生成的代码参考。

枚举和嵌套类

生成的代码包含PhoneType与您的.proto枚举对应的枚举。您可以参考这个类型Person::PhoneType及其作为值的Person::MOBILEPerson::HOMEPerson::WORK(实现细节是稍微复杂一点,但你并不需要了解他们使用ENUM)。

编译器还为您生成了一个嵌套类Person::PhoneNumber。如果查看代码,可以看到实际调用了“真实”类Person_PhoneNumber,但是在内部定义的typedef Person允许您将其视为嵌套类。唯一不同的情况是,如果你想在另一个文件中转发声明类 - 你不能在C++中转发声明嵌套类型,但你可以转发声明Person_PhoneNumber

标准消息方法

每个消息类还包含许多其他方法,可用于检查或操作整个消息,包括:

  • bool IsInitialized() const; 检查是否已设置所有必填字段。

  • string DebugString() const; 返回消息的人类可读表示,对调试特别有用。

  • void CopyFrom(const Person& from); 使用给定消息的值覆盖消息。

  • void Clear(); 清除所有元素回到空状态。

以下部分中描述的这些和I / O方法实现了Message所有C ++协议缓冲区类共享的接口。有关详细信息,请参阅完整的API文档Message。

解析和序列化

最后,每个协议缓冲区类都有使用协议缓冲区二进制格式编写和读取所选类型消息的方法。这些包括:

  • bool SerializeToString(string output) const;* 序列化消息并将字节存储在给定的字符串中。请注意,字节是二进制的,而不是文本; 我们只将该 string 类用作方便的容器。

  • bool ParseFromString(const string& data); 解析给定字符串中的消息。

  • bool SerializeToOstream(ostream output) const;* 将消息写入给定的 C++ ostream

  • bool ParseFromIstream(istream input);* 解析来自给定 C++ 的消息 istream

这些只是解析和序列化提供的几个选项。再次,请参阅MessageAPI参考以获取完整列表。

写一条消息

现在尝试使用协议缓冲类。地址簿应用程序能够做的第一件事是将个人详细信息写入地址簿文件。为此,需要创建并填充协议缓冲区类的实例,然后将它们写入输出流。

这是一个程序,它从文件中读取一个AddressBook,根据用户输入在AddressBook文件中添加一个新的Person,然后再将新文本写回文件。直接调用或引用协议编译器生成的代码的部分将突出显示。

#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;// This function fills in a Person message based on user input.
void PromptForAddress(tutorial::Person* person) {cout << "Enter person ID number: ";int id;cin >> id;person->set_id(id);cin.ignore(256, '\n');cout << "Enter name: ";getline(cin, *person->mutable_name());cout << "Enter email address (blank for none): ";string email;getline(cin, email);if (!email.empty()) {person->set_email(email);}while (true) {cout << "Enter a phone number (or leave blank to finish): ";string number;getline(cin, number);if (number.empty()) {break;}tutorial::Person::PhoneNumber* phone_number = person->add_phones();phone_number->set_number(number);cout << "Is this a mobile, home, or work phone? ";string type;getline(cin, type);if (type == "mobile") {phone_number->set_type(tutorial::Person::MOBILE);} else if (type == "home") {phone_number->set_type(tutorial::Person::HOME);} else if (type == "work") {phone_number->set_type(tutorial::Person::WORK);} else {cout << "Unknown phone type.  Using default." << endl;}}
}// Main function:  Reads the entire address book from a file,
//   adds one person based on user input, then writes it back out to the same
//   file.
int main(int argc, char* argv[]) {// Verify that the version of the library that we linked against is// compatible with the version of the headers we compiled against.GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2) {cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;return -1;}tutorial::AddressBook address_book;{// Read the existing address book.fstream input(argv[1], ios::in | ios::binary);if (!input) {cout << argv[1] << ": File not found.  Creating a new file." << endl;} else if (!address_book.ParseFromIstream(&input)) {cerr << "Failed to parse address book." << endl;return -1;}}// Add an address.PromptForAddress(address_book.add_people());{// Write the new address book back to disk.fstream output(argv[1], ios::out | ios::trunc | ios::binary);if (!address_book.SerializeToOstream(&output)) {cerr << "Failed to write address book." << endl;return -1;}}// Optional:  Delete all global objects allocated by libprotobuf.google::protobuf::ShutdownProtobufLibrary();return 0;
}

注意GOOGLE_PROTOBUF_VERIFY_VERSION宏。在使用C ++协议缓冲区库之前执行此宏是一种很好的做法 - 尽管不是绝对必要的。它验证您没有意外链接到与您编译的标头版本不兼容的库版本。如果检测到版本不匹配,程序将中止。请注意,每个.pb.cc文件在启动时都会自动调用此宏。

还要注意ShutdownProtobufLibrary()程序结束时的调用。所有这一切都是删除协议缓冲区库分配的所有全局对象。对于大多数程序来说这是不必要的,因为该过程无论如何都要退出,操作系统将负责回收其所有内存。但是,如果您使用需要释放每个最后一个对象的内存泄漏检查程序,或者您正在编写可以由单个进程多次加载和卸载的库,那么您可能希望强制协议缓冲区清除所有内容。

阅读消息

当然,如果无法从中获取任何信息,那么地址簿就不会有多大用处!此示例读取上面示例创建的文件并打印其中的所有信息。

#include <iostream>
#include <fstream>
#include <string>
#include "addressbook.pb.h"
using namespace std;// Iterates though all people in the AddressBook and prints info about them.
void ListPeople(const tutorial::AddressBook& address_book) {for (int i = 0; i < address_book.people_size(); i++) {const tutorial::Person& person = address_book.people(i);cout << "Person ID: " << person.id() << endl;cout << "  Name: " << person.name() << endl;if (person.email() !=  "") {cout << "  E-mail address: " << person.email() << endl;}for (int j = 0; j < person.phones_size(); j++) {const tutorial::Person::PhoneNumber& phone_number = person.phones(j);switch (phone_number.type()) {case tutorial::Person::MOBILE:cout << "  Mobile phone #: ";break;case tutorial::Person::HOME:cout << "  Home phone #: ";break;case tutorial::Person::WORK:cout << "  Work phone #: ";break;}cout << phone_number.number() << endl;}}
}// Main function:  Reads the entire address book from a file and prints all
//   the information inside.
int main(int argc, char* argv[]) {// Verify that the version of the library that we linked against is// compatible with the version of the headers we compiled against.GOOGLE_PROTOBUF_VERIFY_VERSION;if (argc != 2) {cerr << "Usage:  " << argv[0] << " ADDRESS_BOOK_FILE" << endl;return -1;}tutorial::AddressBook address_book;{// Read the existing address book.fstream input(argv[1], ios::in | ios::binary);if (!address_book.ParseFromIstream(&input)) {cerr << "Failed to parse address book." << endl;return -1;}}ListPeople(address_book);// Optional:  Delete all global objects allocated by libprotobuf.google::protobuf::ShutdownProtobufLibrary();return 0;
}

编译

g++ -Wall -std=c++11 write.cpp addressbook.pb.cc -o write `pkg-config --cflags --libs protobuf`
g++ -Wall -std=c++11 read.cpp addressbook.pb.cc -o read `pkg-config --cflags --libs protobuf`

Protobuf扩展

在释放使用协议缓冲区的代码之后迟早,您无疑会想要“改进”协议缓冲区的定义。如果你希望你的新缓冲区向后兼容,并且你的旧缓冲区是向前兼容的 - 而且你几乎肯定想要这个 - 那么你需要遵循一些规则。在新版本的协议缓冲区中:

  • 不得更改任何现有字段的标记号。

  • 不得添加或删除任何必填字段。

  • 可以删除可选或重复的字段。

  • 可以添加新的可选或重复字段,但必须使用新的标记号(即从未在此协议缓冲区中使用的标记号,甚至不包括已删除的字段)。

(这些规则有一些例外,但它们很少使用。)

如果您遵循这些规则,旧代码将很乐意阅读新消息并简单地忽略任何新字段。对于旧代码,已删除的可选字段将只具有其默认值,删除的重复字段将为空。新代码也将透明地读取旧消息。但是,请记住旧的消息中不会出现新的可选字段,因此您需要明确检查它们是否已设置has_,或者在.proto文件中提供合理的默认值[default = value]标签号后面。如果未为可选元素指定默认值,则使用特定于类型的默认值:对于字符串,默认值为空字符串。对于布尔值,默认值为false。对于数字类型,默认值为零。另请注意,如果添加了新的重复字段,则新代码将无法判断它是否为空(通过新代码)或从未设置(通过旧代码),因为没有has_标记。

优化

C ++协议缓冲区库经过了极大的优化。但是,正确使用可以进一步提高性能。以下是从库中挤出最后一滴速度的一些提示:

尽可能重用消息对象。消息尝试保留它们分配用于重用的任何内存,即使它们被清除。因此,如果您连续处理具有相同类型和类似结构的许多消息,则每次重新使用相同的消息对象来加载内存分配器是个好主意。但是,随着时间的推移,对象会变得臃肿,特别是如果您的消息在“形状”上有所不同,或者您偶尔构造的消息比平常大得多。

您应该通过调用SpaceUsed方法来监视消息对象的大小,并在它们变得太大时删除它们。
您的系统内存分配器可能没有针对从多个线程分配大量小对象进行良好优化。请尝试使用Google的tcmalloc。

高级用法

协议缓冲区的用途不仅仅是简单的访问器和序列化。请务必浏览C ++ API参考,以了解您可以使用它们做些什么。

协议消息类提供的一个关键特性是反射。您可以迭代消息的字段并操纵它们的值,而无需针对任何特定的消息类型编写代码。使用反射的一种非常有用的方法是将协议消息转换为与其他编码(例如XMLJSON)之间的转换。更高级的反射使用可能是找到两个相同类型的消息之间的差异,或者开发一种“协议消息的正则表达式”,您可以在其中编写与某些消息内容匹配的表达式。如果您运用自己的想象力,可以将协议缓冲区应用于比您最初预期更广泛的问题!

Message::Reflection界面 提供反射。
我的github地址

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

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

相关文章

如何调整反光镜和座椅的位置 为您支招

【太平洋汽车网 学车频道】首先要进行座椅的高度调整&#xff0c;上下调整座椅让头部离车顶至少还有一拳的距离。如果座椅调得太高&#xff0c;车辆在颠簸时头部容易碰到车顶&#xff0c;调得太矮了又会影响视线。然后是前后距离的调整&#xff0c;当脚踩住制动踏板至最深处时…

关于hexo与github使用过程中的问题与笔记

快速阅读 如何用github 和hexo 创建一个blog 1.github中要新建一个与用户名同一样的仓库&#xff0c; 如:homehe.github.io - 必须是io后缀。一个帐户 只能建立一个2. 绑定域名 &#xff0c; A记录指向ip, cname记录指向homehe.github.io 3. 配置sshkey - 个人设置 -> SSH a…

CSS 中 的 margin、border、padding 区别 (内边距、外边距)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 图解CSS padding、margin、border属性 W3C组织建议把所有网页上的对像都放在一个盒(box)中&#xff0c;设计师可以通过创建定义来控制这…

CMake 常用的预定义变量

CMake 常用的预定义变量 PROJECT_NAME : 通过 project() 指定项目名称 PROJECT_SOURCE_DIR : 工程的根目录 PROJECT_BINARY_DIR : 执行 cmake 命令的目录 CMAKE_CURRENT_SOURCE_DIR : 当前 CMakeList.txt 文件所在的目录 CMAKE_CURRENT_BINARY_DIR : 编译目录&#xff0c;…

什么是转向灯?使用转向灯有何技巧?

什么是转向灯&#xff1f;如何使用转向灯&#xff1f;新手司机对车辆还不是很熟悉&#xff0c;如何正确使用转向灯&#xff0c;尤其是在不同路段中该怎么正确使用转向灯&#xff0c;成为了很多新手们的困扰之一&#xff0c;今天我们就来为大家解决这个问题吧&#xff01; 转向灯…

基于Flask开发企业级REST API应用(一)

关于我 编程界的一名小小程序猿&#xff0c;目前在一个创业团队任team lead&#xff0c;技术栈涉及Android、Python、Java和Go&#xff0c;这个也是我们团队的主要技术栈。 Github&#xff1a;github.com/hylinux1024 微信公众号&#xff1a;angrycode 前面对Python WEB框架Fla…

解决:Do not use built-in or reserved HTML elements as component id: form

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 1. vue 新写了个组件&#xff0c;运行工程成功&#xff0c;但界面没有出效果&#xff0c;F12 提示有一个警告&#xff1a; Do not use …

移动语义,右值引用

移动语义 目录 右值引用变量是左值move库函数移动构造函数和移动赋值移动操作库容器和异常移动赋值操作符移动后的对象必须是可以析构的合成移动操作右值移动左值拷贝右值在无法被移动时进行拷贝拷贝和交换赋值操作与移动移动迭代器右值引用和成员函数右值与左值引用的成员函…

集合练习:登录注册功能

需求&#xff1a; 1、登录账号唯一&#xff0c;在注册时验证输入的账号是否可用&#xff0c;若已存在&#xff0c;则不可用&#xff0c;若不存在则可用2、登录时使用账号密码进行验证1 /**2 * author Administrator3 * 登录信息 4 */5 public class UserLogin {6 …

vue 通信、传值的多种方式(超详细)

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 一、通过路由带参数进行传值 ①两个组件 A和B,A组件通过query把orderId传递给B组件&#xff08;触发事件可以是点击事件、钩子函数等&am…

新手开车 驾驶小秘诀要牢记

有很多人刚买到新车兴奋异常&#xff0c;凭着并不熟练的驾驶技术&#xff0c;过了几天的车瘾后发现&#xff0c;刚买的车怎么出现了这样那样的问题 有很多人刚买到新车兴奋异常&#xff0c;凭着并不熟练的驾驶技术&#xff0c;过了几天的车瘾后发现&#xff0c;刚买的车怎么出现…

chrome中Google插件导出导入

导出插件&#xff1a; 一般电脑默认将你安装的插件存放的位置在&#xff1a;C:\Users&#xff08;用户&#xff09;\你的电脑名称\AppData\Local\Google\Chrome\User Data\Default\Extensions 这个文件夹下。这里的 AppData 是个隐藏文件夹&#xff0c;需要显示隐藏文件夹才行…

科目三电子路考操作流程

如果你已经通过科目二场内五项考试&#xff0c;正准备参加科目三电子路考的话&#xff0c;不妨看看由邕江驾校李师傅操作讲解的电子路考考试流程演示视频&#xff0c;每项都有详细介绍操作要领及评判标准哦。 科目三考试项目&#xff1a;上车准备、起步、路口左转弯、通过学校区…

vue Bus 总线 组件间通信

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 有时候两个组件也需要通信&#xff08;非父子关系&#xff09;。当然Vue2.0提供了Vuex&#xff0c;但在简单的场景下&#xff0c;可以使…

C++没有调用析构函数

github地址 在项目中遇到一个问题&#xff0c;析构函数没有调用产生了内存泄露。 具体见valgrind检测libevent内存泄露 我们看两个例子 demo1 class Test1; void del(Test1* obj){delete obj; } class Test1{ public:Test1(){printf("Test1\r\n");}~Test1(){pri…

实际操作之路考的这些事

辛苦了这么久练习路考&#xff0c;今天终于实际操作到我路考了。上车以后。关上车门。把考试的单地上给考官。还没有认真的去看考官一眼。于是就听到考官用低沉的声音对我说&#xff1a;你好&#xff01;当时就感觉有一点意外。没想到考官你这么有礼貌。然后我就没那么紧张了&a…

[C# 网络编程系列]专题十二:实现一个简单的FTP服务器

引言&#xff1a; 休息一个国庆节后好久没有更新文章了&#xff0c;主要是刚开始休息完心态还没有调整过来的&#xff0c; 现在差不多进入状态了&#xff0c; 所以继续和大家分享下网络编程的知识&#xff0c;在本专题中将和大家分享如何自己实现一个简单的FTP服务器。在我们平…

vue 2 使用 Bus.js 实现兄弟 (非父子) 组件通信 简单案例

前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。点击跳转到教程。 vue2中废弃了$dispatch和$broadcast广播和分发事件的方法。父子组件中可以用props和$emit()。如何实现非父子组件间的通信&#xff0c;可…

jenkins自动化部署

jenkins自动化部署 github地址 首先设置源码地址&#xff0c;jenkins会从仓库中拉取最新代码 拉取代码后运行shell脚本自动进行编译 cd mediaService cmake -S . -B cmake-build-release-hisi3531 -DCMAKE_C_COMPILER/opt/hisi-linux/x86-arm/arm-hisiv500-linux/target/bin…

JS-[IIFE闭包]

JS-IIFE&闭包 IIFE(立即调用函数表达式)示例IIFE实现单例模式闭包计数器例子非闭包实现闭包实现IIFE闭包实现内存泄露解决IIFE(立即调用函数表达式) 示例 (function iife(){ //直接执行&#xff0c;无需调用console.log("hello"); })(); //上面相当于function ii…