【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中只需要 ...
随机推荐
- npm相关知识整理
语义化版本 major: 重大变化,不兼容老版本 minor: 新增功能,兼容老版本 patch: 修复bug,兼容老版本 依赖版本号 * 匹配最新版本的依赖 ^ 匹配最近的大版本依赖,比如^1.2. ...
- Cisco Packet Tracer Student(思科网络模拟器)模拟集线器和嗅探攻击
一.集线器简介 集线器是局域网内的基础设备,工作于OSI中的物理层,作用是将接收的信号进行放大再传输,集线器是纯硬件设施,集线器开发之初就没考虑过软件层面的操作,所以不具备像路由器.交换机等设备那样具 ...
- Zend Studio,php 生成报错
Zend Studio Description Resource Path Location Type Undefined CSS file ("../red-treeview.css&q ...
- IDEA快捷键之html篇-1
前端IDE中Emmet插件快捷输入HTML代码 前端IDE如VSCode.Atom.Sublime Text和Intellij Idea中使用Emmet插件快捷输入HTML代码的介绍 前端IDE中 ...
- 3.Android高仿网易云音乐-首页复杂发现界面布局和功能/RecyclerView复杂布局
0.效果图 效果图依次为发现界面顶部,包含首页轮播图,水平滚动的按钮,推荐歌单:然后是发现界面推荐单曲,点击单曲就是直接进入播放界面:最后是全局播放控制条上点击播放列表按钮显示的播放列表弹窗. 1.整 ...
- for(int i=0;i<1000;i++)与 for(int i=1;i<=1000;i++)。 if ( i%500){}//前者表示0-501一个循环,后者1-500一个循环
`package com.Itbz; import java.sql.Connection; import java.sql.PreparedStatement; /** 向mysql数据库批量添加数 ...
- 为你的网站加上live2d的动态小挂件,博君一晒
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_122 喜欢二次元的朋友一定对大名鼎鼎的live2d技术并不陌生,live2D是一种应用于电子游戏的绘图渲染技术,技术由日本Cybe ...
- HTTP配置
目录 HTTP配置 虚拟主机 相同IP不同端口 不同IP相同端口 相同IP相同端口不同域名 Linux修改hosts文件 Windows修改hosts文件 配置https HTTP配置 虚拟主机 虚拟 ...
- Java版的防抖(debounce)和节流(throttle)
概念 防抖(debounce) 当持续触发事件时,一定时间段内没有再触发事件,事件处理函数才会执行一次,如果设定时间到来之前,又触发了事件,就重新开始延时. 防抖,即如果短时间内大量触发同一事件,都会 ...
- FutureTask源码深度剖析
FutureTask源码深度剖析 前言 在前面的文章自己动手写FutureTask当中我们已经仔细分析了FutureTask给我们提供的功能,并且深入分析了我们该如何实现它的功能,并且给出了使用Ree ...