前言
本文是作者学习该项目时的笔记和体会,是笔记向的,希望能对也在实现这个项目的朋友有一定的帮助,部分内容可能由于记录不全导致前后完整性缺失,会在后续逐渐补充
- 什么是RPC
RPC(Remote Procedure Call)远程过程调⽤,是⼀种通过⽹络从远程计算机上请求服务,⽽不需要
了解底层⽹络通信细节。RPC可以使⽤多种⽹络协议进⾏通信, 如HTTP、TCP、UDP等, 并且在
TCP/IP⽹络四层模型中跨越了传输层和应⽤层。简⾔之RPC就是像调⽤本地⽅法⼀样调⽤远程⽅法。
过程可以理解为业务处理、计算任务,更直⽩的说,就是程序/⽅法/函数等,就是像调⽤本地⽅法⼀样
调⽤远程⽅法。
人话:远程调用 Netcal
让调用者感知不到调用了远程方法

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

发布订阅:
一个推送客户端+一个接受客户端+一个发布订阅服务器
配置环境:

形成.bak,免得设置失败了回不来
./ect用于存放系统的配置文件

build.sh/install之后,会在上一层生成一个build目录
照着这个文章去测试是否安装正确
我测试下来是:

语法温习:
右值引用完美转发//可变参数
右值的临时变量就是右值引用。
可变函数参数:
可变参数模板
Json
可以嵌套:数组 字符串 数字 对象

JsonCpp
Json-Value类:

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


Muduo库
基于Reactor
单个Reactor容易压力太大
故:one loop per thread
常见接口
- 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;
}
每一个TcpServer都得有一个EventLoop,在传参的时候方便传
由于服务器的特殊性,一般来说服务器本身可能不止一个IP地址,所以绑定到0.0.0.0接受所有IP,更为合适。
3.为了让服务器先断开时不被TCP的挥手给影响从而导致bind error,所以设置成kReusePort可以端口复用
4.stdbind类内函数记得加上&
5._baseloop的loop是死循环,先start再loop
- 没有协议层,所以直接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);
}
}
- 客户端其实就是连接方式和处理功能变化的服务器,所以初始化时照样需要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异步执行方法:
- async
cpp
std::future<int> result_future = std::async(std::launch::async, aysnc_task);
//...
int result = result_future.get();
- 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
日志
- 简单日志,不用考虑多线程
简单的AI用例:
2.注意可变参数在宏中的处理
在函数中的处理可能会不一样。
3.在C语言中,当多个字符串字面量在同一个表达式中相邻时,它们会被自动连接起来形成一个单独的字符串。这是C语言的一个特性,称为字符串字面量的拼接。
4.这个format必须和前面空开,不然会识别失败,字符串字面量的拼接时记得加上空格!
uuid
种子相同(都采用时间),随机数一样的几率就会很大,机器随机数是真的硬件随机数,但是硬件随机数会很慢。
因此,综上,我们在C++中使用的是以机器随机数当种子来生成伪随机数。
C++生成一个范围里的随机数:
#include <iomanip>是C++标准库中的一个头文件,它提供了对输入/输出流的格式化操作的支持。这个头文件定义了一系列的操纵符(manipulators),这些操纵符可以控制输出(如std::cout)和输入(如std::cin)流的格式。以下是一些常用的操纵符:
std::setw(int width):设置下一个字段的最小宽度。
std::setfill(char ch):设置字段宽度不足时的填充字符。
std::setprecision(int precision):设置浮点数的精度(即小数点后的位数)。
std::fixed:以固定点格式输出浮点数。
std::scientific:以科学计数法格式输出浮点数。
std::hex:以十六进制格式输出整数。
std::dec:以十进制格式输出整数。
std::oct:以八进制格式输出整数。
std::left:左对齐输出。
std::right:右对齐输出。
std::internal:在符号和数值之间对齐输出。
cppss<<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确定是使用这种方法之后,再执行。
现在的代码:
cppclass 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
接下来是服务的注册与发现服务:
服务注册的作用与原理
服务注册是分布式系统实现高可用性的关键机制。其核心功能是让服务节点在注册中心登记自身提供的服务能力,从而构建一个可动态扩展的服务网络。
服务发现的功能解析
RPC调用方需要通过服务发现机制确定可用的服务节点。该过程本质上是向注册中心查询指定服务的可用节点,并将这些节点信息缓存以供后续调用使用。
服务下线机制
系统采用长连接实时监测服务节点的在线状态。当服务提供方断开连接时,系统会执行以下流程:
- 查询该节点提供的所有服务
- 分析曾发现过这些服务的调用方
- 向相关调用方发送服务下线通知
- 服务上线通知机制
由于服务发现通常是一次性操作(调用方不会重复查询),当新服务节点上线时,已缓存的调用方无法感知。因此系统需要:
- 主动向所有发现过该服务的调用方
- 推送服务上线通知
- 确保调用方及时获取最新服务节点信息
服务提供方一定是有一个服务器,也有一个客户端,服务器是让用户去进行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的情况,不需要connetcionDiscover同样:
然后是最关键的RpcClient
对RegistryClient的实现:
cppclass 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里去

要么从连接池获取,要么直接获取
cppbool call(const std::string method, const Json::Value ¶ms, 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都要这样获取一遍吗?我们可以把这一端封装出来
cppBaseClient::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的搭建框架:
cppnamespace 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










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















































