目录
- 说明/前言
- 原生的httplib会阻塞你的主线程
- 解决httplib阻塞主线程的问题
- BashController - 面向对象风格使用httplib
- 自定义controller --- MyController.h文件
- 自定义controller --- TestController.h文件
- 使用controller功能的main.cpp文件
- 测试结果
- 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编译
~~~~~~~~
- 确保你的编译器最低支持c++11
原生的httplib会阻塞你的主线程
~~~~~~~~
cpp-httplib是一个简单易用, 跨平台, 开发快速, 仅头文件的c++ http库. 只需要把一个头文件包含到你的代码里, 就可以用很少的代码完成一个http server, 就像下面这样. 但是, 它会阻塞当前线程, 导致while循环中的cout不执行, 看下面的代码
#include <httplib.h>
#include <chrono>
using namespace httplib;
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;
svr.listen("0.0.0.0", 8080);
while (true)
{
cout << "main thread running ..." << endl;
this_thread::sleep_for(chrono::seconds(2));
}
return 0;
}
执行结果
~~~~~~~~
阻塞的原因是因为, 在httplib的listen函数中有一个while循环, 其中不停的执行accept()函数, 用来监听端口对应的文件描述符, 所以, 只要你开一个子线程, 让listen函数在子线程中执行, 就不会阻塞主线程了
~~~~~~~~
但其实你不用太关心原因, 甚至不用操心线程, 因为这些我都给你封装好了 _
解决httplib阻塞主线程的问题
~~~~~~~~
把HttpServer.h复制到httplib的同级目录下, 在你的程序中include它, 通过HttpServer.h使用httplib, 就不会阻塞你的主线程了 , 如下:(HttpServer.h代码较多, 放到文章最后了)
~~~~~~~~
HttpServer.h的代码在文章的最后, 你可以直接复制使用
#include <httplib.h>
#include <chrono>
#include <HttpServer.h>
using namespace httplib;
using namespace std;
int main()
{
HttpServer* hs = new HttpServer("0.0.0.0", 8080);
Server* svr = hs->getHttplibServer();
svr->Get("/hi", [](const httplib::Request &, httplib::Response &res)
{
res.set_content("Hello World!", "text/plain");
});
cout << "start http server" << endl;
hs->listenInThread();
while (true)
{
cout << "main thread running ..." << endl;
this_thread::sleep_for(chrono::seconds(2));
}
return 0;
}
执行结果
BashController - 面向对象风格使用httplib
~~~~~~~
- HttpServer.h不但能自动为你的httplib::Server对象创建子线程, 还提供了一种类似于java spring mvc风格的http服务器开发方式, 以解耦你的代码. 让你的程序更加清晰明了.
~~~~~~~
- 你只需要继承BashController, 并按下面的方式编写的你服务器响应代码.
自定义controller — MyController.h文件
继承BashController, 重写bind函数, 像下面这样
#pragma once
#include "HttpServer.h"
class MyController : public httplib::BaseController
{
void bind() override
{
server->Get("/hi", BIND_CONTROLLER(MyController::get_helloWorld));
}
void get_helloWorld(const httplib::Request& req, httplib::Response& resp)
{
std::string jsonData = "{\"status\": true, \"code\": 2023}";
resp.set_content(jsonData, "Application/json");
}
};
自定义controller — TestController.h文件
另一个自定义的controler, 写法和MyController完全相同
#pragma once
#include <iostream>
#include "HttpServer.h"
class TestController : public httplib::BaseController
{
void bind() override
{
server->Get("/test", BIND_CONTROLLER(TestController::hello));
}
void hello(const httplib::Request& req, httplib::Response& resp)
{
std::string jsonData = "{\"status\": true, \"code\": 2023}";
resp.set_content(jsonData, "Application/json");
}
};
使用controller功能的main.cpp文件
当你把自定义的controllor写到头文件中, 只在main中include它们时, 你的main函数会非常简洁和清晰, 像下面这样:
#include <HttpServer.h>
#include <chrono>
using namespace httplib;
using namespace std;
#include <MyController.h>
#include <TestController.h>
int main()
{
HttpServer* hs = new HttpServer("0.0.0.0", 8080);
MyController() >> hs;
TestController() >> hs;
hs->listenInThread();
while (true)
{
cout << "main thread running ..." << endl;
this_thread::sleep_for(chrono::seconds(2));
}
}
测试结果
HttpServer.h
~~~~~~~~
具体的封装代码, 把它放在httplib.h的同级目录下, 就可以按文章中提到的方式使用他们了
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <unordered_map>
#include <functional>
#include <typeinfo>
#include <httplib.h>
#define BIND_CONTROLLER(__selector__) \
std::bind(&__selector__, this, std::placeholders::_1, std::placeholders::_2)
namespace 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
{
{
std::unique_lock<std::mutex> lock(mutex_);
shutdown_ = true;
}
cond_.notify_all();
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;
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();
auto listenInThread();
void listenInLocal();
~HttpServer();
};
class BaseController
{
protected:
virtual void bind()
{
throw std::string("must override ", __FUNCTION__);
}
httplib::Server *server = nullptr;
public:
BaseController() = default;
BaseController(BaseController &) = delete;
void operator>>(httplib::Server *_server)
{
server = _server;
this->bind();
}
void initToServer(httplib::Server *_server)
{
server = _server;
this->bind();
}
void operator>>(httplib::HttpServer *_server)
{
server = _server->getHttplibServer();
this->bind();
}
void initToServer(httplib::HttpServer *_server)
{
server = _server->getHttplibServer();
this->bind();
}
~BaseController()
{
if (server != nullptr)
{
server = nullptr;
}
}
};
}
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);
}
httplib::HttpServer::~HttpServer()
{
if (this->server != nullptr)
{
delete (server);
}
poolUseNum--;
if (poolUseNum == 0 && serverPool != nullptr)
{
delete (serverPool);
serverPool = nullptr;
}
}
``
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)