ProtoBuf—3

news2024/11/25 1:43:57

文章目录

  • 1、默认值
  • 2、更新规则
  • 3、未知字段
  • 4、option字段
  • 5、通信录网络版
  • 6、总结

1、默认值

反序列化消息时,如果被反序列化的二进制序列中不包含某个字段,反序列化对象中相应字段时,就会设置为该字段的默认值。不同的类型对应的默认值不同:

对于字符串,默认值为空字符串。
对于字节(bytes),默认值为空字节。
对于布尔值,默认值为false。
对于数值类型,默认值为0。
对于枚举,默认值是第一个定义的枚举值,必须为0。
对于消息字段,未设置该字段。它的取值是依赖于语言。
对于设置了repeated的字段的默认值是空的(通常是相应语言的一个空列表)。
对于消息字段、oneof字段和any字段,C++和Java语言中都有has_方法来检测当前字段是否被设置。
对于标量数据类型,在proto3语法下,没有生成has_方法。

2、更新规则

如果现有的消息类型已经不再满足我们的需求,例如需要扩展一个字段,在不破坏任何现有代码的情况下更新消息类型非常简单。遵循如下规则即可:

新增:

不要和老字段冲突即可

修改:

1、禁止修改任何已有字段的字段编号。
2、int32,uint32,int64,uint64和 bool是完全兼容的。可以从这些类型中的一个改为另一个,而不破坏前后兼容性。若解析出来的数值与相应的类型不匹配,会采用与C++一致的处理方案(例如,若将64位整数当做32位进行读取,它将被截断为32位)。
3、sint32和sint64相互兼容但不与其他的整型兼容。
4、string 和 bytes在合法UTF-8字节前提下也是兼容的。
5、bytes包含消息编码版本的情况下,嵌套消息与bytes也是兼容的。
6、fixed32 与sfixed32兼容,fixed64与sfixed64兼容。
7、enum 与int32,uint32,int64和uint64兼容(注意若值不匹配会被截断)。但要注意当反序列化消息时会根据语言采用不同的处理方案:例如,未识别的proto3枚举类型会被保存在消息中,但是当消息反序列化时如何表示是依赖于编程语言的。整型字段总是会保持其的值。
8、oneof:
将一个单独的值更改为新oneof类型成员之一是安全和二进制兼容的。
若确定没有代码一次性设置多个值那么将多个字段移入一个新oneof类型也是可行的。
将任何字段移入已存在的 oneof类型是不安全的。

删除:

不能直接删除已经存在的老字段,否则在实现业务代码逻辑上,会造成一些影响,比如数据损坏等
如果要删除老字段,要保证不使用已经被删除的或者已经被注释掉的字段编号

举个例子:
最开始client和service共用同一份ProtoBuf文件,只是包名不同,但由于某些情况需要int32 age = 2;改为int32 birthday = 2;

#service
syntax = "proto3";
package s_contacts;

//联系人
message PeopleInfo
{
    string name = 1;    //姓名
    //int32 age = 2;      //年龄
    int32 birthday = 2; //生日

    message Phone       
    {
        string number = 1;
    }  

    repeated Phone phone = 3;   //电话号码
}

//通讯录
message Contacts
{
    repeated PeopleInfo contacts = 1;
}
//service.cc
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void AddPeopleInfo(s_contacts::PeopleInfo *people)
{
    cout << "新增联系人" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    people->set_name(name);

    cout << "请输入联系人生日:";
    int birthday;
    cin >> birthday;
    people->set_birthday(birthday);
    cin.ignore(256, '\n');

    for (int i = 0;; ++i)
    {
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        s_contacts::PeopleInfo_Phone *phone = people->add_phone();
        phone->set_number(number);
    }

    cout << "添加联系人成功" << endl;
}

int main()
{
    s_contacts::Contacts contacts;

    // 读取本地已经存在的通讯录文件
    fstream input("../contacts.bin", ios::in | ios::binary);
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }
    else if (!contacts.ParseFromIstream(&input))
    {
        cerr << "parse error" << endl;
        input.close();
        return -1;
    }
    // 向通讯录中添加一个联系人
    AddPeopleInfo(contacts.add_contacts());

    // 将通讯录写入本地文件
    fstream output("../contacts.bin", ios::out | ios::trunc | ios::binary);
    if (!contacts.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    input.close();
    output.close();

    return 0;
}
#client
syntax = "proto3";
package c_contacts;

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

    message Phone       
    {
        string number = 1;
    }  

    repeated Phone phone = 3;   //电话号码
}

//通讯录
message Contacts
{
    repeated PeopleInfo contacts = 1;
}
#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;

void PrintContacts(c_contacts::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
        cout << "------------联系人" << i + 1 << "------------" << endl;
        const ::c_contacts::PeopleInfo &people = contacts.contacts(i);
        cout << "姓名" << people.name() << endl;
        cout << "年龄" << people.age() << endl;
        for (int j = 0; j < people.phone_size(); ++j)
        {
            const ::c_contacts::PeopleInfo_Phone &phone = people.phone(j);
            cout << "电话" << j + 1 << ":" << phone.number() << endl;
        }
    }
}

int main()
{
    c_contacts::Contacts contacts;
    // 读取本地已存在的文件
    fstream input("../contacts.bin", ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "parse error" << endl;
        input.close();
        return -1;
    }

    // 打印通讯录列表
    PrintContacts(contacts);
    input.close();
    return 0;
}

数据经过序列化,反序列化后数据就会不一致

在这里插入图片描述

如果非要删除,再重新添加,就需要用到reserved字段,被reserved修饰的编号成为保留编号,reserved不仅能修饰编号,还能修饰名称

syntax = "proto3";
package s_contacts;

//联系人
message PeopleInfo
{
    reserved 2, 10 to 12;
    reserved "age";
    string name = 1;    //姓名
    //int32 age = 2;      //年龄
    int32 birthday = 2; //生日

    message Phone       
    {
        string number = 1;
    }  

    repeated Phone phone = 3;   //电话号码
}

//通讯录
message Contacts
{
    repeated PeopleInfo contacts = 1;
}

如果此时还是用了保留编号或者名称,则会编译报错

在这里插入图片描述

将birthday编号改为4,再编译,然后再测试,统一了
在这里插入图片描述

3、未知字段

在前面一小节中,我们向service中的contacts.proto新增了‘生日’字段,但对于client相关的代码并没有任何改动。验证后发现新代码序列化的消息(service)也可以被旧代码(client)解析。并且这里要说的是,新增的‘生日’字段在旧程序(client)中其实并没有丢失,而是会作为旧程序的未知字段。

本来,proto3在解析消息时总是会丢弃未知字段,但在3.5版本中重新引入了对未知字段的保留机制。所以在3.5或更高版本中,未知字段在反序列化时会被保留,同时也会包含在序列化的结果中。

修改client.cc中的代码,获取对应的未知字段

#include <iostream>
#include <fstream>
#include "contacts.pb.h"
using namespace std;
using namespace c_contacts;
using namespace google::protobuf;

void PrintContacts(c_contacts::Contacts &contacts)
{
    for (int i = 0; i < contacts.contacts_size(); ++i)
    {
        cout << "------------联系人" << i + 1 << "------------" << endl;
        const ::c_contacts::PeopleInfo &people = contacts.contacts(i);
        cout << "姓名" << people.name() << endl;
        cout << "年龄" << people.age() << endl;
        for (int j = 0; j < people.phone_size(); ++j)
        {
            const ::c_contacts::PeopleInfo_Phone &phone = people.phone(j);
            cout << "电话" << j + 1 << ":" << phone.number() << endl;
        }

        // 通过GetReflection()拿到对应的Reflection*
        const Reflection *reflection = PeopleInfo::GetReflection();
        const UnknownFieldSet &set = reflection->GetUnknownFields(people);
        for (int j = 0; j < set.field_count(); ++j)
        {
            const UnknownField &unknown_field = set.field(j);
            cout << "未知字段:" << j + 1 << " "
                 << "编号:" << unknown_field.number();
            switch (unknown_field.type())
            {
            case UnknownField::Type::TYPE_VARINT:
                cout << " 值:" << unknown_field.varint() << endl;
                break;
            case UnknownField::Type::TYPE_LENGTH_DELIMITED:
                cout << " 值:" << unknown_field.length_delimited() << endl;
                break;
            //case...
            default:
                break;
            }
        }
    }
}

int main()
{
    c_contacts::Contacts contacts;
    // 读取本地已存在的文件
    fstream input("../contacts.bin", ios::in | ios::binary);
    if (!contacts.ParseFromIstream(&input))
    {
        cerr << "parse error" << endl;
        input.close();
        return -1;
    }

    // 打印通讯录列表
    PrintContacts(contacts);
    input.close();
    return 0;
}

结果:

在这里插入图片描述

4、option字段

ProtoBuf为我们提供了option字段,它用于为消息、字段或枚举定义附加的元数据和配置选项。Option 字段允许您在不修改消息定义的情况下,为特定的消息或字段提供额外的指令和配置。

举一个简单了例子:

syntax = "proto3";

message PeopleInfo
{
    string name = 1;
}

对上述proto文件进行编译,查看对应的.h文件

在这里插入图片描述

对proto文件进行修改,再编译,再查看.h文件

syntax = "proto3";
option optimize_for = LITE_RUNTIME;

message PeopleInfo
{
    string name = 1;
}

在这里插入图片描述

对比发现,两者的代码确实不同

选项分类
选项的完整列表在google/protobuf/descriptor.proto中定义。部分代码如下:

syntax = "proto2";						//descriptor.proto 使用proto2语法版本
message Fileoptions { ... }				//文件选项定义在FileOptions消息中			
message Messageoptions { ... }			//消息类型选项定义在MessageOptions消息中
message Fieldoptions { ... }			//消息字段选项定义在FieldOptions消息中
message Oneof0ptions { ... }			//oneof字段选项定义在 OneofOptions消息中
message EnumOptions { ... }				//枚举类型选项定义在EnumOptions消息中
message EnumValueOptions { ... }		//枚举值选项定义在 EnumValueOptions消息中
message Serviceoptions { ... }			//服务选项定义在Serviceoptions消息中
message MethodOptions { ... }			//服务方法选项定义在Method0ptions消息中
......

由此可见,选项分为文件级、消息级、字段级等等,但并没有一种选项能作用于所有类型

常用选项列举
optimize_for:该选项为文件选项,可以设置protoc编译器的优化级别,分别为SPEED、CODE_SIZE、LITE_RUNTIME。受该选项影响,设置不同的优化级别,编译.proto文件后生成的代码内容不同。

  • SPEED : protoc编译器将生成的代码是高度优化的,代码运行效率高,但是由此生成的代码编译后会占用更多的空间。SPEED 是默认选项
  • CODE_SIZE : proto编译器将生成最少的类,会占用更少的空间,是依赖基于反射的代码来实现序列化、反序列化和各种其他操作。但和SPEED恰恰相反,它的代码运行效率较低。这种方式适合用在包含大量的.proto文件,但并不盲目追求速度的应用中。
  • LITE_RUNTIME:生成的代码执行效率高,同时生成代码编译后的所占用的空间也是非常少。这是以牺牲Protocol Buffer提供的反射功能为代价的,仅仅提供encoding+序列化功能,所以我们在链接BP库时仅需链接libprotobuf-lite,而非libprotobuf。这种模式通常用于资源有限的平台,例如移动手机平台中。

allow_alias:允许将相同的常量值分配给不同的枚举常量,用来定义别名。该选项为枚举选项。
举个例子:

enum PhoneType
{
    option allow_alias = true;
    MP = 0;
    TEL = 1;
    LANDLINE = 1;   //若不加 option allow_alias = true; 这一行则会编译报错
}

ProtoBuf允许自定义选项并使用。但该功能大部分场景都用不到,有兴趣的可以参考

https://developers.google.cn/protocol-buffers/docs/proto?hl=zh-cn#customoptions

5、通信录网络版

对前面所学到的知识进行总结,设计一个网络版通信录,只实现了新增功能。
在此之前需要下载一下cpp-httplib开源库:

https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_githup_accelerator

在这里插入图片描述

整体的目录结构
在这里插入图片描述

service端:
main.cc

#include <iostream>
#include "httplib.h"
#include "add_contact.pb.h"

using namespace std;
using namespace httplib;

class ContactsExecption
{
private:
    std::string message;
public:
    ContactsExecption(std::string str = "A problem") : message(str){}
    std::string what() const
    {
        return message;
    }
};


void PrintContacts(add_contact::AddContactRequest &req)
{
    cout << "姓名" << req.name() << endl;
    cout << "年龄" << req.age() << endl;
    for (int j = 0; j < req.phone_size(); ++j)
    {
        const ::add_contact::AddContactRequest_Phone &phone = req.phone(j);
        cout << "电话" << j + 1 << ":" << phone.number();
        // 联系人电话1:123456789 (MP)
        cout << "     (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
    }
}


static unsigned int random_char()
{
    //用于随机数引擎获得随机种子
    std::random_device rd;
    //mt19973是C++11新特性,它是一种随机数算法,用法与rand()函数类似,但是mt19973具有速度快,周期长的特点
    //作用是生成伪随机
    std::mt19937 gen(rd());
    //随机生成一个整数i,范围[0,255]
    std::uniform_int_distribution<> dis(0, 255);
    return dis(gen);
}


// 生成UUID(通用唯一标识符)
static std::string generate_hex(const unsigned int len)
{
    std::stringstream ss;
    //生成 len 个16进制随机数,将其拼接而成
    for(auto i = 0; i < len; ++i)
    {
        const auto rc = random_char();
        std::stringstream hexstream;
        //十六进制输出
        hexstream << std::hex << rc;
        auto hex = hexstream.str();
        ss << (hex.length() < 2 ? '0' + hex : hex);
    }
    return ss.str();
}

void set_uid()
{

}

int main()
{
    cout << "————————————————启动服务————————————————" << endl;
    Server server;
    server.Post("/contacts/add", [](const Request &req, Response &res) {
        cout<<"收到post请求"<<endl;

        //反序列化 request:req.bady
        add_contact::AddContactRequest request;
        add_contact::AddContactResponse response;
        try
        {
            if(!request.ParseFromString(req.body))
            {
                throw ContactsExecption("AddContactRequest反序列化失败!");
            }

            //新增联系人,持久化存储通讯录----->打印新增联系人信息
            PrintContacts(request);

            //构造 response:res.body
            response.set_success(true);
            response.set_uid(generate_hex(10));

            //res.body(序列化response)
            string response_str;
            if(!response.SerializeToString(&response_str))
            {
                throw ContactsExecption("AddContactResponse序列化失败!");
            }
            res.status = 200;
            res.body = response_str;
            res.set_header("Content-Type", "application/protobuf");

        }catch(const ContactsExecption& e) { 
            res.status = 500;
            response.set_success(false);
            response.set_error_desc(e.what());
            string response_str;
            if(response.SerializeToString(&response_str))
            {
                res.body = response_str;
                res.set_header("Content-Type", "application/protobuf");
            }
            cout << "/contacts/add 发生异常,异常信息" << e.what() << endl;
        }
    });

    // 绑定8080端口,并且将端口对外开放
    server.listen("0.0.0.0", 8080);
    return 0;
}

add_contact.proto:

syntax = "proto3";
package add_contact;

message AddContactRequest
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    
    message Phone
    {
        string number = 1;
        enum PhoneType
        {
            MP = 0; //移动电话
            TEL = 1;//固定电话
        }
        PhoneType type = 2;
    }
    repeated Phone phone = 3;
}

message AddContactResponse
{
    bool success = 1;       //服务是否调用成功
    string error_desc = 2;  //错误原因
    string uid = 3;
}

makefile:

service:main.cc add_contact.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf -lpthread

.PHONY:clean
clean:
	rm -f service

client端:
main.cc:

#include <iostream>
#include "httplib.h"
#include "ContactsExecption.h"
#include "add_contact.pb.h"
using namespace std;
using namespace httplib;

#define CONTACTS_HOST "127.0.0.1"
#define CONTACTS_PORT 8080

void menu()
{
    cout << "---------------------------------------------------" << endl
         << "----------------请选择对通信录的操作-----------------" << endl
         << "-------------------1、新增联系人--------------------" << endl
         << "-------------------2、删除联系人--------------------" << endl
         << "-------------------3、查看联系人列表----------------" << endl
         << "-------------------4、查看联系人的详细信息-----------" << endl
         << "-------------------0、退出--------------------------" << endl
         << "---------------------------------------------------" << endl;
}


void buildAddContactRequest(add_contact::AddContactRequest* req)
{
    cout << "新增联系人" << endl;
    cout << "请输入联系人姓名:";
    string name;
    getline(cin, name);
    req->set_name(name);

    cout << "请输入联系人年龄:";
    int age;
    cin >> age;
    req->set_age(age);
    cin.ignore(256, '\n');

    for (int i = 0;; ++i)
    {
        cout << "请输入联系人电话" << i + 1 << "(只输入回车完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty())
        {
            break;
        }
        add_contact::AddContactRequest_Phone* phone = req->add_phone();
        phone->set_number(number);

        cout << "请输入该电话类型(1.移动电话  2.固定电话): ";
        int type;
        cin >> type;
        cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(add_contact::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);
            break;
        default:
            cout << "选择有误" << endl;
            break;
        }
    }
}

void addContact()
{
    Client cli(CONTACTS_HOST, CONTACTS_PORT);

    //构造 req
    add_contact::AddContactRequest req;
    buildAddContactRequest(&req);

    //序列化
    string req_str;
    if(!req.SerializeToString(&req_str))
    {
        throw ContactsExecption("AddContactRequest序列化失败!");
    }

    //发起post调用
    auto res = cli.Post("/contacts/add", req_str, "application/protobuf");
    if(!res)
    {
        string err_desc;
        err_desc.append("/contacts/add 链接失败!错误信息:" )
                .append(httplib::to_string(res.error()));
        throw ContactsExecption(err_desc);
    }


    //反序列化 resp
    add_contact::AddContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    //反序列化失败并且status不为200
    if(!parse && res->status != 200)
    {
        string err_desc;
        err_desc.append("/contacts/add 调用失败!" )
                .append(std::to_string(res->status))
                .append("(").append(res->reason).append(")");
        throw ContactsExecption(err_desc);
    }
    //反序列化成功但status不为200
    else if(res->status != 200)
    {
        string err_desc;
        err_desc.append("/contacts/add 调用失败!" )
                .append(std::to_string(res->status))
                .append("(").append(res->reason).append(") 错误原因:")
                .append(resp.error_desc());
        throw ContactsExecption(err_desc);
    }
    //判断服务端是否序列化成功
    else if(!resp.success())
    {
        string err_desc;
        err_desc.append("/contacts/add 结果异常" )
                .append("异常原因:")
                .append(resp.error_desc());
        throw ContactsExecption(err_desc);
    }

    //打印结果
    cout << "新增联系人成功,联系人ID:" << resp.uid() << endl; 
}



int main()
{
    enum POTION
    {
        QUIT = 0,
        ADD,
        DEL,
        FIND_ALL,
        FIND_ONE
    };
    while (true)
    {
        menu();
        cout << "请选择:";
        int choose;
        cin >> choose;
        getchar();
        try
        {
            switch (choose)
            {
                case POTION::QUIT:
                    cout << "程序退出" << endl;
                    return 0;
                case POTION::ADD:
                    addContact();
                case POTION::DEL:
                case POTION::FIND_ALL:
                case POTION::FIND_ONE:
                    break;
                default:
                    cout << "选择有误,请重新选择" << endl;
            }
        } catch(const ContactsExecption& e)
        {
            cout << "操作通讯录时发生异常" <<endl
                 << "异常信息:" << e.what() <<endl;
        }
    }
    return 0;
}

add_contact.proto:

syntax = "proto3";
package add_contact;

message AddContactRequest
{
    string name = 1;    //姓名
    int32 age = 2;      //年龄
    
    message Phone
    {
        string number = 1;
        enum PhoneType
        {
            MP = 0; //移动电话
            TEL = 1;//固定电话
        }
        PhoneType type = 2;
    }
    repeated Phone phone = 3;
}

message AddContactResponse
{
    bool success = 1;       //服务是否调用成功
    string error_desc = 2;  //错误原因
    string uid = 3;
}

makefile:

client:main.cc add_contact.pb.cc
	g++ -o $@ $^ -std=c++11 -lprotobuf -lpthread

.PHONY:clean
clean:
	rm -f client

最终的运行结果:

在这里插入图片描述

6、总结

序列化协议通用性格式可读性序列化大小序列化性能适用场景
JSON通用(json,xml已经成为多种行业标准的编写工具)文本格式轻量(使用键值对方式,压缩了一定的空间)web项目。因为浏览器对json数据支持非常好,有很多内建的函数支持
XML通用文本格式重量(数据冗余,因为需要成对的闭合标签)XML作为一种扩展标记语言,衍生出了HTML、RDF/RDFS,它强调数据结构化的能力和可读性
ProtoBuf独立(ProtoBuf只是Google内部的工具)二进制格式差(只能反序列化后得到真正可读的数据)轻量(比json更轻量,传输起来带宽和速度会有优化)适合高性能,对响应速度有要求的数据传输场景。ProtoBuf比JSON、XML更小,更快

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

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

相关文章

使用GeoPandas进行地理空间数据可视化

大家好&#xff0c;在当今数据驱动的世界中&#xff0c;将信息可视化到地图上可以提供有价值的见解&#xff0c;帮助有效地传达复杂的模式。GeoPandas是一个建立在pandas和shapely之上的Python库&#xff0c;使用户能够通过将地理空间数据与各种变量合并来创建令人惊叹的地图。…

css实现鼠标悬停时滑出层提示

css实现鼠标悬停时滑出层提示的方法介绍 这是一个简单的鼠标悬停提示特效&#xff0c;类似于alt标签&#xff0c;不过这一种是用纯CSS实现&#xff0c;扩展性好&#xff0c;而且在提示的层里可以加入图片或其它布局&#xff0c;这个要根据你的需要了。 代码如下: <!DOCTYPE…

一次日志配置未生效问题排查记录

某天排查业务问题时,在我司的日志收集平台上,未能发现相关业务服务接口访问日志。经过和相关同事确定,发现业务服务未能将接口访问日志吐到日志收集平台,由此开启一段有点漫长的排查之旅。 业务服务是典型的SpringBoot web应用&#xff0c;日志记录采用slf4jlog4j2组合。 通过…

《BlazePose: On-device Real-time Body Pose tracking》在移动设备端实时人体姿态追踪(2006)

BlazePose: On-device Real-time Body Pose tracking 论文地址&#xff1a;https://arxiv.org/pdf/2006.10204.pdf GoogleBolg&#xff1a;https://ai.googleblog.com/2020/08/on-device-real-time-body-pose-tracking.html 模型下载地址&#xff1a;https://github.com/PIN…

clop勒索软件攻击活动频发,西门子能源中招

自6月初被通报利用MOVEit Transfer服务器中的零日漏洞窃取加密组织数据后&#xff0c;clop勒索软件攻击活动频繁&#xff0c;全球陆续发生了多起clop软件攻击事件。本周&#xff0c;Clop团伙在其数据泄露网站上列出了西门子能源公司的信息&#xff0c;表示该公司的数据被泄露。…

must declare a single constructor

1、异常详细描述 Test ignored.org.junit.platform.commons.PreconditionViolationException: Class [org.bc.offer.datastructure.Stack] must declare a single constructorProcess finished with exit code -12、源码 package org.bc.offer.datastructure;import org.jun…

SUI Token释放计划时间表和几个相关问题

Sui主网于2023年5月3日正式上线&#xff0c;网络原生token SUI用于链上交易、支付gas费用、保护网络以及提供链上流动性。SUI的长期流通总供应量为100亿。在主网上线时&#xff0c;大约有5%的token在流通&#xff0c;其余的将按照下面的图表所示的计划释放&#xff0c;以保持网…

特异性抑制剂:FAPI-4 NH2 ,FAPI4Amine,成纤维细胞活化蛋白 (FAP) 抑制剂连接氨基

编辑来源||陕西新研博美生物科技有限公司小编MISSwu FAPI-4-NH2中FAPI是FAP的特异性抑制剂。在这些FAPI中&#xff0c;含有DOTA配体(FAPI-04)的[68Ga]Ga-DOTA-FAPI-04 PET/CT能显示出良好的体内药代动力学&#xff0c;导致快速kidney清除和注射后10分钟至3小时的低背景活性。 …

JAVA C++的权限区别

访问级别的名称和个数&#xff1a;在C中&#xff0c;有三个访问级别关键字&#xff1a;public、protected和private。而在Java中&#xff0c;有四个访问级别关键字&#xff1a;public、protected、private和默认&#xff08;没有关键字修饰&#xff09;。 默认访问级别不同&…

【三层交换机】网络杂谈(16)之三层交换机技术

涉及知识点 什么是三层交换机&#xff0c;三层交换技术的由来&#xff0c;三层交换机&#xff0c;三层交换的应用范例。深入了解三层交换机技术。 原创于&#xff1a;CSDN博主-《拄杖盲学轻声码》&#xff0c;更多内容可去其主页关注下哈&#xff0c;不胜感激 文章目录 涉及知…

SAP-QM质量管理视图字段解析

QM物料授权:如果在物料主记录中输入物料授权组,则系统将检查(在物料基础上)用户是否具有对以上名称对象的授权。如果未输入授权组,则不进行授权检查。要通过授权检查,用户需要 物料授权,其中包含有字段值所表示的相关授权组。 收货处理时间:收货后的加盐时间(天数)如…

深度学习(五)—— 卷积神经网络(CNN)

卷积神经网络&#xff08;CNN&#xff09; 1 CNN的组成2 卷积层2.1 卷积的计算2.2 多通道卷积2.3 多卷积核卷积2.4 特征图大小2.5 卷积层 api 实现 3 池化层3.1 最大池化3.2 平均池化 4 全连接层5 CNN的构建5.1 数据加载5.2 数据处理5.3 模型搭建5.4 模型编译5.5 模型训练5.6 模…

Go语言使用net/http实现简单登录验证和文件上传功能

最近再看Go语言web编程&#xff0c;使用net/http模块编写了一个简单的登录验证和文件上传的功能&#xff0c;在此做个简单记录。 目录 1.文件目录结构 2.编译运行 3.用户登录 4.文件上传 5.mime/multipart模拟form表单上传文件 代码如下&#xff1a; package mainimport …

【C语言】递归实战,通过几个例子带你深入走进递归算法

君兮_的个人主页 勤时当勉励 岁月不待人 C/C 游戏开发 Hello&#xff0c;这里是君兮_&#xff0c;今天给大家带来一篇递归的实战教学文章&#xff0c;由于递归算法不仅对于初学者十分不易理解并且在我们以后的数据结构中也非常重要。我们今天就通过几个应用递归的实际例子来给…

Apache Doris 在头部票务平台的应用实践:报表开发提速数十倍、毫秒级查询响应

作者&#xff5c;国内某头部票务平台 大数据开发工程师 刘振伟 本文导读&#xff1a; 随着在线平台的发展&#xff0c;票务行业逐渐实现了数字化经营&#xff0c;企业可以通过在线销售、数字营销和数据分析等方式提升运营效率与用户体验。基于此&#xff0c;国内某头部票务平…

【Java】Java核心 81:Git 教程(4)差异比较 版本回退

文章目录 06.GIT本地操作-差异比较目标内容小结 07.GIT本地操作-版本回退目标内容小结 在Git中&#xff0c;可以使用差异比较命令和版本回退命令来查看文件之间的差异并回退到早期的版本。 以下是对这些操作的简要解释&#xff1a; 差异比较&#xff1a;你可以使用git diff命…

本地Linux 部署 Dashy 并远程访问

文章目录 简介1. 安装Dashy2. 安装cpolar3.配置公网访问地址4. 固定域名访问 转载自cpolar极点云文章&#xff1a;本地Linux 部署 Dashy 并远程访问 简介 Dashy 是一个开源的自托管的导航页配置服务&#xff0c;具有易于使用的可视化编辑器、状态检查、小工具和主题等功能。你…

video-04-videojs配置及使用

videojs是一种轻框架&#xff0c;可以帮我们快速开发一个video视频组件 目录 一、参考资料 二、引入videojs 三、简单了解使用 四、配置项和事件 4.1 常用配置项 4.2 常用事件 4.3 常用方法 4.4 网络状态 4.5 播放状态 4.6 视频控制 五、实例&#xff08;可直接复制…

升级iOS 17测试版后如何降级?iOS17降级教程

对于已经升级到 iOS 17 测试版的用户&#xff0c;如果在体验过程中&#xff0c;感觉到并不是那么稳定&#xff0c;例如出现应用程序不适配、电池续航下降、功能无法正常启用等问题&#xff0c;想要进行降级操作&#xff0c;可以参考本教程。 降级前注意事项&#xff1a; 1.由于…

Android 自定义手写签字板,签署姓名,签名

各位大佬好又来记笔记了~ 今天要做的是签字板&#xff0c;实现客户签名功能&#xff0c;直接看效果&#xff1a; 逐个进行签字&#xff0c;可以避免连笔导致识别不清问题。就是想要客户一个一个写&#xff0c;认真写~~。 下面方框显示的“王某才” 其实是三张图片&#xff0c;…