Protobuf数据交互实战

news2024/12/23 13:36:05

"no one gonna make me down" 

        在之前呢,我们介绍了什么protobuf以及它的语法、数据类型。 一句老话说得好,"多说不练,假把式!"。因此,本篇会选择以protobuf的语法,完成一个简易的通讯录,一个是文件版的,一个是网络版的。这样才能让我亲切地感受到,protobuf以及和它拥有类似功能的Json、xml这些数据交换格式,它们是如何运作的。

---前言


一、通讯录1

(1) 通讯录格式设计

(2) 通讯录功能实现

        那么要编写文件版的demo代码,首先就需要两个.cc文件,其中一个文件是用来写入(out),一个是读取(in),而Protobuf的数据格式最终是以二进制码转换存储的。

write.cc:

#include <iostream>
#include <fstream>
#include <string>

#include "contact.pb.h"

using namespace std;

const std::string file_name = "./contact.txt";

// 添加联系人操作 之后实现
void AddContact() {}

int main()
{
    contacts::Contact con;
    // 可能需要先加载本地文件
    fstream input(file_name, ios::in | ios::binary);
    // 从打开的文件流里进行序列化到con里
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }

    if (con.ParseFromIstream(&input) == false)
    {
        cerr << "Parse error!" << endl;
        input.close();
        return -1;
    }

    // 可以进行 增添联系人
    AddContact();

    // 联系人添加完毕后 写回文件

    fstream output(file_name, ios::out | ios::binary);
    if (!con.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;
    input.close();
    output.close();
    return 0;
}

        

read.cc:

#include <iostream>
#include <fstream>
#include <string>

#include "contact.pb.h"
using namespace std;

const std::string file_name = "./contact.txt";

// 打印通讯录
void PrintContact() {}

int main()
{
    contacts::Contact con;

    fstream input(file_name, ios::in | ios::binary);
    if (!con.ParseFromIstream(&input))
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

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

增加一个联系人:

姓名、年龄、电话设置:

设置地址字段:

设置其他联系方式和备注:

write.cc代码:

#include <iostream>
#include <fstream>
#include <string>

#include "contact.pb.h"

using namespace std;

const std::string file_name = "./contact.txt";

// 添加联系人操作
void AddContact(contacts::PeopleInfo *people)
{
    cout << "-------------新增联系⼈-------------" << endl;
    string name;
    cout << "请输入联系人姓名: ";
    getline(cin, name);
    people->set_name(name);

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

    for (int i = 0;; i++)
    {
        contacts::PeopleInfo_Phone *phone = people->add_phone();
        cout << "请输入联系人电话" << i + 1 << "(只输⼊回⻋完成电话新增):";
        string number;
        getline(cin, number);
        if (number.empty() == true)
        {
            cout << "电话信息填充完毕" << endl;
            break;
        }
        phone->set_number(number);
        cout << "请输入该电话类型(1、移动电话   2、固定电话): ";
        int type;
        cin >> type;

        switch (type)
        {
        case 1:
            phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
        default:
            cout << "选择有误!" << endl;
            break;
        }
        cin.ignore(256, '\n');
    }
    
    // 录入地址
    // 先将数据存入 消息字段address中
    contacts::Address address;
    cout << "请输入联系人家庭地址:";
    string home_address;
    getline(cin, home_address);
    address.set_home_address(home_address);
    cout << "请输入联系人单位地址:";
    string unit_address;
    getline(cin, unit_address);
    address.set_unit_address(unit_address);
    // 转为Any字段
    // mutable_data: 这里会为Any开辟内存空间 并返回这个内存空间的地址
    people->mutable_data()->PackFrom(address);

    cout << "请选择要添加的其他联系方式(1、qq     2、微信号):";
    int other_contact;
    cin >> other_contact;
    cin.ignore(256, '\n');
    if (1 == other_contact)
    {
        cout << "请输入联系人qq号: ";
        string qq;
        getline(cin, qq);
        people->set_qq(qq);
    }
    else if (2 == other_contact)
    {
        cout << "请输入联系人微信号: ";
        string wechat;
        getline(cin, wechat);
        people->set_wechat(wechat);
    }
    else
    {
        cout << "选择有误,未成功设置其他联系方式!" << endl;
    }

    for (int i = 0;; i++)
    {
        cout << "请输入备注" << i + 1 << "标题(只输入回车完成备注新增):";
        string remark_key;
        getline(cin, remark_key);
        if (remark_key.empty())
        {
            break;
        }

        cout << "请输入备注" << i + 1 << "内容: ";
        string remark_value;
        getline(cin, remark_value);
        people->mutable_remark()->insert({remark_key, remark_value});
    }

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

int main()
{
    contacts::Contact con;
    // 可能需要先加载本地文件
    fstream input(file_name, ios::in | ios::binary);
    // 从打开的文件流里进行序列化到con里
    if (!input)
    {
        cout << "contacts.bin not find, create new file!" << endl;
    }

    if (con.ParseFromIstream(&input) == false)
    {
        cerr << "Parse error!" << endl;
        input.close();
        return -1;
    }

    // 可以进行 增添联系人
    AddContact(con.add_con());

    // 联系人添加完毕后 写回文件
    fstream output(file_name, ios::out | ios::binary);
    if (!con.SerializeToOstream(&output))
    {
        cerr << "write error!" << endl;
        input.close();
        output.close();
        return -1;
    }

    cout << "write success!" << endl;

    input.close();
    output.close();
    return 0;
}

打印联系人信息:

 打印姓名、年龄、电话:

地址信息:

其他联系方式与备注:

read.cc完整代码:

#include <iostream>
#include <fstream>
#include <string>

#include "contact.pb.h"
using namespace std;

const std::string file_name = "./contact.txt";

// 打印通讯录
void PrintContact(contacts::Contact &con)
{
    for (int i = 0; i < con.con_size(); ++i)
    {
        cout << "---------------联系人" << i + 1 << "---------------" << endl;
        const contacts::PeopleInfo &people = con.con(i);

        cout << "联系人姓名: " << people.name() << std::endl;
        cout << "联系人年龄: " << people.age() << std::endl;

        for (int j = 0; j < people.phone_size(); j++)
        {
            const contacts::PeopleInfo_Phone &phone = people.phone(j);
            // 联系人电话1:1311111  (MP)
            cout << "联系人电话" << j + 1 << ":" << phone.number();
            cout << "   (" << phone.PhoneType_Name(phone.type()) << ")"
                 << " ";
        }
        cout << endl;

        // 对Any字段 可以通过查看是否设置来确认打印情况
        if (people.has_data() && people.data().Is<contacts::Address>())
        {
            contacts::Address address;
            // adress -> any
            // people.data().PackFrom()

            // any -> address
            people.data().UnpackTo(&address);
            if (!address.home_address().empty())
            {
                cout << "联系人家庭地址:" << address.home_address() << endl;
            }
            else if (!address.unit_address().empty())
            {
                cout << "联系人家庭地址:" << address.home_address() << endl;
            }
        }

        switch (people.other_contact_case())
        {
        case contacts::PeopleInfo::OtherContactCase::kQq:
            cout << "联系人qq: " << people.qq() << endl;
            break;
        case contacts::PeopleInfo::OtherContactCase::kWechat:
            cout << "联系人微信号: " << people.wechat() << endl;
            break;
        default:
            cout << " " << endl;
            break;
        }

        if (people.remark_size())
        {
            cout << "备注信息:" << endl;
        }
        for (auto it = people.remark().cbegin(); it != people.remark().cend(); it++)
        {
            cout << "   " << it->first << ": " << it->second << endl;
        }
        
    }
}

int main()
{
    contacts::Contact con;

    fstream input(file_name, ios::in | ios::binary);
    if (!con.ParseFromIstream(&input))
    {
        cerr << "parse error!" << endl;
        input.close();
        return -1;
    }

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

(3) 测试 

        代码编写的部分已经完成了,那么来看看简易版的通讯录是怎样的?

         我们先对文件进行写入,因为存在二进制,很多信息是可读性不高。

         我们此时调用read进程,将文件里的内容读出来,

         那么此时原安静躺在磁盘上的二进制文件的内容,是能被可读性强地展示出来。


二、基于Protbuf的网络实战版通讯录

(1) 环境搭建

        Httplib库:cpp-httplib是个开源的库,是⼀个c++封装的http库,使⽤这个库可以在linux、
windows平台下完成http客⼾端、http服务端的搭建。使⽤起来⾮常⽅便,只需要包含头⽂件
httplib.h即可。编译程序时,需要带上-lpthread选项。
源码库地址:https://github.com/yhirose/cpp-httplib

镜像仓库: https://gitcode.net/mirrors/yhirose/cpp-httplib?utm_source=csdn_github_accelerator

        值得注意的是,在大多数Centos的环境下,yum自带的gcc/g++编译器的最新版本是4.8.5发布于2015年,年代久远。译该项⽬会出现异常。将gcc/g++升级为更⾼版本可解决问题。当然,同时安装这两款编译器是不会冲突的。

# 安装gcc 8版本
yum install -y devtoolset-8-gcc devtoolset-8-gcc-c++

# 启⽤版本 否则还是会默认使用原g++4.8.5的版本
source /opt/rh/devtoolset-8/enable

        

如何使用httplib?

        这里写了一份小的demo代码来搭建一个简易的http服务器。

  server:

#include <iostream>
// 只需要将该httplib.h文件引入即可
#include "../../cpp-httplib/httplib.h"

using namespace std;
using namespace httplib;

int main()
{
  cout << "---服务器启动---" << endl;
  Server server;
  
  // 服务端接收到Post请求  这里传了个Lambda表达式,自动填写响应状态码
  server.Post("/test-Post", [](const Request &req, Response &resp)
   {
    cout << "接收到Post请求" << endl;
    resp.status = 200; 
   });

  // 服务端接收到Get请求  这里传了个Lambda表达式,自动填写响应状态码
  server.Get("/test-Get", [](const Request &req, Response &resp)
  {
      cout << "接收到Post请求" << endl;
      resp.status = 200; 
  });

  // bind端口
  server.listen("0.0.0.0",8085);
  return 0;
}

     

client:

#include <iostream>
#include "../../cpp-httplib/httplib.h"
using namespace httplib;
using namespace std;
#define CONTACTS_HOST "127.0.0.1"
#define CONTACTS_PORT 8085

int main()
{
    Client cli(CONTACTS_HOST, CONTACTS_PORT);

    Result res1 = cli.Post("/test-Post");
    if (res1->status == 200)
    {
        cout << "调用post成功!" << endl;
    }

    Result res2 = cli.Get("/test-Get");
    if (res2->status == 200)
    {
        cout << "调用Get成功!" << endl;
    }

    return 0;
}

        我们分别启动服务器,进行基于http协议通信: 

(2) 业务逻辑 

        下面就开始代码实战!

(3) Server服务端

实用的小程序功能块:        

        随机数生成 与 备注信息插入

#include <sstream>
#include <random>
#include <google/protobuf/map.h>

class Util
{
public:
    static unsigned int random_char()
    {
        // 获取随机数种子
        std::random_device rd;
        // mt19937C++11的新特性,类似于rand(),但是速度跟快周期长
        std::mt19937 gen(rd());
        // 限制范围生成i [0,255]
        std::uniform_int_distribution<> dis(0, 255);
        return dis(gen);
    }

    // 生成唯一标识UID
    static std::string generate_uid(const unsigned int len = 4)
    {
        std::stringstream ss;
        for (int 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();
    }

    static void map_copy(google::protobuf::Map<std::string, std::string> *target, const google::protobuf::Map<std::string, std::string> &source)
    {
        if (nullptr == target)
        {
            std::cout << "map_copy warning, target is nullptr!" << std::endl;
            return;
        }

        for (auto it = source.cbegin(); it != source.cend(); ++it)
        {
            target->insert({it->first, it->second});
        }
    }
};

        异常类,简单的打印信息:

#pragma once
// 自定义异常类
#include <iostream>
#include <string>
class ContactException
{
private:
    std::string msg;
public:
    ContactException(std::string str ):msg(str){}
    std::string& what(){
        msg += "A problem happend";
        return msg;
    }
};

功能实现的proto:

        对于客户端而言,它会有四个选项来发送不同的req,并且得到不同的resp。对于服务端而言,它也应该根据网络拿到客户端的req,并且将处理结果resp给客户端。如何进行这些数据的交互呢?当然是我们正在讲的.proto文件!

 response:

// base 默认携带 退出码和错误描述
syntax = "proto3";
package base_response;

message BaseResponse {
    bool success = 1;                 // 返回结果
    string error_desc = 2;            // 错误描述
}


// add_contact_resp
syntax = "proto3";
package add_contact_resp;

import "base_response.proto";

message AddContactResponse{
    base_response.BaseResponse base_resp = 1;
    string uid = 2;
}   


// del_contact_resp
syntax = "proto3";
package del_contact_resp;

import "base_response.proto";   // 引入base_response

// 删除一个联系人 resp
message DelContactResponse {
  base_response.BaseResponse base_resp = 1;
  string uid = 2;
}


// find_all_contacts_resp
syntax = "proto3";
package find_all_contacts_resp;

import "base_response.proto";  // 引入base_response

// 联系人摘要信息
message PeopleInfo {
  string uid = 1;                   // 联系人ID
  string name = 2;                  // 姓名
}

// 查询所有联系人 resp
message FindAllContactsResponse {
  base_response.BaseResponse base_resp = 1;
  repeated PeopleInfo contacts = 2;
}


// find_one_contact_resp
syntax = "proto3";
package find_one_contact_resp;

import "base_response.proto";  // 引入base_response
// 查询一个联系人 resp
message FindOneContactResponse {
  base_response.BaseResponse base_resp = 1;

  string uid = 2;                   // 联系人ID
  string name = 3;                  // 姓名
  int32 age = 4;                    // 年龄

  message Phone {
    string number = 1;     // 电话号码
    enum PhoneType {
      MP = 0;    // 移动电话
      TEL = 1;   // 固定电话
    }
    PhoneType type = 2;    // 类型
  }
  
  repeated Phone phone = 5;         // 电话
  map<string, string> remark = 6;   // 备注
}

        

request:

// add_contact_req
syntax = "proto3";
package add_contact_req;
// 新增联系人 req
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;         // 电话
  map<string, string> remark = 4;   // 备注
}

// del_contact_req
syntax = "proto3";
package del_contact_req;

// 删除一个联系人 req
message DelContactRequest {
  string uid = 1;                    // 联系人ID
}

// find_one_contact_req
syntax = "proto3";
package find_one_contact_req;

// 查询一个联系人 req
message FindOneContactRequest {
  string uid = 1;                    // 联系人ID
}

        对于request就不需要为find_all_contact定制一个req请求,因为一旦收到这个指示,直接返回

find_all_contact的resp就行了。

          那么以上是"Client端"(之后就不再细讲了)和Server端都需要具备的数据交换格式信息。

入口函数main.cc:

Server_Storage:

         如何将数据进行持久化存储,其实也是需要考究的。在这里呢,我们不打算复杂化,介入Mysql数据库来管理这些通讯录信息,而是继续采用本地持续化存储的策略。

Contact.proto: 存储格式

syntax = "proto3";
package contacts;

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

  message Phone {
    string number = 1;     // 电话号码
    enum PhoneType {
      MP = 0;    // 移动电话
      TEL = 1;   // 固定电话
    }
    PhoneType type = 2;    // 类型
  }
  
  repeated Phone phone = 4;         // 电话
  map<string, string> remark = 5;   // 备注
}

// 通讯录
// <uid,信息>
message Contacts {
  map<string, PeopleInfo> contacts = 1;
}

 ContactMapper: 写入、读取文件的模块

#include "contact.pb.cc"
#include "../common/ContactException.h"
#include <fstream>
#define CONTACT_TXT "contacts.txt"
class ContactsMapper
{
public:
    void selectContacts(contacts::Contacts *contacts) const;
    void insertContacts(contacts::Contacts &contacts) const;
};

#include "ContactsMapper.h"
// 文件操作
void ContactsMapper::selectContacts(contacts::Contacts *con) const
{
    std::fstream input(CONTACT_TXT, std::ios::in | std::ios::binary);

    if(!con->ParseFromIstream(&input)){
        input.close();
        throw ContactException("ContactsMapper::selectContacts) Failed to parse contacts.");
    }

    input.close();
}

void ContactsMapper::insertContacts(contacts::Contacts &con) const
{
    // 写入
    std::fstream output(CONTACT_TXT,std::ios::out | std::ios::binary);
    if(!con.SerializeToOstream(&output)){
        output.close();
        throw ContactException("ContactsMapper::insertContacts) Failed to write contacts.");
    }

    output.close();
}

        

Server_Control:真正后台操控数据存储、读取的功能模块。

        我们使用的Client端、Server端互相定义的协议格式(req.proto\resp.proto),在这里进行处理。

add\buildPeopleInfo\printAddContactRequest:

// 仅仅用作 Server端查看
void ContactServer::printAddContactRequest(add_contact_req::AddContactRequest &request) const
{
    cout << "---> (ContactsServer::add) AddContactRequest:" << endl;
    cout << "姓名:" << request.name() << endl;
    cout << "年龄:" << request.age() << endl;
    for (auto &phone : request.phone())
    {
        int j = 1;
        cout << "电话" << j++ << ": " << phone.number();
        cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << endl;
    }

    if (request.remark_size())
    {
        cout << "备注信息: " << endl;
    }
    for (auto it = request.remark().cbegin(); it != request.remark().cend(); ++it)
    {
        cout << "    " << it->first << ": " << it->second << endl;
    }
}

void ContactServer::buildPeopleInfo(contacts::PeopleInfo *people, add_contact_req::AddContactRequest &req) const
{
    // 生成随机数 种子
    std::string uid = Util::generate_uid(10);
    // 简单的数据更新
    people->set_uid(uid);
    people->set_name(req.name());
    people->set_age(req.age());

    for (auto &phone : req.phone())
    {
        contacts::PeopleInfo_Phone *peo_phone = people->add_phone();
        peo_phone->set_number(phone.number());

        switch (phone.type())
        {
        case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP:
            peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_MP);
            break;
        case add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL:
            peo_phone->set_type(contacts::PeopleInfo_Phone_PhoneType::PeopleInfo_Phone_PhoneType_TEL);
            break;
        default:
            break;
        }
    }

    // 拷贝备注信息
    Util::map_copy(people->mutable_remark(), req.remark());
}

void ContactServer::add(add_contact_req::AddContactRequest &req, add_contact_resp::AddContactResponse *resp) const
{
    // 0.先打印提交上来的信息(可以忽略)
    printAddContactRequest(req);

    // 1.先读取已经存在的
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 2.转换为存入文件的消息对象
    google::protobuf::Map<std::string, contacts::PeopleInfo> *map_contact = con.mutable_contacts(); // 通过con 开辟新的空间
    contacts::PeopleInfo people;
    // 通过req 构建新的people字段
    buildPeopleInfo(&people, req);
    // {uid,message}
    map_contact->insert({people.uid(), people});

    // 3.向磁盘里写入
    contactsMapper.insertContacts(con);
    resp->set_uid(people.uid());
    resp->mutable_base_resp()->set_success(true);

    // 打印日志
    cout << "---> (ContactsServer::add) Success to write contacts." << endl;
}

del:

void ContactServer::del(del_contact_req::DelContactRequest &req, del_contact_resp::DelContactResponse *resp) const
{
    // 删除哪条记录
    cout << "---> (ContactsServer::del) DelContactRequest: uid: " << req.uid() << endl;
    // 1.先读取contacts 方便比对删除
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);
    // 不含uid 直接返回
    if (con.contacts().find(req.uid()) == con.contacts().end())
    {
        cout << "---> (ContactsServer::del) not find uid: " << req.uid() << endl;
        resp->set_uid(-1);
        // 设置返回报头
        resp->mutable_base_resp()->set_success(false);
        resp->mutable_base_resp()->set_error_desc("not be found");
        return;
    }

    // 含uid 删除用户
    con.mutable_contacts()->erase(req.uid());

    // 写回磁盘
    contactsMapper.insertContacts(con);

    // 构造resp
    resp->mutable_base_resp()->set_success(true);
    // 日志打印
    cout << "---> (ContactsServer::del) Success to del contact, uid: " << req.uid() << endl;
}

find_one\buildFindOneContactResponse:

void ContactServer::buildFindOneContactResponse(const contacts::PeopleInfo &people,
                                                find_one_contact_resp::FindOneContactResponse *resp) const
{
    if (nullptr == resp)
    {
        return;
    }
    // 设置信息
    resp->mutable_base_resp()->set_success(true);
    resp->set_uid(people.uid());
    resp->set_name(people.name());
    resp->set_age(people.age());

    // 将people里的phone 设置进resp中
    for (auto &phone : people.phone())
    {
        find_one_contact_resp::FindOneContactResponse_Phone* resp_phone = resp->add_phone();    
        resp_phone->set_number(phone.number());

        switch(phone.type())
        {
            case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP:
                resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_MP);
                break;
            case find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL:
                resp_phone->set_type(find_one_contact_resp::FindOneContactResponse_Phone_PhoneType::FindOneContactResponse_Phone_PhoneType_TEL);
        }
    }
}

void ContactServer::find_one(find_one_contact_req::FindOneContactRequest &req, find_one_contact_resp::FindOneContactResponse *resp) const
{
    // 读取原contact
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 获取map地图
    const google::protobuf::Map<std::string, contacts::PeopleInfo> &map_contacts = con.contacts();
    auto it = map_contacts.find(req.uid());
    // 不含uid 返回

    if (it == map_contacts.end())
    {
        cout << "---> (ContactServer::find_one) not found uid" << req.uid() << endl;
        resp->set_uid(req.uid());
        resp->mutable_base_resp()->set_success(false);
        resp->mutable_base_resp()->set_error_desc("查无此人");
        return;
    }

    // 含uid 构建resp
    buildFindOneContactResponse(it->second, resp);
    return;
}

find_all\buildFindAllContactsResponse:

void ContactServer::buildFindAllContactsResponse(contacts::Contacts &con,
                                                 find_all_contacts_resp::FindAllContactsResponse *resp) const
{
    if (resp == nullptr)
    {
        return;
    }

    resp->mutable_base_resp()->set_success(true);
    // 遍历 con内部
    for (auto it = con.contacts().cbegin(); it != con.contacts().cend(); ++it)
    {
        find_all_contacts_resp::PeopleInfo *people = resp->add_contacts();
        people->set_uid(it->first);
        people->set_name(it->second.name());
    }
}

void ContactServer::find_all(find_all_contacts_resp::FindAllContactsResponse *resp) const
{
    // 打印日志
    cout << "---> (ContactsServer::findAll) " << endl;

    // 获取通讯录
    contacts::Contacts con;
    contactsMapper.selectContacts(&con);

    // 转换为resp对象
    buildFindAllContactsResponse(con, resp);
}


        我们将Server功能实现模块的代码工作完成后,就可以填补Server_Entry的缺漏代码了。完成一次完整的客户端代码函数调用、响应的流程可以认为会走这几步流程:

● 创建req、resp对象(输出型参数)

● 将Request的内容进行反序列化到req当中

● 调用功能函数,传入(req,resp)

● resp的内容序列化到一个容器里

● 最后填入返回resp剩余信息 

#include <iostream>
#include "../../../../../../cpp-httplib/httplib.h"
#include "../server/ContactsServer.h"
#include "../common/ContactException.h"

#include "Request/add_contact_request.pb.h"
#include "Request/del_contact_request.pb.h"
#include "Request/find_one_contact_request.pb.h"

#include "Response/add_contact_response.pb.h"
#include "Response/find_one_contact_response.pb.h"
#include "Response/find_all_contacts_response.pb.h"
#include "Response/del_contact_response.pb.h"

using std::cerr;
using std::cout;
using std::endl;
using namespace httplib;

int main()
{
    cout << "---> 服务启动..." << endl;
    Server srv; // 创建服务器对象
    ContactServer contact_server; // 创建功能函数控制

    srv.Post("/contacts/add", [&contact_server](const Request &req, Response &resp) 
    {
        // 1.创建 输出型对象
        add_contact_req::AddContactRequest request;
        add_contact_resp::AddContactResponse response;
        // 功能函数可能出现异常情况
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse AddContactRequest error!");
            }

            // 3. 调用功能函数
            contact_server.add(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize AddContactResponse error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/add 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.Post("/contacts/del", [&contact_server](const Request &req, Response &resp) 
    {
        del_contact_req::DelContactRequest request;
        del_contact_resp::DelContactResponse response;
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse DelContactRequest  error!");
            }

            // 3. 调用功能函数
            contact_server.del(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize DelContactRequest error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/del 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }

    });

    srv.Get("/contacts/fine-one", [&contact_server](const Request &req, Response &resp) 
    {
        find_one_contact_req::FindOneContactRequest request;
        find_one_contact_resp::FindOneContactResponse response;
        try
        {
            // 2.反序列化
            if(!request.ParseFromString(req.body)){
                throw ContactException("Parse FindOneContactRequest  error!");
            }

            // 3. 调用功能函数
            contact_server.find_one(request,&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize FindOneContactRequest  error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/find_one 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.Get("/contacts/find-all", [&contact_server](const Request &req, Response &resp) 
    {
        find_all_contacts_resp::FindAllContactsResponse response;
        try
        {
            // 3. 调用功能函数
            contact_server.find_all(&response);

            // 4.resp的内容序列化到一个容器里 
            std::string resp_str;
            if(!response.SerializeToString(&resp_str)){
                throw ContactException("Serialize FindAllContactsResponse  error");
            }

            resp.body = resp_str;
            resp.set_header("Content-Type","application/protobuf");
            resp.status = 200;
        }
        catch(ContactException& e)
        {
            cerr << "---> /contacts/find_all 发现异常!!!" << endl;
            std::cout << e.what() << std::endl; 
            // 设置报头
            resp.status = 500;
            base_response::BaseResponse* baseResponse = response.mutable_base_resp();
            baseResponse->set_success(false);
            baseResponse->set_error_desc(e.what());

            std::string response_str;
            if(response.SerializeToString(&response_str))
            {
                // 写进正文
                resp.body = response_str;
                resp.set_header("Content-Type","application/protobuf");
            }
        }
    });

    srv.listen("0.0.0.0", 8085);
    return 0;
}


(4) Client端

        Client端入口,很明显写了一个很挫的交互页面。 

Client_Control:

addContact/buildAddContactRequest:

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

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

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

        std::cout << "选择此电话类型 (1、移动电话   2、固定电话) : ";
        int type;
        std::cin >> type;
        std::cin.ignore(256, '\n');
        switch (type)
        {
        case 1:
            phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_MP);
            break;
        case 2:
            phone->set_type(add_contact_req::AddContactRequest_Phone_PhoneType::AddContactRequest_Phone_PhoneType_TEL);
            break;
        default:
            std::cout << "----非法选择,使用默认值!" << std::endl;
            break;
        }
    }

    for (int i = 1;; i++)
    {
        std::cout << "请输入备注" << i << "标题 (只输入回车完成备注新增): ";
        std::string remark_key;
        getline(std::cin, remark_key);
        if (remark_key.empty())
        {
            break;
        }

        std::cout << "请输入备注" << i << "内容: ";
        std::string remark_value;
        getline(std::cin, remark_value);
        req->mutable_remark()->insert({remark_key, remark_value});
    }
}

void ContactServer::addContact()
{
    // 进行http的连接建立
    Client cli(port);

    // 构建 request 请求
    add_contact_req::AddContactRequest req;
    buildAddContactRequest(&req);

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

    // 发起Post请求
    auto res = cli.Post("contact/add",req_str,"applicationi/protbuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/add 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    // 反序列化Response
    add_contact_resp::AddContactResponse resp;
    bool parse = resp.ParseFromString(res->body);

    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        // 处理服务异常
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 处理结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/add/' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    std::cout << "---> 新增联系人成功,联系人ID: " << resp.uid() << std::endl;
}

del:

void ContactServer::delContact()
{
    httplib::Client cli(port);

    del_contact_req::DelContactRequest req;
    std::cout << "请输入要删除的联系人id: ";
    std::string uid;
    getline(std::cin, uid);
    req.set_uid(uid);

    // 反序列化
    std::string req_str;
    if (!req.SerializeToString(&req_str))
    {
        throw ContactException("DelContactRequest序列化失败!");
    }

    // 发起Post请求
    auto res = cli.Post("contact/del", req_str, "application/protobuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/del 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    // 反序列化
    del_contact_resp::DelContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/del' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/del' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/del' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

     // 正常返回,打印结果
    std::cout << "---> 成功删除联系人,被删除的联系人ID为:" << resp.uid() << std::endl;
}

find_all_Contact\printFindAllContactsResponse:

void ContactServer::printFindAllContactsResponse(find_all_contacts_resp::FindAllContactsResponse &resp)
{
    if (0 == resp.contacts_size())
    {
        std::cout << "还未添加任何联系人" << std::endl;
        return;
    }
    for (auto contact : resp.contacts())
    {
        std::cout << "联系人姓名: " << contact.name() << " 联系人ID: " << contact.uid() << std::endl;
    }
}

void ContactServer::find_all_Contact()
{
    httplib::Client cli(port);

    // 查询所有联系人没有需要填写的 req报头
    auto res = cli.Get("/contacts/find-all");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/find-all 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    find_all_contacts_resp::FindAllContactsResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("get '/contacts/find-all' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        // 服务端异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-all' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-all' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    printFindAllContactsResponse(resp);
}

find_one_Contact\printFindOneContactResponse:

void ContactServer::printFindOneContactResponse(find_one_contact_resp::FindOneContactResponse &resp)
{
    std::cout << "姓名:" << resp.name() << std::endl;
    std::cout << "年龄:" << resp.age() << std::endl;
    for (auto &phone : resp.phone())
    {
        int j = 1;
        std::cout << "电话" << j++ << ": " << phone.number();
        std::cout << "  (" << phone.PhoneType_Name(phone.type()) << ")" << std::endl;
    }
    if (resp.remark_size())
    {
        std::cout << "备注信息: " << std::endl;
    }
    for (auto it = resp.remark().cbegin(); it != resp.remark().cend(); ++it)
    {
        std::cout << "    " << it->first << ": " << it->second << std::endl;
    }
}

void ContactServer::find_one_Contact()
{
    httplib::Client cli(port);
    // 构建 request 请求
    find_one_contact_req::FindOneContactRequest req;
    std::cout << "请输入要查询的联系人id: ";
    std::string uid;
    getline(std::cin, uid);
    req.set_uid(uid);

    // 序列化 request
    std::string req_str;
    if (!req.SerializeToString(&req_str))
    {
        throw ContactException("FindOneContactRequest序列化失败!");
    }

    auto res = cli.Post("/contacts/find-one", req_str, "application/protobuf");
    if (!res)
    {
        std::string err_desc;
        err_desc.append("/contacts/find-one 链接错误!错误信息:")
            .append(httplib::to_string(res.error()));
        throw ContactException(err_desc);
    }

    find_one_contact_resp::FindOneContactResponse resp;
    bool parse = resp.ParseFromString(res->body);
    // 处理异常
    if (res->status != 200 && !parse)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 失败:")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")");
        throw ContactException(err_desc);
    }
    else if (res->status != 200)
    {
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 失败 ")
            .append(std::to_string(res->status))
            .append("(")
            .append(res->reason)
            .append(")  错误原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }
    else if (!resp.base_resp().success())
    {
        // 结果异常
        std::string err_desc;
        err_desc.append("post '/contacts/find-one' 结果异常:")
            .append("异常原因:")
            .append(resp.base_resp().error_desc());
        throw ContactException(err_desc);
    }

    // 正常返回,打印结果
    std::cout << "---> 查询到联系人ID为: " << resp.uid() << " 的信息:" << std::endl;
    printFindOneContactResponse(resp);
}

(5) 测试

        我们分别编译完client与server文件。

        我们举例插入一名通讯录人员小王,

server端:

 

client端:

        我们可以通过其id查找到该用户信息。

        再次新增或用户后,查看详细联系人列表。

         

        同样,也可以按照id对用户进行删除      

         本代码功能演示也就这样。


        

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

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

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

相关文章

uniapp 微信小程序 自定义弹框+picker下拉选择列表+输入表单:拒绝-选择理由弹窗

效果&#xff1a; 1、template <!-- 拒绝-选择理由弹窗--> <view class"reason-popover" v-if"showReasonDialog"><view class"reason-modal"><view class"reason-title"><text>请选择拒绝理由<…

文心一言 VS 讯飞星火 VS chatgpt (64)-- 算法导论6.5 3题

文心一言 VS 讯飞星火 VS chatgpt &#xff08;64&#xff09;-- 算法导论6.5 3题 三、要求用最小堆实现最小优先队列&#xff0c;请写出 HEAP-MINIMUM、HEAP-EXTRACT-MIN、HEAP DECREASE-KEY 和 MIN-HEAP-INSERT 的伪代码。 文心一言&#xff1a; 以下是使用最小堆实现最小…

界面控件DevExpress WPF数据编辑器组件,让数据处理更灵活!(二)

界面控件DevExpress WPF编辑器库可以帮助用户提供直观的用户体验&#xff0c;具有无与伦比的运行时选项和灵活性。WPF数据编辑器提供了全面的掩码和数据验证支持&#xff0c;可以独立使用&#xff0c;也可以作为容器控件(如DevExpress WPF Grid和WPF TreeList)中的单元格编辑器…

ubantu 安装 sudo apt install ubuntu-restricted-extras. 如何 OK

在安装的时候&#xff0c;不知道如何 Ok 使用 回车键&#xff0c;空格键 均不行&#xff0c;使用 Tab 键 &#xff0c;然后再使用 回车键。 Configuring ttf-mscorefonts-installer 答案是使用 Tab 键。

【Python】pyecharts 模块 ③ ( 使用 pyecharts 模块绘制折线图 )

文章目录 一、使用 pyecharts 模块绘制折线图1、折线图绘制过程2、完整代码示例 pyecharts 画廊网站 : https://gallery.pyecharts.org/#/ 在该网站可查看官方示例 一、使用 pyecharts 模块绘制折线图 1、折线图绘制过程 首先 , 导入 折线图 Line 对象 , 该类定义在 pyecharts…

Go http.Get不需要defer res.Body.Close()

前戏&#xff1a; go net/http包&#xff0c;必须要手动关闭嘛&#xff1f;非也。线上程序为啥协程数量直线上升&#xff0c;因为使用的姿势不对&#xff0c;请换个姿势。 干货&#xff1a; 手动关闭&#xff0c;释放资源 defer res.Body.Close() &#xff08;这是一个好习…

LeetCode[剑指Offer51]数组中的逆序对

难度&#xff1a;Hard 题目&#xff1a; 在数组中的两个数字&#xff0c;如果前面一个数字大于后面的数字&#xff0c;则这两个数字组成一个逆序对。输入一个数组&#xff0c;求出这个数组中的逆序对的总数。 示例 1: 输入: [7,5,6,4] 输出: 5 限制&#xff1a; 0 < 数组…

C++【STL】queue和deque 容器详解

C【STL】queue和deque 容器详解 一级目录 二级目录 三级目录 1. 什么是queue容器&#xff1f; Queue是一种先讲先出( First In First Out,FIFO )的数据结构&#xff0c;它有两个出口。 queue模版类的定义在头文件中。 include 定义queue对象的示例代码如下∶ queue<i…

【华为c# OD机考参考答案】01---IPv4地址转换成整数

题目 1、题目 01---IPv4地址转换成整数2、解图思路 1、IP地址转为二进制 2、二进制转十进制 3、注意事项 1、IP地址的范围判断 2、空字符串判断 3、非法字符判断 4、考点 1、string的split 、convert等相关用法 2、正则表达式 3、进制转换 4、理解32位整数的意思 5、代码 判…

2023华为OD统一考试(B卷)题库清单(持续收录中)以及考点说明

目录 专栏导读2023 B卷 “新加题”&#xff08;100分值&#xff09;2023Q2 100分2023Q2 200分2023Q1 100分2023Q1 200分2022Q4 100分2022Q4 200分牛客练习题 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#xff09;》。 刷的越多&…

【实用工具】-Git+GitLab

1. Git介绍 1.1 什么是Git&#xff1f; Git是一个分布式版本控制系统&#xff0c;用于跟踪和管理项目代码的变化。它是由Linus Torvalds于2005年创建的&#xff0c;旨在帮助开发者更好地协作、追踪代码的更改&#xff0c;并轻松地回滚到之前的版本。 Git相比传统的集中式版本…

网络安全 Day17-计算机网络知识02

计算机网络知识02 1. 交换机2. 网络知识名词3. ARP3.1 ARP通讯原理3.2 arp欺骗3.3 ARP欺骗与预防3.4 排查ARP病毒 4. 路由器 1. 交换机 什么是交换机 实现一个网络内多台主机之间通讯的设备用于电信号转发和放大的网络设备 常见的交换机&#xff1a;以太网交换机&#xff0c;电…

【Java】SpringBoot下写一个全局捕获异常的怎么实现?

文章目录 前言一、全局异常处理方式一1.1 自定义全局异常类1.2 手动抛出异常1.3 测试打印 二、全局异常处理方式二2.1 定义基础接口类2.2 定义枚举类2.3 自定义异常类2.4 自定义数据传输2.5 自定义全局异常处理2.6 测试代码 总结 前言 在日常项目开发中&#xff0c;异常是常见…

Nginx 用户指南:安装、配置和基本使用

前言 本用户指南将帮助您了解如何安装、配置和基本使用 Nginx。Nginx 是一款高性能的开源 Web 服务器和反向代理服务器&#xff0c;具有强大的性能和灵活性&#xff0c;可以满足各种 Web 服务器需求。 安装 Nginx a. 在 Ubuntu 上安装&#xff1a; $ sudo apt update $ sud…

(转载)神经网络遗传算法函数极值寻优(matlab实现)

本博客的完整代码获取&#xff1a; https://www.mathworks.com/academia/books/book106283.html 1案例背景 对于未知的非线性函数,仅通过函数的输入输出数据难以准确寻找函数极值。这类问题可以通过神经网络结合遗传算法求解,利用神经网络的非线性拟合能力和遗传算法的非线性…

【前端动画】科技感扫描效果 css动画animation

扫描出现动画 参考了网友写的二维码扫描 <template><div><div class"scan-content"><img style"width: 2rem;height: 2rem;" src"../../assets/images/eye.png" alt"" /><div class"line">…

四个现实中的商品样例,帮助你理解如何使用css【前端CSS入门样例】

实现商品列表 <!DOCTYPE html> <html><head><meta charset"utf-8"><title>商品列表图片</title><style>.row > img {width: 15%;}</style></head><body><div class"row"><img sr…

【NOSQL】MongoDB

MongoDB MongoDB简介体系结构Linux系统中的安装启动和连接&#xff08;1&#xff09;先到官网下载压缩包——>解压——>重命名新建几个目录&#xff0c;分别用来存储数据和日志&#xff1a;新建并修改配置文件官网下载MongoDB Compass MongoDB简介 MongoDB是一个开源、高…

从零开始学习 Java:简单易懂的入门指南(二)

Java基础语法 1. 注释1.1使用的技巧1.2注意点 2. 关键字2.1 概念2.2 第一个关键字class 3. 字面量3.1区分技巧 4. 变量4.1 什么是变量&#xff1f;4.2 变量的定义格式4.2.1 格式详解4.2.2 常用的数据类型4.2.3 变量的注意事项 4.3 变量的练习 5. 数据类型5.1 Java语言数据类型的…

CentOS7系统MBR、GRUB2、内核启动流程报错问题

目录 &#x1f969;Linux启动流程 &#x1f969;MBR修复 &#x1f36d;1、模拟损坏 &#x1f36d;2、重启测试 &#x1f36d;3、修复MBR &#x1f36d;4、测试系统 &#x1f969;GRUB2修复 &#x1f36d;1、模拟损坏 &#x1f36d;2、修复GRUB2 &#x1f36d;3、测试系统 &…