C++实现RPC分布式网络通信框架(八)-------项目框架梳理及总结

由于项目的代码量比较大,所以不再将代码挨个贴出,如果需要本项目的源码可以到Git上拉取,地址:https://github.com/MrHeadQ/MPRPC
在上一个博客的代码看完之后,整体的代码逻辑基本完成,后续会加入日志和节点管理模块,但基本影响不大,在加入剩下的两个模块后,到此我们就可以开始整理我们的整个项目了。项目的总结将会以下面三个方面进行:
1.项目文件架构;
2.项目的运行架构;
3.相应的中间件使用情况

由于项目的整理会很长,所以此篇博客的量可能会很大,请斟酌阅读。

一、项目的文件架构

1.整体文件框架

在这里插入图片描述
在整个项目中,文件组织格式如上,分门别类的将proto文件,cc文件,h文件,txt文件都组织在上述各个文件夹中,在编写代码时需要使用到自动化编译环境,所以我们需要先了解文件组织形式,这样才可以包含文件以至于编译不至于出错,而且有条理的组织文件会让使用者也更好的理清如何使用。在接下来的博文中会将整体文件的思路理清。

2.各文件的作用

在这里插入图片描述

(1)bin文件夹

存放生成的可执行文件和配置文件当使用框架时需要进入该文件来执行相应的可执行文件,并读取配置文件。

(2)build文件夹

因为我们在整个项目的编译过程中使用到了Cmake,所以在整个项目的编码完成后,我们需要进入这个文件夹来执行自动编译,这样可以将文件的所有编译的中间文件都存放到build文件夹中,不会使得编译过程中的文件很乱的存在其他文件夹中而影响阅读或者使用。

(3)example文件夹

在该文件夹中我们提供了使用的示例,因为RPC项目需要将proto文件中生成的相应类的虚函数进行重写(作为本地业务调用),所以在example中提供了如何实现相应的方法或者说如何使用该框架。这部分会在下面详细叙述。

(4)lib文件夹

在最终结束时,我们将该项目生成的所有文件打包成为一个静态库交付用户使用,该静态库就存放在lib文件夹中,而且我们将项目的日志也输出到了这个文件夹中。

(5)src文件夹

项目的所有源文件都存放在这个文件夹中,该文件夹在后续会进行细致的分析。

(6)test文件夹

存放项目的各种测试文件,在项目的实现过程中存放了protobuf的测试文件,如果需要可以看下proto文件生成的pb.h文件和pb.cc文件。

(7)项目的使用文件

CMakeLists.txt:自动化编译的文件,该文件的组织形式会在第三部分:相应的中间件的使用情况中叙述;
autobuild.sh:当使用时对所有文件进行自动化编译生成可执行文件的脚本,在本项目中已经为其加上了可执行权限,使用时可以直接使用如下命令

./autobuild.sh

README.txt:项目的整体介绍,因为在Git上上传的比较早,所以该文件并没有完善,可以忽略。

3.src文件夹下的文件作用

src文件中保存的是我们整个项目最干的干货,所有很重要的文件都在保存该文件夹下,include文件夹包含了所有的头文件,因为lockqueue.h部分是使用模板实现的,所以没有写对应的cc文件,其余各个文件都是有相应的cc文件存在的。所以include文件夹不再赘述,直接开始其余文件的描述。

(1)include:包含各个cc文件的头文件

(2)CMakeLists.txt

还是那句话,设计了CMakeLists的都是为了编译更方便,依旧在第三部分:相应的中间件的使用情况中叙述;

(3)lockqueue.h

在我们项目日志部分的实现的过程中,从项目的全局出发来说,我们不可能让整个项目只有一个线程,如果只有一个线程,就会导致所有的业务逻辑阻塞,比如:在写日志的过程中我们也需要时间和资源,但如果我们的在全局只开一个线程,那写日志时就不可以进行正常的业务逻辑了,而且写日志这个操作是一个磁盘I/O操作,这样就会更加的消耗时间。所以综合多方面考虑,我们需要再开一个线程来进行写日志的操作,那么设计这个线程时,如果同时涌入多个日志信息,处理不当时依旧会导致其阻塞,毕竟一对多的模式很难不阻塞,那我们该如何处理?增加一个缓冲区,所以我们设计了一个日志的队列,将线程生产的日志写入到队列中,然后在写日志线程从队列中取日志信息,再写入到磁盘中,但同时,我们也需要考虑到线程安全,所以在写日志时需要使用到条件变量和互斥锁来实现线程安全,此时只需要让应答链接的线程入队,而另一个线程出队写日志即可。

//异步写日志的日志队列
template<typename T>
class LockQueue
{
    
    
public:
    //多个worker线程都会写日志queue
    void Push(const T &data){
    
    };
    //一个线程读日志queue
    T Pop(){
    
    };
private:
    std::queue<T> m_queue;
    std::mutex m_mutex;
    std::condition_variable m_condvariable;
};

(4)logger.cc

该部分是我们的日志模块,在逻辑上思考,我们的日志对象只需要有一个即可,所以使用到了设计模式中的单例模式。另外我们的日志需要设置相应级别,毕竟从逻辑方面考虑我们的日志不可能只有一种正常状态,可能还会有错误信息,所以使用一个枚举类型来记录相应级别。在单例模式的设计上不再叙述了。在该类的构造函数中我们将写日志的线程设置为分离线程,这样就不会出现主线程和子线程的冲突情况了,并实现了两个成员方法:设置日志级别和写日志方法,写日志的方法负责将日志写到队列中。而为了方便使用,我们提供了两个宏来让用户使用更加简便,可以在实际应用中更便捷的写日志。

enum LogLevel
{
    
    
    INFO,//普通日志信息
    ERROR,//错误信息
};
//mprpc框架提供的日志系统
class Logger
{
    
    
public:
    //获取日志的单例
    static Logger& GetInstance();
    void StLogLevel(LogLevel level);//设置日志级别
    void Log(std::string msg);//写日志
private:
    int m_loglevel;//记录日志级别
    LockQueue<std::string> m_lckQueue;//日志缓冲队列

    //设置成单例模式,删除掉其拷贝构造,构造函数设置成私有
    Logger();//设置为分离线程,如果使用宏的话则不会使用到该方法
    Logger(const Logger&)=delete;
    Logger(Logger&&)=delete;
};
//定义宏对用户实现自己写日志的效果
#define LOG_INFO(logmsgformat,...)
#define LOG_ERR(logmsgformat,...)    

(5)mprpcapplication.cc

MprpcApplication是我们项目的框架最重要的部分,也使用到了单例模式,负责初始化整个框架,在该部分中我们会读取配置文件,并和对方建立连接,

//mprpc框架的基础类,负责框架的一些初始化操作,使用单例模式设计
class MprpcApplication
{
    
    
public:
	//初始化框架,按输入的命令进行命令解析,若解析成功则进行配置文件读取以及其他的逻辑
    static void Init(int argc,char **argv);
    //获取框架的单例
    static MprpcApplication& GetInstance();
    //读取配置文件
    static MprpcConfig& GetConfig();
private:
    static MprpcConfig m_config;
    MprpcApplication(){
    
    };
    MprpcApplication(const MprpcApplication&)=delete;
    MprpcApplication(MprpcApplication&&)=delete;
};
//类外初始化静态变量
MprpcConfig MprpcApplication::m_config;

//在cc文件中实现成全局的方法,如果命令错误,则使用该方法输出帮助信息
void ShowArgHelp()
{
    
    
    std::cout<<"format:command -i <configfile>"<<std::endl;
}

(6)mprpcconfig.cc

在上一步框架启动后需要读取配置文件,该部分就是实现配置文件加载的,根据配置文件的特性,在配置文件的存储中我们使用了unordered_map,但由于配置文件的书写中可能会有很多的空格回车等,所以我们又提供了去掉空格的成员方法供加载配置文件时调用,加载配置文件的功能由LoadConfigFile方法实现。而在我们提供RPC服务时还需要读取某个节点的地址等,所以再增加一个Load方法用于返回相应的值。

//配置文件为键值对,且不需要排序,所以使用无序哈希
//配置文件为:rpcserverip,rpcserverport,zookeeperip,zookeeperport

//框架读取配置文件类
class MprpcConfig
{
    
    
public:
    //负责解析加载配置文件
    void LoadConfigFile(const char *file);
    //查询相应配置端
    std::string Load(std::string const &key);
private:

    std::unordered_map<std::string,std::string> m_configMap;
    //去掉字符串前后的空格
    void Trim(std::string &src_buf);
};

(7)mprpccontroller.cc

在我们使用RPC框架时,我们需要被调用方给调用方返回调用的情况,在protobuf中我们定义的方法都会生成对应的类,而所有方法最终都会被底层的MprpcChannel::CallMethod()方法调用,而该方法的参数中有一个google::protobuf::RpcController* controller,该参数是为了携带远程调用的控制(状态)信息的,包括是否失败和失败的原因,其实现如下:

class MprpcController:public google::protobuf::RpcController
{
    
    
public:
	//构造函数
    MprpcController();//初始化状态
    void Reset();//重置状态
    bool Failed()const;//用以查询成功或失败的状态
    std::string ErrorText()const;//用于记录错误信息
    void SetFailed(const std::string& reason);//设置是否失败
private:
    bool m_failed;//RPC方法执行过程中的状态
    std::string m_errtext;//RPC方法执行过程中的错误信息
};

(8)mprpcchannel.cc

所有通过stub代理对象调用的rpc方法都从这里处理,统一做方法调用的数据序列化和网络发送,在这部分中就使用到了muduo库,muduo库实现了本项目中的所有网络业务,所以此部分也算是最核心的部分,CallMethod()方法的参数如下:

参数名 意义
method RPC调用的方法
controller 用于携带执行状态信息
request 请求信息(参数等)
response 响应信息,执行结果等
done 回调函数

虽然只有一个方法,但代码很长,所以简化一下只写逻辑:

class MprpcChannel:public google::protobuf::RpcChannel
{
    
    
public:
    //
    void CallMethod(method,controller,request,response,done);
    {
    
    
    	const google::protobuf::ServiceDescriptor* sd=method->service();//获取服务对象提供的服务
    	std::string service_name=sd->name();//service的name
    	std::string method_name=method->name();//method的name

    	//获取参数的序列化字符串长度 args_size
    	uint32_t args_size=0;
    	std::string args_str;
    	//已简化,从request中序列化参数,序列化成功后填充args_size,失败则向controller中填充错误信息
    
    	//定义rpc的请求header
    	mprpc::RpcHeader rpcHeader;
    	//已简化,需要向相应变量中填充值

    	uint32_t header_size=0;
    	std::string rpc_header_str;
    	//已简化,从rpcHeader中序列化参数,序列化成功后填充header_size,失败则向controller中填充错误信息

    	//组织待发送的rpc请求的字符串
    	std::string send_rpc_str;
    	send_rpc_str.insert(0,std::string((char *)&header_size,4));//header_size
    	send_rpc_str+=rpc_header_str;//rpcheader
    	send_rpc_str+=args_str;//args
    
    	//使用TCP编程,完成rpc方法的远程调用
    	int clientfd=socket(AF_INET,SOCK_STREAM,0);
    	//已简化,创建socket失败则填充错误信息到controller中

    	//rpc调用方向调用service_name服务,需要查询zk上该服务所在的host信息
    	ZkClient zkCli;
    	zkCli.Start();
    	std::string method_path="/"+service_name+"/"+method_name;
    	//获取ip地址和端口号
    	std::string host_data=zkCli.GetData(method_path.c_str());
    	//已简化,主机获取失败则填充错误信息到controller中	
    	int idx=host_data.find(":");//分割符
    	//已简化,IP地址获取失败则填充错误信息到controller中
    	std::string ip=host_data.substr(0,idx);
    	uint32_t port=atoi(host_data.substr(idx+1,host_data.size()-idx).c_str());

    	struct sockaddr_in server_addr;
    	//已简化,向地址中填充响应的参数

    	//链接rpc服务节点
    	if(-1==connect(clientfd,(struct sockaddr*)&server_addr,sizeof(server_addr)))
    	//已简化,链接失败则填充错误信息到controller中

    	//发送rpc请求
    	if(-1==send(clientfd,send_rpc_str.c_str(),send_rpc_str.size(),0))
    	//已简化,发送失败则填充错误信息到controller中

    	//接受rpc请求的响应值
    	char recv_buf[1024]={
    
    0};
    	int recv_size=0;
    	if(-1==(recv_size=recv(clientfd,recv_buf,1024,0)))
    	//已简化,响应失败则填充错误信息到controller中

    	std::cout<<recv_buf<<std::endl;

    	//反序列化rpc调用的响应数据
    	std::string response_str(recv_buf,0,recv_size);
    	if(!response->ParsePartialFromArray(recv_buf,recv_size))
    	//已简化,响应反序列化失败则填充错误信息到controller中
    	close(clientfd);
	}
};

(9)rpcheader.proto

在上一个部分中我们需要一个rpc请求头,来防止TCP的粘包问题,所以我们在此定义一个rpcheader.proto文件,并生成对应的变量名等。

syntax="proto3";

package mprpc;

//RpcHeader的主要信息,防止TCP粘包,需要将其长度实现说明好
message RpcHeader
{
    
    
    bytes service_name=1;
    bytes method_name=2;
    uint32 args_size=3;
}

(10)rpcprovider.cc

RpcProvider是框架提供的专门负责发布rpc服务的网络对象类,在程序运行的开始我们就要启动一个RpcProvider,其中NotifyService()方法做服务发现,而Run方法负责启动rpc服务节点,并提供rpc服务。提供了多个回调函数,在使用过程中会阐述。

class RpcProvider
{
    
    
public:
    //这里是框架提供给外部使用的,可以发布rpc方法的函数接口
    //此处应该使用Service类,而不是指定某个方法
    void NotifyService(google::protobuf::Service *service);

    //启动rpc服务节点,开始提供rpc远程网络调用服务
    void Run();
private:
    muduo::net::EventLoop m_eventLoop;

    //service服务类型信息
    struct ServiceInfo
    {
    
    
        google::protobuf::Service *m_service;//保存服务对象
        std::unordered_map<std::string,const google::protobuf::MethodDescriptor*> m_methodMap;//保存服务方法
    };

    //存储注册成功的服务对象和其服务方法的所有信息
    std::unordered_map<std::string,ServiceInfo> m_serviceMap;
    //新的socket链接回调
    void OnConnection(const muduo::net::TcpConnectionPtr&);
    //已建立连接用户的读写事件回调
    void OnMessge(const muduo::net::TcpConnectionPtr&,muduo::net::Buffer*,muduo::Timestamp);
    //Closure的回调操作,用于序列化RPC的响应和网络发送
    void SendRpcResponse(const muduo::net::TcpConnectionPtr&,google::protobuf::Message* );
};

(11)zookeeperutil.cc

在本项目中zk主要做节点管理,所有的RPC节点都在zk服务配置中心中注册自己的地址以及自己所有的方法,当一台机器请求方法时,先经过zk的查找,然后再按查找到的路径去调用相关方法,所以zk是需要先启动起来的。

//全局的watcher
void global_watcher(zhandle_t *zh,int type,int state,const char *path,void *watcherCtx)
{
    
    
    if(type==ZOO_SESSION_EVENT)//回调的消息类型是和会话相关的消息类型
    {
    
    
        if(state==ZOO_CONNECTED_STATE)//zkclient和zkserver链接成功
        {
    
    
            sem_t *sem=(sem_t*)zoo_get_context(zh);
            sem_post(sem);//信号量资源加一
        }
    }
}

//封装的zk客户端类
class ZkClient
{
    
    
public:
    ZkClient();
    ~ZkClient();
    //zkclinet启动链接zkserver,调用原生API,进行节点管理
    void Start();
    在zkserver上根据指定的path创建znode节点
    void Create(const char *path,const char *data,int datalen,int state=0);
    //获取节点,传入参数指定的znode节点路径,或者znode节点的值
    std::string GetData(const char *path);

private:
    //zk的客户端句柄
    zhandle_t *m_zhandle;
};

4.example文件夹下的文件作用

(1)user.proto

在前面我们说到我们使用到了protobuf做本项目中数据的序列化和反序列化,而我们也需要protobuf去生成业务中的类,所以在这里我们使用protobuf定义消息的类型和服务的类型,再者就是因为这个proto文件在调用方和被调用方二者之间都要使用,所以定义在全局,便于查找。在这个proto文件中我们定义了消息类型和服务类型,当我们对proto文件执行生成时,则会生成相应作用域下的各个类,而protobuf也会为各个变量生成相应的读写方法:
在这里插入图片描述

syntax ="proto3";

package RPC;

option cc_generic_services=true;

message ResultCode
{
    
    
    int32 errcode=1;
    bytes errmsg=2;
}

message LoginRequest
{
    
    
    bytes name=1;
    bytes pwd=2;
}

message LoginResponse
{
    
    
    ResultCode result=1;
    bool success=2;
}

message RegisterRequest
{
    
    
    uint32 id=1;
    bytes name=2;
    bytes pwd=3;
}

message RegisterResponse
{
    
    
    ResultCode result=1;
    bool success=2;
}

//定义服务类型,
service UserServiceRpc
{
    
    
    rpc Login(LoginRequest)returns(LoginResponse);
    rpc Register(RegisterRequest)returns(RegisterResponse);
}

(2)caller

calluserservice.cc

caller是为了说明RPC如何工作而提供的代码示例,caller扮演一个发起调用者的角色,因为调用者是调用对方的方法,所以他并不需要提供什么方法的重写之类的,只需要启动框架然后创建stub对象,打包参数,然后调用对方的方法就可以了。

#include<iostream>
#include"mprpcapplication.h"
#include"user.pb.h"
#include"mprpcchannel.h"

int main(int argc,char **argv)
{
    
    
    //整个程序启动以后,想使用mprpc来获取rpc服务调用,一定需要先调用框架的初始化函数(只初始化一次)
    MprpcApplication::Init(argc,argv);

    //演示调用远程发布的rpc方法Login
    //创建stub对象
    RPC::UserServiceRpc_Stub stub(new MprpcChannel);

    //rpc方法的请求参数
    RPC::LoginRequest request;
    request.set_name("zhangsan");
    request.set_pwd("123");

    //rpc方法的响应
    RPC::LoginResponse response;
    //发起rpc方法的调用,同步的rpc调用过程,MprpcChannel::callmethod
    stub.Login(nullptr,&request,&response,nullptr);
    //stub.Login();//RpcChannel->RpcChannel::callMethod 集中来做所有rpc方法调用的参数序列化和网络发送

    //一次rpc调用完成,读取调用的结果
    if(0==response.result().errcode())
    {
    
    
        std::cout<<"rpc login response success:"<<response.success()<<std::endl;
    }
    else
    {
    
    
        std::cout<<"rpc login response error:"<<response.result().errmsg()<<std::endl;
    }
    return 0;
}
CMakeLists.txt

在第三部分:相应的中间件的使用情况中叙述;

(3)callee

userservice.cc

callee也是为了说明RPC如何工作而提供的代码示例,callee扮演一个被调用者的角色,因为被调用者是对方调用,所以需要提供指定方法的重写,只需要启动框架然后创建stub对象,打包参数,然后调用对方的方法就可以了。

#include<iostream>
#include<string>
#include"rpcprovider.h"
#include"mprpcapplication.h"
#include"../user.pb.h"

/*
UserService原来是一个本地服务,提供了两个进程内的本地方法:Login和GetFriendList
*/
//继承了RPC::UserServiceRpc,就封装成了一个RPC方法
class UserService:public RPC::UserServiceRpc
{
    
    
public:
    //登入系统的方法
    bool Login(std::string name,std::string pwd)
    {
    
    
        std::cout<<"doing local service:Login"<<std::endl;
        std::cout<<"name:"<<name<<" pwd"<<pwd<<std::endl;
        return true;
    }

    //新增的测试方法
    bool Register(uint32_t id,std::string name,std::string pwd)
    {
    
    
        std::cout<<"doing Register service:Login"<<std::endl;
        std::cout<<"id:"<<id<<" name:"<<name<<" pwd:"<<pwd<<std::endl;
        return true;
    }

    void Register(::google::protobuf::RpcController* controller,
                       const ::RPC::RegisterRequest* request,
                       ::RPC::RegisterResponse* response,
                       ::google::protobuf::Closure* done)
    {
    
    
        uint32_t id=request->id();
        std::string name=request->name();
        std::string pwd=request->pwd();

        //开始做本地业务
        bool ret=Register(id,name,pwd);

        //填充回调结果
        response->mutable_result()->set_errcode(0);
        response->mutable_result()->set_errmsg("");
        response->set_success(ret);

        done->Run();
    }
    
    void Login(::google::protobuf::RpcController* controller,
                       const ::RPC::LoginRequest* request,
                       ::RPC::LoginResponse* response,
                       ::google::protobuf::Closure* done)
    {
    
    
        //框架给业务上报了请求参数:LoginRequest,应用程序取出相应的已反序列化的数据来做本地业务
        std::string name=request->name();
        std::string pwd=request->pwd();

        //做本地业务
        bool loginresult=Login(name,pwd);

        //把响应写入,包括错误码,错误信息和运行结果
        RPC::ResultCode *Code=response->mutable_result();
        Code->set_errcode(0);
        Code->set_errmsg("");
        response->set_success(loginresult);

        //执行回调操作  执行响应对象数据的序列化和网络发送
        done->Run();
    }
};

int main(int argc,char **argv)
{
    
    
    //先调用框架的初始化操作 provider -i config.conf,从init方法读取配置服务,比如IP地址和端口号
    MprpcApplication::Init(argc,argv);

    //项目提供者,让我们可以发布该服务
    RpcProvider provider;
    //把UserService对象发布到rpc节点上
    provider.NotifyService(new UserService());
    
    //启动一个rpc服务发布节点,run以后,进程进入阻塞状态,等待远程的rpc请求
    provider.Run();

    return 0;
}
CMakeLists.txt

在第三部分:相应的中间件的使用情况中叙述;

如果这里的代码逻辑不是很懂的话就看下面的运行架构

二、项目的运行架构

写了这么多,可能还是有点云里雾里,接下来我们从运行时的角度出发,通篇走一遍运行时各个类,各个方法,各个文件的调用过程,基于我们已经生成了可执行文件的情况下。

1.部署准备

(1)可执行文件生成

在我们所有文件都编写好后,先对对应的proto文件生成相应的pb.h和pb.cc文件,在包含proto文件的各个目录下执行:

protoc test.proto --cpp_out=./

这样就生成了我们的proto文件。然后编写好我们的CMakeLists.txt就可以执行自动化编译了。进入build文件夹中:

执行cmake …

执行后会出现如下界面,说明cmake执行成功。
在这里插入图片描述
然后需要将文件进行生成,继续在当前目录下执行:

make

执行成功后会出现如下界面:
在这里插入图片描述
这就说明make也执行成功了,此时bin文件夹下就会出现我们的可执行文件。
在这里插入图片描述

(2)环境要求

1.在所有文件都准备好的情况下,我们需要先开启zookeeper服务,如果不开启zk服务就直接运行文件时,就会出现像下面这种情况:
在这里插入图片描述
可以看到服务端未开启,并且拒绝了我们客户端的请求,所以需要先开启服务端:进入到安装zookeeper的文件夹下,并进入到bin目录:

wang@wang-virtual-machine:~/Env_Set/zookeeper-3.4.10/bin$

然后执行:

./zkServer.sh start

在这里插入图片描述
可以看到zk的服务已经启动起来了,此时我们就来进入整个运行过程。

2.启动服务端

启动服务器的运行过程如下:
在这里插入图片描述
当进入bin目录并执行

./provider -i test.conf

后,运行界面如下:
在这里插入图片描述
当服务成功启动后,就可以启动我们的客户端发起调用请求了。

3.客户端准备过程

启动服务端的运行过程如下:
在这里插入图片描述
执行命令:

./consumer -i test.conf

在这里插入图片描述

4.交互过程

在客户端已经准备好之后,接下来是网络交互的部分,因为在服务端设置了两个回调函数,所以涉及两端的交互式通信,所以我们需要整体来看:
在这里插入图片描述
理论分析完毕之后,我们来看一下运行的情况:
调用方

在这里插入图片描述
被调用方

在这里插入图片描述
至此,一次RPC调用结束,服务提供者不停止,等待下一次调用。

三、相应的中间件使用情况

1.CMake

CMake在此项目中起的作用不可谓不大,不止这个项目,只要是自动化编译环境,CMake都是一个利器,前面也偷了那么久的懒了,咱们在这里解决一下CMake的在整个项目中的架构和编写问题。

(1)CMake在项目中的架构

在这里插入图片描述
以上就是整个项目的cmake架构,其实只需要在需要进行编译的源文件的相应文件夹下写一个cmake文件,然后记得加上子目录即可。

(2)CMake的编写

在这里只介绍项目需要使用到的各个点,如果想详细了解可以找一下详细的博客学习。

#设置cmake的最低版本和项目名称
cmake_minimum_required(VERSION 3.0)
project(mprpc)

#生成debug版本,可以进行gdb调试
set(CMAKE_BUILD_TYPE "Debug")

#设置项目可执行文件的输出路径
set(EXECUTABLE_OUTPUT_PATH ${
    
    PROJECT_SOURCE_DIR}/bin)

#设置项目库文件输出的路径
set(LIBRARY_OUTPUT_PATH ${
    
    PROJECT_SOURCE_DIR}/lib)

#设置项目编译头文件搜索路径 -I
include_directories(${
    
    PROJECT_SOURCE_DIR}/src/include)
include_directories(${
    
    PROJECT_SOURCE_DIR}/example)

#设置项目库文件搜索路径 -L
link_directories(${
    
    PROJECT_SOURCE_DIR}/lib)

#在子目录下寻找makefile文件
#src包含了mprpc框架的所有代码
add_subdirectory(src)

#example包含了mprpc框架使用的示例代码
add_subdirectory(example)

整体的项目架构都是按照这个来写的,剩下的就是加一些子目录,在这里不再赘述了。

2.zookeeper

项目看到这里应该可以看出来,zookeeper的主要作用就是管理各个RPC节点,我们将每个RPC节点的IP地址端口,以及他拥有的方法都注册到zk上,然后当有人需要使用此方法时先到zk上查找哪个znode节点有此方法,然后再到相应的znode节点上调用方法,zk可以避免高并发时调用者的盲目寻找,是一个很有力的辅助工具,但zk的作用远不止此,笔者如果有机会会写一篇zk的博客用来扫盲。

3.muduo库

muduo库在此项目中的作用其实并没有多大,只是负责了一下网络数据的收发,但muduo库是一个十分优秀的工具,现在各大公司的有些招聘岗位会直接将“使用过muduo库者优先”作为筛选条件,此次项目中使用到muduo库其实也算大材小用了,后续会好好学习一下muduo库,争取把这个优秀的工具吃透。

4.protobuf

在这个项目中protobuf还是起了很大的作用,想想你写一个proto文件之后他就可以给你生成相应的C++文件,而且是小白也会使用的,笔者在此项目中使用protobuf的感觉就是:爽!免去了很多麻烦的事情,但作为一个程序猿,不能光会用,还是要了解底层原理,后续也会完善一下protobuf的学习。

四、写在最后

这个项目是笔者做过的最难的项目,前前后后花了三个月时间实现和整理,使用到了很多以前没有用过的东西,起初在配置ubuntu的环境时有一个软件依赖关系总是解决不了最后历经波折还是屈服了,选择另一个版本的虚拟机软件,这才配置好了zk的环境,protobuf和muduo库都是第一次接触,学起来还是比较吃力,虽然zk以前了解过,但是真正用起来还是有点费劲。项目到这里就完了,秋招也正在进行中,压力还是比较大的,希望自己能够找到个好工作吧,加油!期待反转!!!

历经千帆,愿归来仍是少年。----2020/8/29–23:42

猜你喜欢

转载自blog.csdn.net/qq_45132647/article/details/108277615