Httplib库源码粗度
1、Httplib简介
Httplib 是一个轻量级的 HTTP 客户端和服务器库,专注于简洁性和易用性。这个库特别适合那些需要快速集成 HTTP 功能的 C++ 项目。Httplib 提供了高效的 API 来处理 HTTP 请求和响应,同时也支持 HTTPS 和本地 HTTP 服务器的功能。我个人认为它是一个只有头文件的库,优点是方便使用,但是如果再项目中多次包含,再编译的时候相当于编译了多次,这是弊端。
2、值得学习的地方
2.1 std::enable_if和std::remove_extent
template<class T, class… Args>这句时定义一个模版,类型是T,Args…是构造T对象时所需要的构造参数。std::forward的作用是完美转发,保持参数原有的左值和右值属性。当T不是数组时第一个模版函数的返回值是std::unique_ptr。
// 这段代码如金子般耀眼,刚开始看到时,没看明白,哈哈。
template <class T, class... Args>
typename std::enable_if<!std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(Args &&...args) {
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template <class T>
typename std::enable_if<std::is_array<T>::value, std::unique_ptr<T>>::type
make_unique(std::size_t n) {
typedef typename std::remove_extent<T>::type RT;
return std::unique_ptr<T>(new RT[n]);
}
std::enable_if用于实现条件性编译。它可以在模板编程中控制某些模板是否被启用。下面代码是其具体实现,
// 设计这玩意的人太牛逼了
template<bool B, class T = void>
struct enable_if {
// 如果 B 为 true,定义一个类型 `type` 为 `T`
using type = T;
};
// 特化版本,当 B 为 false 时,没有 `type` 成员
template<class T>
struct enable_if<false, T> {};
对于第二个模版函数,std::remove_extent的作用就是T是int[10]的时候,萃取出int。
2.2 自定义hash表键值
直接上代码,这段代码展示了自定义哈希值的方法,以及自定义比较方法,自定义比较方法到是用过,但是第一次见到自定义计算哈希值的,值得学习,一个好的计算哈希值的方法是要把数据均匀的分布到桶上来减少数据冲突的。
struct hash {
size_t operator()(const std::string &key) const {
return hash_core(key.data(), key.size(), 0);
}
size_t hash_core(const char *s, size_t l, size_t h) const {
return (l == 0) ? h
: hash_core(s + 1, l - 1,
// Unsets the 6 high bits of h, therefore no
// overflow happens
(((std::numeric_limits<size_t>::max)() >> 6) &
h * 33) ^
static_cast<unsigned char>(to_lower(*s)));
}
};
using Headers =
std::unordered_multimap<std::string, std::string, hash, detail::case_ignore::equal_to>;
2.3 DataSink类的定义
这段代码个人认为值得学习的地方是DataSink中有data_sink_streambuf的引用,data_sink_streambuf又有DataSink的引用,不知道大家是不是有的时候也会向我一样纠结数据初始化的顺序,其实C++考虑问题从内存的角度去考虑,很多问题就可以想通了,在执行构造函数时先由分配器分配内存,然后再执行构造函数,也就是说分配器分配内存时已经知道这个对象要分配多大的内存,分配好了内存,将指针配置好就可以了。
class DataSink {
public:
DataSink() : os(&sb_), sb_(*this) {}
DataSink(const DataSink &) = delete;
DataSink &operator=(const DataSink &) = delete;
DataSink(DataSink &&) = delete;
DataSink &operator=(DataSink &&) = delete;
std::function<bool(const char *data, size_t data_len)> write;
std::function<bool()> is_writable;
std::function<void()> done;
std::function<void(const Headers &trailer)> done_with_trailer;
std::ostream os;
private:
class data_sink_streambuf final : public std::streambuf {
public:
explicit data_sink_streambuf(DataSink &sink) : sink_(sink) {}
protected:
std::streamsize xsputn(const char *s, std::streamsize n) override {
sink_.write(s, static_cast<size_t>(n));
return n;
}
private:
DataSink &sink_;
};
data_sink_streambuf sb_;
};
2.4 ThreadPool类的定义
下面这段代码其实并不难,但是它实现了一个任务池和线程池一体的任务管理器,并且是线程安全的,这是一个由n个消费线程处理任务的类,只需enqueue任务,线程就会自动的去执行任务,并且每一个任务都有绑定它所属的线程池,并且任务的参数和返回值必须满足std::function<void()>,这个类设计的非常巧妙,值得学习。
class TaskQueue {
public:
TaskQueue() = default;
virtual ~TaskQueue() = default;
virtual bool enqueue(std::function<void()> fn) = 0;
virtual void shutdown() = 0;
virtual void on_idle() {}
};
class ThreadPool final : public TaskQueue {
public:
explicit ThreadPool(size_t n, size_t mqr = 0)
: shutdown_(false), max_queued_requests_(mqr) {
while (n) {
threads_.emplace_back(worker(*this));
n--;
}
}
ThreadPool(const ThreadPool &) = delete;
~ThreadPool() override = default;
bool enqueue(std::function<void()> fn) override {
{
std::unique_lock<std::mutex> lock(mutex_);
if (max_queued_requests_ > 0 && jobs_.size() >= max_queued_requests_) {
return false;
}
jobs_.push_back(std::move(fn));
}
cond_.notify_one();
return true;
}
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(ThreadPool &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 = pool_.jobs_.front();
pool_.jobs_.pop_front();
}
assert(true == static_cast<bool>(fn));
fn();
}
#if defined(CPPHTTPLIB_OPENSSL_SUPPORT) && !defined(OPENSSL_IS_BORINGSSL) && \
!defined(LIBRESSL_VERSION_NUMBER)
OPENSSL_thread_stop();
#endif
}
ThreadPool &pool_;
};
friend struct worker;
std::vector<std::thread> threads_;
std::list<std::function<void()>> jobs_;
bool shutdown_;
size_t max_queued_requests_ = 0;
std::condition_variable cond_;
std::mutex mutex_;
};
3、与Sylar框架相比解析Http请求和响应的区别
不管什么框架在处理这些事情的时候做的事情都是一样的,解析Http请求,响应,然后组装Http response,序列化,返回数据流,接收端反序列化,解析数据,数据清洗。Httplib库在进行Http消息解析的时候使用的是字符串处理,而Sylar框架中使用的是状态机的方法,两种方式不一样。
对比项 | Httplib | Sylar框架 |
---|---|---|
Http消息解析方法 | 字符串处理 | 状态机 |
解析速度 | 较慢 | 快 |
实现困难度 | - | 较难 |
代码维护难度 | - | 难 |
不同协议容错性 | - | 强 |
内存资源使用 | 可能会释放分配内存 | 可以做到每个请求只有一个内存 |
是否支持高并发 | 适用于轻量级的服务器 | 支持 |
框架没有好坏之分,选择什么样的框架要看什么样的场景,即使再简单的框架也有值得学习的东西,不断充实自己,集百家之所长,努力搭建技术护城墙,摆脱35岁危机(不一定能做到,哈哈哈)。