项目介绍
RPC(Remote Procedure Call)
即远程过程调用,是一种通过网络从远程计算机程序中请求服务而不需要了解底层网络实现细节的一种 协议 。
RPC(Remote Procedure Call)
可以使用多种网络协议进行通信,如 HTTP 、TCP 、UDP等。就像调用本地方法一样调用远程方法。
RPC框架
- 序列化协议
- 通信协议
- 连接复用
- 服务注册
- 服务发现
- 服务订阅和通知
- 负载均衡
- 服务监控
- 同步调用
- 异步调用
技术选型
目前RPC的实现方案主要有两种:
-
- client和server继承公共接口:
- 根据IDL(接口描述语言)定义公共接口
- 编写代码生成器根据 IDL 语言生成相关的C++、Java代码
- 客户端和服务器向上继承公共接口
- 缺点:使用Protobuf、json可以定义IDL接口,并生成RPC相关代码,其中Protobuf需要通过
protoc
生成强类型代码,灵活性受限,且对理解不友好,如果用Json定义IDL语言,需要自己编写代码生成器,难度较大,暂不考虑这种方案
-
实现一个远程调用接口call,然后传入函数名参数来调用RPC接口,我们采用这种方案
-
网络传输的参数和返回值映射到对应RPC接口上:
使用JSON类型,设计好参数和返回值协议 -
网络传输:
使用muduo库来实现网络传输 -
序列化和反序列化
使用JSON来实现
开发环境LInux
- Linux (Centos-7.6 / Ubuntu6.4)
- Vscode / Vim
- g++ / gdb
- Makefile
Muduo库
muduo是是由陈硕大佬开发,是一个基于 非阻塞IO 和 事件驱动 的C++高并发TCP网络编程库。它是一款基于主从Reactor模型的网络库,其使用的线程模型是 one loop per thread:
- 一个线程只能有一个事件循环,用于响应计时器和IO时间
- 一个文件描述符只能由一个线程进行读写,也就是一个TCP连接必须归属于某个EventLoop
C++11异步操作
std::future
std::future
是C++11标准库中的一个模版类,它表示一个 异步操作的结果 ,当我们在多线程编程中使用异步任务时,std::future
可以帮助我们在需要的时候获取任务的执行结果。它的一个主要特性就是能够阻塞当前线程,直到异步操作完成,确保我们在获取结果时不会遇到未完成的操作。
应用场景:
- 异步任务:当我们在后台执行一些耗时操作时,如网络请求和计算密集型任务等,
std::future
可以表示异步执行的结果。通过创建新线程执行任务,实现任务的并行处理,提高程序的效率 - 并发控制:我们可以通过使用
std::future
是线程之间的同步,确保任务完成后再获取结果并继续执行后续操作 - 结果获取:
std::future
提供了一种安全的方式来获取异步任务的结果,我们可以使用std::future::get()
函数来获取异步任务的执行结果,并且这个函数会阻塞当前线程,直到异步操作完成,确保后序操作在结果获取后执行,同时保证了线程之间的同步。
std::async
std::async
是一种将任务与 std::future
关联的方法,它创建并运行一个异步任务,并返回一个与任务关联的 future 对象。
其中 std::async
是否启动一个新线程,或者在等待 futrue 时,任务是否同步运行都取决于你给的参数 std::launch
:
std::launch::deferred
表明该函数会被延迟调用,直到调用 get() 或者 wait() 才会开始执行任务std::launch::async
表示函数会在自己创建的线程上面运行std::launch::deferred | std::launch::async
取决于系统内部的资源的自动决策,当高资源可用时,采用std::launch::async
,创建新线程实现并发,相反,则采std::launch::deferred
策略,在调用线程上执行,避免资源竞争和崩溃风险。
示例代码
#include <iostream>
#include <future>
#include <chrono>
std::string async_task()
{
std::cout << "任务执行中" << std::endl;
return "获取成功";
}
int main()
{
std::future<std::string> result = std::async(std::launch::deferred,async_task);
std::this_thread::sleep_for(std::chrono::seconds(10));
std::cout << "任务在等待10s后再执行" << std::endl;
std::string name = result.get();
std::cout << name << std::endl;
return 0;
}
结果:
任务在等待10s后再执行
任务执行中
获取成功
std::string async_task()
{
std::cout << "任务正在执行" << std::endl;
return "获取成功";
}
int main()
{
std::future<std::string> result = std::async(std::launch::async,async_task);
std::this_thread::sleep_for(std::chrono::seconds(10));
std::string name = result.get();
std::cout << name << std::endl;
return 0;
}
结果:
任务正在执行
获取成功
std::packaged_task
std::packaged_task
就是将任务和 std::future
绑定在一起的模版,是一种对任务的封装,我们可以通过std::packaged_task
对象获取任务相关联的std::future
对象,通过调用 get_future() 方法获得。std::packaged_task
的模版参数是函数签名。
可以把std::future
和 std::async
看成是分开的,而std::packaged_task
则是一个整体
示例代码
#include <iostream>
#include <future>
#include <thread>
#include <utility>
std::string async_task()
{
std::cout << "任务执行中" << std::endl;
return "获取成功";
}
int main()
{
// 1. 创建 packaged_task 并获取 future
std::packaged_task<std::string()> task(async_task);
std::future<std::string> result = task.get_future();
// 2. 将任务移动到新线程中异步执行
std::thread t(std::move(task));
t.detach(); // 或 t.join()
// 3. 获取结果(阻塞直到任务完成)
std::string name = result.get(); // 若任务未执行会在此阻塞
std::cout << name << std::endl;
return 0;
}
结果:
任务执行中
获取成功
std::promise
std::promise
提供了一种设置值的方式,它可以在设置之后通过相关联的 std::futrue
对象进行读取。其实就是之前std::futrue
需要等待异步函数的返回值,但std::promise
提供了一种手动方式让std::futrue
就绪
示例代码
void task(std::promise<int> promise_result)
{
int result = 2;
std::cout << "task result: " << result << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(5));
promise_result.set_value(result);
}
int main()
{
std::promise<int> promise_result;
std::future<int> future_result = promise_result.get_future();
std::thread task_thread(task,std::move(promise_result));
task_thread.detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "执行其他操作" << std::endl;
int result = future_result.get();
std::cout << "result: " << result << std::endl;
return 0;
}
结果:
task result: 2
执行其他操作
result: 2