【C++ 脚手架】cpp-httplib 与 websocketpp 库的介绍与使用

一、cpp-httplib

1.1 介绍

cpp-httplib 是一个基于 C++11 标准的、单文件、跨平台的 HTTP/HTTPS 库,非常适合用于快速搭建 HTTP 客户端或服务端,⽀持同步和异步操作。

特点

  • 易于集成 :它是一个 Header-only 库,使用时只需将 httplib.h 头文件包含到项目中即可,无需复杂的编译和链接配置。

  • 跨平台:支持 Windows、Linux、macOS 等多种操作系统。

  • 支持同步和异步操作

  • 简单易用:API 设计简洁,易于学习和使用。

Http 请求格式:

Http 响应格式:

1.2 安装

bash 复制代码
git clone https://github.com/yhirose/cpp-httplib.git

cd cpp-httplib

cp httplib.h /path/..

# 在代码中直接引用 #include "httplib.h"

1.3 头文件

cpp 复制代码
#include "httplib.h"

1.4 核心接口

  • Request 对象:封装了客户端请求的所有信息(请求方法、请求的资源路径、请求头、请求的查询字符串、请求正文等)。

  • Response 对象:用于构建服务器响应(响应状态码、响应头、响应正文等)。

  • 方法路由表 :为每个 HTTP 方法维护一个路由表(哈希表),key 是一个 正则表达式字符串value 是你为这个请求的 URL 设置的 回调函数

    • 为什么使用正则表达式? 因为正则表达式提供了强大的路由匹配能力,有一些方法的资源路径后可能会携带不同的路径参数,通过正则表达式可以精准匹配格式,用一个回调函数可以同时处理这些不同参数的请求。

    • 捕获结果存入 req.matches[1] 中。

  • 监听listen() 方法启动服务器,持续接收和处理请求。

cpp 复制代码
namespace httplib {

// Range: 表示一个字节范围,用于 Range 请求(断点续传)
// first: 起始位置,second: 结束位置,ssize_t 是有符号大小类型
using Range = std::pair<ssize_t, ssize_t>;
// Ranges: 多个范围段的集合,支持多段 Range 请求
using Ranges = std::vector<Range>;

// MultipartFormData: 表示 multipart/form-data 格式的表单数据
// 用于文件上传等场景
struct MultipartFormData {
    std::string name;           // 表单字段名
    std::string content;        // 字段内容(文件内容或文本)
    std::string filename;       // 文件名
    std::string content_type;   // 内容类型,如 "image/jpeg"
};

// MultipartFormDataItems: 多个表单数据项(支持多文件上传)
using MultipartFormDataItems = std::vector<MultipartFormData>;

// MultipartFormDataMap: 以字段名为键的表单数据映射
using MultipartFormDataMap = std::multimap<std::string, MultipartFormData>;

// Params: URL 查询参数和表单参数的存储结构
using Params = std::multimap<std::string, std::string>;

// Match: 正则表达式匹配结果,存储路径参数捕获组
using Match = std::smatch;

struct Request {
    std::string method;
    std::string path;
    Headers headers;
    std::string body;
    Params params;
    MultipartFormDataMap files;
    Ranges ranges;
    Match matches;
};

struct Response {
    std::string version;
    int status = -1;
    std::string reason;
    Headers headers;
    std::string body;
    // 设置响应体字符串内容和内容类型
    void set_content(const std::string &s, const std::string &content_type);
    // 设置响应头
    void set_header(const std::string &key, const std::string &val);
};

class Server {
    using Handler = std::function<void(const Request &, Response &)>;
    // 设置静态文件挂载点
    // mount_point: URL 路径前缀,如 "/"
    // dir: 本地文件系统目录,如 "./wwwroot"
    // 访问 /index.html 会返回 ./wwwroot/index.html
    bool set_mount_point(const std::string &mount_point,
        const std::string &dir,
        Headers headers = Headers()
    );
    Server &Get(const std::string &pattern, Handler handler);
    Server &Post(const std::string &pattern, Handler handler);
    Server &Put(const std::string &pattern, Handler handler);
    Server &Delete(const std::string &pattern, Handler handler);
    bool listen(const std::string &host, int port);
};

class Client {
    explicit Client(const std::string &host, int port);
    Result Get(const std::string &path, const Headers &headers);
    Result Get(const std::string &path, const Params &params,
    const Headers &headers, Progress progress = nullptr);
    Result Post(const std::string &path, const std::string &body,
    const std::string &content_type);
    Result Post(const std::string &path, const Params &params);
    Result Post(const std::string &path, const Headers &headers,
    const Params &params);
    Result Post(const std::string &path, const MultipartFormDataItems &items);
    Result Put(const std::string &path, const std::string &body,
    const std::string &content_type);
    Result Delete(const std::string &path, const std::string &body,
    const std::string &content_type);
};

class Result {
    operator bool() const { return res_ != nullptr; }
    const Response &value() const { return *res_; }
    Response &value() { return *res_; }
    const Response &operator*() const { return *res_; }
    Response &operator*() { return *res_; }
    const Response *operator->() const { return res_.get(); }
    Response *operator->() { return res_.get(); }
}
}

1.5 使用案例

httpserver.cc

cpp 复制代码
#include <iostream>
#include "httplib.h"

void HelloWorld(const httplib::Request & req, httplib::Response & resp) {
    std::cout << req.method << std::endl;
    std::cout << req.path << std::endl;
    std::cout << req.body << std::endl;
    //打印头部字段信息
    for (auto it : req.headers) {
        std::cout << it.first << " = " << it.second << std::endl;
    }
    //打印查询字符串
    for (auto it : req.params) {
        std::cout << it.first << " = " << it.second << std::endl;
    }
    std::string html_body = "<html><body><h1>Hello World</h1></body></html>";
    // httplib 会自动添加 Content-Length 字段
    resp.set_content(html_body, "text/html");
    resp.status = 200;
}

int main() {

    httplib::Server server;
    server.Get("/hello" , HelloWorld);
    server.Get(R"(/numbers/(\d+))" , [](const httplib::Request & req, httplib::Response & resp){
        std::cout << req.method << std::endl;
        std::cout << req.path << std::endl;
        std::cout << req.body << std::endl;
        //打印头部字段信息
        for (auto it : req.headers) {
            std::cout << it.first << " = " << it.second << std::endl;
        }
        //打印查询字符串
        for (auto it : req.params) {
            std::cout << it.first << " = " << it.second << std::endl;
        }
        // 打印捕捉参数
        std::cout << req.matches[1] << std::endl;
        std::string numb = req.matches[1];
        std::string html_body = "<html><body><h1> " + numb  + "</h1></body></html>";
        resp.set_content(html_body, "text/html");
        resp.status = 200;
    });
    server.listen("0.0.0.0" , 8080);

    return 0;
}

Makefile

makefile 复制代码
server: httpserver.cc
	g++ -std=c++11 -o $@ $^ 
clean:
	rm -f server

二、Websocket

2.1 介绍

WebSocket 是一种在单个 TCP 连接上实现全双工通信的协议。简单说,它让客户端和服务器可以随时互相发送消息,不再像传统 HTTP 那样必须一问一答。

原理:

WebSocket 协议本质上是⼀个基于 TCP 的协议。为了建⽴⼀个 WebSocket 连接,客⼾端浏览器⾸先要向服务器发起⼀个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了⼀些附加头信息,通过这个附加头信息完成握⼿过程并升级协议的过程。

客户端请求

http 复制代码
GET /ws HTTP/1.1
Host: example.com
Connection: Upgrade // 希望升级协议
Upgrade: websocket  // 升级协议格式
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 客户端与服务器通信的钥匙
Sec-WebSocket-Version: 13  // 版本 

服务器响应

http 复制代码
HTTP/1.1 101 Switching Protocols
Connection: Upgrade // 升级协议
Upgrade: websocket  // 升级的协议格式
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 服务端与客户端通讯的钥匙

握手成功后,TCP 连接被复用,后续通信切换为 WebSocket 协议。

2.2 报文格式

  • FIN:WebSocket传输数据以消息为概念单位,⼀个消息有可能由⼀个或多个帧组成,FIN字段为1表⽰末尾帧。

  • RSV1~3:保留字段,只在扩展时使⽤,若未启⽤扩展则应置0,若收到不全为0的数据帧,且未协商扩展则⽴即终⽌连接。

  • opcode:标志当前数据帧的类型:

    • 0x0:继续帧
    • 0x1:文本帧
    • 0x2:二进制帧
    • 0x3-7:保留,暂未使用
    • 0x8:关闭连接
    • 0x9:ping
    • 0xA:pong
    • 其余:保留,暂未使用
  • mask:表⽰Payload数据是否被编码,若为1则必有Mask-Key,⽤于解码Payload数据,仅客⼾端发送给服务端的消息需要设置。

  • Payload len:表示实际载荷数据的长度,假设Payload len = x

    • x 为 0 ~ 126:数据的长度为x字节
    • x 为 126:后续 2 个字节代表⼀个16位的⽆符号整数,该⽆符号整数的值为数据的⻓度
    • x 为 127:后续8个字节代表⼀个64位的⽆符号整数(最⾼位为0),该⽆符号整数的值为数据的⻓度
  • Mask-key:如果 MASK=1,接收方需要对载荷数据进行 XOR 解密,解码规则,payload_data[i] ^= masking_key[i % 4]

  • Payload data:报⽂携带的载荷数据。

2.3 Websocketpp 介绍

WebSocketpp 是⼀个跨平台的开源(BSD许可证)头部专⽤ C++ 库,它允许将 WebSocket 客⼾端和服务器功能集成到 C++ 程序中。在最常⻅的配置中,全功能⽹络 I/O 由 boost 库中的 Asio ⽹络库提供。

2.4 Websocketpp 安装

bash 复制代码
sudo apt-get install libboost-dev libboost-system-dev libwebsocketpp-dev

2.5 Websocketpp 头文件包含

cpp 复制代码
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

2.6 Websocketpp 编译选项

makefile 复制代码
g++ -std=c++11 echo_server.cpp -o echo_server -lboost_system -lpthread

2.7 Websocketpp 常用接口

cpp 复制代码
namespace websocketpp {
    typedef lib::weak_ptr<void> connection_hdl; // weak_ptr
    template <typename config>
    class endpoint : public config::socket_type {
        typedef lib::shared_ptr<lib::asio::steady_timer> timer_ptr;
        typedef typename connection_type::ptr connection_ptr; // shared_ptr
        typedef typename connection_type::message_ptr message_ptr; // shared_ptr
        // 回调函数
        typedef lib::function<void(connection_hdl)> open_handler;
        typedef lib::function<void(connection_hdl)> close_handler;
        typedef lib::function<void(connection_hdl)> http_handler;
        typedef lib::function<void(connection_hdl,message_ptr)> message_handler;

        /* websocketpp::log::alevel::none 禁⽌打印所有⽇志*/
        void set_access_channels(log::level channels); /*设置⽇志打印等级*/
        void clear_access_channels(log::level channels); /*清除指定等级的⽇志*/
        /*设置指定事件的回调函数*/
        void set_open_handler(open_handler h); /*websocket握⼿成功回调处理函数*/
        void set_close_handler(close_handler h); /*websocket连接关闭回调处理函数*/
        void set_message_handler(message_handler h); /*websocket消息回调处理函数*/
        void set_http_handler(http_handler h); /*http请求回调处理函数*/
        /*发送数据接⼝*/
        void send(connection_hdl hdl, std::string& payload, frame::opcode::value op);
        void send(connection_hdl hdl, void* payload, size_t len, frame::opcode::value op);
        /*关闭连接接⼝*/
        void close(connection_hdl hdl, close::status::value code, std::string& reason);
        /*获取connection_hdl 对应连接的connection_ptr*/
        connection_ptr get_con_from_hdl(connection_hdl hdl);
        /*websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度器*/
        void init_asio();
        /*设置是否启⽤地址重⽤*/
        void set_reuse_addr(bool value);
        /*设置endpoint的绑定监听端⼝*/
        void listen(uint16_t port);
        /*对io_service对象的run接⼝封装,⽤于启动服务器*/
        std::size_t run();
        /*websocketpp提供的定时器,以毫秒为单位*/
        timer_ptr set_timer(long duration, timer_handler callback);
    };
    template <typename config>
    class server : public endpoint<connection<config>,config> {
        /*初始化并启动服务端监听连接的accept事件处理*/
        void start_accept();
    };
    namespace message_buffer {
        /*获取websocket请求中的payload数据类型*/
        frame::opcode::value get_opcode();
        /*获取websocket中payload数据*/
        std::string const & get_payload();
    };

    // 日志等级
    namespace log {
        struct alevel {
            static level const none = 0x0;
            static level const connect = 0x1;
            static level const disconnect = 0x2;
            static level const control = 0x4;
            static level const frame_header = 0x8;
            static level const frame_payload = 0x10;
            static level const message_header = 0x20;
            static level const message_payload = 0x40;
            static level const endpoint = 0x80;
            static level const debug_handshake = 0x100;
            static level const debug_close = 0x200;
            static level const devel = 0x400;
            static level const app = 0x800;
            static level const http = 0x1000;
            static level const fail = 0x2000;
            static level const access_core = 0x00003003;
            static level const all = 0xffffffff;
        };
    }

    // 连接对象
    template <typename config>
    class connection : public config::transport_type::transport_con_type , public config::connection_base
    {
        /*发送数据接⼝*/
        error_code send(std::string&payload, frame::opcode::value op=frame::opcode::text);
        /*获取http请求头部*/
        std::string const & get_request_header(std::string const & key)
        /*获取请求正⽂*/
        std::string const & get_request_body();
        /*设置响应状态码*/
        void set_status(http::status_code::value code);
        /*设置http响应正⽂*/
        void set_body(std::string const & value);
        /*添加http响应头部字段*/
        void append_header(std::string const & key, std::string const & val);
        /*获取http请求对象*/
        request_type const & get_request();
        /*获取connection_ptr 对应的 connection_hdl */
        connection_hdl get_handle();
    };

    namespace frame {
        namespace opcode {
            enum value {
                continuation = 0x0,
                text = 0x1,
                binary = 0x2,
                ...
                close = 0x8,
                ping = 0x9,
                pong = 0xA,
                ...
            }
        }
    }
}

2.8 Websocketpp 使用流程

  1. 包含头文件:引入 WebSocket++ 服务端和 ASIO 配置的头文件。

  2. 定义服务端类型:通过模板参数指定使用 ASIO 无加密配置的服务端类型。

  3. 实例化服务端对象 :创建 server 类型的实例 echo_server

  4. 配置日志级别:设置访问日志通道并屏蔽帧载荷日志。

  5. 初始化 ASIO :调用 init_asio() 初始化底层异步 I/O 系统。

  6. 注册消息处理器 :通过 set_message_handler 绑定消息处理函数。

  7. 启用端口复用 :调用 set_reuse_addr(true) 避免重启时端口被占用。

  8. 监听端口 :通过 listen(9002) 绑定端口并开始监听。

  9. 开始接收连接 :调用 start_accept() 启动接受客户端连接的流程。

  10. 启动事件循环 :调用 run() 进入阻塞状态,持续处理网络事件和回调。

2.9 Websocketpp 使用示例

server.cc

cpp 复制代码
#include <iostream>
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

using namespace std;

typedef websocketpp::server<websocketpp::config::asio> websocketsvr;
typedef websocketsvr::message_ptr message_ptr;

// websocket连接成功的回调函数
void OnOpen(websocketsvr *server,websocketpp::connection_hdl hdl){
    cout<<"连接成功"<<endl;
}

// websocket连接成功的回调函数
void OnClose(websocketsvr *server,websocketpp::connection_hdl hdl){
    cout<<"连接关闭"<<endl;
}

// websocket连接收到消息的回调函数
void OnMessage(websocketsvr *server,websocketpp::connection_hdl hdl,message_ptr msg){
    cout << "收到消息" << msg->get_payload() << endl;
    // 收到消息将相同的消息发回给websocket客户端
    server->send(hdl, msg->get_payload(), websocketpp::frame::opcode::text);
}

// websocket连接异常的回调函数
void OnFail(websocketsvr *server,websocketpp::connection_hdl hdl){
    cout<<"连接异常"<<endl;
}

// 处理http请求的回调函数 返回一个html欢迎页面
void OnHttp(websocketsvr *server,websocketpp::connection_hdl hdl){
    cout<<"处理http请求"<<endl;
    websocketsvr::connection_ptr con = server->get_con_from_hdl(hdl);
    std::stringstream ss;
    ss << "<!doctype html><html><head>"
        << "<title>hello websocket</title><body>"
        << "<h1>hello websocketpp</h1>"
        << "</body></head></html>";
    con->set_body(ss.str());
    con->set_status(websocketpp::http::status_code::ok);
}

int main(){
    // 使用websocketpp库创建服务器
    websocketsvr server;
    // 设置websocketpp库的日志级别
    // all表示打印全部级别日志
    // none表示什么日志都不打印
    server.set_access_channels(websocketpp::log::alevel::none);
    /*初始化asio*/
    server.init_asio();
    // 注册http请求的处理函数
    server.set_http_handler(std::bind(&OnHttp, &server, std::placeholders::_1));
    // 注册websocket请求的处理函数
    server.set_open_handler(std::bind(&OnOpen, &server, std::placeholders::_1));
    server.set_close_handler(std::bind(&OnClose, &server,std::placeholders::_1));
    server.set_message_handler(std::bind(&OnMessage,&server,std::placeholders::_1,std::placeholders::_2));
    // 监听8888端口
    server.listen(8888);
    // 开始接收tcp连接
    server.start_accept();
    // 开始运行服务器
    server.run();
    
    return 0;
}

makefile

makefile 复制代码
server: server.cc
	g++ -std=c++17 -o $@ $^ -lboost_system -lpthread
clean:
	rm -f server

websocket 客户端

html 复制代码
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Test Websocket</title>
</head>

<body>
    <input type="text" id="message">
    <button id="submit">提交</button>
    <script>
        // 创建 websocket 实例
        // ws://192.168.51.100:8888
        // 类⽐http
        // ws表⽰websocket协议
        // 192.168.51.100 表⽰服务器地址
        // 8888表⽰服务器绑定的端⼝
        let websocket = new WebSocket("ws://192.168.51.100:8888");
        // 处理连接打开的回调函数
        websocket.onopen = function () {
            console.log("连接建⽴");
        }
        // 处理收到消息的回调函数
        // 控制台打印消息
        websocket.onmessage = function (e) {
            console.log("收到消息: " + e.data);
        }
        // 处理连接异常的回调函数
        websocket.onerror = function () {
            console.log("连接异常");
        }
        // 处理连接关闭的回调函数
        websocket.onclose = function () {
            console.log("连接关闭");
        }
        // 实现点击按钮后, 通过 websocket实例 向服务器发送请求
        let input = document.querySelector('#message');
        let button = document.querySelector('#submit');
        button.onclick = function () {
            console.log("发送消息: " + input.value);
            websocket.send(input.value);
        }
    </script>
</body>

</html>
相关推荐
故事和你912 小时前
洛谷-入门6-函数与结构体
开发语言·数据结构·c++·算法·动态规划
老四啊laosi2 小时前
[C++进阶] 21. 红黑树
c++·红黑树
TON_G-T2 小时前
WebSocket实现长链接
网络·websocket·网络协议
2501_921649492 小时前
WebSocket 金融实时行情推送 API 实战解析:低延迟、高可用架构设计与落地
websocket·网络协议·金融·node.js
像素猎人2 小时前
蓝桥杯OJ716【限定第一步和最后一步爬台阶的经典例题】【动态规划】
c++·算法·动态规划
Q741_1472 小时前
每日一题 力扣 3474. 字典序最小的生成字符串 贪心 字符串 C++ 题解
c++·算法·leetcode·贪心
wqww_12 小时前
Java 前后端 WebSocket 完整实现
java·开发语言·websocket
小此方2 小时前
Re:从零开始的 C++ STL篇(九)AVL树太“较真”,红黑树更“现实”:一文讲透工程中的平衡之道
开发语言·数据结构·c++·算法·stl
进击的荆棘2 小时前
C++起始之路——二叉搜索树
数据结构·c++·stl