【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中只需要 ...
随机推荐
- Python调用Outlook发邮件
调用Outlook发送邮件 需安装pypiwin32模块:pip install pypiwin32 1. 发送普通邮件 import win32com.client as win32 outlook ...
- PostgreSQL 9.1 飞升之路
PostgreSQL upgrade 以升级 PostgreSQL 9.1 至 PostgreSQL 11 (跨越 9.2.9.3.9.4.9.5.9.6.10 六个大版本) 为例,本文将分享一下过去 ...
- 星际争霸的虫王IA退役2年搞AI,自叹不如了
------------恢复内容开始------------ 金磊 发自 凹非寺 量子位|公众号 QbitA 这年头,直播讲AI,真算不上什么新鲜事.但要是连职业电竞选手,都开播主讲呢?没开玩笑,是真 ...
- 编译kubeadm使生成证书有效期为100年
目录 问题 编译 检查结果 问题 当我使用kubeadm部署成功k8s集群时在想默认生成的证书有效期是多久,如下所示 /etc/kubernetes/pki/apiserver.crt #1年有效期 ...
- Mark IntelliJ IDEA 2018.2.3破解
来源:https://blog.csdn.net/qq_38060935/article/details/90377761
- centos一些mysql常用命令
# service mysqld status #命令来查看mysql 的启动状态,active (running) 是运行中 systemctl start mysqld.service ...
- java的Test 如何使用@Autowired注解
1.配置来至bean.xml @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "class ...
- Winsock Client Code
以下代码来自MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/ms737591(v=vs.85).aspx #define W ...
- 算法竞赛进阶指南0x14 Hash
组成部分: 哈希函数: 链表 AcWing137. 雪花雪花雪花 因为所需要数据量过于大,所以只能以O(n)的复杂度. 所以不可能在实现的过程中一一顺时针逆时针进行比较,所以采用一种合适的数据结构. ...
- python中的标识符和保留字
保留字,有一些单词被赋予了特定的意义,这些单词不能作为对象的名字 想要快速获取python中的关键字可以通过以下的程 序来快速实现 import keyword print(keyword.kwlis ...