RPC
远程过程调用(Remote Procedure Call,RPC)是一种计算机通信协议,它允许一台计算机(客户端)通过网络向另一台计算机(服务器)请求服务或调用程序,就好像这个程序是在本地执行一样。RPC 隐藏了网络通信的细节,使得开发分布式应用程序更为简单。RPC 强调的是程序间的交互,而不是底层的传输机制。
RPC 系统一般包括以下几个关键组成部分:
- 客户端:发起远程调用请求的一方。
- 服务器:提供服务或执行程序代码响应远程调用的一方。
- 存根(Stub):在客户端和服务器上分别存在的存根提供了一个代理,使得远程调用看起来如同本地调用一样。客户端存根打包(序列化)调用信息并发送到服务器,服务器存根解包(反序列化)并执行程序,然后返回结果。
- 传输层:RPC 使用的网络传输协议,常见的有 TCP/IP 和 UDP。
RPC 的工作流程大致如下:
- 客户端程序调用本地的客户端存根。
- 客户端存根打包(序列化)调用的方法和参数,并通过网络发送到服务器。
- 服务器上的服务器存根接收到请求,解包(反序列化)出方法和参数。
- 服务器存根按照解包出来的信息调用本地的程序。
- 服务器程序执行并将结果返回给服务器存根。
- 服务器存根再次打包这些结果,并发送回客户端存根。
- 客户端存根解包响应,并将结果回传给客户端程序。
RPC 的优点包括:
- 抽象化的复杂性:RPC 抽象了网络间通信的复杂性,开发者可以像调用本地程序一样调用远程程序,无需关心底层通信协议的实现。
- 语言无关性:很多 RPC 系统支持不同编程语言之间的调用,这使得构建多语言的分布式系统变得可能。
- 高效性:RPC 可以高效地处理客户端和服务器之间的通信,特别是一些支持异步调用的 RPC 系统。
同时,RPC 也有一些缺点:
- 网络依赖:RPC 依赖网络连接,网络延迟和中断可能会影响系统的稳定性和相应时间。
- 复杂性和调试:虽然 RPC 抽象了分布式通信的复杂性,但在调试和排查分布式系统错误时可能会比本地程序更加困难。
- 安全性考虑:通过网络公开方法可能增加安全风险,务必考虑适当的认证、授权和加密措施。
RPC 技术的应用非常广泛,从传统的客户端-服务端应用到现代的微服务架构,RPC 在很多分布式系统中都发挥着关键作用。
概叙
rpclib 是一个用于 C++ 的 RPC(远程过程调用)库,提供了客户端和服务器的实现。它使用现代 C++14 构建,因此需要较新的编译器。主要亮点如下:
- 暴露你的程序函数,使其可以通过 RPC(远程过程调用)被调用(来自实现了 msgpack-rpc 的任何语言)
- 通过 RPC 调用函数(属于用任何语言编写的程序)
- 无需学习 IDL(接口定义语言)
- 在你的构建中无需集成代码生成步骤,只需 C++
Demo
主要的核心设计,是通过绑定、回调返回不同的参数类型实现rpc的CS架构。
基本使用
rpc/server端
#include <iostream>
#include "rpc/server.h"
void foo() { std::cout << "foo was called!" << std::endl; }
void bad(int x) {
if (x == 5) {
throw std::runtime_error("x == 5. I really don't like 5.");
}
}
int main() {
// Create a server that listens on port 8080, or whatever the user selected
rpc::server srv("0.0.0.0", rpc::constants::DEFAULT_PORT);
// Binding the name "foo" to free function foo.
// note: the signature is automatically captured
srv.bind("foo", &foo);
// Binding a lambda function to the name "add".
srv.bind("add", [](int a, int b) { return a + b; });
// Throwing an exception will cause the server to write
// an error response. This call will make it also
// suppress the exception (note that this is not default
// because this behavior might hide errors in the
// code).
srv.suppress_exceptions(true);
srv.bind("bad", &bad);
// Run the server loop.
srv.run();
return 0;
}
rpc/client端
#include <iostream>
#include "rpc/client.h"
int main() {
rpc::client client("127.0.0.1", rpc::constants::DEFAULT_PORT);
auto result = client.call("add", 2, 3).as<int>();
std::cout << "The result is: " << result << std::endl;
return 0;
}
多元化回调类型
mandelbrot/server
#include <iostream>
#include <math.h>
#include <time.h>
#include "rpc/server.h"
#include "mandelbrot.h"
int mandelbrot(double cr, double ci, int max_iterations) {
int i = 0;
double zr = 0.0, zi = 0.0;
while (i < max_iterations && zr * zr + zi * zi < 4.0) {
double temp = zr * zr - zi * zi + cr;
zi = 2.0 * zr * zi + ci;
zr = temp;
i++;
}
return i;
}
double to_real(int x, int width, double minR, double maxR) {
double range = maxR - minR;
return x * (range / width) + minR;
}
double to_im(int y, int height, double minI, double maxI) {
double range = maxI - minI;
return y * (range / height) + minI;
}
int main() {
int maxN = 255;
double minR = -1.5, maxR = 0.8, minI = -1.0, maxI = 1.0;
rpc::server srv(rpc::constants::DEFAULT_PORT);
srv.bind("get_time", []() {
time_t rawtime;
struct tm *timeinfo;
time (&rawtime);
timeinfo = localtime(&rawtime);
return asctime(timeinfo);
});
srv.bind("get_mandelbrot", [&](int width, int height) {
pixel_data data;
for (int x = 0; x < width; x++) {
for (int y = 0; y < height; y++) {
double cr = to_real(x, width, minR, maxR);
double ci = to_im(y, height, minI, maxI);
int n = mandelbrot(cr, ci, maxN);
unsigned char r = ((int)(fabs(n * cosf(n))) % 256);
unsigned char g = ((n * 3) % 256);
unsigned char b = (n % 256);
data.push_back({r, g, b});
}
}
return data;
});
srv.async_run(2);
std::cout << "Press [ENTER] to exit the server." << std::endl;
std::cin.ignore();
return 0;
}
mandelbrot/client
#include <iostream>
#include "SFML/Window.hpp"
#include "SFML/Graphics.hpp"
#include "rpc/client.h"
#include "mandelbrot.h"
int main() {
const int width = 1024, height = 768;
rpc::client c("127.0.0.1", rpc::constants::DEFAULT_PORT);
std::cout << "Calling get_mandelbrot asynchronically" << std::endl;
auto result_obj = c.async_call("get_mandelbrot", width, height);
std::cout << "Calling get_time synchronically" << std::endl;
auto current_time = c.call("get_time").as<std::string>();
std::cout << "Current time: " << current_time << std::endl;
sf::Image image;
image.create(width, height, sf::Color::Black);
std::cout << "Waiting for get_mandelbrot result" << std::endl;
auto result = result_obj.get().as<pixel_data>();
std::cout << "Got mandelbrot data, displaying..." << std::endl;
for (size_t y = 0; y < height; ++y) {
for (size_t x = 0; x < width; ++x) {
auto item = result[x * height + y];
auto color = sf::Color(item.r, item.g, item.b, 255);
image.setPixel(x, y, color);
}
}
sf::RenderWindow window(sf::VideoMode(width, height), "rpc mandelbrot client");
sf::Texture texture;
texture.loadFromImage(image, sf::IntRect(0, 0, width, height));
sf::Sprite sprite;
sprite.setTexture(texture, true);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
window.draw(sprite);
window.display();
}
return 0;
}
calculator
server
#include <iostream>
#include "rpc/server.h"
#include "rpc/this_handler.h"
double divide(double a, double b) {
if (b == 0.0) {
rpc::this_handler().respond_error(
std::make_tuple(1, "Division by zero"));
}
return a / b;
}
struct subtractor {
double operator()(double a, double b) {
return a - b;
}
};
struct multiplier {
double multiply(double a, double b) {
return a * b;
}
};
int main() {
rpc::server srv(rpc::constants::DEFAULT_PORT);
subtractor s;
multiplier m;
// It's possible to bind non-capturing lambdas
srv.bind("add", [](double a, double b) { return a + b; });
// ... arbitrary callables
srv.bind("sub", s);
// ... free functions
srv.bind("div", ÷);
// ... member functions with captured instances in lambdas
srv.bind("mul", [&m](double a, double b) { return m.multiply(a, b); });
srv.run();
return 0;
}
client
#include <iostream>
#include "rpc/client.h"
#include "rpc/rpc_error.h"
int main() {
rpc::client c("localhost", rpc::constants::DEFAULT_PORT);
try {
std::cout << "add(2, 3) = ";
double five = c.call("add", 2, 3).as<double>();
std::cout << five << std::endl;
std::cout << "sub(3, 2) = ";
double one = c.call("sub", 3, 2).as<double>();
std::cout << one << std::endl;
std::cout << "mul(5, 0) = ";
double zero = c.call("mul", five, 0).as<double>();
std::cout << zero << std::endl;
std::cout << "div(3, 0) = ";
double hmm = c.call("div", 3, 0).as<double>();
std::cout << hmm << std::endl;
} catch (rpc::rpc_error &e) {
std::cout << std::endl << e.what() << std::endl;
std::cout << "in function '" << e.get_function_name() << "': ";
using err_t = std::tuple<int, std::string>;
auto err = e.get_error().as<err_t>();
std::cout << "[error " << std::get<0>(err) << "]: " << std::get<1>(err)
<< std::endl;
return 1;
}
return 0;
}