前言

本文介绍一个有趣的 通过C++实现的 持久化的http_server demo,这样我们通过http通信之后的数据可以持久化存储,即使server挂了,数据也不会丢失。我们的http_sever 也就能够真正得作为一个后端server了。

本身持久化这个能力是数据库提供的,像通用的http交互数据都会通过SQL server或者MySQL这样的存储系统来存储关系型数据,而这里我只是调度了一个单机存储引擎作为持久化存储。

主要用到的一些技术:

  1. mongoose C语言 网络通信库,在此库基础上实现了一个C++的httpserver
  2. Rocksdb 单机存储引擎,作为持久化通信数据的存储。
  3. 通过模版工厂来优雅得创建http url 和 对应其操作的实现。

代码地址:PersistentHttpserver

实现过程

1. HTTPSERVER C++封装

这里mongoose的通信库基本都已经实现了http应用层及以下的通信接口的封装,包括接受http协议的数据并解析或者封装成http请求并发送,我这里需要做的仅仅是做一些接口调用使用C++实现就可以了。

基本接口如下:

class HttpServer {public:HttpServer() {}~HttpServer() {Close();if (kv_engine_) {delete kv_engine_;kv_engine_ = nullptr;}}void Init(const std::string &port); // Init some variablebool Start(); // Start a http server with a portbool Close();// Send a message as a http requeststatic void SendHttpRsp(mg_connection *connection, std::string rsp);static mg_serve_http_opts s_server_option;static KVEngine *kv_engine_; // For persistent the dataprivate:// Listen the event on the portstatic void OnHttpEvent(mg_connection *connection, int event_type,void *event_data);// Handle the http request with the definite url.static void HandleHttpEvent(mg_connection *connection,http_message *http_req);std::string m_port_;mg_mgr m_mgr_;
};

2. Rocksdb单机引擎使用

大家需要高级功能可以扩展,这里仅仅是使用了一些基本的接口调度起了rocksdb

#pragma once#include <iostream>
#include <string>#include "rocksdb/db.h"
#include "rocksdb/options.h"class KVEngine {public:KVEngine(std::string path) : path_(path) {}~KVEngine() {if (db_) {delete db_;db_ = nullptr;}}void Init() {opt_.create_if_missing = true;if (Open() != "ok") {std::cout << "Open db failed " << std::endl;}}std::string Open() {auto s = rocksdb::DB::Open(opt_, path_, &db_);if (!s.ok()) {return s.ToString();}return "ok";}std::string Get(const std::string& key) {if (nullptr == db_) {return "db_ is nullptr, please init it.";}std::string tmp_val;auto s = db_->Get(rocksdb::ReadOptions(), key, &tmp_val);if (!s.ok()) {return "not ok";}return tmp_val;}std::string Put(const std::string& key, const std::string& val) {if (nullptr == db_) {return "db_ is nullptr, please init it.";}auto s = db_->Put(rocksdb::WriteOptions(), key, val);if (!s.ok()) {std::cout << "Put failed " << s.ToString() << std::endl;return s.ToString();}return "ok";}private:std::string path_;rocksdb::DB* db_;rocksdb::Options opt_;
}

3. 模版工厂来创建 url 及其 handler

通过如下模版工厂,我们后续增加更多的URL的时候,不需要更改httpserver.cpp源码 ,仅仅需要增加一个扩展类 及其 实现,并将这个映射添加到全局映射表中就可以了。

template <class OperationType_t>
class OperationRegister {public:virtual OperationType_t* CreateOperation(const std::string& op_name, mg_connection* conn, http_message* hm) = 0;protected:OperationRegister() = default;virtual ~OperationRegister() = default;
};// Factory class template
template <class OperationType_t>
class OperationFactory {public:// Single pattern of the factorystatic OperationFactory<OperationType_t>& Instance() {static OperationFactory<OperationType_t> instance;return instance;}void RegisterOperation(const std::string& op_name,mg_connection* conn, http_message* hm,OperationRegister<OperationType_t>* reg) {operationRegister[op_name] = reg;}OperationType_t* GetOperation(const std::string& op_name,mg_connection* conn, http_message* hm) {if (operationRegister.find(op_name) != operationRegister.end()) {return operationRegister[op_name]->CreateOperation(op_name, conn, hm);}return nullptr;}private:// We don't allow to constructor, copy constructor and align constructorOperationFactory() = default;~OperationFactory() = default;OperationFactory(const OperationFactory&) = delete;const OperationFactory& operator= (const OperationFactory&) = delete;std::map<std::string, OperationRegister<OperationType_t>* > operationRegister;
};// An template class to create the detail Operation
template <class OperationType_t, class OperationImpl_t>
class OperationImplRegister : public OperationRegister<OperationType_t> {public:explicit OperationImplRegister(const std::string& op_name, mg_connection* conn, http_message* hm) {OperationFactory<OperationType_t>::Instance().RegisterOperation(op_name, conn, hm, this);}OperationType_t* CreateOperation(const std::string& op_name, mg_connection* conn, http_message* hm) {return new OperationImpl_t(op_name, conn, hm);}
};

后续仅仅需要将对应的URL 字符串 及其实现类添加到如下映射表中就可以了。

// Register all the http request's input string and their Class pointer.
void InitializeAllOp(mg_connection* conn, http_message* hm) {static bool initialize = false;if (!initialize) {static OperationImplRegister<Url, GetValue>getValue("/test/getvalue", conn, hm);static OperationImplRegister<Url, SetValueUrl>setValue("/test/setvalue", conn, hm);static OperationImplRegister<Url, RouteUrl>routeUrl("/", conn, hm);initialize = true;}
}

我们在实际HandleHttpEvent逻辑中就不需要做任何更改,十分友好得提升了代码得可扩展性。

void HttpServer::HandleHttpEvent(mg_connection *connection, http_message *http_req) {std::string req_str = std::string(http_req->message.p, http_req->message.len);std::string url = std::string(http_req->uri.p, http_req->uri.len);InitializeAllOp(connection, http_req);// Register the operation for the urlauto *judge = new JudgeOperation(connection, http_req);auto res = judge->Judge(url);if (res != "ok") {SendHttpRsp(connection, res);}
}

关于模版工厂的细节可以参考:C++ 通过模版工厂实现 简单反射机制

编译及使用

1. 编译

编译之前需要确保测试环境已经成功安装了rocksdb。

git clone https://github.com/BaronStack/PersistentHttpserver.git
cd PersistentHttpserver
make httpserver

rocksdb的on mac安装:brew install rocksdb

rocksdb的on linux安装:rocksdb-Install

2. 使用

  • 第一个console : ./httpserver

  • 第二个console:

    ╰─$ curl -d "value=firstvalue" 127.0.0.1:7999/test/setvalue
    { "result": ok }
    

    设置了一个数值之后可以看到httpserver运行的目录处 生成了一个db目录:

    db
    |-- 000010.sst
    |-- 000013.sst
    |-- 000016.sst
    |-- 000019.sst
    |-- 000025.sst
    |-- 000030.log
    |-- CURRENT
    |-- IDENTITY
    |-- LOCK
    |-- LOG
    |-- MANIFEST-000029
    |-- OPTIONS-000029
    `-- OPTIONS-000032
    

    停止第一个./httpserver 进程,重新运行,在第二个终端再此输入获取数据的请求命令

    ╰─$ curl -d "value=firstvalue" 127.0.0.1:7999/test/getvalue
    { "result": firstvalue }
    

    可以看到能够获取到重启server之前的数据。

有了针对HTTP-SERVER的持久化能力和友好的可扩展性代码,那我就可以持续玩一些有持久化能力的用户需求了。

当然实际中的httpserver请求上层的封装到底层的存储服务都会复杂千万倍,正真能够支持分布式一致性服务的底层存储 不论是newSQL还是NoSQL都会是非常复杂的实现过程。

手把手教你 用C++实现一个 可持久化 的http_server相关推荐

  1. python界面设计-手把手教你用Python设计一个简单的命令行界面

    原标题:手把手教你用Python设计一个简单的命令行界面 对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰.今天,我们就来教大家如何设计功能完整的 Pyth ...

  2. 手把手教你用C#写一个刷屏软件

    手把手教你用C#写一个刷屏轰炸软件 成品展示 环境准备 新建项目 程序思路 程序部分 完整代码 成品展示 环境准备 VS2019 新建项目 打开界面绘制 打开工具箱开始放置按钮标签以及文本框 最后设计 ...

  3. IP门禁:手把手教你用PHP实现一个IP防火墙

    最近我遇到一个需求,我的一台服务器总是遭到端口扫描和恶意登录攻击,对此可以怎么办呢?似乎除了内网隔离.增强密码认证.证书登录.设置防火墙iptables,网上找不到什么别的方案,对了,还用堡垒机的方案 ...

  4. 超详细——手把手教你用threejs实现一个酷炫的模型发光扫描效果(三)

    上一篇文章 voidjay,公众号:web前端可视化超详细--手把手教你用threejs实现一个酷炫的模型发光扫描效果(二) 上一篇文章已完成基本效果的实现,本文则完成整个项目的灵魂:发光效果以及模型 ...

  5. 手把手教你使用nodejs编写一个【使用远程仓库模板,快速创建项目模块】的cli(命令行)

    目录 实现步骤 初始化cli项目 项目目录 创建交互式命令 拉取远程仓库代码,读取仓库中的模板 拉取远程仓库代码 ora 终端 loading 读取仓库中的模板 将选择的模板复制写入目标项目 Comm ...

  6. 如何用python开发游戏_手把手教你用Python完成一个控制台小游戏-阿里云开发者社区...

    很多人想学Python程序设计或者已经了解过一点Python程序设计基础,却没办法开发出一个项目. 今天,通过演示一个简单的控制台小游戏制作,手把手教你如何用Python编写一个游戏程序,即便你是个新 ...

  7. 手把手教你用ESP32 制作一个游戏机,小白可上手

    MAKER: JuanF92/译:趣无尽 相逢已是初识 MicroByte 是一款微型主机,能够运行 NES.GameBoy.GameBoy Color.Game Gear 和 Sega Master ...

  8. 手把手教你用Python打造一个语音合成系统

    击上方"Python爬虫与数据挖掘",进行关注 回复"书籍"即可获赠Python从入门到进阶共10本电子书 今 日 鸡 汤 大弦嘈嘈如急雨,小弦切切如私语. / ...

  9. 技术流 | 手把手教你用Python设计一个命令行界面

    作者 | Yannick Wolff 译者 | 刘旭坤 整理 | Jane 出品 | Python大本营 对 Python 程序来说,完备的命令行界面可以提升团队的工作效率,减少调用时可能碰到的困扰. ...

最新文章

  1. 关于手机已处理里重复单据的处理办法
  2. pandas移除dataframe字符串数据列中的前N个字符(remove the first n characters from values from column of dataframe)
  3. [Ubuntu 12.10] Openstack 多节点安装--前期准备网络拓扑
  4. Javascript中函数提升和变量提升
  5. 在PHP当中制作隔行换色的效果以及制作上下翻页的效果!
  6. pycharm 怎么修改函数(变量)名及其引用?全局修改(批量重命名)(ctrl + f6)
  7. 大数据洞察画像自动化实践
  8. 为什么你的应用程序需要崩溃
  9. mysql 函数修改无效_MySQL:无效使用组函数
  10. MVC一个action对应多个视图的写法
  11. sfidsk创建可启动分区问题
  12. Glide 4.x之生命周期与Activity的绑定原理详解
  13. 人人商城生成app教程_apicloud版人人商城app打包教程
  14. 常用积分类型(积分公式)
  15. c语言旋转led时钟设计报告,《基于单片机的LED旋转时钟设计报告》.doc
  16. Javaweb ajax实现分页
  17. LaTeX技巧-排版大括号
  18. 中国大学MOOC C语言程序设计(大连理工大学) 课后编程题 第五周题解(个人向仅供参考)
  19. 4.通过Opencv采集摄像头视频数据
  20. python-藏头诗与成语接龙(爬虫)

热门文章

  1. MyEclipse中运行环境jre、编译级别、tomcat运行环境区别
  2. php设置backlog,高并发调优backlog多大合适?
  3. mysql 集群 增加服务器_MYSQL集群服务配置
  4. 扑克牌排序_JAVA 扑克牌排序打印,并进行洗牌
  5. css毛玻璃效果白边_CSS3毛玻璃效果(blur)有白边问题的解决方法
  6. php layui table,layui table 相关问题汇总
  7. java 线程 通过interrupted_Java线程的传说(1)——中断线程Interrupted的用处
  8. mybatis参数有list和实体类_Mybatis的几种传参方式,你了解吗?
  9. Linux那些事儿 之 戏说USB(20)设备的生命线(三)
  10. kernfs_addrm_start kernfs_add_one