cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发

目录

  • 说明/前言
  • 原生的httplib会阻塞你的主线程
  • 解决httplib阻塞主线程的问题
  • BashController - 面向对象风格使用httplib
      • 自定义controller --- TestController.h文件
      • 使用controller功能的main.cpp文件
      • get请求测试结果
      • post请求测试结果
  • HttpServer.h

说明/前言

         ~~~~~~~~          - cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库, 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 但它仍有一些不足之处, 比如会阻塞你的主线程(cpp-httplib的github地址)
         ~~~~~~~~          - 在不影响cpp-httplib原生代码的基础上, 对其进行了进一步的封装, 且完全和httplib解耦
         ~~~~~~~~          - 封装类为header-only, 直接inlude使用(封装的具体代码在文章最后的HttpServer.h中)
         ~~~~~~~~          - 避免了httplib原生使用方式阻塞主线程的问题
         ~~~~~~~~          - 在支持原生开发方式的基础上, 增加了BashController类,模仿java的spring mvc的开发方式, 以解耦代码, 支持较大型的web项目
         ~~~~~~~~          - 正文主要讲述使用方式, 封装的具体代码在文章最后, 可以直接复制使用
         ~~~~~~~~          - 跨平台, 可在linux和window中使用, 但原生的cpp-httplib对mingw支持不太好, windows下尽量使用msvc编译
         ~~~~~~~~          - 关于httplib更高级的用法, 请直接到(cpp-httplib的github地址)参考
         ~~~~~~~~          - 确保你的编译器最低支持c++14

原生的httplib会阻塞你的主线程


         ~~~~~~~~         cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库. 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 就像下面这样. 但是, 它会阻塞当前线程, 导致while循环中的cout不执行, 看下面的代码

#include 
#include 

using namespace httplib;

using namespace std;

int main()
{
	httplib::Server svr;

	svr.Get("/hi", [](const httplib::Request &, httplib::Response &res) 
	{
	  res.set_content("Hello World!", "text/plain");
	});
	
	cout << "start http server" << endl; 

	// listen函数后, 将阻塞当前线程
	svr.listen("0.0.0.0", 8080);
	
	// 由于主线程被阻塞, 因此下面的代码不会执行 
    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2)); // 睡眠2秒
    }
	
	return 0;
}

执行结果
cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发_第1张图片

         ~~~~~~~~         阻塞的原因是因为, 在httplib的listen函数中有一个while循环, 其中不停的执行accept()函数, 用来监听端口对应的文件描述符, 所以, 只要你开一个子线程, 让listen函数在子线程中执行, 就不会阻塞主线程了
         ~~~~~~~~         但其实你不用太关心原因, 甚至不用操心线程, 因为这些我都给你封装好了 _

解决httplib阻塞主线程的问题


         ~~~~~~~~         把HttpServer.h复制到httplib的同级目录下, 在你的程序中include它, 通过HttpServer.h使用httplib, 就不会阻塞你的主线程了 , 如下:(HttpServer.h代码较多, 放到文章最后了)

         ~~~~~~~~          HttpServer.h的代码在文章的最后, 你可以直接复制使用

#include 
#include 
#include  // HttpServer.h头文件管理httplib::Server对象

using namespace httplib;
using namespace std;

int main()
{
	// 实例化一个HttpServer对象hs, 内部会自动初始化httplib::Server
	HttpServer* hs = new HttpServer("0.0.0.0", 8080);
	
    // 获取hs内部的httplib::Server指针
    Server* svr = hs->getHttplibServer();
    
    // 使用httplib写服务器
	svr->Get("/hi", [](const httplib::Request &, httplib::Response &res) 
	{
	  res.set_content("Hello World!", "text/plain");
	});
   	cout << "start http server" << endl; 
	
	// HttpServer对象调用listenInThread(), 把listen函数放入一个子线程运行
	// 注意, 子线程运行后不能让main函数退出, 因为主线程被关闭后, 所有的子线程会被强制关闭
	hs->listenInThread();

    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2));
    }
    
	return 0;
}

执行结果
cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发_第2张图片

BashController - 面向对象风格使用httplib


        ~~~~~~~         - HttpServer.h不但能自动为你的httplib::Server对象创建子线程, 还提供了一种类似于java spring mvc风格的http服务器开发方式, 以解耦你的代码. 让你的程序更加清晰明了.
        ~~~~~~~         - 你只需要继承BashController, 并按下面的方式编写的你服务器响应代码.

自定义controller — TestController.h文件


自定义controler, 解耦代码

#pragma once

#include "Httpserver.h"

class TestController : public httplib::BaseController
{
    // 注意: 必须重写bind()函数, 并在内部填写映射关系, 否则会抛出异常
    void bind() override
    {
		 // BindController可以绑定httplib中所有参数为(const httplib::Request& req, httplib::Response& resp)的方法
        server->Get("/get", BindController(&TestController::getApi, this));  
        server->Post("/post", BindController(&TestController::postApi, this));
        
    }
    
	// 注意:Request和Response这两个参数必须是引用类型
    void getApi(const httplib::Request& req, httplib::Response& resp)
    {
        
        std::string jsonData = "{\"type\": \"get\", \"code\": 2001}";

        resp.set_content(jsonData, "application/json");
        
    }
    
	// 注意:Request和Response这两个参数必须是引用类型
    void postApi(const httplib::Request& req, httplib::Response& resp)
    {
        std::string data = req.body;
        std::cout << "接收请求数据: \n" << data << std::endl;
        
        std::string jsonData = "{\"type\": \"post\", \"code\": 2001}";
        resp.set_content(jsonData, "application/json");
    }
};

使用controller功能的main.cpp文件


当你把自定义的controllor写到头文件中, 只在main中include它们时, 你的main函数会非常简洁和清晰, 像下面这样:

#include 
#include 


using namespace httplib;
using namespace std;

// #include  // include你自定义的controllor
#include   // include你自定义的controllor


int main()
{
    HttpServer* hs = new HttpServer("0.0.0.0", 8080);

    // MyController() >> hs;		// 初始化MyController对象, 并注入到hs
    TestController() >> hs;		// 初始化TestController对象, 并注入到hs
    
    hs->listenInThread();		// 子线程中启动http server


    while (true)
    {
        cout << "main thread running ..." << endl;
        this_thread::sleep_for(chrono::seconds(2));
    }
}

get请求测试结果


cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发_第3张图片

post请求测试结果

cpp-httplib 避免阻塞主线程, c++封装httplib,httplib面向对象开发_第4张图片

HttpServer.h


         ~~~~~~~~          具体的封装代码, 把它放在httplib.h的同级目录下, 就可以按文章中提到的方式使用他们了

// 将HttpServer.h放到httplib.h文件的同级目录下

#pragma once
#include 
#include 
#include 
#include 
#include 
#include 
#include 

namespace httplib
{
    // 线程池(复制的httplib的线程池, 添加了动态增加线程数量的功能)
    class HttpServerThreadPool : public TaskQueue
    {
    public:
        
        explicit HttpServerThreadPool(size_t n) : shutdown_(false)
        {
            while (n)
            {
                threads_.emplace_back(worker(*this));
                n--;
            }
        }

        void addAThread()
        {
            threads_.emplace_back(worker(*this));
        }
        
        std::size_t getThreadNum()
        {
            return threads_.size();
        }
        
        HttpServerThreadPool(const ThreadPool &) = delete;
        ~HttpServerThreadPool() override = default;

        void enqueue(std::function<void()> fn) override
        {
            {
                std::unique_lock<std::mutex> lock(mutex_);
                jobs_.push_back(std::move(fn));
            }

            cond_.notify_one();
        }

        void shutdown() override
        {
            // Stop all worker threads...
            {
                std::unique_lock<std::mutex> lock(mutex_);
                shutdown_ = true;
            }

            cond_.notify_all();

            // Join...
            for (auto &t : threads_)
            {
                t.join();
            }
        }

    private:
        struct worker
        {
            explicit worker(HttpServerThreadPool &pool) : pool_(pool) {}

            void operator()()
            {
                for (;;)
                {
                    std::function<void()> fn;
                    {
                        std::unique_lock<std::mutex> lock(pool_.mutex_);

                        pool_.cond_.wait(
                            lock, [&]
                            { return !pool_.jobs_.empty() || pool_.shutdown_; });

                        if (pool_.shutdown_ && pool_.jobs_.empty())
                        {
                            break;
                        }

                        fn = std::move(pool_.jobs_.front());
                        pool_.jobs_.pop_front();
                    }

                    assert(true == static_cast<bool>(fn));
                    fn();
                }
            }

            HttpServerThreadPool &pool_;
        };
        friend struct worker;

        std::vector<std::thread> threads_;
        std::list<std::function<void()>> jobs_;

        bool shutdown_;

        std::condition_variable cond_;
        std::mutex mutex_;
    };

    class BaseController;

    // HttpServer, 用于管理httplib::Server对象
    class HttpServer
    {
    private:
        std::string host = "";
        int port = -1;
        static httplib::HttpServerThreadPool *serverPool;
        httplib::Server *server = new httplib::Server();
        static std::atomic<int> poolUseNum;
        int socket_flags;
        void buildServerThreadPool();
        
        friend class BaseController;

    public:
        HttpServer(const HttpServer &) = delete;
        HttpServer() = delete;

        HttpServer(const std::string &_host, int _port, int _socket_flags = 0) : host(std::move(_host)), port(_port), socket_flags(_socket_flags) {}
        HttpServer(int _port, int _socket_flags = 0) : host(std::move("0.0.0.0")), port(_port), socket_flags(_socket_flags) {}

        httplib::Server* getHttplibServer();
        /*
            如果serverPool为null, 将为static serverPool 创建一个新的HttpThreadPool, 并在该线程池中监听
            如果serverPool不为null, 将直接使用static serverPool, 在线程池中执行httplib::Server的listen函数
        */
        auto listenInThread();

        // 在本地监听httplib::Server的listen函数
        void listenInLocal();

        // 释放server指针,如果poolUseNum为0, 也将释放serverPool
        ~HttpServer();
    };

    // BashController, 模仿java spring mvc的开发风格
    class BaseController
    {

    protected:
        // 必须重写bind方法, 在其中绑定具体的请求响应地址和请求响应方法, 否则抛出一个string异常
        virtual void bind()
        {
            throw std::string("must override ", __FUNCTION__);
        }
        httplib::Server *server = nullptr;

        // 绑定函数
        template<class Func, class T>
        auto BindController(Func&& func, T&& obj)
        {
            httplib::Server::Handler handler = std::bind(func, obj, std::placeholders::_1, std::placeholders::_2);
            return handler;
        }


    public:
        BaseController() = default;
        BaseController(BaseController &) = delete;

        void operator>>(httplib::Server *_server)
        {
            server = _server;
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void initToServer(httplib::Server *_server)
        {
            server = _server;
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void operator>>(httplib::HttpServer *_server)
        {

            server = _server->getHttplibServer();
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }

        void initToServer(httplib::HttpServer *_server)
        {

            server = _server->getHttplibServer();
            // print("{} init to server", typeid(*this).name());
            this->bind();
        }



        ~BaseController()
        {
            if (server != nullptr)
            {
                server = nullptr;
            }
            // print("destroy controller");
        }
    };

}

httplib::HttpServerThreadPool *httplib::HttpServer::serverPool = nullptr;
std::atomic_int httplib::HttpServer::poolUseNum(0);

void httplib::HttpServer::buildServerThreadPool()
{
    poolUseNum++;

    if (serverPool == nullptr)
    {
  
        serverPool = new httplib::HttpServerThreadPool{(std::size_t)poolUseNum.load()};
    }

}

httplib::Server *
httplib::HttpServer::getHttplibServer()
{
    return server;
}

auto httplib::HttpServer::listenInThread()
{

    buildServerThreadPool();

    if (serverPool != nullptr)
    {
        serverPool->addAThread();
    }

    std::cout << "listen to " << port << std::endl;
    return serverPool->enqueue(std::bind(&httplib::Server::listen, server, host, port, socket_flags));
}

void httplib::HttpServer::listenInLocal()
{
    server->listen(host, port, socket_flags);
    // print("listen in port {}", port);
}

httplib::HttpServer::~HttpServer()
{

    if (this->server != nullptr)
    {
        delete (server);
    }

    poolUseNum--;
    if (poolUseNum == 0 && serverPool != nullptr)
    {
        delete (serverPool);
        serverPool = nullptr;
    }
}

你可能感兴趣的:(c++)