文章目录
- 一、功能测试
-
- [1. 两台主机之间的直接Rpc服务功能测试](#1. 两台主机之间的直接Rpc服务功能测试)
- [2. 基于注册中心的服务注册与服务发现功能测试](#2. 基于注册中心的服务注册与服务发现功能测试)
- [3. 主题消息发布订阅功能测试](#3. 主题消息发布订阅功能测试)
- 二、项目总结
项目代码:https://github.com/luoyu524/Json_Rpc
项目文档:https://blog.csdn.net/2402_86681376/category_13166697.html
一、功能测试
这个项目的三个主要功能:
- RPC远程调用功能
- 服务的注册与发现以及服务的下线/上线通知
- 消息主题的发布与订阅
1. 两台主机之间的直接Rpc服务功能测试
server:
cpp
#include "../../common/details.hpp"
#include "../../server/server.hpp"
void Add(const Json::Value& req, Json::Value& rsp)
{
int num1 = req["num1"].asInt();
int num2 = req["num2"].asInt();
rsp = num1 + num2;
}
int main(int argc, char** argv)
{
if(argc != 3)
{
std::cout << "错误,你应该输入"ip + port"" << std::endl;
return 0;
}
std::string ip = argv[1];
int port = std::stoi(argv[2]);
std::unique_ptr<RPC::server::ServiceDescFactory> desc_factory(new RPC::server::ServiceDescFactory());
desc_factory->setMethodName("Add");
desc_factory->setParamsDesc("num1", RPC::server::ValType::INTEGRAL);
desc_factory->setParamsDesc("num2", RPC::server::ValType::INTEGRAL);
desc_factory->setReturnType(RPC::server::ValType::INTEGRAL);
desc_factory->setCallback(Add);
RPC::server::RpcServer server(RPC::Address(ip, port));
server.registerMethod(desc_factory->build());
server.start();
return 0;
}
client:
cpp
#include "../../client/client.hpp"
#include "../../common/details.hpp"
#include <unistd.h>
void callback(const Json::Value& result)
{
std::cout << "Callback rpc result: " << result.asInt() << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "错误,你应该输入服务端的"ip + port"" << std::endl;
return 0;
}
std::string ip = argv[1];
int port = std::stoi(argv[2]);
RPC::client::RpcClient client(false, ip, port);
Json::Value params, result;
// 同步方式rpc调用测试
params["num1"] = 100;
params["num2"] = 200;
bool ret = client.call("Add", params, result);
if (ret == true)
{
std::cout << "Sync rpc result: " << result.asInt() << std::endl;
}
sleep(2);
// 异步方式rpc调用测试
RPC::client::RpcCaller::JsonAsyncResponse res_future;
params["num1"] = 12;
params["num2"] = 24;
ret = client.call("Add", params, res_future);
if (ret == true)
{
result = res_future.get();
std::cout << "Async rpc result: " << result.asInt() << std::endl;
}
sleep(2);
// 回调方式rpc调用测试
params["num1"] = 900;
params["num2"] = 360;
ret = client.call("Add", params, callback);
sleep(2);
// 故意传错误的参数类型测试一下
params["num1"] = "abcd";
params["num2"] = 10.12;
ret = client.call("Add", params, result);
if (ret == true)
{
std::cout << "Sync rpc result: " << result.asInt() << std::endl;
}
return 0;
}
Makefile文件:
makefile
CFLAG= -std=c++17 -I ../../../build/release-install-cpp11/include/
LFLAG= -L../../../build/release-install-cpp11/lib -ljsoncpp -lmuduo_net -lmuduo_base -pthread
all : client server
server : test_server.cpp
g++ $(CFLAG) $^ -o $@ $(LFLAG)
client : test_client.cpp
g++ $(CFLAG) $^ -o $@ $(LFLAG)
.PHONY:clean
clean:
rm -rf server client
先启动服务端,再启动客户端:
测试效果:

2. 基于注册中心的服务注册与服务发现功能测试
注册中心server:
cpp
#include "../../common/details.hpp"
#include "../../server/server.hpp"
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cout << "错误,你应该输入注册中心的port" << std::endl;
return 0;
}
int port = std::stoi(argv[1]);
RPC::server::RegistryServer reg_server(port);
reg_server.start();
return 0;
}
服务提供者server:
cpp
#include "../../common/details.hpp"
#include "../../server/server.hpp"
void Add(const Json::Value& req, Json::Value& rsp)
{
int num1 = req["num1"].asInt();
int num2 = req["num2"].asInt();
rsp = num1 + num2;
}
int main(int argc, char** argv)
{
if (argc != 5)
{
std::cout << "错误,你应该依次输入注册中心的"ip + port",自己的"ip + port"" << std::endl;
return 0;
}
std::string reg_ip = argv[1];
int reg_port = std::stoi(argv[2]);
std::string server_ip = argv[3];
int server_port = std::stoi(argv[4]);
std::unique_ptr<RPC::server::ServiceDescFactory> desc_factory(new RPC::server::ServiceDescFactory());
desc_factory->setMethodName("Add");
desc_factory->setParamsDesc("num1", RPC::server::ValType::INTEGRAL);
desc_factory->setParamsDesc("num2", RPC::server::ValType::INTEGRAL);
desc_factory->setReturnType(RPC::server::ValType::INTEGRAL);
desc_factory->setCallback(Add);
RPC::server::RpcServer server(RPC::Address(server_ip, server_port), true, RPC::Address(reg_ip, reg_port));
server.registerMethod(desc_factory->build());
server.start();
return 0;
}
服务发现者client:
cpp
#include "../../client/client.hpp"
#include "../../common/details.hpp"
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "错误,你应该输入注册中心的"ip + port"" << std::endl;
return 0;
}
std::string ip = argv[1];
int port = std::stoi(argv[2]);
// true---启用注册中心
RPC::client::RpcClient client(true, ip, port);
while (1)
{
Json::Value params, result;
// 同步方式rpc调用测试
params["num1"] = 100;
params["num2"] = 200;
bool ret = client.call("Add", params, result);
if (ret == true)
{
std::cout << "Sync rpc result: " << result.asInt() << std::endl;
}
sleep(2);
}
return 0;
}
Makefile:
makefile
CFLAG= -std=c++17 -I ../../../build/release-install-cpp11/include/
LFLAG= -L../../../build/release-install-cpp11/lib -ljsoncpp -lmuduo_net -lmuduo_base -pthread
all : registry_server rpc_server rpc_client
registry_server : registry_server.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
rpc_server : rpc_server.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
rpc_client : rpc_client.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
.PHONY:clean
clean:
rm -rf registry_server rpc_server rpc_client
简单演示:
JsonRpc框架项目,基于注册发现的Rpc功能简单测试
3. 主题消息发布订阅功能测试
主题中转中心server:
cpp
#include "../../server/server.hpp"
int main(int argc, char** argv)
{
if (argc != 2)
{
std::cout << "错误,你应该输入主题中转中心的port" << std::endl;
return 0;
}
int port = std::stoi(argv[1]);
auto server = std::make_shared<RPC::server::TopicServer>(port);
server->start();
return 0;
}
主题消息发布者client:
cpp
#include "../../client/client.hpp"
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "错误,你应该输入主题中转中心的ip+port" << std::endl;
return 0;
}
std::string ip = argv[1];
int port = std::stoi(argv[2]);
// 1. 实例化客户端对象
auto client = std::make_shared<RPC::client::TopicClient>(ip, port);
// 2. 创建主题
bool ret = client->createTopic("topic_hello");
if (ret == false)
{
std::cerr << "创建主题失败!" << std::endl;
}
// 3. 向主题发布消息
for (int i = 0; i < 10; i++)
{
client->publish("topic_hello", "Hello World-" + std::to_string(i));
}
client->shutdown();
return 0;
}
主题消息订阅者client:
cpp
#include "../../client/client.hpp"
#include <unistd.h>
void callback(const std::string& key, const std::string& msg)
{
std::cout << key << "主题收到推送过来的消息: " << msg << std::endl;
}
int main(int argc, char** argv)
{
if (argc != 3)
{
std::cout << "错误,你应该输入主题中转中心的ip+port" << std::endl;
return 0;
}
std::string ip = argv[1];
int port = std::stoi(argv[2]);
// 1. 实例化客户端对象
auto client = std::make_shared<RPC::client::TopicClient>(ip, port);
// 2. 创建主题
bool ret = client->createTopic("topic_hello");
if (ret == false)
{
std::cerr << "创建主题失败!" << std::endl;
}
// 3. 订阅主题
ret = client->subscribe("topic_hello", callback);
if (ret == false)
{
std::cerr << "订阅主题失败!" << std::endl;
}
sleep(10);
// 4. 等待->退出
client->shutdown();
return 0;
}
Makefile文件:
makefile
CFLAG= -std=c++17 -I ../../../build/release-install-cpp11/include/
LFLAG= -L../../../build/release-install-cpp11/lib -ljsoncpp -lmuduo_net -lmuduo_base -pthread
all : server publish_client subscribe_client
server : server.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
publish_client : publish_client.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
subscribe_client : subscribe_client.cpp
g++ -g $(CFLAG) $^ -o $@ $(LFLAG)
.PHONY:clean
clean:
rm -rf server publish_client subscribe_client
简单演示:
JsonRpc项目框架,基于主题订阅发布的消息转发功能测试
二、项目总结
回头再来看这个项目的三大功能:
- 实现了基础的RPC远程调用功能
- 基于服务注册与发现的rpc远程调用功能
- 消息主题的发布与订阅
框架设计分为了三层:抽象层,具象层,业务层。
- 抽象:针对底层的网络通信和协议部分进行了抽象,降低框架的依赖,提高灵活度。未来如果想使用其他的通信方式与协议,方便插拔式修改,提高可维护性。
- 具象:针对抽象的功能进行具体的实现(muduo库搭建高性能客户端服务器,TLV应用层协议格式,消息类型)
- 业务:基础Rpc功能,服务注册与发现,主题发布与订阅。
具体的代码模块划分:
- 应用层协议抽象与实现
- 网络通信模块的抽象与实现
- 消息的抽象与实现
- rpc客户端与服务端业务
- 服务注册与服务发现业务(注册者、发现者、注册中心)
- 发布订阅业务(发布者、订阅者、主题中转中心)
为什么我们有服务注册这个功能?
- 服务注册主要是实现分布式系统,让系统更加健壮
- 一个主机节点,将自己能提供的服务在注册中心进行登记
为什么我们有服务发现这个功能? - rpc调用者需要知道哪个主机节点能为自己提供指定的服务
- 服务发现就是向注册中心询问上一点问题,将节点信息保存起来待用
为什么我们有服务上线与服务下线? - 项目中我们使用长连接进行判断服务主机是否在线,一旦服务提供者断开连接,就需要在注册中心里分析哪些调用者发现过这些服务,对他们进行下线通知。
- 我们认为调用方不会进行二次服务发现,因此一旦中间有新的主机可以提供指定的服务,调用方是不知道的。所以,一旦新的服务提供者上线,则对发现者进行一次服务上线通知。
注册中心如何实现服务信息的管理?
- 注册中心服务端需要提供服务注册、服务发现请求的业务处理接口
- 维护
hash<method, vector<provider>>,来告诉服务发现者谁能提供指定服务 - 维护
hash<method, vector<discoverer>>,来知道对哪些发现者进行上线下线通知 - 维护
hash<connection, discoverer>,来管理通信连接与服务发现者的一 一对应。当一个连接断开的时候,清理相关数据
服务注册与发现客户端的功能?
- 服务注册者与服务发现者是两个人,根本是不同的两个主机
- 作为服务注册者:要连接注册中心,使用注册中心的注册接口进行服务注册。内部其实包含一个服务注册客户端、一个rpc服务端提供rpc服务。
- 作为服务发现者:要连接注册中心,使用它的服务发现接口获取能提供服务的主机信息,自己维护
hash<method, vector<host>>,一次发现,多次使用。再通过一定选择算法,选择一个提供者,与其连接。
然而,这里有长连接和短连接的区分:短连接是,服务发现后,获取了服务提供者信息,实例化客户端对象与其连接,进行rpc调用,完成后关闭客户端。优点是思想简单,用时创建用完断开;缺点是rpc调用效率低,且异步处理时较为麻烦。长连接是,在客户端这边维护一个连接池,第一次实例化一个客户端进行rpc调用后并不关闭客户端,而是保持连接。后续进行相同方法调用时直接取出对象调用即可。优点是进行重复rpc调用后效率高,复用连接;缺点是在服务下线时,需要对连接池的连接进行处理。所以,项目中我们使用的是长连接方法。
发布订阅模块,提供以下功能接口:
- 主题的创建(主题不存在则创建,存在则返回OK)
- 主题的删除
- 主题的订阅
- 主题的取消订阅
- 主题的消息发布
发布订阅功能的主题中转中心服务端怎么管理信息的?
- 需要提供上述功能接口。
- 维护
hash<connection, subsciber>,来管理通信连接与主题订阅者的一 一对应。当一个连接断开的时候,清理相关数据 - 维护
hash<topic, vector<subsciber>>,来广播所有的订阅者新来的主题消息。
但是,我们这个项目还是有可优化空间,例如可以:
- 实现负载上报系统
- 扩展负载均衡算法。本项目中使用的是轮流方法访问服务注册者,还可以有:
- 源地址hash算法
- 随机算法
- 根据上报系统中服务的负载决定
- 扩展发布订阅的转发策略。本项目中使用的是广播方法,一个主题消息要通知所有订阅者;但是如果主题消息是某种任务,可以不是广播而是让一个订阅接收即可。同时本项目中没有对消息进行持久化保持,也就是说一个新订阅者,是不知道在它连接之前发布的消息。
- 实现服务心跳检查,超时管理,比如LRU方法。