官方的C++异步服务端API样例可读性并不好,理解起来非常的费劲,各种状态机也并不明了,整个运行过程也容易读不懂,因此此处参考网上的博客进行了重写,以求顺利读懂。

C++异步服务端实例,详细注释版

gRPC使用C++实现异步服务端的基本逻辑:

  • 构建数据结构来存储需要处理的请求及其上下文信息,此处使用HandlerContext,相当于对到达的请求的封装
  • 首先注册各个接口的HandlerContext,放入完成队列CompletionQueue中,当请求到达时,根据类型封装进对应的HandlerContext,由于是异步客户端,需要保证后面到达的请求也有HandlerContext用,所以用一个就要再创建一个空的放回去
  • 运行完的接口,其HandlerContext需要销毁

以下代码的关键为run()方法中的逻辑以及HandlerContext的设置,每一步都有注释,可以详细观看

  1. //官方样例的异步服务代码可读性太差了,状态机绕来绕去不直观,在这里参考网上的博客进行重写,类名随意
  2. class AsyncSideCarServiceImplNew final
  3. {
  4. private:
  5. // 当前服务器的地址
  6. std::string server_address_;
  7. // 当前服务器的完成队列
  8. std::unique_ptr<ServerCompletionQueue> cq_;
  9. // 当前服务器的异步服务
  10. SideCarService::AsyncService service_;
  11. // 服务器实例
  12. std::unique_ptr<Server> server_;
  13. struct HandlerContextBase
  14. {
  15. int type_; //请求的接口是哪个,1表示http,2表示download,3表示upload,后续有需要可以再添加
  16. int status_; //当前处理状态,1表示处理请求构建响应,2表示发送响应
  17. ServerContext ctx_; // rpc服务的上下文信息
  18. };
  19. //请求的上下文结构
  20. template <typename RequestType, typename ResponseType>
  21. struct HandlerContext : public HandlerContextBase
  22. {
  23. RequestType req_; //请求数据类型
  24. ResponseType resp_; //响应数据类型
  25. ServerAsyncResponseWriter<ResponseType> responder_; //响应器
  26. HandlerContext() : responder_(&ctx_) {} //构造方法
  27. };
  28. // 定义好各个接口的上下文
  29. typedef HandlerContext<HttpRequest, HttpResponse> HandlerHttpContext;
  30. typedef HandlerContext<DownloadRequest, DownloadResponse> HandlerDownloadContext;
  31. typedef HandlerContext<UploadRequest, UploadResponse> HandlerUploadContext;
  32. public:
  33. ~AsyncSideCarServiceImplNew()
  34. {
  35. server_->Shutdown();
  36. // 关闭服务器后也要关闭完成队列
  37. cq_->Shutdown();
  38. }
  39. //构造时传入IP:Port即可
  40. AsyncSideCarServiceImplNew(std::string server_address) : server_address_(server_address) {}
  41. // 服务器与队列的关闭放入了析构函数中
  42. void Run()
  43. {
  44. // std::string server_address = "localhost:50052";
  45. // 服务器构建器
  46. ServerBuilder builder;
  47. // 服务器IP与端口指定,第二个参数表示该通道不经过身份验证
  48. builder.AddListeningPort(server_address_, grpc::InsecureServerCredentials());
  49. // 注册服务
  50. builder.RegisterService(&service_);
  51. // 为当前服务器创建完成队列
  52. cq_ = builder.AddCompletionQueue();
  53. // 构建并启动服务器
  54. server_ = builder.BuildAndStart();
  55. std::cout << "AysncSideCarServer_New is listening on " << server_address_ << std::endl;
  56. // 为各个接口创建请求上下文,然后注册请求到服务端
  57. HandlerHttpContext *http_context = new HandlerHttpContext;
  58. http_context->type_ = 1;
  59. http_context->status_ = 1;
  60. HandlerDownloadContext *download_context = new HandlerDownloadContext;
  61. download_context->type_ = 2;
  62. download_context->status_ = 1;
  63. HandlerUploadContext *upload_context = new HandlerUploadContext;
  64. upload_context->type_ = 3;
  65. upload_context->status_ = 1;
  66. // 注册服务,参数从前到后分别是:rpc服务上下文,rpc请求对象,异步响应器,新的rpc请求使用的完成队列,通知完成使用的完成队列,唯一标识tag标识当前这次请求的上下文
  67. service_.Requesthttp(&http_context->ctx_, &http_context->req_, &http_context->responder_, cq_.get(), cq_.get(), http_context);
  68. service_.Requestdownload(&download_context->ctx_, &download_context->req_, &download_context->responder_, cq_.get(), cq_.get(), download_context);
  69. service_.Requestupload(&upload_context->ctx_, &upload_context->req_, &upload_context->responder_, cq_.get(), cq_.get(), upload_context);
  70. //创建线程池,用于运行请求的接口
  71. ThreadPool pool(THREAD_POOL_SIZE);//THTREAD_POOL_SIZE自行定义
  72. //不断从完成队列中取出请求,这里的请求都是在上面注册过的
  73. while (true)
  74. {
  75. HandlerContextBase *handler_context = nullptr;
  76. bool ok = false;
  77. GPR_ASSERT(cq_->Next((void **)&handler_context, &ok));
  78. GPR_ASSERT(ok);
  79. //请求接口的类型,1是http,2是download,3是upload
  80. int type = handler_context->type_;
  81. //根据状态分别处理,1表示要进行接口调用,2表示已经完成,可以销毁该请求上下文了
  82. if (handler_context->status_ == 2)
  83. {
  84. switch (type)
  85. {
  86. case 1:
  87. delete (HandlerHttpContext *)handler_context;
  88. break;
  89. case 2:
  90. delete (HandlerDownloadContext *)handler_context;
  91. break;
  92. case 3:
  93. delete (HandlerUploadContext *)handler_context;
  94. break;
  95. }
  96. continue;
  97. }
  98. //从完成队列中取出来了一个请求上下文来处理当前请求,就需要再放回去一个给后续到达的请求用
  99. switch (type)
  100. {
  101. case 1:
  102. {
  103. HandlerHttpContext *http_context = new HandlerHttpContext;
  104. http_context->type_ = 1;
  105. http_context->status_ = 1;
  106. // 注册服务,参数从前到后分别是:rpc服务上下文,rpc请求对象,异步响应器,新的rpc请求使用的完成队列,通知完成使用的完成队列,唯一标识tag标识当前这次请求的上下文
  107. service_.Requesthttp(&http_context->ctx_, &http_context->req_, &http_context->responder_, cq_.get(), cq_.get(), http_context);
  108. }
  109. break;
  110. case 2:
  111. {
  112. HandlerDownloadContext *download_context = new HandlerDownloadContext;
  113. download_context->type_ = 2;
  114. download_context->status_ = 1;
  115. service_.Requestdownload(&download_context->ctx_, &download_context->req_, &download_context->responder_, cq_.get(), cq_.get(), download_context);
  116. }
  117. break;
  118. case 3:
  119. {
  120. HandlerUploadContext *upload_context = new HandlerUploadContext;
  121. upload_context->type_ = 3;
  122. upload_context->status_ = 1;
  123. service_.Requestupload(&upload_context->ctx_, &upload_context->req_, &upload_context->responder_, cq_.get(), cq_.get(), upload_context);
  124. }
  125. break;
  126. }
  127. //当前请求上下文的任务进行执行,放入线程池中去运行
  128. pool.enqueue([type, handler_context, this]()
  129. {
  130. switch (type)
  131. {
  132. case 1:
  133. {
  134. HandlerHttpContext *h = (HandlerHttpContext *)handler_context;
  135. Status status = http(&h->ctx_, &h->req_, &h->resp_);
  136. h->status_ = 2; //设置状态为完成接口调用,准备进行响应
  137. //调用responder_进行异步的响应发送,三个参数分别为发送的响应、状态码、请求处理在服务端的唯一tag
  138. h->responder_.Finish(h->resp_, status, handler_context);
  139. }
  140. break;
  141. case 2:
  142. {
  143. HandlerDownloadContext *h = (HandlerDownloadContext *)handler_context;
  144. Status status = download(&h->ctx_, &h->req_, &h->resp_);
  145. h->status_ = 2;
  146. h->responder_.Finish(h->resp_, status, handler_context);
  147. }
  148. break;
  149. case 3:
  150. {
  151. HandlerUploadContext *h = (HandlerUploadContext *)handler_context;
  152. Status status = upload(&h->ctx_, &h->req_, &h->resp_);
  153. h->status_ = 2;
  154. h->responder_.Finish(h->resp_, status, handler_context);
  155. }
  156. break;
  157. }
  158. });
  159. }
  160. }
  161. private:
  162. Status http(ServerContext *context, const HttpRequest *request,
  163. HttpResponse *response)
  164. {
  165. response->set_httpresult("http is ok");
  166. return Status::OK;
  167. }
  168. Status download(ServerContext *context, const DownloadRequest *request,
  169. DownloadResponse *response)
  170. {
  171. response->set_downloadresult("download is ok");
  172. return Status::OK;
  173. }
  174. Status upload(ServerContext *context, const UploadRequest *request,
  175. UploadResponse *response)
  176. {
  177. response->set_uploadresult("upload is ok");
  178. return Status::OK;
  179. }
  180. };

参考博文:https://www.cnblogs.com/oloroso/p/11345266.html

线程池源码

其中可以使用线程池同时运行多个RPC请求的接口,线程池的代码此处也一并放出来了,来源于github

github地址:https://github.com/progschj/ThreadPool.git

  1. #ifndef THREAD_POOL_H
  2. #define THREAD_POOL_H
  3. #include <vector>
  4. #include <queue>
  5. #include <memory>
  6. #include <thread>
  7. #include <mutex>
  8. #include <condition_variable>
  9. #include <future>
  10. #include <functional>
  11. #include <stdexcept>
  12. class ThreadPool
  13. {
  14. public:
  15. ThreadPool(size_t);
  16. template <class F, class... Args>
  17. auto enqueue(F &&f, Args &&...args)
  18. -> std::future<typename std::result_of<F(Args...)>::type>;
  19. ~ThreadPool();
  20. private:
  21. // need to keep track of threads so we can join them
  22. std::vector<std::thread> workers;
  23. // the task queue
  24. std::queue<std::function<void()>> tasks;
  25. // synchronization
  26. std::mutex queue_mutex;
  27. std::condition_variable condition;
  28. bool stop;
  29. };
  30. // the constructor just launches some amount of workers
  31. inline ThreadPool::ThreadPool(size_t threads)
  32. : stop(false)
  33. {
  34. for (size_t i = 0; i < threads; ++i)
  35. workers.emplace_back(
  36. [this]
  37. {
  38. for (;;)
  39. {
  40. std::function<void()> task;
  41. {
  42. std::unique_lock<std::mutex> lock(this->queue_mutex);
  43. this->condition.wait(lock,
  44. [this]
  45. { return this->stop || !this->tasks.empty(); });
  46. if (this->stop && this->tasks.empty())
  47. return;
  48. task = std::move(this->tasks.front());
  49. this->tasks.pop();
  50. }
  51. task();
  52. }
  53. });
  54. }
  55. // add new work item to the pool
  56. template <class F, class... Args>
  57. auto ThreadPool::enqueue(F &&f, Args &&...args)
  58. -> std::future<typename std::result_of<F(Args...)>::type>
  59. {
  60. using return_type = typename std::result_of<F(Args...)>::type;
  61. auto task = std::make_shared<std::packaged_task<return_type()>>(
  62. std::bind(std::forward<F>(f), std::forward<Args>(args)...));
  63. std::future<return_type> res = task->get_future();
  64. {
  65. std::unique_lock<std::mutex> lock(queue_mutex);
  66. // don't allow enqueueing after stopping the pool
  67. if (stop)
  68. throw std::runtime_error("enqueue on stopped ThreadPool");
  69. tasks.emplace([task]()
  70. { (*task)(); });
  71. }
  72. condition.notify_one();
  73. return res;
  74. }
  75. // the destructor joins all threads
  76. inline ThreadPool::~ThreadPool()
  77. {
  78. {
  79. std::unique_lock<std::mutex> lock(queue_mutex);
  80. stop = true;
  81. }
  82. condition.notify_all();
  83. for (std::thread &worker : workers)
  84. worker.join();
  85. }
  86. #endif

【gRPC】C++异步服务端优化版,多服务接口样例的更多相关文章

  1. Python中的Tcp协议应用之TCP服务端-线程版

    利用线程实现,一个服务端同时服务多个客户端的需求. TCP服务端-线程版代码实现: import socket import threading def handle_client_socket(ne ...

  2. atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系

    atitit.组件化事件化的编程模型--服务端控件(1)---------服务端控件与标签的关系 1. 服务器控件是可被服务器理解的标签.有三种类型的服务器控件: 1 1.1. HTML 服务器控件  ...

  3. 服务端使用Zookeeper注册服务地址,客户端从Zookeeper获取可用的服务地址。

    一个轻量级分布式RPC框架--NettyRpc - 阿凡卢 - 博客园 http://www.cnblogs.com/luxiaoxun/p/5272384.html 这个RPC框架使用的一些技术所解 ...

  4. day112:MoFang:种植园使用websocket代替http&服务端基于flask-socketio提供服务&服务端响应信息&种植园页面显示初始化

    目录 1.种植园使用websocket代替http 2.服务端基于socket提供服务 3.服务端响应信息 4.种植园页面展示 1.种植园使用websocket代替http 我们需要完成的种植园,是一 ...

  5. 服务端提供的JSON数据接口与用户端接收解析JSON数据

    JSON格式的服务接口:http://www.cnblogs.com/visec479/articles/4118338.html 首先来了解下JSON格式解析 json结构的格式就是若干个 键/值( ...

  6. Android上传图片到服务器,服务端利用.NET WCFRest服务读取文件的解决方案

    在项目中遇到要将Android设备拍摄的照片上传的服务器,将文件保存在服务器本地的文件夹中,数据库中保存的是图片文件名.整个上传是将图片生成二进制流通过HTTP请求上传到服务端,服务端是基于.NET环 ...

  7. mpush 服务端配置 for windows 服务自动运行

    mpush 服务端配置 以下安装部分是参照官方的步骤, 一.安装jdk1.8并配置环境变量 示例:  http://www.cnblogs.com/endv/p/6439860.html 二.Wind ...

  8. Mina学习+手写服务端+通过telnet连接服务端

    1. 2. 3. 4.MinaServer.java package com.mina; import java.io.IOException;import java.net.InetSocketAd ...

  9. PHP服务端优化全面总结

    一.优化PHP原则 1.1PHP代码的优化 (1)升级最新的PHP版本 鸟哥PPT里的对比数据,就是WordPress在PHP5.6执行100次会产生70亿次的CPU指令执行数目,而在PHP7中只需要 ...

随机推荐

  1. Python调用Outlook发邮件

    调用Outlook发送邮件 需安装pypiwin32模块:pip install pypiwin32 1. 发送普通邮件 import win32com.client as win32 outlook ...

  2. PostgreSQL 9.1 飞升之路

    PostgreSQL upgrade 以升级 PostgreSQL 9.1 至 PostgreSQL 11 (跨越 9.2.9.3.9.4.9.5.9.6.10 六个大版本) 为例,本文将分享一下过去 ...

  3. 星际争霸的虫王IA退役2年搞AI,自叹不如了

    ------------恢复内容开始------------ 金磊 发自 凹非寺 量子位|公众号 QbitA 这年头,直播讲AI,真算不上什么新鲜事.但要是连职业电竞选手,都开播主讲呢?没开玩笑,是真 ...

  4. 编译kubeadm使生成证书有效期为100年

    目录 问题 编译 检查结果 问题 当我使用kubeadm部署成功k8s集群时在想默认生成的证书有效期是多久,如下所示 /etc/kubernetes/pki/apiserver.crt #1年有效期 ...

  5. Mark IntelliJ IDEA 2018.2.3破解

    来源:https://blog.csdn.net/qq_38060935/article/details/90377761

  6. centos一些mysql常用命令

    # service mysqld status    #命令来查看mysql 的启动状态,active (running) 是运行中 systemctl start mysqld.service    ...

  7. java的Test 如何使用@Autowired注解

    1.配置来至bean.xml @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "class ...

  8. Winsock Client Code

    以下代码来自MSDN:https://msdn.microsoft.com/en-us/library/windows/desktop/ms737591(v=vs.85).aspx #define W ...

  9. 算法竞赛进阶指南0x14 Hash

    组成部分: 哈希函数: 链表 AcWing137. 雪花雪花雪花 因为所需要数据量过于大,所以只能以O(n)的复杂度. 所以不可能在实现的过程中一一顺时针逆时针进行比较,所以采用一种合适的数据结构. ...

  10. python中的标识符和保留字

    保留字,有一些单词被赋予了特定的意义,这些单词不能作为对象的名字 想要快速获取python中的关键字可以通过以下的程 序来快速实现 import keyword print(keyword.kwlis ...