【gRPC】C++异步服务端优化版,多服务接口样例
官方的C++异步服务端API样例可读性并不好,理解起来非常的费劲,各种状态机也并不明了,整个运行过程也容易读不懂,因此此处参考网上的博客进行了重写,以求顺利读懂。
C++异步服务端实例,详细注释版
gRPC使用C++实现异步服务端的基本逻辑:
- 构建数据结构来存储需要处理的请求及其上下文信息,此处使用HandlerContext,相当于对到达的请求的封装
- 首先注册各个接口的HandlerContext,放入完成队列CompletionQueue中,当请求到达时,根据类型封装进对应的HandlerContext,由于是异步客户端,需要保证后面到达的请求也有HandlerContext用,所以用一个就要再创建一个空的放回去
- 运行完的接口,其HandlerContext需要销毁
以下代码的关键为run()方法中的逻辑以及HandlerContext的设置,每一步都有注释,可以详细观看
//官方样例的异步服务代码可读性太差了,状态机绕来绕去不直观,在这里参考网上的博客进行重写,类名随意
class AsyncSideCarServiceImplNew final
{
private:
// 当前服务器的地址
std::string server_address_;
// 当前服务器的完成队列
std::unique_ptr<ServerCompletionQueue> cq_;
// 当前服务器的异步服务
SideCarService::AsyncService service_;
// 服务器实例
std::unique_ptr<Server> server_;
struct HandlerContextBase
{
int type_; //请求的接口是哪个,1表示http,2表示download,3表示upload,后续有需要可以再添加
int status_; //当前处理状态,1表示处理请求构建响应,2表示发送响应
ServerContext ctx_; // rpc服务的上下文信息
};
//请求的上下文结构
template <typename RequestType, typename ResponseType>
struct HandlerContext : public HandlerContextBase
{
RequestType req_; //请求数据类型
ResponseType resp_; //响应数据类型
ServerAsyncResponseWriter<ResponseType> responder_; //响应器
HandlerContext() : responder_(&ctx_) {} //构造方法
};
// 定义好各个接口的上下文
typedef HandlerContext<HttpRequest, HttpResponse> HandlerHttpContext;
typedef HandlerContext<DownloadRequest, DownloadResponse> HandlerDownloadContext;
typedef HandlerContext<UploadRequest, UploadResponse> HandlerUploadContext;
public:
~AsyncSideCarServiceImplNew()
{
server_->Shutdown();
// 关闭服务器后也要关闭完成队列
cq_->Shutdown();
}
//构造时传入IP:Port即可
AsyncSideCarServiceImplNew(std::string server_address) : server_address_(server_address) {}
// 服务器与队列的关闭放入了析构函数中
void Run()
{
// std::string server_address = "localhost:50052";
// 服务器构建器
ServerBuilder builder;
// 服务器IP与端口指定,第二个参数表示该通道不经过身份验证
builder.AddListeningPort(server_address_, grpc::InsecureServerCredentials());
// 注册服务
builder.RegisterService(&service_);
// 为当前服务器创建完成队列
cq_ = builder.AddCompletionQueue();
// 构建并启动服务器
server_ = builder.BuildAndStart();
std::cout << "AysncSideCarServer_New is listening on " << server_address_ << std::endl;
// 为各个接口创建请求上下文,然后注册请求到服务端
HandlerHttpContext *http_context = new HandlerHttpContext;
http_context->type_ = 1;
http_context->status_ = 1;
HandlerDownloadContext *download_context = new HandlerDownloadContext;
download_context->type_ = 2;
download_context->status_ = 1;
HandlerUploadContext *upload_context = new HandlerUploadContext;
upload_context->type_ = 3;
upload_context->status_ = 1;
// 注册服务,参数从前到后分别是:rpc服务上下文,rpc请求对象,异步响应器,新的rpc请求使用的完成队列,通知完成使用的完成队列,唯一标识tag标识当前这次请求的上下文
service_.Requesthttp(&http_context->ctx_, &http_context->req_, &http_context->responder_, cq_.get(), cq_.get(), http_context);
service_.Requestdownload(&download_context->ctx_, &download_context->req_, &download_context->responder_, cq_.get(), cq_.get(), download_context);
service_.Requestupload(&upload_context->ctx_, &upload_context->req_, &upload_context->responder_, cq_.get(), cq_.get(), upload_context);
//创建线程池,用于运行请求的接口
ThreadPool pool(THREAD_POOL_SIZE);//THTREAD_POOL_SIZE自行定义
//不断从完成队列中取出请求,这里的请求都是在上面注册过的
while (true)
{
HandlerContextBase *handler_context = nullptr;
bool ok = false;
GPR_ASSERT(cq_->Next((void **)&handler_context, &ok));
GPR_ASSERT(ok);
//请求接口的类型,1是http,2是download,3是upload
int type = handler_context->type_;
//根据状态分别处理,1表示要进行接口调用,2表示已经完成,可以销毁该请求上下文了
if (handler_context->status_ == 2)
{
switch (type)
{
case 1:
delete (HandlerHttpContext *)handler_context;
break;
case 2:
delete (HandlerDownloadContext *)handler_context;
break;
case 3:
delete (HandlerUploadContext *)handler_context;
break;
}
continue;
}
//从完成队列中取出来了一个请求上下文来处理当前请求,就需要再放回去一个给后续到达的请求用
switch (type)
{
case 1:
{
HandlerHttpContext *http_context = new HandlerHttpContext;
http_context->type_ = 1;
http_context->status_ = 1;
// 注册服务,参数从前到后分别是:rpc服务上下文,rpc请求对象,异步响应器,新的rpc请求使用的完成队列,通知完成使用的完成队列,唯一标识tag标识当前这次请求的上下文
service_.Requesthttp(&http_context->ctx_, &http_context->req_, &http_context->responder_, cq_.get(), cq_.get(), http_context);
}
break;
case 2:
{
HandlerDownloadContext *download_context = new HandlerDownloadContext;
download_context->type_ = 2;
download_context->status_ = 1;
service_.Requestdownload(&download_context->ctx_, &download_context->req_, &download_context->responder_, cq_.get(), cq_.get(), download_context);
}
break;
case 3:
{
HandlerUploadContext *upload_context = new HandlerUploadContext;
upload_context->type_ = 3;
upload_context->status_ = 1;
service_.Requestupload(&upload_context->ctx_, &upload_context->req_, &upload_context->responder_, cq_.get(), cq_.get(), upload_context);
}
break;
}
//当前请求上下文的任务进行执行,放入线程池中去运行
pool.enqueue([type, handler_context, this]()
{
switch (type)
{
case 1:
{
HandlerHttpContext *h = (HandlerHttpContext *)handler_context;
Status status = http(&h->ctx_, &h->req_, &h->resp_);
h->status_ = 2; //设置状态为完成接口调用,准备进行响应
//调用responder_进行异步的响应发送,三个参数分别为发送的响应、状态码、请求处理在服务端的唯一tag
h->responder_.Finish(h->resp_, status, handler_context);
}
break;
case 2:
{
HandlerDownloadContext *h = (HandlerDownloadContext *)handler_context;
Status status = download(&h->ctx_, &h->req_, &h->resp_);
h->status_ = 2;
h->responder_.Finish(h->resp_, status, handler_context);
}
break;
case 3:
{
HandlerUploadContext *h = (HandlerUploadContext *)handler_context;
Status status = upload(&h->ctx_, &h->req_, &h->resp_);
h->status_ = 2;
h->responder_.Finish(h->resp_, status, handler_context);
}
break;
}
});
}
}
private:
Status http(ServerContext *context, const HttpRequest *request,
HttpResponse *response)
{
response->set_httpresult("http is ok");
return Status::OK;
}
Status download(ServerContext *context, const DownloadRequest *request,
DownloadResponse *response)
{
response->set_downloadresult("download is ok");
return Status::OK;
}
Status upload(ServerContext *context, const UploadRequest *request,
UploadResponse *response)
{
response->set_uploadresult("upload is ok");
return Status::OK;
}
};
参考博文:https://www.cnblogs.com/oloroso/p/11345266.html
线程池源码
其中可以使用线程池同时运行多个RPC请求的接口,线程池的代码此处也一并放出来了,来源于github
github地址:https://github.com/progschj/ThreadPool.git
#ifndef THREAD_POOL_H
#define THREAD_POOL_H
#include <vector>
#include <queue>
#include <memory>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <future>
#include <functional>
#include <stdexcept>
class ThreadPool
{
public:
ThreadPool(size_t);
template <class F, class... Args>
auto enqueue(F &&f, Args &&...args)
-> std::future<typename std::result_of<F(Args...)>::type>;
~ThreadPool();
private:
// need to keep track of threads so we can join them
std::vector<std::thread> workers;
// the task queue
std::queue<std::function<void()>> tasks;
// synchronization
std::mutex queue_mutex;
std::condition_variable condition;
bool stop;
};
// the constructor just launches some amount of workers
inline ThreadPool::ThreadPool(size_t threads)
: stop(false)
{
for (size_t i = 0; i < threads; ++i)
workers.emplace_back(
[this]
{
for (;;)
{
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->queue_mutex);
this->condition.wait(lock,
[this]
{ return this->stop || !this->tasks.empty(); });
if (this->stop && this->tasks.empty())
return;
task = std::move(this->tasks.front());
this->tasks.pop();
}
task();
}
});
}
// add new work item to the pool
template <class F, class... Args>
auto ThreadPool::enqueue(F &&f, Args &&...args)
-> std::future<typename std::result_of<F(Args...)>::type>
{
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...));
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(queue_mutex);
// don't allow enqueueing after stopping the pool
if (stop)
throw std::runtime_error("enqueue on stopped ThreadPool");
tasks.emplace([task]()
{ (*task)(); });
}
condition.notify_one();
return res;
}
// the destructor joins all threads
inline ThreadPool::~ThreadPool()
{
{
std::unique_lock<std::mutex> lock(queue_mutex);
stop = true;
}
condition.notify_all();
for (std::thread &worker : workers)
worker.join();
}
#endif
【gRPC】C++异步服务端优化版,多服务接口样例的更多相关文章
- Python中的Tcp协议应用之TCP服务端-线程版
利用线程实现,一个服务端同时服务多个客户端的需求. TCP服务端-线程版代码实现: import socket import threading def handle_client_socket(ne ...
- atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系
atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系 1. 服务器控件是可被服务器理解的标签.有三种类型的服务器控件: 1 1.1. HTML 服务器控件 ...
- 服务端使用Zookeeper注册服务地址,客户端从Zookeeper获取可用的服务地址。
一个轻量级分布式RPC框架--NettyRpc - 阿凡卢 - 博客园 http://www.cnblogs.com/luxiaoxun/p/5272384.html 这个RPC框架使用的一些技术所解 ...
- day112:MoFang:种植园使用websocket代替http&服务端基于flask-socketio提供服务&服务端响应信息&种植园页面显示初始化
目录 1.种植园使用websocket代替http 2.服务端基于socket提供服务 3.服务端响应信息 4.种植园页面展示 1.种植园使用websocket代替http 我们需要完成的种植园,是一 ...
- 服务端提供的JSON数据接口与用户端接收解析JSON数据
JSON格式的服务接口:http://www.cnblogs.com/visec479/articles/4118338.html 首先来了解下JSON格式解析 json结构的格式就是若干个 键/值( ...
- Android上传图片到服务器,服务端利用.NET WCFRest服务读取文件的解决方案
在项目中遇到要将Android设备拍摄的照片上传的服务器,将文件保存在服务器本地的文件夹中,数据库中保存的是图片文件名.整个上传是将图片生成二进制流通过HTTP请求上传到服务端,服务端是基于.NET环 ...
- mpush 服务端配置 for windows 服务自动运行
mpush 服务端配置 以下安装部分是参照官方的步骤, 一.安装jdk1.8并配置环境变量 示例: http://www.cnblogs.com/endv/p/6439860.html 二.Wind ...
- Mina学习+手写服务端+通过telnet连接服务端
1. 2. 3. 4.MinaServer.java package com.mina; import java.io.IOException;import java.net.InetSocketAd ...
- PHP服务端优化全面总结
一.优化PHP原则 1.1PHP代码的优化 (1)升级最新的PHP版本 鸟哥PPT里的对比数据,就是WordPress在PHP5.6执行100次会产生70亿次的CPU指令执行数目,而在PHP7中只需要 ...
随机推荐
- NC20032 [HNOI2003]激光炸弹
NC20032 [HNOI2003]激光炸弹 题目 题目描述 一种新型的激光炸弹,可以摧毁一个边长为R的正方形内的所有的目标. 现在地图上有 \(n\) (\(N ≤ 10000\))个目标,用整数 ...
- Web开发小妙招:巧用ThreadLocal规避层层传值
摘要:我们可以在处理每次请求的过程中,无需从Controller以及Service中的方法层层传值,只需要直接通过该局部变量取值即可. 本文分享自华为云社区<拦截器中巧用ThreadLocal规 ...
- 谷歌邮箱使用python自动化发送邮件出现smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password not accepted.的解决
今天学习使用邮箱自动化办公的过程中run时一直出现如下错误;smtplib.SMTPAuthenticationError: (535, b'5.7.8 Username and Password n ...
- NOI / 2.1基本算法之枚举 1749:数字方格
描述: 如上图,有3个方格,每个方格里面都有一个整数a1,a2,a3.已知0 <= a1, a2, a3 <= n,而且a1 + a2是2的倍数,a2 + a3是3的倍数, a1 + a2 ...
- Jmeter-记一次自动化造数引发的BeanShell写入excel实例
一.前言 最近工作和生活说忙也忙,说不忙也不忙,但就是已经感觉很长时间没有get新的技术技能了,就是一丢丢的那种也没有,哈哈哈,今天就来讲一下最近get到的小技能吧. 工作中,由于某个需求需要几百条数 ...
- 基于 Hexo 从零开始搭建个人博客(五)
阅读本篇前,请先阅读前几篇文章: 基于 Hexo 从零开始搭建个人博客(一) 基于 Hexo 从零开始搭建个人博客(二) 基于 Hexo 从零开始搭建个人博客(三) 基于 Hexo 从零开始搭建个人博 ...
- WPF 制作 Windows 屏保
分享如何使用WPF 制作 Windows 屏保 WPF 制作 Windows 屏保 作者:驚鏵 原文链接:https://github.com/yanjinhuagood/ScreenSaver 框架 ...
- DQL基础查询和DQL条件查询
DQL:查询表中的记录 1.语法 select 字段列表 from 表名列表 where 条件列表 group by 分组字段 having 分组之后的条件 order by 排序 limit 分页限 ...
- for循环和while循环dowhile循环
第四章 循环语句 4.1循环概述 循环语句可以在满足循环条件的情况下,反复执行某一段代码,这段代码被重复执行的代码被称为循环体语句,当反复执行这个循环体的时候,需要在核实的时候吧循环判断条件修改为fa ...
- 4.5省选模拟solution
\(4.5\)省选测试\(solution\) 题面可是我精心准备(咕咕咕)了一周写出来的,大家就当看故事吧(那里面的人物确实是存在的,\(E\)就是本人啦,也算是对一段经历的回忆吧,所以这套考试的题 ...