目录
一.前言
httplib库我已经使用了非常多次,同时我也熟悉tcp/ip协议族,另外这个第三方库的源码也相对较少,不到两万行,所以我打算开始阅读这个第三方库的源码,以达到源码级精通这个库。
写这个博客的目的就是记录这个第三方库的源码编写逻辑,将它在我的笔记和大脑中模块化。在阅读过程中如果遇到不熟悉的地方,我也会编写代码来学习并且将代码记录在这个笔记中。
二.版本
#define CPPHTTPLIB_VERSION "0.31.0" // 人看的版本号,主版本为0表示正在快速迭代
#define CPPHTTPLIB_VERSION_NUM "0x001F00" // 代码中比较使用的版本号
在库开头就指出了库的版本。
“0.31.0”中三个数字分别是主版本号,次版本号,修订号。主版本号为0说明库正在快速迭代中,不是非常稳定的正式版本。在我写下这个笔记的时候最新版已经是“0.34.0”了,仅仅过了一个星期。“0.31.0”是点分十进制给人看的版本号,另外“0x001F00”是代码中程序员所使用的版本号。这和ip地址是一个思想,点分十进制的ip地址就是便于人使用。
三.平台兼容性检查
#if defined(_WIN32) && !defined(_WIN64)
#if defined(_MSC_VER)
#pragma message( \
"cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler.")
#else
#warning \
"cpp-httplib doesn't support 32-bit Windows. Please use a 64-bit compiler."
#endif
#elif defined(__SIZEOF_POINTER__) && __SIZEOF_POINTER__ < 8
#warning \
"cpp-httplib doesn't support 32-bit platforms. Please use a 64-bit compiler."
#elif defined(__SIZEOF_SIZE_T__) && __SIZEOF_SIZE_T__ < 8
#warning \
"cpp-httplib doesn't support platforms where size_t is less than 64 bits."
#endif
#ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
#error \
"cpp-httplib doesn't support Windows 8 or lower. Please use Windows 10 or later."
#endif
#endif
这段代码用于平台兼容性检查。其中不建议32位的操作系统使用这个库,会报警告,更是直接不允许Windows10以下的版本使用这个库,直接编译不过去。在《Effective C++》这本书中建议我们不要忽略警告,所以我们在使用这个库时,要避免32位的操作系统。
其中一些注意的点:
- #if defined(某某某) 这样的预处理语句支持多条件语句,例如 && || 等,在单条件下等同于 #ifdef 某某某;
- #pragma message用于在vs编译器下在编译时打印信息;
- #warning用于在其他编译器下打印警告信息;
- 判断指针的大小用于其它操作系统下32位和64位的判断,判断size_t的大小用于进一步判断是不是32位的操作系统,如果是则报警告;
- _WIN32在32位和64位Windows操作系统中都会定义,_WIN64仅在64位Windows操作系统中定义。
我使用上述注意点的代码如下:
Windows环境下代码:
#include <iostream>
using namespace std;
#define TESTMACRO1
#if defined(TESTMACRO1) || defined(TESTMACRO2)
#pragma message("Hello httplib!")
#endif
#ifdef _WIN32
#pragma message("兼容32位")
#endif
#ifdef _WIN64
#pragma message("兼容64位")
#endif
int main()
{
return 0;
}
32位下运行:

64位下运行:

Linux环境下代码:
#include <iostream>
using namespace std;
int main()
{
cout << __SIZEOF_POINTER__ << endl;
cout << __SIZEOF_SIZE_T__ << endl;
return 0;
}
运行结果:

可见我的Linux机器是64位的。
四.配置
库中配置使用宏来和条件编译来实现的。
// 空闲连接保持时间
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND 5
#endif
// 每十毫秒检查长连接们是否超时,新版本默认是长连接的
#ifndef CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND
#define CPPHTTPLIB_KEEPALIVE_TIMEOUT_CHECK_INTERVAL_USECOND 10000
#endif
// 空闲连接的最大数量
#ifndef CPPHTTPLIB_KEEPALIVE_MAX_COUNT
#define CPPHTTPLIB_KEEPALIVE_MAX_COUNT 100
#endif
// TCP三次握手阶段的超时时间
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND
#define CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND 300
#endif
// 微秒
#ifndef CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND
#define CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND 0
#endif
// 超时未读取到数据就断连?
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_READ_TIMEOUT_USECOND 0
#endif
// 写操作的超时控制
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_SERVER_WRITE_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_SECOND 300
#endif
#ifndef CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_READ_TIMEOUT_USECOND 0
#endif
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_SECOND 5
#endif
#ifndef CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND
#define CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND 0
#endif
// 全流程超时控制
#ifndef CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND
#define CPPHTTPLIB_CLIENT_MAX_TIMEOUT_MSECOND 0
#endif
// 客户端大请求优化 expext : 100-continue
#ifndef CPPHTTPLIB_EXPECT_100_THRESHOLD
#define CPPHTTPLIB_EXPECT_100_THRESHOLD 1024
#endif
// 客户端等待服务器响应 100 Continue 的最长时间
#ifndef CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND
#define CPPHTTPLIB_EXPECT_100_TIMEOUT_MSECOND 1000
#endif
// 服务器早期响应等待时长
#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_THRESHOLD (1024 * 1024)
#endif
#ifndef CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND
#define CPPHTTPLIB_WAIT_EARLY_SERVER_RESPONSE_TIMEOUT_MSECOND 50
#endif
// 空闲时间检查间隔
#ifndef CPPHTTPLIB_IDLE_INTERVAL_SECOND
#define CPPHTTPLIB_IDLE_INTERVAL_SECOND 0
#endif
// Windows下特殊处理
#ifndef CPPHTTPLIB_IDLE_INTERVAL_USECOND
#ifdef _WIN32
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 1000
#else
#define CPPHTTPLIB_IDLE_INTERVAL_USECOND 0
#endif
#endif
// uri最大长度
#ifndef CPPHTTPLIB_REQUEST_URI_MAX_LENGTH
#define CPPHTTPLIB_REQUEST_URI_MAX_LENGTH 8192
#endif
// 头部字段最大长度
#ifndef CPPHTTPLIB_HEADER_MAX_LENGTH
#define CPPHTTPLIB_HEADER_MAX_LENGTH 8192
#endif
// 头部字段最大长度
#ifndef CPPHTTPLIB_HEADER_MAX_COUNT
#define CPPHTTPLIB_HEADER_MAX_COUNT 100
#endif
// 限制HTTP 重定向最大跳转次数
#ifndef CPPHTTPLIB_REDIRECT_MAX_COUNT
#define CPPHTTPLIB_REDIRECT_MAX_COUNT 20
#endif
// 限制单次 multipart/form-data 表单请求中文件字段最大数量的宏定义
#ifndef CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT
#define CPPHTTPLIB_MULTIPART_FORM_DATA_FILE_MAX_COUNT 1024
#endif
// 有效数据最大长度
#ifndef CPPHTTPLIB_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_PAYLOAD_MAX_LENGTH (100 * 1024 * 1024) // 100MB
#endif
// 限制 application/x-www-form-urlencoded 格式表单请求体最大长度的宏定义
#ifndef CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH
#define CPPHTTPLIB_FORM_URL_ENCODED_PAYLOAD_MAX_LENGTH 8192
#endif
// 限制HTTP Range 请求中分段范围最大数量
#ifndef CPPHTTPLIB_RANGE_MAX_COUNT
#define CPPHTTPLIB_RANGE_MAX_COUNT 1024
#endif
// 控制 TCP Nagle 算法的宏,减少网络中小数据包的数量
#ifndef CPPHTTPLIB_TCP_NODELAY
#define CPPHTTPLIB_TCP_NODELAY false
#endif
// 控制 IPv6 套接字是否仅绑定 IPv6 地址
#ifndef CPPHTTPLIB_IPV6_V6ONLY
#define CPPHTTPLIB_IPV6_V6ONLY false
#endif
// 应用层接收缓冲区大小
#ifndef CPPHTTPLIB_RECV_BUFSIZ
#define CPPHTTPLIB_RECV_BUFSIZ size_t(16384u)
#endif
// 应用层发送缓冲区大小
#ifndef CPPHTTPLIB_SEND_BUFSIZ
#define CPPHTTPLIB_SEND_BUFSIZ size_t(16384u)
#endif
// 定义压缩 / 解压缩操作缓冲区大小
#ifndef CPPHTTPLIB_COMPRESSION_BUFSIZ
#define CPPHTTPLIB_COMPRESSION_BUFSIZ size_t(16384u)
#endif
#ifndef CPPHTTPLIB_THREAD_POOL_COUNT
#define CPPHTTPLIB_THREAD_POOL_COUNT \
((std::max)(8u, std::thread::hardware_concurrency() > 0 \
? std::thread::hardware_concurrency() - 1 \
: 0))
#endif
// 接收标记位,默认阻塞读取
#ifndef CPPHTTPLIB_RECV_FLAGS
#define CPPHTTPLIB_RECV_FLAGS 0
#endif
// 发送标记位,默认阻塞发送
#ifndef CPPHTTPLIB_SEND_FLAGS
#define CPPHTTPLIB_SEND_FLAGS 0
#endif
// listen的第二个参数,已完成三次握手,但还没有被服务器accept
#ifndef CPPHTTPLIB_LISTEN_BACKLOG
#define CPPHTTPLIB_LISTEN_BACKLOG 5
#endif
// 请求行和请求体一行的最大长度
#ifndef CPPHTTPLIB_MAX_LINE_LENGTH
#define CPPHTTPLIB_MAX_LINE_LENGTH 32768
#endif
这部分宏只有在后续源码中真正使用的时候才能具体叙述它们的作用,我在代码中已经提前做了简单的注释。
代码中用到了hardware_concurrency()函数,我也使用一下。
Windows环境:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
cout << std::thread::hardware_concurrency() << endl;
return 0;
}
运行结果:

我的Windows电脑cpu有12核心。
Linux环境:
#include <iostream>
#include <thread>
using namespace std;
int main()
{
cout << __SIZEOF_POINTER__ << endl;
cout << __SIZEOF_SIZE_T__ << endl;
cout << std::thread::hardware_concurrency() << endl;
return 0;
}
运行结果:

我的Linux机器cpu有2个核心。
五.Windows平台专属头文件和跨平台处理
Windows环境的一致化处理和头文件:
#ifdef _WIN32
#ifndef _CRT_SECURE_NO_WARNINGS
#define _CRT_SECURE_NO_WARNINGS
#endif //_CRT_SECURE_NO_WARNINGS
作用是屏蔽 MSVC 对传统 C 标准库函数的安全性警告。
#ifndef _CRT_NONSTDC_NO_DEPRECATE
#define _CRT_NONSTDC_NO_DEPRECATE
#endif //_CRT_NONSTDC_NO_DEPRECATE
作用是屏蔽 MSVC 对「非标准 C 运行时函数 / 宏」的废弃警告。
#if defined(_MSC_VER)
#if _MSC_VER < 1900
#error Sorry, Visual Studio versions prior to 2015 are not supported
#endif
_MSC_VER值为1900代表是2015版本,这个版本之前的版本不被允许使用。
_MSC_VER的值与版本对应关系如下:
- vs2015:_MSC_VER = 1900
- vs1017:_MSC_VER = 1910 ~ 1916
- vs2019:_MSC_VER = 1920 ~ 1929
- vs2022:_MSC_VER = 1930 ~ 1949
我使用的vs2022,输出的_MSC_VER为1942 。

表示19.42版本的意思。
#pragma comment(lib, "ws2_32.lib")
MSVC 下自动链接 Windows 套接字库 ws2_32.lib,避免网络函数链接错误。
#ifndef _SSIZE_T_DEFINED
using ssize_t = __int64;
#define _SSIZE_T_DEFINED
#endif
#endif // _MSC_VER
由于Windows下vs中没有ssize_t这个类型,而又为了跨平台的统一,所以这里将它定义为64位8字节整形。其它环境下是本身就有的。
#ifndef S_ISREG
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
#endif // S_ISREG
#ifndef S_ISDIR
#define S_ISDIR(m) (((m) & S_IFDIR) == S_IFDIR)
#endif // S_ISDIR
由于Windows下没有S _ISREG和S_ISDIR这 两个宏,所以添加以达到跨平台的要求。
以下是我在Window下自己使用S _ISREG的代码:
#include <iostream>
#include <sys/stat.h>
using namespace std;
#define S_ISREG(m) (((m) & S_IFREG) == S_IFREG)
int main()
{
struct stat st1;
char dirPath1[] = "C:\\Users\\gudan\\Desktop\\selenium";
stat(dirPath1, &st1);
struct stat st2;
char regPath2[] = "C:\\Users\\gudan\\Desktop\\Head First设计模式第二版.pdf";
stat(regPath2, &st2);
if (S_ISREG(st1.st_mode)) cout << "1是普通文件" << endl;
else cout << "1不是普通文件" << endl;
if (S_ISREG(st2.st_mode)) cout << "2是普通文件" << endl;
else cout << "2不是普通文件" << endl;
return 0;
}
运行结果:

Linux环境下使用则不需要自己写这个宏了:
#include <iostream>
#include <sys/stat.h>
using namespace std;
int main()
{
char file[] = "./makefile";
struct stat st1;
stat(file, &st1);
if(S_ISREG(st1.st_mode)) cout << "是普通文件" << endl;
else cout << "不是普通文件" << endl;
char dir[] = "../test02";
struct stat st2;
stat(dir, &st2);
if(S_ISREG(st2.st_mode)) cout << "是普通文件" << endl;
else cout << "不是普通文件" << endl;
return 0;
}
运行结果:

#ifndef NOMINMAX
#define NOMINMAX
#endif // NOMINMAX
禁用 Windows 头文件中的 min()/max() 宏,解决与 C++ 标准库的命名冲突。
#include <io.h> // Windows 底层 I/O 操作,兼容 Unix 风格
#include <winsock2.h> // Windows 套接字编程的基础
#include <ws2tcpip.h> // 扩展了 TCP/IP 相关的网络功能
// Windows 下实现 Unix 域套接字的头文件(本地通信),兼容性兜底逻辑
#if defined(__has_include)
#if __has_include(<afunix.h>)
// afunix.h uses types declared in winsock2.h, so has to be included after it.
#include <afunix.h>
#define CPPHTTPLIB_HAVE_AFUNIX_H 1
#endif
#endif
总结为Windows环境下的套接字编程头文件,后续源码中使用到具体接口时再做具体说明。
#ifndef WSA_FLAG_NO_HANDLE_INHERIT
#define WSA_FLAG_NO_HANDLE_INHERIT 0x80
#endif
创建的套接字句柄不会被子进程继承。在我们创建子进程时,子进程会写时拷贝父进程的资源,这里就是让子进程不要拷贝父进程的套接字句柄。 这与这个库创建子进程的目的有关。
using nfds_t = unsigned long;
using socket_t = SOCKET;
using socklen_t = int;
这里就是为了跨平台的统一。其中nfds_t表示文件描述符的数量,作为poll()函数的参数类型之一。 socket_t就是监听套接字类型,socklen_t表示套接字编程原信息结构体的大小,就是那个sockaddr_in或者其它结构体。
六.总结
为了篇幅不太大,不超过1万字,本篇博客就先到此。下篇博客将会继续介绍Linux环境下的专属头文件,公共头文件,以及后面的源码等等。
4445

被折叠的 条评论
为什么被折叠?



