C++新手项目-JsonRPC框架

前言

本文是作者学习该项目时的笔记和体会,是笔记向的,希望能对也在实现这个项目的朋友有一定的帮助,部分内容可能由于记录不全导致前后完整性缺失,会在后续逐渐补充

  1. 什么是RPC
    RPC(Remote Procedure Call)远程过程调⽤,是⼀种通过⽹络从远程计算机上请求服务,⽽不需要
    了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信, 如HTTP、TCP、UDP等, 并且在
    TCP/IP⽹络四层模型中跨越了传输层和应⽤层。简⾔之RPC就是像调⽤本地⽅法⼀样调⽤远程⽅法。
    过程可以理解为业务处理、计算任务,更直⽩的说,就是程序/⽅法/函数等,就是像调⽤本地⽅法⼀样
    调⽤远程⽅法。
    人话:远程调用 Netcal
    让调用者感知不到调用了远程方法

为什么要有注册中心:
提升系统的健壮性:


发布订阅:

一个推送客户端+一个接受客户端+一个发布订阅服务器


配置环境:

形成.bak,免得设置失败了回不来

./ect用于存放系统的配置文件

build.sh/install之后,会在上一层生成一个build目录

照着这个文章去测试是否安装正确

入门高性能C++网络库Muduo - 知乎

我测试下来是:

语法温习:

右值引用完美转发//可变参数

右值的临时变量就是右值引用。

可变函数参数:

可变参数模板

Json

可以嵌套:数组 字符串 数字 对象

JsonCpp

Json-Value类:

streamwriter 是一个纯虚的类,只能依靠工厂化的设计模式

可以嵌套


Muduo库

基于Reactor

单个Reactor容易压力太大

故:one loop per thread

常见接口

  1. TcpServer

Connection到来的时候我应该怎么调、每一个Connection 有消息到来的时候我又应该怎么调?

设置线程数量----设置子Reactor数量

EventLoop中的poller_就是基于epoll实现的

而其中的loop就是epoll_wait

EventLoop里有三种辅助任务超时控制方法,比如我们之前讲在reactor中讲的超时重传机制

回调函数的格式:

TcpConnection

对tcp连接进行封装和管理

  • TcpConnection与epoll的交互TcpConnection类封装了一个TCP连接,每个TcpConnection对象都与一个socket文件描述符相关联。当TcpConnection被创建后,它会将对应的socket文件描述符通过Channel类注册到epoll中。这样,当该socket上有事件发生时,epoll会通知对应的Channel,进而触发TcpConnection中预设的事件处理函数。

还有缓冲区类:

注意,粘包问题是存在于应用层

此时先取4字节,是因为应用层规定了先发4字节的长度

是用来解决粘包问题的:

当然,想自定义操作可以使k获得起始地址 ,根据11 12行的内容获得具体可读数据

这个connection()接口是反指指针,Client没有提供send接口,获得connecion之后才方便发送数据


库管理

尖括号包含需要指定编译路径

编译库的时候,注意makefile中要添加-I,双引号是在当前目录下找,我们一致使用<> 和 在目录中添加-I+地址,这样就能让<>也去对应的位置找。-I确定include去哪里,-L确定去哪里链接库。头文件中只有函数的声明,库中才是函数的实现

demo:

服务器端:

cpp 复制代码
#include "muduo/net/TcpServer.h"
#include "muduo/net/EventLoop.h"
#include "muduo/net/TcpConnection.h"
#include <iostream>
#include <string>
#include <unordered_map>

class DicServer
{
public:
    DicServer(int port)
        : _svr(&_baseloop, muduo::net::InetAddress("0.0.0.0", port),
               "DicServer", muduo::net::TcpServer::kReusePort)
    {
        
        _svr.setConnectionCallback(std::bind(&DicServer::_OnConnectionCB,this,std::placeholders::_1));
        _svr.setMessageCallback(std::bind(&DicServer::_OnMessageCB,
            this,std::placeholders::_1,std::placeholders::_2,std::placeholders::_3));
    }

    void start()
    {
        //先监听,再进行死循环的事件监控
        _svr.start();
        _baseloop.loop();
    }

private:
    void _OnConnectionCB(const muduo::net::TcpConnectionPtr& con_ptr)
    {
        if(con_ptr->connected())
        {
            std::cout<<"连接建立"<<std::endl;
        }
        else 
        {
            std::cout<<"连接断开"<<std::endl;
        }
    }

    void _OnMessageCB(const muduo::net::TcpConnectionPtr& con_ptr,muduo::net::Buffer* buf,muduo::Timestamp t)
    {
        static std::unordered_map<std::string,std::string> dic = 
        {
            {"hello","你好"},
            {"apple","苹果"}
        };

        std::string ret = buf->retrieveAllAsString();//暂时没有应用层协议,直接拿即可
        std::string res;
        if(dic.find(ret)==dic.end())
        {
            res = "未知单词";
        }
        else
        {
            //找到了
            res = dic[ret];
        }

        con_ptr->send(res);
    } 

private:
    muduo::net::EventLoop _baseloop;
    muduo::net::TcpServer _svr;
};


int main()
{
    DicServer svr(8889) ;
    svr.start();

    return 0;
}
  1. 每一个TcpServer都得有一个EventLoop,在传参的时候方便传

  2. 由于服务器的特殊性,一般来说服务器本身可能不止一个IP地址,所以绑定到0.0.0.0接受所有IP,更为合适。

3.为了让服务器先断开时不被TCP的挥手给影响从而导致bind error,所以设置成kReusePort可以端口复用

4.stdbind类内函数记得加上&

5._baseloop的loop是死循环,先start再loop

  1. 没有协议层,所以直接retrieveAllAsString()

客户端:

cpp 复制代码
#include <muduo/net/TcpClient.h>
#include <muduo/net/EventLoop.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/base/CountDownLatch.h>
#include <muduo/net/EventLoopThread.h>
#include <iostream>
#include <string>
#include <unordered_map>

class DicClient
{
public:
    DicClient()
        : _baseloop(_loop_thread.startLoop()), _latch(1), _client(_baseloop, muduo::net::InetAddress("127.0.0.1", 8889), "DicClient")
    {
        _client.setConnectionCallback(std::bind(&DicClient::_OnConnectionCB, this, std::placeholders::_1));
        _client.setMessageCallback(std::bind(&DicClient::_OnMessageCB,
                                             this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3));

        _client.connect(); // 非阻塞接口!执行后续代码时不一定连接上了,需要一个Latch
        _latch.wait();
    }

    void send(std::string msg)
    {
        if (_con_ptr)
        {
            _con_ptr->send(msg);
        }
        else
        {
            std::cout << "链接为空\n";
        }
    }

private:
    void _OnConnectionCB(const muduo::net::TcpConnectionPtr &conptr)
    {
        if (conptr->connected())
        {
            std::cout << "连接建立" << std::endl;
            _con_ptr = conptr;  // 客户端一旦有新链接建立好了,就马上用一个TcpConnectionPtr去获得一个_con_ptr
            _latch.countDown(); // 计数-1,为0时唤醒阻塞
        }
        else
        {
            std::cout << "连接断开" << std::endl;
            _con_ptr.reset();
        }
    }

    void _OnMessageCB(const muduo::net::TcpConnectionPtr &con_ptr, muduo::net::Buffer *buf, muduo::Timestamp t)
    {
        std::string res = buf->retrieveAllAsString();
        std::cout << res << std::endl;
    }

private:
    muduo::CountDownLatch _latch;
    muduo::net::TcpConnectionPtr _con_ptr;
    muduo::net::EventLoopThread _loop_thread; // 客户端不能直接死循环,因为客户端需要主动进行发送,因此需要另一个线程来执行loop
    muduo::net::EventLoop *_baseloop;         // client端要发起connection和进行通信,为了监听读写,也需要一个Reactor
    muduo::net::TcpClient _client;
};

int main()
{
    DicClient client;
    while (1)
    {
        // std::cout << "please search: \n";
        std::string search;
        std::cin >> search;
        client.send(search);
    }
}
  1. 客户端其实就是连接方式和处理功能变化的服务器,所以初始化时照样需要eventloop

2.客户端的InetAddress是指的他要去访问的端口,和socket是一样的

3.客户端需要一个发送接口,发送接口需要一个connection,所以必须在服务器调用新连接到来时候的回调函数中获得这个connection并执行发送

4.直接在构造函数中进行connect,但是connect是非阻塞的,需要使用latch加锁,这个类在base里。锁的值初始化为1,执行connect后立即进行wait,直到回调函数被调用,才让latch--,结束等待

5.客户端内部的reactor不能直接启动,因为loop是死循环,一旦死循环将无法自主执行send

需要一个线程执行这个任务,陈硕大佬已经写好了,就是EventLoopThread,在baseloop的初始化里添加这个参数即可。

_Onconnection的写法在demo中也是如上实现,简单来说就是,有新连接来的时候也触发,有老链接断开的时候也触发。


C++异步操作

get没有结果的时候会阻塞:

三种C++11异步执行方法:

  1. async
cpp 复制代码
std::future<int> result_future = std::async(std::launch::async, aysnc_task);
//...
int result = result_future.get();
  1. packaged_task
cpp 复制代码
std::packaged_task<int(int, int)> task(add);
//...
std::future<int> result_future = task.get_future();
task(1,2);

 //获取异步任务结果
int result = result_future.get();
cpp 复制代码
#include <iostream>
#include <future>

int Add(int num1,int num2)
{
    std::cout<<"into add!"<<std::endl;
    return num1+num2;
}

int main()
{
    //1.封装任务
    std::packaged_task<int(int,int)> task(Add);//封装task任务包

    //2.获取future对象
    std::future<int> res = task.get_future();

    //3.执行任务
    task(11,22);

    //4.获取结果
    std::cout<<res.get()<<std::endl;

    return 0;

}

package_task任务包不能直接拷贝,又需要传出去给其他线程用,否则无意义,所以一般使用智能指针去传

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <memory>

int Add(int num1, int num2) {
    std::cout << "into add!\n";
    return num1 + num2;
}

int main()
{
    //1. 封装任务
    auto task = std::make_shared<std::packaged_task<int(int, int)>>(Add);

    //2. 获取任务包关联的future对象
    std::future<int> res = task->get_future();

    std::thread thr([task](){
        (*task)(11, 22);
    });


    //3. 获取结果
    std::cout << res.get() << std::endl;
    thr.join();
    return 0;
}

3.promise

一样的,get不到就会阻塞

promise抛给其他线程之后,就可以不管了,在主线程中拿结果即可

同样,promise也有作用域和生命周期的问题,建议使用智能指针

cpp 复制代码
#include <iostream>
#include <future>
#include <thread>
#include <memory>

int Add(int num1,int num2)
{
    std::cout<<"into add!"<<std::endl;
    return num1+num2;
}

//promise是一个模板类,是对于结果的封装
int main()
{
    // 1.先实例化一个用于获取结果的promise对象
    //std::promise<int> pro;生命周期问题
    std::shared_ptr<std::promise<int>> pro = std::make_shared<std::promise<int>>();

    // 2.实例化一个future,用于得到异步结果
    std::future<int> fut = pro->get_future();

    // 3.给future设置结果就能使用了
    std::thread thr([pro]()
    {
        int sum = Add(11,22);
        pro->set_value(sum);
    }) ;

    
    std::cout<<fut.get()<<std::endl;
    thr.join();

    return 0;
}

三种异步的总结:

future是自己要用的,不好传给其他线程,但既然是异步,一定需要把什么东西给其他线程,所以async用函数的办法自己启动一个其他线程;package_task用封装成类似于function_t的形式传给其他线程;promise则是用同样的一个获取结果的类传给其他线程------可以说future是自己用、promise是别人用,两个类都是用于获得结果的。


理解项目功能需求:

分布式:任何一个服务提供者都可以成为注册中心,避免注册中心崩溃的情况

一个客户端只能连接一个服务器,一个服务器喝客户端构成完整的五元组

代码实现

Detail

日志

  1. 简单日志,不用考虑多线程

简单的AI用例:

2.注意可变参数在中的处理

在函数中的处理可能会不一样。

3.在C语言中,当多个字符串字面量在同一个表达式中相邻时,它们会被自动连接起来形成一个单独的字符串。这是C语言的一个特性,称为字符串字面量的拼接。

4.这个format必须和前面空开,不然会识别失败,字符串字面量的拼接时记得加上空格!

uuid

种子相同(都采用时间),随机数一样的几率就会很大,机器随机数是真的硬件随机数,但是硬件随机数会很慢。

因此,综上,我们在C++中使用的是以机器随机数当种子来生成伪随机数。

C++生成一个范围里的随机数:


#include <iomanip> 是C++标准库中的一个头文件,它提供了对输入/输出流的格式化操作的支持。这个头文件定义了一系列的操纵符(manipulators),这些操纵符可以控制输出(如 std::cout)和输入(如 std::cin)流的格式。

以下是一些常用的操纵符:

  1. std::setw(int width):设置下一个字段的最小宽度。

  2. std::setfill(char ch):设置字段宽度不足时的填充字符。

  3. std::setprecision(int precision):设置浮点数的精度(即小数点后的位数)。

  4. std::fixed:以固定点格式输出浮点数。

  5. std::scientific:以科学计数法格式输出浮点数。

  6. std::hex:以十六进制格式输出整数。

  7. std::dec:以十进制格式输出整数。

  8. std::oct:以八进制格式输出整数。

  9. std::left:左对齐输出。

  10. std::right:右对齐输出。

  11. std::internal:在符号和数值之间对齐输出。

cpp 复制代码
ss<<std::setw(2)<<std::setfill('0')<<std::hex<<distribution(generator);

UUID(Universally Unique Identifier), 也叫通⽤唯⼀识别码,通常由32位16进制数字字符组成。
UUID的标准型式包含32个16进制数字字符,以连字号分为五段,形式为8-4-4-4-12的32个字符, 如:550e8400-e29b-41d4-a716-446655440000。
但是注意,我们生成的一个随机数是32位,相当于是两个16进制数字
另外,序号需要被加锁,也可以采用原子操作,从而实现无锁开发

避免"魔幻数字"!

抽象层实现

消息层次抽象

比如,setmethod setParams都是给客户端用的,用完之后序列化发出去;服务端接受到之后先反序列化,再Get,就可以获取到Req里我们所需要的信息

消息抽象层测试

注意,具体的几个类里面,可能有父类没有声明的接口,那么是调用不了的,可能需要dynamic_pointer_cast

总测试代码如下:

cpp 复制代码
#include "message.hpp"

using std::cout;
using std::endl;
int main()
{
    // 测试Rpc的Message
    // lsnmrpc::BaseMessage::Ptr Rpcreq= lsnmrpc::MessageFactory::create(lsnmrpc::MType::REQ_RPC);
    // lsnmrpc::RpcRequest::Ptr rrp = lsnmrpc::MessageFactory::create<lsnmrpc::RpcRequest>();
    //  lsnmrpc::BaseMessage::Ptr Rpcreq = lsnmrpc::MessageFactory::create(lsnmrpc::MType::REQ_RPC);
    //  lsnmrpc::RpcRequest::Ptr rrp = std::dynamic_pointer_cast<lsnmrpc::RpcRequest>(Rpcreq);
    //  rrp->SetMethod("Add");
    //  Json::Value par;
    //  par["num1"] = 11;
    //  par["num2"] = 22;
    //  rrp->SetParameters(par);
    //  std::string body = rrp->serialize();
    //  cout<<body<<endl;
    //  rrp->SetMethod("Add1");
    //  bool ret = rrp->unserialize(body);
    //  if(rrp->check())
    //  cout<<rrp->GetMethod()<<endl;
    //  cout<<rrp->GetParameters()["num1"]<<" "<<rrp->GetParameters()["num2"]<<endl;
    //  lsnmrpc::RpcResponse::Ptr Rspptr= lsnmrpc::MessageFactory::create<lsnmrpc::RpcResponse>();
    //  Rspptr->SetRcode(lsnmrpc::RCode::RCODE_OK);
    //  Json::Value res;
    //  res["res"] = 33;
    //  Rspptr->SetResult(res);
    //  Rspptr->SetRid(lsnmrpc::UUID::uuid());
    //  std::cout<<Rspptr->serialize();

    // 测试TOPIC
    //  lsnmrpc::TopicRequest::Ptr tpp = lsnmrpc::MessageFactory::create<lsnmrpc::TopicRequest>();
    //  tpp->SetTopic_key("MUSIC");
    //  //tpp->SetTopic_optype(lsnmrpc::TopicOptype::TOPIC_CREATE);
    //  tpp->SetTopic_optype(lsnmrpc::TopicOptype::TOPIC_PUBLISH);
    //  tpp->SetTopicMsg("hello lsnm");
    //  tpp->SetRid(lsnmrpc::UUID::uuid());
    //  if(tpp->check())
    //  {
    //      std::cout<<tpp->serialize()<<endl;
    //      std::cout<<tpp->GetTopic_key()<<endl;
    //      std::cout<<(int)tpp->GetTopic_optype()<<endl;
    //  }
    //  std::cout<<tpp->GetRid()<<endl;

    // lsnmrpc::BaseMessage::Ptr base2 = lsnmrpc::MessageFactory::create(lsnmrpc::MType::RSP_TOPIC);
    // lsnmrpc::TopicResponse::Ptr tpp2 = std::dynamic_pointer_cast<lsnmrpc::TopicResponse>(base2);

    // cout<<tpp2->serialize()<<endl;

    // cout<<"I got here"<<endl;
    // tpp2->SetRcode(lsnmrpc::RCode::RCODE_OK);
    // cout<<tpp2->serialize()<<endl<<errReason(tpp2->GetRcode())<<endl;

    // 测试Service
    lsnmrpc::ServiceRequest::Ptr srp = lsnmrpc::MessageFactory::create<lsnmrpc::ServiceRequest>();
    lsnmrpc::JsonMessage::Address addr;
    addr.first = "81.70.12.246";
    addr.second = 8080;
    srp->SetHost(addr);
    srp->SetMethod("HAHAHA~");
    srp->SetService_optype(lsnmrpc::ServiceOptype::SERVICE_REGISTRY);
    if (srp->check())
    {
        std::cout << srp->serialize() << endl;
        std::cout << srp->GetMethod() << endl;
        std::cout << (int)srp->GetService_optype()<< endl;
    }

    lsnmrpc::BaseMessage::Ptr base3 = lsnmrpc::MessageFactory::create(lsnmrpc::MType::RSP_SERVICE);
    lsnmrpc::ServiceResponse::Ptr srp2 = std::dynamic_pointer_cast<lsnmrpc::ServiceResponse>(base3);
    srp2->SetService_optype(lsnmrpc::ServiceOptype::SERVICE_DISCOVERY);

    std::vector<lsnmrpc::JsonMessage::Address> addrs;
    addrs.push_back(addr);
    addrs.emplace_back(std::make_pair("127.0.0.1",4399));

    srp2->SetHost(addrs);
    srp2->SetMethod("Sub");
  
    srp2->SetRcode(lsnmrpc::RCode::RCODE_OK);
    if (srp2->check())
    {
        std::cout << srp2->serialize() << endl;
        std::cout << errReason(srp2->GetRcode()) << endl;
        //std::cout << (int)srp->GetService_optype()<< endl;

        auto ans = srp2->GetHost();
        int count = 1;
        for(auto& e: ans)
        {
            cout<<"number"<<count++<<" "<<e.first<<" : "<<e.second<<endl;
        }
    }
    return 0;
}

Muduo

Server(必看!!)

*********注意***************

千万不要混淆一个问题,在Server的基类中,我们设计了三个回调函数,这三个回调函数都是由外部注册的,指导我们封装的服务器的。 而我们(包括demo中)进行的setMessageCallback和setConnectionCallback,是muduo库内部的方法,是告诉底层的muduo库的epoll,当接到一个新的连接或者一个连接断开、或者有消息到来时,muduo库里该回调什么方法------------因此,我们在构造函数里先注册底层muduo库里应该回调的方法,也就是注册onMessage和onConnection进去。在onMessage和onConnection中处理数据时,当我们处理了差不多收到的消息或者连接时,再回调基类中我们自己设计的三种三种更进一步的方法(比如Rpc调用请求的具体处理方法,或者Topic的具体处理方法!)

因此,这副demo里的_OnMessageCB或者_OnConnectionCB的参数,都是规定了的,只要理解这一点即可

Client

调试bug:

这个里面坚决不能用+=,必须用append,因为强转之后,只有一个char*,他+=的时候,如果+=到的第一个字节是0,就几把完蛋了。

每次灵机一动,觉得这么写更好,就要他妈出错!
服务器端执行完之后:又显示length is too short

客户端执行完之后:也再次显示length is too short

注意一个现象:在发送完之后,又再次执行了OnMessage,所以会有这个消息,这样也保证了我们能读完缓冲区

DisPatch

注意,之前是人给MessageCallBack进行设置

这个Dispatch的OnMessageCallBack是应该注册到:

如上的是人为是设置,理应由Disapatch的OnMessageCallBack去被设置进去,也就是每当获取一个完整的连接,就进行分发。

而每一种具体方法的处理,就需要我们先注册到Dispacther里,当Dispatcher确定是使用这种方法之后,再执行。

现在的代码:

cpp 复制代码
class Dispatcher
    {
    public:
        using Ptr = std::shared_ptr<Dispatcher>;

        void registryHandler(lsnmrpc::MType mtype, const lsnmrpc::MessageCallBack &handler)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            //_handlers[mtype] = handler;
            auto ret = _handlers.insert(std::make_pair(mtype, handler));
            if (!ret.second)
            {
                ILOG("方法已经存在");
            }
        }

        //用户层把这个注册进去,相当于服务器里拿到的具体数据都会走以下这个函数处理,至于为什么_handlers也是MessageCallBack,是为了在参数上更方便,在形式上更容易理解
        void OnMessage(const lsnmrpc::BaseConnection::Ptr &conn, const lsnmrpc::BaseMessage::Ptr &msg)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _handlers.find(msg->GetMType());

            if (it!=_handlers.end())
            {
                //该方法存在
                it->second(conn,msg);
            }
        }

    private:
        std::mutex _mutex;
        std::unordered_map<lsnmrpc::MType, lsnmrpc::MessageCallBack> _handlers;
    };

封装一下使用:

服务器:

cpp 复制代码
#include "message.hpp"
#include "net.hpp"
#include "Dispatch.hpp"


// void OnMessage(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
// {
//     std::string info = mes->serialize();
//     std::cout<<info<<std::endl;

//     auto resp = lsnmrpc::MessageFactory::create<lsnmrpc::RpcResponse>();
//     resp->SetRcode(lsnmrpc::RCode::RCODE_OK);
//     resp->SetMType(lsnmrpc::MType::RSP_RPC);
//     //resp->SetRid(lsnmrpc::UUID::uuid());
//     resp->SetRid("11111");
//     resp->SetResult(33);

//     conn->send(resp);
// } 

void OnRPCRequest(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
{
    std::cout<<"收到了RPCRequest"<<std::endl;
    std::string info = mes->serialize();
    std::cout<<info<<std::endl;

    lsnmrpc::RpcResponse::Ptr rpc_response = lsnmrpc::MessageFactory::create<lsnmrpc::RpcResponse>();
    rpc_response->SetMType(lsnmrpc::MType::RSP_RPC);
    rpc_response->SetRcode(lsnmrpc::RCode::RCODE_OK);
    rpc_response->SetRid("11111");
    rpc_response->SetResult(33);

    conn->send(rpc_response);

}

void OnTopicRequest(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
{
    std::cout<<"收到了RPCRequest"<<std::endl;
    std::string info = mes->serialize();
    std::cout<<info<<std::endl;

    lsnmrpc::TopicResponse::Ptr topic_response = lsnmrpc::MessageFactory::create<lsnmrpc::TopicResponse>();
    topic_response->SetMType(lsnmrpc::MType::RSP_TOPIC);
    topic_response->SetRcode(lsnmrpc::RCode::RCODE_OK);
    topic_response->SetRid("11111");

    conn->send(topic_response);
}


int main()
{
    lsnmrpc::BaseServer::Ptr server = lsnmrpc::ServerFactory::create(8889);
    lsnmrpc::Dispatcher::Ptr dispatcher = std::make_shared<lsnmrpc::Dispatcher>();
    server->SetMessageCallBack
    (
       std::bind(&lsnmrpc::Dispatcher::OnMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2)
    );
    //注册映射关系
    dispatcher->registryHandler(lsnmrpc::MType::REQ_RPC,OnRPCRequest);
    dispatcher->registryHandler(lsnmrpc::MType::REQ_TOPIC,OnTopicRequest);

    server->start();
    return 0;
}

客户端:

cpp 复制代码
#include "message.hpp"
#include "net.hpp"
#include "Dispatch.hpp"
#include <thread>


// void OnMessage(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
// {
//     std::string info = mes->serialize();
//     std::cout<<info<<std::endl;
// } 


void OnRPCResponse(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
{
    std::cout<<"收到了RPC响应";
    std::string info = mes->serialize();
    std::cout<<info<<std::endl;
}

void OnTopicResponse(const lsnmrpc::BaseConnection::Ptr& conn, const lsnmrpc::BaseMessage::Ptr& mes)
{
    std::cout<<"收到了Topic响应";
    std::string info = mes->serialize();
    std::cout<<info<<std::endl;
}



int main()
{
    //lsnmrpc::BaseServer::Ptr server = lsnmrpc::ServerFactory::create();
    lsnmrpc::BaseClient::Ptr client = lsnmrpc::ClientFactory::create("127.0.0.1",8889);



    //将Dispatcher的方法注册到客户端中去
    lsnmrpc::Dispatcher::Ptr dispatcher = std::make_shared<lsnmrpc::Dispatcher>();
    client->SetMessageCallBack
    (
       std::bind(&lsnmrpc::Dispatcher::OnMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2)
    );
    //注册映射关系
    dispatcher->registryHandler(lsnmrpc::MType::RSP_RPC,OnRPCResponse);
    dispatcher->registryHandler(lsnmrpc::MType::RSP_TOPIC,OnTopicResponse);



    //构造一个RPC请求
    auto RPCrequest = lsnmrpc::MessageFactory::create<lsnmrpc::RpcRequest>();
    Json::Value parameters;
    parameters["num1"] = 11;
    parameters["num2"] = 22;
    RPCrequest->SetParameters(parameters);
    RPCrequest->SetMethod("Add");
    RPCrequest->SetMType(lsnmrpc::MType::REQ_RPC);
    //request->SetRid(lsnmrpc::UUID::uuid());
    RPCrequest->SetRid("11111");



    //构造一个Topic请求
    auto Topicrequest = lsnmrpc::MessageFactory::create<lsnmrpc::TopicRequest>();
    Topicrequest->SetMType(lsnmrpc::MType::REQ_TOPIC);
    Topicrequest->SetRid("2222");
    Topicrequest->SetTopic_key("music");
    Topicrequest->SetTopic_optype(lsnmrpc::TopicOptype::TOPIC_CREATE);



    //发送
    client->connect();
    client->send(RPCrequest);
    client->send(Topicrequest);

    std::this_thread::sleep_for(std::chrono::seconds(100));
    //sleep(10);
    client->shutdown();
    return 0;
}

解决父类指针BaseMessage没有未声明的接口

貌似是没有问题的,但是在Response里有一个这样的问题:

message作为父类,是不能调用我们实现的Get方法的。但是我们的Response系列函数一定需要Get方法,比如GetMethod。

方案1:使用dynamic_cast_pointer,但是显然是增加用户使用成本的

方案2:使用一个新的类封装不同消息该进行的调用,但是Dispatch中是需要一个哈希表来存所有的内容的,哈希表的第二个元素必须是固定的类型

而且,既然现在的MessageCallback都已经决定了,一定是Dispatch模块注册到服务器处理完整消息的时候的调用,所以这些不同消息类型的回调函数,是没有必要必须按照MessageCallback的格式设置的。

所以:

这样还是不同类型的:


利用C++多态解决:

用多态的思想解决这个问题更加优雅:

cpp 复制代码
#include "net.hpp"
#include "message.hpp"
#include <mutex>
#include <unordered_map>
#include <functional>

namespace lsnmrpc
{

   
    class CallBack
    {
    public:
        using Ptr = std::shared_ptr<CallBack>;
        virtual void OnMessage(const lsnmrpc::BaseConnection::Ptr &conn, const lsnmrpc::BaseMessage::Ptr &msg) = 0;
    };

    //基类指针放进Dispatcher的哈希表里,
    template<typename T>
    class CallBackT : public CallBack
    {
    public:
        using Ptr = std::shared_ptr<CallBackT<T>>;
        using MessageCallback_T = std::function<void(const lsnmrpc::BaseConnection::Ptr &conn, const std::shared_ptr<T> &msg)>;

        CallBackT(const MessageCallback_T& msgCallBack_t)
        : _handler(msgCallBack_t)
        {}

        virtual void OnMessage(const lsnmrpc::BaseConnection::Ptr &conn, const lsnmrpc::BaseMessage::Ptr &msg) override
        {
            std::shared_ptr<T> type_msg = std::dynamic_pointer_cast<T>(msg);
            _handler(conn,type_msg);
        }
    private:
        MessageCallback_T _handler;
    };

    class Dispatcher
    {
    public:
        using Ptr = std::shared_ptr<Dispatcher>;

        template<typename T>
        void registryHandler(lsnmrpc::MType mtype, const typename CallBackT<T>::MessageCallback_T &handler)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            //_handlers[mtype] = handler;
            CallBack::Ptr cb = std::make_shared<CallBackT<T>>(handler);
            auto ret = _handlers.insert(std::make_pair(mtype, cb));
            if (!ret.second)
            {
                ILOG("方法已经存在");
            }
        }

        //用户层把这个注册进去,相当于服务器里拿到的具体数据都会走以下这个函数处理,至于为什么_handlers也是MessageCallBack,是为了在参数上更方便,在形式上更容易理解
        void OnMessage(const lsnmrpc::BaseConnection::Ptr &conn, const lsnmrpc::BaseMessage::Ptr &msg)
        {
            std::unique_lock<std::mutex> lock(_mutex);
            auto it = _handlers.find(msg->GetMType());
            //DLOG("msg->GetMType() : %d",(int)msg->GetMType());

            if (it!=_handlers.end())
            {
                //该方法存在
                it->second->OnMessage(conn,msg);
                return ;
            }

            ELOG("收到未知类型消息");
        }

    private:
        std::mutex _mutex;
        std::unordered_map<lsnmrpc::MType, lsnmrpc::CallBack::Ptr> _handlers;
    };
}

最后就能单独使用各种具体消息类型的方法了:

注册函数这么写即可:


RPCRouter

可以用vim直接查看VType应该有哪些类型


ServiceDescribe有点复杂,采用建造者模式而非工厂模式

也就是在里面进行设置,返回一个完整的对象------先建立好各个零件,再拼起来

Requestor框架

RPCCaller

框架


重点注意,RpcCaller中,如果想实现异步调用,因为用户的本质不是使用BaseMessage,而是想拿到一个个的json::Value,所以应该使用回调send的方法,当Rquestor中的OnResponse触发时,采取回调去给BaseMessage赋值,再用promise把BaseMessage的GetResult()给带出来(需要对BaseMessage进行dynamic_cast)


整个RPC过程的梳理:


记得,Call的三种方法都需要测试:

RPC联调:

服务端代码:

cpp 复制代码
#include "../../source/server/rcp_router.hpp"
#include <mutex>

void Add(const Json::Value& params,Json::Value& res)
{
    int n1 = params["num1"].asInt();
    int n2 = params["num2"].asInt();

    res = n1+n2;
}

int main()
{
    //构造服务端对应的rpc_router
    lsnmrpc::server::RPCRouter::Ptr rpc_router = std::make_shared<lsnmrpc::server::RPCRouter>();
    //std::unique_ptr<lsnmrpc::server::SDFactory> desc_factory = std::make_unique<lsnmrpc::server::SDFactory>();
    std::unique_ptr<lsnmrpc::server::SDFactory> desc_factory(new lsnmrpc::server::SDFactory());

    desc_factory->SetParametersDescribe("num1",lsnmrpc::server::VType::INTEGRAL);
    desc_factory->SetParametersDescribe("num2",lsnmrpc::server::VType::INTEGRAL);

    desc_factory->SetMethodName("Add");
    desc_factory->SetReturnType(lsnmrpc::server::VType::INTEGRAL);
    desc_factory->SetCallback(Add);

    //以上是其他服务器提供的一个方法,把他注册到rpc_router里
    rpc_router->registryMethod(desc_factory->build());

    //创造服务器,绑定方法,准备运行
    lsnmrpc::BaseServer::Ptr server = lsnmrpc::ServerFactory::create(8889);
    lsnmrpc::Dispatcher::Ptr dispatcher = std::make_shared<lsnmrpc::Dispatcher>();
    
    //注册映射关系->把rpc的方法注册到dispatcher里
    auto OnRPCRequest = std::bind(&lsnmrpc::server::RPCRouter::OnRpcRequest,rpc_router.get(),std::placeholders::_1,std::placeholders::_2);
    dispatcher->registryHandler<lsnmrpc::RpcRequest>(lsnmrpc::MType::REQ_RPC,OnRPCRequest);
    // dispatcher->registryHandler<lsnmrpc::TopicRequest>(lsnmrpc::MType::REQ_TOPIC,OnTopicRequest);


    //把dispatcher的方法注册到服务器里
    server->SetMessageCallBack
    (
       std::bind(&lsnmrpc::Dispatcher::OnMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2)
    );

    server->start();
    return 0;

}

客户端代码:

cpp 复制代码
#include "../../source/client/requestor.hpp"
#include "../../source/client/rpccaller.hpp"
#include <thread>

//第三种调用的callback
void callback(const Json::Value& resp)
{
    ILOG("result3 in callback is %d",resp.asInt());
}

int main()
{
    auto requestor = std::make_shared<lsnmrpc::client::Requestor>();
    auto rpccaller = std::make_shared<lsnmrpc::client::RpcCaller>(requestor);

    lsnmrpc::BaseClient::Ptr client = lsnmrpc::ClientFactory::create("127.0.0.1",8889);

    //将Dispatcher的方法注册到客户端中去----所有的请求,都会经过requestor,所以reuestor封装了所有的注册
    lsnmrpc::Dispatcher::Ptr dispatcher = std::make_shared<lsnmrpc::Dispatcher>();
    
    auto cb = std::bind(&lsnmrpc::client::Requestor::OnResponse,requestor.get(),std::placeholders::_1,std::placeholders::_2);
    dispatcher->registryHandler<lsnmrpc::BaseMessage>(lsnmrpc::MType::RSP_RPC,cb);

    //再把Dispacth的方法注册到服务器中去
    client->SetMessageCallBack
    (
       std::bind(&lsnmrpc::Dispatcher::OnMessage,dispatcher.get(),std::placeholders::_1,std::placeholders::_2)
    );

    //发送
    client->connect();
    lsnmrpc::BaseConnection::Ptr conn = client->GetConnection();

    Json::Value params;
    params["num1"] = 1123;
    params["num2"] = 29;
    Json::Value result1;
    bool ret = rpccaller->call(conn,"Add",params,result1);
    if(ret != false)
    {
        //std::cout<<result1.asInt()<<std::endl;
        ILOG("result1 is %d",result1.asInt());
    }


    
    params["num1"] = 11;
    params["num2"] = 239;
    Json::Value result2;
    lsnmrpc::client::RpcCaller::JsonAsyncResp resp;
    ret = rpccaller->call(conn,"Add",params,resp);
    if(ret != false)
    {
        result2 = resp.get();
        ILOG("result2 is %d",result2.asInt());
    }


    params["num1"] = 7745;
    params["num2"] = 239;
    //Json::Value result3;
    //lsnmrpc::client::RpcCaller::JsonAsyncResp resp;
    ret = rpccaller->call(conn,"Add",params,callback);
    if(ret != false)
    {
        ILOG("----------\n");
    }
    

    std::this_thread::sleep_for(std::chrono::seconds(10));
    //sleep(10);
    client->shutdown();
    return 0;
}

遇到的bug:

SDMnager的构造没有手动传入,std::mutex是0,导致抛异常,手动加构造函数即可
果然,作为异步IO,"---------"会先打出来,然后再是cal中异步执行的结果

RPC模块就完成了


Registry

接下来是服务的注册与发现服务:

  1. 服务注册的作用与原理

    服务注册是分布式系统实现高可用性的关键机制。其核心功能是让服务节点在注册中心登记自身提供的服务能力,从而构建一个可动态扩展的服务网络。

  2. 服务发现的功能解析

    RPC调用方需要通过服务发现机制确定可用的服务节点。该过程本质上是向注册中心查询指定服务的可用节点,并将这些节点信息缓存以供后续调用使用。

  3. 服务下线机制

    系统采用长连接实时监测服务节点的在线状态。当服务提供方断开连接时,系统会执行以下流程:

  • 查询该节点提供的所有服务
  • 分析曾发现过这些服务的调用方
  • 向相关调用方发送服务下线通知
  1. 服务上线通知机制
    由于服务发现通常是一次性操作(调用方不会重复查询),当新服务节点上线时,已缓存的调用方无法感知。因此系统需要:
  • 主动向所有发现过该服务的调用方
  • 推送服务上线通知
  • 确保调用方及时获取最新服务节点信息

服务提供方一定是有一个服务器,也有一个客户端,服务器是让用户去进行RPC调用的,客户端是则是让服务提供方进行服务注册的;而客户端除了caller,还得有一个finder客户端,去找已经上线的服务。同时,每一个服务器都可以作为registry,这样注册中心挂了就还可以有备份。

需要4个hash

整体设计框架,一个ProviderManager,一个DiscoverManager, 我认为还需要一个整体的PDManager来控制两个管理器,方便在Dispatcher中进行回调

cpp 复制代码
#pragma once
#include "../common/net.hpp"
#include "../common/Dispatch.hpp"
#include <vector>
#include <unordered_map>

namespace lsnmrpc
{
    namespace server
    {
        using Address = lsnmrpc::JsonMessage::Address;
        class ProviderManager
        {
        public:
            using Ptr = std::shared_ptr<ProviderManager>;
            struct Provider
            {
                using Ptr = std::shared_ptr<Provider>;
                Address _host;
                std::vector<std::string> _methods; // 一个provider可以提供的多个方法
                BaseConnection::Ptr _conn;
                Provider(const BaseConnection::Ptr &conn, const Address &host);
                Ptr appendMethod(const std::string &newmethod); // 其实,因为是结构体的原因,所有对象都可以直接访问,这样只是为了更优雅
            };

            // 当新的服务者加入的时候(加入之后的服务者可以通过appendMethod添加方法进去)
            Provider::Ptr appendProvider(const BaseConnection::Ptr &new_conn, const Address &host)
            {
            }

            // 当一个服务提供者断开连接的时候,获取他的信息--用于进行服务下线通知
            Provider::Ptr getProvider(const BaseConnection::Ptr &conn)
            {
            }

            // 当一个服务提供者断开连接的时候,用于删除他的信息(是否和上一个接口冗余?)
            void delProvider(const BaseConnection::Ptr &conn)
            {
            }

        private:
            std::mutex _mutex;
            std::unordered_map<std::string, std::vector<Provider::Ptr>> _providers; // 谁提供了该方法,可能用于RR轮询
            std::unordered_map<BaseConnection::Ptr, Provider::Ptr> _conns;          // 连接与提供者之间的映射
        };

        class DiscoverManager
        {
        public:
            using Ptr = std::shared_ptr<DiscoverManager>;
            struct Discover
            {
                using Ptr = std::shared_ptr<Discover>;
                BaseConnection::Ptr _conn;
                std::vector<std::string> _asked_methods; // 一个发现者查询过的方法
                Discover(const BaseConnection::Ptr &conn);
                void appendMethods(const std::string &newmethod)
                {
                    // 增加查询过的方法
                }
            };

            // 当新的发现者到来的时候(不存在就创建)
            Discover::Ptr addDiscover(const BaseConnection::Ptr &conn)
            {
            }

            // 当发现者断开连接,找到发现者信息,删除相关数据
            void delDiscover(const BaseConnection::Ptr &conn)
            {
            }

            // 新服务上线或者老服务下线的通知
            void onlineNotify(const std::string &method, const Address &host)
            {
            }

            void offlineNotify(const std::string &method, const Address &host)
            {
            }

        private:
            std::mutex _mutex;
            std::unordered_map<std::string, std::vector<Discover::Ptr>> _Discovers; // 进行服务通知时,都是根据谁发现了这个方法来通知
            std::unordered_map<BaseConnection::Ptr, Discover::Ptr> _conns;          // 连接与发现者之间的映射
        };

        // 封装一下这两个方法
        class PDManager // provider_discover_manager
        {
        public:
            void onServiceRequest(const BaseConnection::Ptr& conn,lsnmrpc::ServiceRequest::Ptr& req)
            {
                //服务操作请求:服务注册/服务发现/
                //这是让dispatch回调的方法
            }

            void onConnShutdown(const BaseConnection::Ptr& conn)
            {
                //当有连接断开时回调此方法,方法内部自动判断是provider断开还是discover断开
            }
        private:
            ProviderManager::Ptr _provider_manager;
            DiscoverManager::Ptr _discover_manager;
        };

    }
}

除此之外,实现完之后应该还有response

cpp 复制代码
private:
            void registryResponse(const BaseConnection::Ptr &conn, lsnmrpc::ServiceRequest::Ptr &req)
            {
                lsnmrpc::ServiceResponse::Ptr rsp = lsnmrpc::MessageFactory::create<ServiceResponse>();
                rsp->SetRid(req->GetRid());
                rsp->SetRcode(RCode::RCODE_OK);
                rsp->SetMType(MType::RSP_SERVICE);
                rsp->SetService_optype(ServiceOptype::SERVICE_REGISTRY);

                conn->send(rsp);
            }

            void discoverResponse(const BaseConnection::Ptr &conn, lsnmrpc::ServiceRequest::Ptr &req)
            {
                lsnmrpc::ServiceResponse::Ptr rsp = lsnmrpc::MessageFactory::create<ServiceResponse>();
                rsp->SetRid(req->GetRid());
                rsp->SetRcode(RCode::RCODE_OK);
                rsp->SetMType(MType::RSP_SERVICE);
                rsp->SetService_optype(ServiceOptype::SERVICE_DISCOVERY);

                std::vector<Address> hosts;
                hosts = _provider_manager->methodsHosts(req->GetMethod());
                if (hosts.empty())
                {
                    rsp->SetRcode(RCode::RCODE_NOT_FOUND_SERVICE);
                    conn->send(rsp);
                    return;
                }

                rsp->SetHost(hosts);
                rsp->SetMethod(req->GetMethod());
                conn->send(rsp);
                return;
            }

            void errorResponse(const BaseConnection::Ptr &conn, lsnmrpc::ServiceRequest::Ptr &req)
            {
                lsnmrpc::ServiceResponse::Ptr rsp = lsnmrpc::MessageFactory::create<ServiceResponse>();
                rsp->SetRid(req->GetRid());
                rsp->SetRcode(RCode::RCODE_INVALID_OPTYPE);
                rsp->SetMType(MType::RSP_SERVICE);
                rsp->SetService_optype(ServiceOptype::SERVICE_UNKNOWN);

                conn->send(rsp);
            }

客户端

客户端的服务提供者相对简单:

cpp 复制代码
#pragma once
#include "../common/net.hpp"
#include "../common/Dispatch.hpp"
#include "requestor.hpp"

namespace lsnmrpc
{
    namespace client
    {
        // 注册端 和 发现端不会出现在同一个主机上
        class Provider
        {
        public:
            using Ptr = std::shared_ptr<Provider>();
            Provider(const Requestor::Ptr &requestor)
                : _requestor(requestor)
            {
            }

            // 向外提供的注册服务的接口:1.连接registry服务器 2.发送数据
            bool resgistryMethod(const BaseConnection::Ptr conn, const std::string &method, const Address &host)
            {
                // 发送注册请求
                lsnmrpc::ServiceRequest::Ptr svc_req = MessageFactory::create<ServiceRequest>();
                svc_req->SetRid(UUID::uuid());
                svc_req->SetMType(MType::REQ_SERVICE);
                svc_req->SetMethod(method);
                svc_req->SetHost(host);
                svc_req->SetService_optype(ServiceOptype::SERVICE_REGISTRY);

                BaseMessage::Ptr msg_rsp;
                bool ret = _requestor->send(conn, svc_req, msg_rsp);

                //采用的同步send,一旦send成功,就会把返回消息放在msg_rsp里
                if (ret == false)
                {
                    ELOG("%s 服务注册失败!", method.c_str());
                    return false;
                }
                auto service_rsp = std::dynamic_pointer_cast<ServiceResponse>(msg_rsp);
                if (service_rsp.get() == nullptr)
                {
                    ELOG("响应类型向下转换失败!");
                    return false;
                }
                if (service_rsp->GetRcode() != RCode::RCODE_OK)
                {
                    ELOG("服务注册失败,原因:%s", errReason(service_rsp->GetRcode()).c_str());
                    return false;
                }
                return true;
            }

        private:
            Requestor::Ptr _requestor;
        };
    }
}

服务发现者会比较麻烦,我决定再加一个类,用来描述服务端返回的ServiceResponse里的vector<address>,因为还有RR轮询需要我们设计,可能会有计数器一类的东西:

框架:

MethodHost相当于是对于vector<Address> _hosts的一层封装,就像封装了一层数据结构的感觉一样。


MethodsHost的技术选型问题:

unordered_set不能下标访问,轮询及其不方便;

vector的增删复杂度更高。

不过轮询的使用比增删的使用多


客户端封装

理解完整个客户端之后再回来看这段:

为什么客户端和服务器都要单独封装一层,就是为了让各种dispatch\rpccaller共享同一个MuduoClient,使用里面的connection去和服务器进行连接。在服务发现模式下,Discover就是这个MuduoClient;在非服务发现模式下,_rcp_client就是这个MuduoClient.

客户端的connection连接过去了,就会被服务器记住,服务器最原始的、自带的几个回调的唯一一个参数就是这个connection。比如1服务发现模式下,Discover就通过这个connection访问到了服务注册中心,服务注册中心就可以通过这个connection进行返回

在RPC客户端中融入进服务注册和服务发现的功能
第一个框架:RegistryClient,这样就可以完成之前在test中完成的一系列操作,包括各种bind等等;注意这里的registryMethod,直接去Provider里面拿就行,因为根本存在一个client的情况,不需要connetcion

Discover同样:

然后是最关键的RpcClient

对RegistryClient的实现:

cpp 复制代码
class RegistryClient
        {
        public:
            using Ptr = std::shared_ptr<RegistryClient>;
            //在构造函数中,传入注册中心的地址信息
            RegistryClient(const std::string& ip,int port)
            : _requestor(std::make_shared<Requestor>())
            , _dispatcher(std::make_shared<Dispatcher>())
            , _provider(std::make_shared<client::Provider>(_requestor))
            {
                //requestor的方法绑定到dispatcher中去
                auto requestor_cb = std::bind(&Requestor::OnResponse,_requestor.get(),std::placeholders::_1,std::placeholders::_2);
                _dispatcher->registryHandler<BaseMessage>(MType::RSP_SERVICE,requestor_cb);

                //dispatcher中的方法绑定到服务器中去
                auto dispatcher_cb = std::bind(&Dispatcher::OnMessage,_dispatcher.get(),std::placeholders::_1,std::placeholders::_2);

                //得先有服务器才能绑定
                _client = ClientFactory::create(ip,port);

                //绑定并且连接
                _client->SetMessageCallBack(dispatcher_cb);
                _client->connect();
            }

            bool registryMethod(const std::string &method, const Address &host)
            {
                //调用_provider注册方法到方法注册中心去
                return _provider->resgistryMethod(_client->GetConnection(),method,host);
            }
        private:
            Requestor::Ptr _requestor;
            Dispatcher::Ptr _dispatcher;
            BaseClient::Ptr _client;//conn字段直接封装在里面即可
            client::Provider::Ptr _provider;
        };

对DiscoveryClient的实现几乎和上述是一样的,但是有些许问题:

Discovery会"毫无征兆"的、不经过requestor的收到一些消息,这部分的消息也需要Dispatcher中有对应的处理方法,所以这个地方要registryMethod两次,也就是再把Discoverer中的onServiceRequest注册到dispatcher中去

然后最后的实现就和registryClient是一样的

RpcClient

现在思考的是,RpcCLient中是采用短链接还是长连接:

短链接思路:

长连接:一轮轮询之后,再次发起相同服务主机的rpc调用之后,就会发现已经连接成功的客户端

坏处是:多一层回调,当下线通知到来时,需要在池子中删除刚刚已经下线的服务

短链接:思想简单

坏处:异步处理麻烦:调用完毕的时候,不是收到响应的时候,调用完毕之后可能很快就会关闭客户端,但其实现在还没有收到响应

采用长连接:

在client的Discover中,收到下线通知,应该再去调用一个回调,删除RpcClient中的这个服务主机。

rpc_client在没有开启这个_enableDiscovery之前,就是一个单纯的rpc调用客户端,比如他里面是一个Add方法,用户仅仅是用这个客户端去调用远端机器的算力去执行这个Add方法;一旦开启了这个使能开关,主要就是使用DiscoveryClient去返回服务提供方的地址。面对这个地址,如果我们还未拥有访问这个地址的客户端,就创建一个访问该Address的客户端,加到下面的unordered_map -》 _rpc_clients里去

要么从连接池获取,要么直接获取

cpp 复制代码
bool call(const std::string method, const Json::Value &params, Json::Value &result)
            {
                // 获取和服务提供者通信的客户端: 1.服务发现(_rcp_client) 2.固定的服务调用(_rcp_client)
                BaseClient::Ptr client;
                if (_enableDiscovery)
                {
                    // 服务发现模式
                    Address target_host;
                    bool ret = _discover_client->discoverMethod(method, target_host);
                    if (ret == false)
                    {
                        ELOG("当前 %s 服务,没有找到服务提供者!", method.c_str());
                        return false;
                    }
                    client = getClient(target_host); // 找找看看现在有没有现成的host对应的访问客户端
                    if (client.get() == nullptr)
                    {
                        // 没有现成的客户端就生成一个
                        client = newClient(target_host);
                    }
                }
                else
                {
                    client = _rpc_client;
                }


                //...
                //通过刚刚得到的客户端,调用rpccaller里对应的call
            }

但是难道三种call都要这样获取一遍吗?我们可以把这一端封装出来

cpp 复制代码
 BaseClient::Ptr obtainConnectedClient(const std::string method)
            {
                // 获取和服务提供者通信的客户端: 1.服务发现(_rcp_client) 2.固定的服务调用(_rcp_client)
                BaseClient::Ptr client;
                if (_enableDiscovery)
                {
                    // 服务发现模式
                    Address target_host;
                    bool ret = _discover_client->discoverMethod(method, target_host);
                    if (ret == false)
                    {
                        ELOG("当前 %s 服务,没有找到服务提供者!", method.c_str());
                        return BaseClient::Ptr();
                    }
                    client = getClient(target_host); // 找找看看现在有没有现成的host对应的访问客户端
                    if (client.get() == nullptr)
                    {
                        // 没有现成的客户端就生成一个
                        client = newClient(target_host);
                    }
                }
                else
                {
                    client = _rpc_client;
                }

                return client;
            }

实现完三种call之后发现,我们没有方法去执行过delClient,说明下线的回调方法还不存在。

由于RR轮询机制的存在,我们的上线思路是,如果此次轮到的host对应的客户端还不存在,就自动创建,所以新增的方法不需要我们去执行回调;但是下线不一样,下线的一个host对应的客户端很可能已经存在于RpcClient的客户端池子里,需要利用回调删除。

RPCServer

注意,为什么说注册中心只会收到注册或者发现两种ServiceOptype,因为所有的上线和下线(另外两种Optype)都来源于以上两种操作,只有发生了注册,才会通知上线;只有有连接断开,才会通知下线。

测试:

第一版测试:先不要注册中心,只测试两个rpc直接交互:

基于服务注册与发现的rpc调用:

需要三个程序,注册中心、客户端、调用服务器

出现的bug:发现者模式下,返回的服务提供方的host一直是0.0.0.0,原因是输出型参数赋值有问题

发布订阅


topic的客户端:5个是对用户的,1个是面对dispatcher的

搭建框架:

cpp 复制代码
namespace lsnmrpc
{
    namespace client
    {
        class TopicManager
        {
        public:
            using Ptr = std::shared_ptr<TopicManager>;

            //(主题的名称,推送过来的消息),其中第一个参数是必须的,用于在哈希表中找到对应的方法
            using SubCallback = std::function<void(const std::string& key,const std::string& msg)>; 
            TopicManager(const Requestor::Ptr requestor)
            :_requestor(requestor)
            {}

            /*
                作为客户端,应该有新建、删除、订阅、取消订阅、推送等五种基本方法,除此之外,还要一个接受到推送消息时注册给dispatcher的方法
            */

            void create(const BaseConnection::Ptr& conn,const std::string& key);//key就是topic的名字
            void remove(const BaseConnection::Ptr& conn,const std::string& key);
            void subscribe(const BaseConnection::Ptr& conn,const std::string& key,const SubCallback& cb);
            void cancle(const BaseConnection::Ptr& conn,const std::string& key);
            void publish(const BaseConnection::Ptr& conn,const std::string& key,const std::string& msg);//第三个参数是要推送的消息
            void onPublish(const BaseConnection::Ptr& conn,const TopicRequest::Ptr& msg);
        private:
            std::mutex _mutex;
            std::unordered_map<std::string,const SubCallback> _callbacks;
            Requestor::Ptr _requestor;

        };
    };
}

这个地方注意,为了避免刚刚订阅就有消息推送,但是消息推送过来的时候还没有建立回调函数,所以先建立映射。

但是这样处理不了订阅失败的情况

最终测试:

再看:订阅端也是没有问题的

完结散花

总结:

项目:json-rpc框架

功能:实现了基础的rpc远程调用功能,以及基于服务注册与发现的rpc远程调用功能,简单的发布订阅功能。 所用技术:json序列化,网络通信-高性能服务器(muduo),rpc,发布订阅。

框架设计:框架分三层进行设计--(抽象层,实现层,业务层) 抽象:针对底层的网络通信和协议部分进行了抽象,降低框架的依赖,提高框架的灵活度,以及可维护性。

实现:针对抽象的功能进行具体的实现(muduo库搭建高性能客户端服务器,TLV应用层协议格式,消息类型) 业务:基础rpc,服务发现与注册以及上线/下线通知,发布订阅。

具体模块划分:

应用层协议抽象与实现

网络通信模块的抽象与实现

消息的抽象与实现

rpc客户端与服务端业务

服务发现与注册业务

发布订阅业务

源码如下:

code of cpp Linux 算法: 第二年开始1的仓库,主要是存放Linux学习机器下写的代码

参考:
https://zhuanlan.zhihu.com/p/460646015
https://zhuanlan.zhihu.com/p/33298916
https://zhuanlan.zhihu.com/p/388848964
源码: https://github.com/cinemast/libjson-rpc-cpp
源码: https://github.com/qicosmos/rest_rpc

相关推荐
晨陌y5 小时前
从 0 到 1 开发 Rust 分布式日志服务:高吞吐设计 + 存储优化,支撑千万级日志采集
开发语言·分布式·rust
Yeniden6 小时前
设计模式>原型模式大白话讲解:就像复印机,拿个原件一复印,就得到一模一样的新东西
java·设计模式·原型模式·1024程序员节
微信api接口介绍6 小时前
微信个人发消息api
运维·服务器·开发语言·前端·网络·微信·ipad
小二·6 小时前
仓颉语言中Channel通道的深度解析:从原理到高并发实践
开发语言
给大佬递杯卡布奇诺6 小时前
FFmpeg 基本数据结构 AVPacket分析
数据结构·c++·ffmpeg·音视频
南方的狮子先生6 小时前
【数据结构】从线性表到排序算法详解
开发语言·数据结构·c++·算法·排序算法·1024程序员节
froginwe116 小时前
HTML5 Audio(音频)
开发语言
程序员皮皮林7 小时前
Java 25 正式发布:更简洁、更高效、更现代!
java·开发语言·python
程序猿编码7 小时前
Linux 文件变动监控工具:原理、设计与实用指南(C/C++代码实现)
linux·c语言·c++·深度学习·inotify