【项目】JsonRpc框架——功能测试、项目总结

文章目录

  • 一、功能测试
    • [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方法。
相关推荐
wunaiqiezixin1 小时前
扫描线算法
算法
RisunJan1 小时前
Linux命令-openssl(强大的安全套接字层密码库)
linux·运维·服务器
无限码力1 小时前
华为非AI方向笔试真题-昇腾NPU协同调度系统(详细思路+多语言题解)
算法·华为·华为机试·华为笔试真题·华为非ai笔试真题
小蒋学算法1 小时前
算法-掉落的方块-线段树
数据结构·算法
Brilliantwxx1 小时前
【算法从零到千】【8-15】滑动窗口
数据结构·算法
超梦dasgg1 小时前
经典的求解图的所有最大完全子图的算法
算法
ZC跨境爬虫1 小时前
跟着 MDN 学JavaScript day_6:JavaScript 中的基础数学——数字与运算符
开发语言·前端·javascript·学习·ecmascript
Lucis__1 小时前
图的高阶算法:从构造最小生成树到求解最短路径问题
数据结构·c++·算法·图论