开源软件:buttonrpc (一)

2024-06-12 00:58
文章标签 软件 开源 buttonrpc

本文主要是介绍开源软件:buttonrpc (一),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

开源软件:buttonrpc (一)

本文采用知识共享署名 4.0 国际许可协议进行许可,转载时请注明原文链接,图片在使用时请保留全部内容,可适当缩放并在引用处附上图片所在的文章链接。

  • 开源地址
  • 编译运行
    • buttonrpc
    • buttonrpc_cpp14
  • 原理
    • RPC
    • buttonrpc 分析
      • Call ID映射:buttonrpc.hpp
      • 序列化和反序列化:Serializer.hpp
      • 网络传输:ZeroMQ

开源地址

buttonrpc

buttonrpc_cpp14

编译运行

buttonrpc

  1. 依赖安装:
sudo apt-get install libzmq3-dev
  1. 编译:

ubuntu 下修改文件

diff --git a/example/main_client.cpp b/example/main_client.cpp
index abdec37..fab93c5 100644
--- a/example/main_client.cpp
+++ b/example/main_client.cpp
@@ -3,7 +3,11 @@#include <ctime>#include "buttonrpc.hpp"+#ifdef _WIN32#include <Windows.h>  // use sleep
+#else
+ #include <unistd.h>
+#endif#define buttont_assert(exp) { \
@@ -64,7 +68,11 @@ int main()buttonrpc::value_t<void> xx = client.call<void>("foo_7", 666);buttont_assert(!xx.valid());+#ifdef _WIN32Sleep(1000);
+#else
+                        sleep(1);
+#endif}return 0;

创建CMakeLists 并编译

cd buttonrpc
touch CMakeLists.txt # 内容见下
mkdir build
cd build
cmake ..
make 
# 这个是cmake最小版本要求
cmake_minimum_required(VERSION 3.1)# Enable C++11,这一段是源码里的
set(CMAKE_CXX_STANDARD 14)
set(CMAKE_CXX_STANDARD_REQUIRED TRUE)# 填写你的项目名称Project
project( RPC )aux_source_directory(${CMAKE_SOURCE_DIR}/src/ DIR_SRCS)
aux_source_directory(${CMAKE_SOURCE_DIR}/example DIR_SRCS)include_directories(${CMAKE_SOURCE_DIR}${CMAKE_SOURCE_DIR}/src
)message("${CMAKE_CURRENT_SOURCE_DIR}")# Find all *.cpp files and store in list cpps
# GLOB这个参数不支持子目录, 用GLOB_RECURSE可以支持子目录。
file(GLOB cpps RELATIVE "${CMAKE_CURRENT_SOURCE_DIR}/example" "${CMAKE_CURRENT_SOURCE_DIR}/example/*.cpp")foreach(mainfile IN LISTS cpps)# Get file name without directoryget_filename_component(mainname ${mainfile} NAME_WE)add_executable(${mainname} ${CMAKE_CURRENT_SOURCE_DIR}/example/${mainfile})target_link_libraries(${mainname} zmq)
endforeach()
  1. 运行demo
./main_server
./main_client

buttonrpc_cpp14

参考 buttonrpc

原理

RPC

RPC(Remote Procedure Call)远程过程调用。要像调用本地的函数一样去调远程函数。

RPC 是需要有三个主要模块:Call ID映射序列化和反序列化网络传输

Call ID映射。我们怎么告诉远程机器我们要调用Multiply,而不是Add或者FooBar呢?在本地调用中,函数体是直接通过函数指针来指定的,我们调用Multiply,编译器就自动帮我们调用它相应的函数指针。但是在远程调用中,函数指针是不行的,因为两个进程的地址空间是完全不一样的。所以,在RPC中,所有的函数都必须有自己的一个ID。这个ID在所有进程中都是唯一确定的。客户端在做远程过程调用时,必须附上这个ID。然后我们还需要在客户端和服务端分别维护一个 {函数 <–> Call ID} 的对应表。两者的表不一定需要完全相同,但相同的函数对应的Call ID必须相同。当客户端需要进行远程调用时,它就查一下这个表,找出相应的Call ID,然后把它传给服务端,服务端也通过查表,来确定客户端需要调用的函数,然后执行相应函数的代码。

序列化和反序列化。客户端怎么把参数值传给远程的函数呢?在本地调用中,我们只需要把参数压到栈里,然后让函数自己去栈里读就行。但是在远程过程调用时,客户端跟服务端是不同的进程,不能通过内存来传递参数。甚至有时候客户端和服务端使用的都不是同一种语言(比如服务端用C++,客户端用Java或者Python)。这时候就需要客户端把参数先转成一个字节流,传给服务端后,再把字节流转成自己能读取的格式。这个过程叫序列化和反序列化。同理,从服务端返回的值也需要序列化反序列化的过程。

网络传输。远程调用往往用在网络上,客户端和服务端是通过网络连接的。所有的数据都需要通过网络传输,因此就需要有一个网络传输层。网络传输层需要把Call ID和序列化后的参数字节流传给服务端,然后再把序列化后的调用结果传回客户端。只要能完成这两者的,都可以作为传输层使用。因此,它所使用的协议其实是不限的,能完成传输就行。尽管大部分RPC框架都使用TCP协议,但其实UDP也可以,而gRPC干脆就用了HTTP2。Java的Netty也属于这层的东西。

buttonrpc 分析

Call ID映射:buttonrpc.hpp

server

server 中主要完成被调用函数的注册,在收到网络发送的数据进行序列化还原后找到对应的调用函数和参数,执行调用再将结果返回.

// server
template<typename F>
void bind(std::string name, F func);template<typename F, typename S>
void bind(std::string name, F func, S* s);

bind 函数两种模板的实现是为了兼容多参数函数的设计,其中 name 为函数名,同时也是对应的函数的索引,func 为函数指针,s 为函数参数.bind 的实现如下:

template<typename F>
void buttonrpc::bind( std::string name, F func )
{m_handlers[name] = std::bind(&buttonrpc::callproxy<F>, this, func, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}template<typename F, typename S>
inline void buttonrpc::bind(std::string name, F func, S* s)
{m_handlers[name] = std::bind(&buttonrpc::callproxy<F, S>, this, func, s, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
}

函数最终被放到m_handlers 中索引,m_handlers的类型如下:

std::map<std::string, std::function<void(Serializer*, const char*, int)>> m_handlers;

映射关系为 函数名(string) — 函数指针

template<typename F>
void buttonrpc::callproxy( F fun, Serializer* pr, const char* data, int len )
{callproxy_(fun, pr, data, len);
}template<typename F, typename S>
inline void buttonrpc::callproxy(F fun, S * s, Serializer * pr, const char * data, int len)
{callproxy_(fun, s, pr, data, len);
}

callproxy_ 实现如下:

template<typename R>
void buttonrpc::callproxy_(std::function<R()> func, Serializer* pr, const char* data, int len)
{typename type_xx<R>::type r = call_helper<R>(std::bind(func));value_t<R> val;val.set_code(RPC_ERR_SUCCESS);val.set_val(r);(*pr) << val;
}
/*-------------------------------------------------------------------------------------------*/
template<typename F>
void callproxy(F fun, Serializer* pr, const char* data, int len);template<typename F, typename S>
void callproxy(F fun, S* s, Serializer* pr, const char* data, int len);// PROXY FUNCTION POINT
template<typename R>
void callproxy_(R(*func)(), Serializer* pr, const char* data, int len) {callproxy_(std::function<R()>(func), pr, data, len);
}template<typename R, typename P1>
void callproxy_(R(*func)(P1), Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1)>(func), pr, data, len);
}template<typename R, typename P1, typename P2>
void callproxy_(R(*func)(P1, P2), Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2)>(func), pr, data, len);
}template<typename R, typename P1, typename P2, typename P3>
void callproxy_(R(*func)(P1, P2, P3), Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3)>(func), pr, data, len);
}template<typename R, typename P1, typename P2, typename P3, typename P4>
void callproxy_(R(*func)(P1, P2, P3, P4), Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3, P4)>(func), pr, data, len);
}template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
void callproxy_(R(*func)(P1, P2, P3, P4, P5), Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3, P4, P5)>(func), pr, data, len);
}// PROXY CLASS MEMBER
template<typename R, typename C, typename S>
void callproxy_(R(C::* func)(), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R()>(std::bind(func, s)), pr, data, len);
}template<typename R, typename C, typename S, typename P1>
void callproxy_(R(C::* func)(P1), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1)>(std::bind(func, s, std::placeholders::_1)), pr, data, len);
}template<typename R, typename C, typename S, typename P1, typename P2>
void callproxy_(R(C::* func)(P1, P2), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2)>(std::bind(func, s, std::placeholders::_1, std::placeholders::_2)), pr, data, len);
}template<typename R, typename C, typename S, typename P1, typename P2, typename P3>
void callproxy_(R(C::* func)(P1, P2, P3), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3)>(std::bind(func, s, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3)), pr, data, len);
}template<typename R, typename C, typename S, typename P1, typename P2, typename P3, typename P4>
void callproxy_(R(C::* func)(P1, P2, P3, P4), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3, P4)>(std::bind(func, s,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4)), pr, data, len);
}template<typename R, typename C, typename S, typename P1, typename P2, typename P3, typename P4, typename P5>
void callproxy_(R(C::* func)(P1, P2, P3, P4, P5), S* s, Serializer* pr, const char* data, int len) {callproxy_(std::function<R(P1, P2, P3, P4, P5)>(std::bind(func, s,std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4, std::placeholders::_5)), pr, data, len);
}// PORXY FUNCTIONAL
template<typename R>
void callproxy_(std::function<R()>, Serializer* pr, const char* data, int len);template<typename R, typename P1>
void callproxy_(std::function<R(P1)>, Serializer* pr, const char* data, int len);template<typename R, typename P1, typename P2>
void callproxy_(std::function<R(P1, P2)>, Serializer* pr, const char* data, int len);template<typename R, typename P1, typename P2, typename P3>
void callproxy_(std::function<R(P1, P2, P3)>, Serializer* pr, const char* data, int len);template<typename R, typename P1, typename P2, typename P3, typename P4>
void callproxy_(std::function<R(P1, P2, P3, P4)>, Serializer* pr, const char* data, int len);template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
void callproxy_(std::function<R(P1, P2, P3, P4, P5)>, Serializer* pr, const char* data, int len);

call_helper 用来辅助判断调用函数是否需要返回值,如果调用函数是void 类型,返回值 默认为 0 ,其实现如下:

template<typename R, typename F>
typename std::enable_if<std::is_same<R, void>::value, typename type_xx<R>::type >::type call_helper(F f) {f();return 0;
}template<typename R, typename F>
typename std::enable_if<!std::is_same<R, void>::value, typename type_xx<R>::type >::type call_helper(F f) {return f();
}

client

// client
template<typename R>
value_t<R> call(std::string name);template<typename R>
inline buttonrpc::value_t<R> buttonrpc::call(std::string name)
{Serializer ds;ds << name;return net_call<R>(ds);
}

这里的 调用函数为了兼容有多参数函数的设计,重载了很多的类型,如下:

template<typename R, typename P1>
value_t<R> call(std::string name, P1);template<typename R, typename P1, typename P2>
value_t<R> call(std::string name, P1, P2);template<typename R, typename P1, typename P2, typename P3>
value_t<R> call(std::string name, P1, P2, P3);template<typename R, typename P1, typename P2, typename P3, typename P4>
value_t<R> call(std::string name, P1, P2, P3, P4);template<typename R, typename P1, typename P2, typename P3, typename P4, typename P5>
value_t<R> call(std::string name, P1, P2, P3, P4, P5);

其实现的目的都是一样:将函数名(string)和参数进行序列化,然后调用网络发送模块.

启动调用函数的索引为 函数名 (string). 如下:

// 处理函数相关
Serializer* buttonrpc::call_(std::string name, const char* data, int len)
{Serializer* ds = new Serializer();if (m_handlers.find(name) == m_handlers.end()) {(*ds) << value_t<int>::code_type(RPC_ERR_FUNCTIION_NOT_BIND);(*ds) << value_t<int>::msg_type("function not bind: " + name);return ds;}auto fun = m_handlers[name];fun(ds, data, len);ds->reset();return ds;
}

m_handlers 中存储了所有的函数映射.

序列化和反序列化:Serializer.hpp

重载 << >>

template<typename T>
Serializer &operator >> (T& i){output_type(i); return *this;
}template<typename T>
Serializer &operator << (T i){input_type(i);return *this;
}

重载 << >> 并调用 input_type output_type 将数据转化为数据流(char类型)用于网络传输

output_type & input_type

template<typename T>
inline void Serializer::output_type(T& t)
{
...
}template<>
inline void Serializer::output_type(std::string& in)
{
...
}template<typename T>
inline void Serializer::input_type(T t)
{
...
}template<>
inline void Serializer::input_type(std::string in)
{
...
}template<>
inline void Serializer::input_type(const char* in)
{
...
}

这里的 input_type output_type 提供了多种的特化和偏特化的实现同样是为了有多参数函数的序列化的需求.

网络传输:ZeroMQ

// network
void buttonrpc::as_client( std::string ip, int port )
{m_role = RPC_CLIENT;m_socket = new zmq::socket_t(m_context, ZMQ_REQ);ostringstream os;os << "tcp://" << ip << ":" << port;m_socket->connect (os.str());
}void buttonrpc::as_server( int port )
{m_role = RPC_SERVER;m_socket = new zmq::socket_t(m_context, ZMQ_REP);ostringstream os;os << "tcp://*:" << port;m_socket->bind (os.str());
}void buttonrpc::send( zmq::message_t& data )
{m_socket->send(data);
}void buttonrpc::recv( zmq::message_t& data )
{m_socket->recv(&data);
}inline void buttonrpc::set_timeout(uint32_t ms)
{// only client can setif (m_role == RPC_CLIENT) {m_socket->setsockopt(ZMQ_RCVTIMEO, ms);}
}void buttonrpc::run()
{// only server can callif (m_role != RPC_SERVER) {return;}while (1){zmq::message_t data;recv(data);StreamBuffer iodev((char*)data.data(), data.size());Serializer ds(iodev);std::string funname;ds >> funname;Serializer* r = call_(funname, ds.current(), ds.size()- funname.size());zmq::message_t retmsg (r->size());memcpy (retmsg.data (), r->data(), r->size());send(retmsg);delete r;}
}

网络传输使用的 ZeroMQ .

这篇关于开源软件:buttonrpc (一)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1052752

相关文章

无需邀请码!Manus复刻开源版OpenManus下载安装与体验

《无需邀请码!Manus复刻开源版OpenManus下载安装与体验》Manus的完美复刻开源版OpenManus安装与体验,无需邀请码,手把手教你如何在本地安装与配置Manus的开源版OpenManu... Manus是什么?Manus 是 Monica 团队推出的全球首款通用型 AI Agent。Man

Ubuntu 怎么启用 Universe 和 Multiverse 软件源?

《Ubuntu怎么启用Universe和Multiverse软件源?》在Ubuntu中,软件源是用于获取和安装软件的服务器,通过设置和管理软件源,您可以确保系统能够从可靠的来源获取最新的软件... Ubuntu 是一款广受认可且声誉良好的开源操作系统,允许用户通过其庞大的软件包来定制和增强计算体验。这些软件

阿里开源语音识别SenseVoiceWindows环境部署

SenseVoice介绍 SenseVoice 专注于高精度多语言语音识别、情感辨识和音频事件检测多语言识别: 采用超过 40 万小时数据训练,支持超过 50 种语言,识别效果上优于 Whisper 模型。富文本识别:具备优秀的情感识别,能够在测试数据上达到和超过目前最佳情感识别模型的效果。支持声音事件检测能力,支持音乐、掌声、笑声、哭声、咳嗽、喷嚏等多种常见人机交互事件进行检测。高效推

金融业开源技术 术语

金融业开源技术  术语 1  范围 本文件界定了金融业开源技术的常用术语。 本文件适用于金融业中涉及开源技术的相关标准及规范性文件制定和信息沟通等活动。

安全管理体系化的智慧油站开源了。

AI视频监控平台简介 AI视频监控平台是一款功能强大且简单易用的实时算法视频监控系统。它的愿景是最底层打通各大芯片厂商相互间的壁垒,省去繁琐重复的适配流程,实现芯片、算法、应用的全流程组合,从而大大减少企业级应用约95%的开发成本。用户只需在界面上进行简单的操作,就可以实现全视频的接入及布控。摄像头管理模块用于多种终端设备、智能设备的接入及管理。平台支持包括摄像头等终端感知设备接入,为整个平台提

软件设计师备考——计算机系统

学习内容源自「软件设计师」 上午题 #1 计算机系统_哔哩哔哩_bilibili 目录 1.1.1 计算机系统硬件基本组成 1.1.2 中央处理单元 1.CPU 的功能 1)运算器 2)控制器 RISC && CISC 流水线控制 存储器  Cache 中断 输入输出IO控制方式 程序查询方式 中断驱动方式 直接存储器方式(DMA)  ​编辑 总线 ​编辑

K8S(Kubernetes)开源的容器编排平台安装步骤详解

K8S(Kubernetes)是一个开源的容器编排平台,用于自动化部署、扩展和管理容器化应用程序。以下是K8S容器编排平台的安装步骤、使用方式及特点的概述: 安装步骤: 安装Docker:K8S需要基于Docker来运行容器化应用程序。首先要在所有节点上安装Docker引擎。 安装Kubernetes Master:在集群中选择一台主机作为Master节点,安装K8S的控制平面组件,如AP

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

免费也能高质量!2024年免费录屏软件深度对比评测

我公司因为客户覆盖面广的原因经常会开远程会议,有时候说的内容比较广需要引用多份的数据,我记录起来有一定难度,所以一般都用录屏工具来记录会议内容。这次我们来一起探索有什么免费录屏工具可以提高我们的工作效率吧。 1.福晰录屏大师 链接直达:https://www.foxitsoftware.cn/REC/  录屏软件录屏功能就是本职,这款录屏工具在录屏模式上提供了多种选项,可以选择屏幕录制、窗口

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX