【网络编程】协议定制+Json序列化与反序列化

news2024/9/17 7:37:08


目录

一、序列化与反序列化的概念

二、自定义协议设计一个网络计算器

2.1TCP协议,如何保证接收方收到了完整的报文呢?

2.2自定义协议的实现

2.3自定义协议在客户端与服务器中的实现

三、使用Json进行序列化和反序列化

3.1jsoncpp库的安装

3.2改造自定义协议

3.3自定义协议的命名区分


网络版计算器代码可参考博主gitee。

一、序列化与反序列化的概念

        序列化是指将对象转换为字节流或其他可存储或传输的格式,以便将其存储在文件中或通过网络发送到另一个系统。反序列化是指将序列化的数据重新转换为对象。在序列化和反序列化过程中,对象的状态信息被保存和恢复,以保证数据的完整性和正确性。在分布式系统中,序列化和反序列化是实现远程方法调用和消息传递的重要技术。

        例如微信发送一条消息,会将头像、昵称、消息内容、发送时间等“结构化”数据进行序列化,形成一个字节流报文,通过网络将该报文发送给接收方,接收方进行反序列化将报文重新拆解为头像、昵称、消息内容、发送时间等“结构化”数据。

二、自定义协议设计一个网络计算器

2.1TCP协议,如何保证接收方收到了完整的报文呢?

1、我们调用的所有发送/接收函数,并不是直接从网络中发送/接收数据,应用层调用的发送/接收函数,本质是一个拷贝函数。
例如客户端发送数据时,应用层调用发送函数将会把应用层的发送缓冲区数据拷贝至传输层的发送缓冲区。传输层自主决定何时将发送缓冲区的数据发送至网络里,再通过网络发送至服务器的接收缓冲区中,所以TCP协议是一种传输控制协议。

2、TCP协议的通信双方的发送缓冲区和接收缓冲区互不干扰,可以双向同时进行通信。TCP是一种全双工的通信协议。

3、如果TCP服务器的读取速度跟不上客户端的发送速度,将会导致服务器接收缓冲区积攒大量的报文,这些报文数据可是一连串的粘连在一起的,如何一条一条的将完整的报文提取出来呢?使用协议!协议设计方式:

  • 定长(例如规定该报文定长为1024字节)
  • 特殊符号(在报文和报文之间增加特殊符号)
  • 自描述方式(自己设计协议)

本文代码协议设计如下图所示:使用该协议设计一个网络计算器。

        如果是UDP协议,UDP客户端,发送报文时只需创建请求,对请求进行序列化后即可发送;接收报文时只需将接收的数据进行反序列化即可。无需进行协议内容的添加与解析。这是因为UDP每次发送与接收都是以数据报的形式,数据是完整的,不像TCP是面向字节流,需要使用相关的协议进行界定报文边界。

2.2自定义协议的实现

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
enum
{
    OK=0,
    DIV_ZERO_ERR,
    MOD_ZERO_ERR,
    OP_ZERO_ERR,
};
#define SEP " "
#define SEP_LEN strlen(SEP)//不能使用sizeof,用sizeof会统计到'\0'
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
//"_exitcode result" -> "content_len"\r\n"_exitcode result"\r\n
//"_x _op _y" -> "content_len"\r\n"_x _op _y"\r\n
std::string enLength(const std::string& text)//text:_x _op _y。添加协议规则,用于构建一个完整的报文(类似"打包")
{
    std::string send_string=std::to_string(text.size());//计算有效载荷的长度"_x _op _y"
    send_string+=LINE_SEP;
    send_string+=text;
    send_string+=LINE_SEP;
    return send_string;
}
//_exitcode result
bool deLength(const std::string& package,std::string* text)//获取报文中的有效载荷(类似"解包")
{
    auto pos=package.find(LINE_SEP);
    if(pos==std::string::npos){return false;}
    int textLen=std::stoi(package.substr(0,pos));//计算有效载荷的长度
    *text=package.substr(pos+LINE_SEP_LINE,textLen);
    return true;
}
class Request//请求类
{
public:
    Request(int x,int y,char op)
        :_x(x)
        ,_y(y)
        ,_op(op)
    {}
    Request()
        :_x(0)
        ,_y(0)
        ,_op(0)
    {}
    bool serialize(std::string* out)//序列化,将成员变量转字符串
    {
        //结构化->"_x _op _y"
        *out="";//清空string对象
        std::string x_tostring=std::to_string(_x);
        std::string y_tostring=std::to_string(_y);
        *out=x_tostring+SEP+_op+SEP+y_tostring;//_x _op _y
        return true;
    }
    bool deserialize(const std::string& in)//反序列化
    {
        //"_x _op _y"->结构化
        auto leftSpace=in.find(SEP);//左边的空格
        auto rightSpace=in.rfind(SEP);//右边的空格
        if(leftSpace==std::string::npos||rightSpace==std::string::npos){return false;}
        if(leftSpace==rightSpace){return false;} 
        //子串提取
        std::string x_tostring=in.substr(0,leftSpace);
        if(rightSpace-(leftSpace+SEP_LEN)!=1){return false;}//表示操作符一定只占1位
        _op=in.substr(leftSpace+SEP_LEN,rightSpace-(leftSpace+SEP_LEN))[0];
        std::string y_tostring=in.substr(rightSpace+SEP_LEN);
        //对x,y进行转换
        _x=std::stoi(x_tostring); 
        _y=std::stoi(y_tostring);
        return true;
    }
public:
    //_x _op _y
    int _x;//左操作数
    int _y;//右操作数
    char _op;//操作符
};

class Response//响应类
{
public:
    Response()
        :_exitCode(0)
        ,_result(0)
    {}
    Response(int exitCode,int result)
        :_exitCode(exitCode)
        ,_result(result)
    {}
    bool serialize(std::string* out)//序列化,将成员变量转string对象
    {
        *out="";//清空string对象
        std::string outString=std::to_string(_exitCode)+SEP+std::to_string(_result);
        *out=outString;
        return true; 
    }
    bool deserialize(const std::string& in)//反序列化
    {
        auto space=in.find(SEP);//找空格
        if(space==std::string::npos){return false;}
        std::string exitString=in.substr(0,space);
        std::string resString=in.substr(space+SEP_LEN);
        if(exitString.empty()||resString.empty()){return false;}//一个字符串为空就false
        _exitCode=std::stoi(exitString);
        _result=std::stoi(resString);
        return true;
    }
public:
    int _exitCode;//0表示计算成功,非零代表除零等错误
    int _result;//运算结果
};

bool recvPackage(int sock,std::string& inbuffer,std::string* text)//服务器/客户端读取报文
{
    //将缓冲区数据拆分成一个个报文"content_len"\r\n"_x _op _y"\r\n
    char buffer[1024];
    while(1)
    {
        ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//阻塞式读取,等价于read接口
        if(n>0)
        {
            buffer[n]=0;//字符串末尾添加'\0'
            inbuffer+=buffer;
            //拆分成一个个报文
            auto pos=inbuffer.find(LINE_SEP);//找\r\n的起始位置
            if(pos==std::string::npos)//没找到说明暂时还没找到\r\n分隔符,跳过本次循环,等待下次读取
            {
                continue;
            }
            std::string textLenString=inbuffer.substr(0,pos);
            int textLen=std::stoi(textLenString);//拿出有效载荷的长度
            int totalLen=textLenString.size()+2*LINE_SEP_LINE+textLen;//单个报文总长度
            if(inbuffer.size()<totalLen)//说明缓冲区长度还不到一个报文大小,需要跳过本次循环继续读取
            {
                continue;
            }
            std::cout<<"截取报文前inbuffer中的内容:\n"<<inbuffer<<std::endl;
            //走到这里,一定有一个完整的报文
            *text=inbuffer.substr(0,totalLen);//取出一个报文
            inbuffer.erase(0,totalLen);//删掉缓冲区中刚刚被提取走的报文数据
            std::cout<<"截取报文后inbuffer中的内容:\n"<<inbuffer<<std::endl;
            break;
        }
        else
        {
            return false;
        }
    }
    return true;
}

代码解释:

本协议设计了一个Request请求类和一个Response响应类,请求类存储计算器的操作数和操作符,响应类存储计算结果和退出码。这两个类中各自实现了对有效载荷的序列化与反序列化的接口。

enLength用于给有效载荷添加报头,即自定义协议的规则。deLength则用于解析收到的报文,剔除报文中的协议报头,提取出其中的有效载荷。协议是明确缓冲区中报文与报文之间边界的一种特殊格式,而enLength和deLength用于添加报头和去掉报头

2.3自定义协议在客户端与服务器中的实现

三、使用Json进行序列化和反序列化

3.1jsoncpp库的安装

        从上方代码可以看到,使用string对象手动进行序列化与反序列化非常麻烦。可以使用Json进行序列化与反序列化操作。

        Json(JavaScript Object Notation)是一种轻量级的数据交换格式,常用于Web应用程序中的数据传输。它是一种基于文本的格式,易于读写和解析。Json格式的数据可以被多种编程语言支持,包括JavaScript、Python、Java、C#、C++等。Json数据由键值对组成,使用大括号表示对象,使用方括号表示数组。

        C++使用Json需要包含Jsoncpp库,yum安装Jsoncpp库指令:先执行第二句,如果报错再执行第一句!

sudo mv /var/lib/rpm/__db.00* /tmp/&&yum clean all
sudo yum install -y jsoncpp-devel

3.2改造自定义协议

makefile:使用jsoncpp库记得在编译时加上-ljsoncpp

cc=g++#将cc变量设置为g++编译器
LD=#-DMYSELF
.PHONY:all
all:calClient calServer

calClient:calClient.cc
	$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
calServer:calServer.cc
	$(cc) -o $@ $^ -std=c++11 -ljsoncpp ${LD}
	
.PHONY:clean
clean:
	rm -f calClient calServer

改造的协议: 

#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <cstring>
#include <jsoncpp/json/json.h>
#include <sys/types.h>
#include <sys/socket.h>
enum
{
    OK=0,
    DIV_ZERO_ERR,
    MOD_ZERO_ERR,
    OP_ZERO_ERR,
};
#define SEP " "
#define SEP_LEN strlen(SEP)//不能使用sizeof,用sizeof会统计到'\0'
#define LINE_SEP "\r\n"
#define LINE_SEP_LINE strlen(LINE_SEP)
//"_exitcode result" -> "content_len"\r\n"_exitcode result"\r\n
//"_x _op _y" -> "content_len"\r\n"_x _op _y"\r\n
std::string enLength(const std::string& text)//text:_x _op _y。添加协议规则,用于构建一个完整的报文(类似"打包")
{
    std::string send_string=std::to_string(text.size());//计算有效载荷的长度"_x _op _y"
    send_string+=LINE_SEP;
    send_string+=text;
    send_string+=LINE_SEP;
    return send_string;
}
//_exitcode result
bool deLength(const std::string& package,std::string* text)//获取报文中的有效载荷(类似"解包")
{
    auto pos=package.find(LINE_SEP);
    if(pos==std::string::npos){return false;}
    int textLen=std::stoi(package.substr(0,pos));//计算有效载荷的长度
    *text=package.substr(pos+LINE_SEP_LINE,textLen);
    return true;
}
class Request//请求类
{
public:
    Request(int x,int y,char op)
        :_x(x)
        ,_y(y)
        ,_op(op)
    {}
    Request()
        :_x(0)
        ,_y(0)
        ,_op(0)
    {}
    bool serialize(std::string* out)//序列化,将成员变量转字符串
    {
#ifdef MYSELF
        //结构化->"_x _op _y"
        *out="";//清空string对象
        std::string x_tostring=std::to_string(_x);
        std::string y_tostring=std::to_string(_y);
        *out=x_tostring+SEP+_op+SEP+y_tostring;//_x _op _y
#else
        //Json序列化
        Json::Value root;//Json::Value万能对象,可接收任何对象
        root["first"]=_x;//自动将_x转换为字符串
        root["second"]=_y;
        root["oper"]=_op;
        //序列化
        Json::FastWriter writer;//Json::StyledWriter write;等价
        *out=writer.write(root);//将root进行序列化,返回值为string对象,接收即可
#endif
        return true;
    }
    bool deserialize(const std::string& in)//反序列化
    {
#ifdef MYSELF
        //"_x _op _y"->结构化
        auto leftSpace=in.find(SEP);//左边的空格
        auto rightSpace=in.rfind(SEP);//右边的空格
        if(leftSpace==std::string::npos||rightSpace==std::string::npos){return false;}
        if(leftSpace==rightSpace){return false;} 
        //子串提取
        std::string x_tostring=in.substr(0,leftSpace);
        if(rightSpace-(leftSpace+SEP_LEN)!=1){return false;}//表示操作符一定只占1位
        _op=in.substr(leftSpace+SEP_LEN,rightSpace-(leftSpace+SEP_LEN))[0];
        std::string y_tostring=in.substr(rightSpace+SEP_LEN);
        //对x,y进行转换
        _x=std::stoi(x_tostring); 
        _y=std::stoi(y_tostring);
#else
        //Json反序列化
        Json::Value root;//Json::Value万能对象,可接收任何对象
        Json::Reader reader;
        reader.parse(in,root);//第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中
        //反序列化
        _x=root["first"].asInt();//默认是字符串,转换为整型
        _y=root["second"].asInt();
        _op=root["oper"].asInt();//转换为整型,整型可以给char类型。
#endif
        return true;
    }
public:
    //_x _op _y
    int _x;//左操作数
    int _y;//右操作数
    char _op;//操作符
};

class Response//响应类
{
public:
    Response()
        :_exitCode(0)
        ,_result(0)
    {}
    Response(int exitCode,int result)
        :_exitCode(exitCode)
        ,_result(result)
    {}
    bool serialize(std::string* out)//序列化,将成员变量转string对象
    {
#ifdef MYSELF
        *out="";//清空string对象
        std::string outString=std::to_string(_exitCode)+SEP+std::to_string(_result);
        *out=outString;
#else
        //Json序列化
        Json::Value root;//Json::Value万能对象,可接收任何对象
        root["exitCode"]=_exitCode;//自动将_exitCode转换为字符串
        root["result"]=_result;
        //序列化
        Json::FastWriter writer;//Json::StyledWriter write;等价
        *out=writer.write(root);//将root进行序列化,返回值为string对象,接收即可
#endif
        return true; 
    }
    bool deserialize(const std::string& in)//反序列化
    {
#ifdef MYSELF
        auto space=in.find(SEP);//找空格
        if(space==std::string::npos){return false;}
        std::string exitString=in.substr(0,space);
        std::string resString=in.substr(space+SEP_LEN);
        if(exitString.empty()||resString.empty()){return false;}//一个字符串为空就false
        _exitCode=std::stoi(exitString);
        _result=std::stoi(resString);
#else
        //Json反序列化
        Json::Value root;//Json::Value万能对象,可接收任何对象
        Json::Reader reader;
        reader.parse(in,root);//第一个参数:解析哪个流;第二个参数:将解析的数据存放到对象中
        //反序列化
        _exitCode=root["exitCode"].asInt();//默认是字符串,转换为整型
        _result=root["result"].asInt();
#endif
        return true;
    }
public:
    int _exitCode;//0表示计算成功,非零代表除零等错误
    int _result;//运算结果
};

bool recvPackage(int sock,std::string& inbuffer,std::string* text)//服务器/客户端读取报文
{
    //将缓冲区数据拆分成一个个报文"content_len"\r\n"_x _op _y"\r\n
    char buffer[1024];
    while(1)
    {
        ssize_t n=recv(sock,buffer,sizeof(buffer)-1,0);//阻塞式读取,等价于read接口
        if(n>0)
        {
            buffer[n]=0;//字符串末尾添加'\0'
            inbuffer+=buffer;
            //拆分成一个个报文
            auto pos=inbuffer.find(LINE_SEP);//找\r\n的起始位置
            if(pos==std::string::npos)//没找到说明暂时还没找到\r\n分隔符,跳过本次循环,等待下次读取
            {
                continue;
            }
            std::string textLenString=inbuffer.substr(0,pos);
            int textLen=std::stoi(textLenString);//拿出有效载荷的长度
            int totalLen=textLenString.size()+2*LINE_SEP_LINE+textLen;//单个报文总长度
            if(inbuffer.size()<totalLen)//说明缓冲区长度还不到一个报文大小,需要跳过本次循环继续读取
            {
                continue;
            }
            std::cout<<"截取报文前inbuffer中的内容:\n"<<inbuffer<<std::endl;
            //走到这里,一定有一个完整的报文
            *text=inbuffer.substr(0,totalLen);//取出一个报文
            inbuffer.erase(0,totalLen);//删掉缓冲区中刚刚被提取走的报文数据
            std::cout<<"截取报文后inbuffer中的内容:\n"<<inbuffer<<std::endl;
            break;
        }
        else
        {
            return false;
        }
    }
    return true;
}
bool recvPackageAll(int sock,std::string& inbuffer,std::vector<std::string>* out)
{
    std::string line; 
    while(recvPackage(sock,inbuffer,&line))
    {
        out->push_back(line);
    }
}

3.3自定义协议的命名区分

        未来在一套系统中可以自定义多种协议,为了区分不同的自定义协议,可以参照如下格式设计协议的格式:

当然前人已经设计好了常见的网络协议,例如http/https。

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

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

相关文章

学系统集成项目管理工程师(中项)系列26_新兴信息技术

1. 云计算 1.1. 基于互联网的超级计算模式&#xff0c;通过互联网来提供大型计算能力和动态易扩展的虚拟化资源 1.2. 通过网络提供可动态伸缩的廉价计算能力 1.3. 特点 1.3.1. 【19上选23】 1.3.2. 超大规模 1.3.3. 虚拟化 1.3.4. 高可靠性 1.3.5. 通用性 1.3.6. 高可…

linux共享内存总结

共享内存函数由shmget、shmat、shmdt、shmctl四个函数组成 头文件&#xff1a; #include <sys/ipc..h> #include<sys/shm.h> // 创建或获取一个共享内存: 成功返回共享内存ID&#xff0c;失败返回-1 int shmget (key_t key, size_t_size, int flag); // 连接共享内…

Java修饰符

4 修饰符(static关键字) 4.1 权限修饰符 4.2 状态修饰符 final(最终态)static(静态)4.2.1 final的特点 final 关键字是最终的意思,可以修饰成员变量,成员方法,类final修饰的特点: 1.修饰方法:表示该方法是最终方法,不能被重写2.修饰变量:表示该变量是常量,不能被…

深入了解Nginx:高性能的开源Web服务器与反向代理

一、Nginx是什么 Nginx是一款轻量级的Web 服务器/反向代理服务器及电子邮件&#xff08;IMAP/POP3&#xff09;代理服务器&#xff0c;也可以作为负载均衡器和HTTP缓存服务器使用。它采用事件驱动、异步非阻塞的处理方式&#xff0c;能够处理大量并发连接和高流量负载&#xff…

推荐试试这个简单好用的手机技巧

技巧一&#xff1a;一键锁屏 除了按住手机电源键进行锁屏外&#xff0c;还有其他一些快捷方法可以实现锁屏操作。 对于苹果手机用户&#xff0c;可以按照以下步骤进行设置&#xff1a; 1.打开手机的设置应用&#xff0c;通常可以在主屏幕或应用列表中找到该图标。 2.在设置…

chatgpt赋能python:Pythonunittest跳过用例:使用unittest中跳过测试用例的方法

Python unittest 跳过用例&#xff1a;使用unittest中跳过测试用例的方法 如果你正在开发一个Python项目&#xff0c;你可能已经使用了Python的unittest模块来编写并运行测试用例。在编写测试用例时&#xff0c;有些情况下你可能不想运行某些测试用例&#xff0c;这时就需要使…

Window10安装SQL Server

一、安装SQL Server 1、进入官网根据个人所需下载对应版本即可&#xff0c;本文是基于SQL Server 2022 Express的安装过程 SQL Server 下载 | Microsofthttps://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2、下载完毕&#xff0c;运行安装指引程序 如若不熟悉…

LC-3机器码编程实验 求成绩等级

一、实验目的 分析和理解指定的需解决问题。利用LC-3的汇编代码设计实现相关程序。通过LC-3仿真器调试和运行相关程序并得到正确的结果。 二、实验内容 对学生的成绩使用数组进行排序。 背景&#xff1a;一位老师需要你帮忙决定学生的成绩&#xff0c;她想要根据学生分数在…

【分立元件】MOSFET如何用于同步整流

在电力电子中我们会使用二极管做开关,当二极管导时,相当于开关闭合,当二极管截止时,相当于开关断开。但是二极管在导通时的管压降在低压电源电路中是一个损耗来源,所以一般我们首选使用的是肖特基二极管,因为肖特基二极管的管压降比较低。 如下所示为非同步BUCK电源拓朴…

小黑坐等政审,论文成果毕业事项基本提交,着手眼睛手术明天准备体检然后出发独自夜爬华山的leetcode之旅:81. 搜索旋转排序数组 II

去除相等的二分法 class Solution:def search(self, nums: List[int], target: int) -> bool:# 数组长度n len(nums)# 双指针left 0right n-1# 二分法迭代while left < right:mid (left right) // 2if nums[mid] target:return Trueif nums[left] nums[mid]:left…

原来CSS的登录界面可以变得这么好看

个人名片&#xff1a; &#x1f60a;作者简介&#xff1a;一名大一在校生&#xff0c;web前端开发专业 &#x1f921; 个人主页&#xff1a;几何小超 &#x1f43c;座右铭&#xff1a;懒惰受到的惩罚不仅仅是自己的失败&#xff0c;还有别人的成功。 &#x1f385;**学习目…

Python之将日志写入到文件(二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

【MySQL 数据库】4、MySQL 事务学习

目录 一、事务简介二、事务相关 SQL 语句(1) 事务提交方式(2) 开启、提交、回滚事务 三、事务的四大特性四、并发事务问题五、事务的隔离级别 一、事务简介 二、事务相关 SQL 语句 (1) 事务提交方式 # 查看当前数据库事务的提交方式 # 1: 自动提交 # 0: 手动提交 select autoc…

解决:在单项目组件里面引入 base.scss/ base.less 等的外部文件不成功的问题

1、问题展示&#xff1a; 其一、问题描述&#xff1a; 在单文件组件里面使用封装在 base.scss 或 base.less 里面的样式用法一直不成功&#xff1b; 其二、代码&#xff1a; // 虽然已经标明了用的是 scss 的语法&#xff0c;但是页面调用 .scss 里的 style 样式还是不成功&a…

【Android学习】Android studio环境搭建-解决下载gradle慢加载mainfest.xml慢的问题

转载&#xff1a;https://blog.csdn.net/qq_31881469/article/details/78646406/ 目录 1、解决网络连接问题 &#xff08;1&#xff09;问题描述 &#xff08;2&#xff09;方法步骤 2、解决内存吃紧问题 &#xff08;1&#xff09;问题描述 &#xff08;2&#xff…

chatgpt赋能python:Python的SEO优化

Python的SEO优化 介绍 Python是一种高级编程语言&#xff0c;旨在提高开发人员的生产力和代码可读性。在过去的10年中&#xff0c;Python已迅速成为最受欢迎的编程语言之一&#xff0c;具有广泛的应用领域&#xff0c;包括数据科学、机器学习、人工智能、网络编程和Web开发。…

chatgpt赋能python:Python集合的差集

Python集合的差集 Python是一门强大的动态语言&#xff0c;不仅应用于广泛的领域&#xff0c;而且它还拥有非常丰富的数据结构。Python中的集合(set)是一种无序、不重复元素的结构&#xff0c;常用于去重&#xff0c;检查成员等操作。 在Python中&#xff0c;可以使用"-…

Java面试知识点(全)-分布式算法- ZAB算法

Java面试知识点(全) 导航&#xff1a; https://nanxiang.blog.csdn.net/article/details/130640392 注&#xff1a;随时更新 研究zookeeper时&#xff0c;必须要了解zk的选举和集群间个副本间的数据一致性。 什么是 ZAB 协议&#xff1f; ZAB 协议介绍 ZAB 协议全称&#xf…

docker安装mysql8.0.33

1 从docker仓库中拉去mysql 8.0 docker pull mysql:8.0如果使用 docker pull mysql 默认拉取的是最新版本的mysql 上面我拉去的是8.0的版本&#xff0c;最后拉取过来的是8.0.33 如果有想要指定的版本&#xff0c;可以直接写指定版本&#xff0c;如&#xff1a; docker pull my…

Python速查表;腾讯大佬的AIGC设计应用汇总;这个世界需要10亿开发者;67个最常用AI工具清单 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 『一份必收藏的 Python 3 速查表』可运行代码中文注释 随着AIGC浪潮的兴起&#xff0c;越来越多小伙伴尝试着使用 GPT 类工具开发小程…