【即时通讯系统】环境搭建5——httplib,websocketpp

目录

一.httplib

1.1.httplib库简介

1.2.httplib内部核心结构讲解

1.2.1.httplib请求类

1.2.3.httplib响应类

1.2.3.Server类

1.2.4.Client类

1.3.httplib库搭建简单服务器

1.3.1.示例1

1.3.2.示例2

二.websocketpp

2.1.websocket协议

2.2.安装websocketpp

2.3.使用示例

2.3.1.示例1

2.3.2.示例2

2.3.3.示例3


一.httplib

话不多说,我们直接安装httplib库

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

它的使用过程非常简单:只需在代码中包含httplib.h文件即可!也就是下面这个文件,我们把它单独拿出来放到我们这个项目专门存放第三方库的地方

接下来如果想要使用httplib库,那么只需要在文件里面包含这个头文件即可。

1.1.httplib库简介

httplib 是一个轻量级的 C++ 库,用于创建 HTTP 服务器和客户端。它的设计简单而灵活,非常容易设置。只需在代码中包含 httplib.h 文件即可!

使得开发者能够在自己的 C++ 项目中轻松地实现基本的 HTTP 通信功能。

特性 说明
简单易用的接口 httplib 提供了简单易用的接口,使得创建 HTTP 服务器和客户端变得非常容易。通过少量的代码,你就能够建立起一个功能完善的 HTTP 服务器或客户端。
支持主要 HTTP 方法 httplib 支持主要的 HTTP 方法,包括 GET、POST、PUT、DELETE 等,让你能够处理各种类型的 HTTP 请求。
灵活的路由处理 使用 httplib,你可以轻松地定义路由和处理函数,根据不同的 URL 请求调用相应的处理函数。这使得构建 RESTful API 或者 Web 应用程序变得非常方便。
支持静态文件服务 httplib 还提供了静态文件服务的功能,你可以将指定目录下的文件直接暴露给客户端访问,而无需额外处理。
简洁的代码库 httplib 的代码库非常简洁,没有过多的依赖,易于集成到你的项目中。这使得它成为了许多开发者选择的首选 HTTP 库之一。
活跃的开发和社区支持 httplib 的开发仍在持续进行中,它的代码库得到了广大开发者社区的支持和贡献,因此可以期待它会持续改进和更新。

总的来说,httplib 是一个功能强大且易于使用的 C++ HTTP 库,适用于各种类型的项目,无论是小型工具还是大型 Web 应用程序。通过它,你可以快速地搭建起自己的 HTTP 服务器或客户端,实现各种网络通信需求。

1.2.httplib内部核心结构讲解

1.2.1.httplib请求类

还记得HTTP请求报头吗

httplib中封装了一个结构------Request,用来保存客户端/浏览器发送的HTTP请求的内容。当服务器获取到客户端的请求,httplib会将请求字符串解析成Request

cpp 复制代码
struct Request {
        //成员变量
        std::string method;//请求方法
        std::string path;//请求资源路径------URL
        Headers headers;//头部字段
        std::string body;//正文部分
        std::string version;//协议版本
        Params params;//查询字符串
        MultipartFormDataMap files;//客户端上传文件的信息
        Ranges ranges;//实现断点续传的请求区间
        //提供的API
        //查询头部字段key是否存在
        bool has_header(const char *key) const;
        //获取头部字段的value
        std::string get_header_value(const char *key, size_t id = 0) const;
        //设置头部字段
        void set_header(const char *key, const char *val);
        //查询客户端是否上传该文件
        bool has_file(const char *key) const;
        //获取文件信息
        MultipartFormData get_file_value(const char *key) const;
   };

其中有一个文件结构体和文件结构体数组

cpp 复制代码
struct MultipartFormData {
        std::string name;//字段名称
        std::string content;//文件内容
        std::string filename;//文件名
        std::string content_type;//文件类型
};
//文件结构体数组
using MultipartFormDataItems = std::vector<MultipartFormData>;

1.2.3.httplib响应类

大家应该都记得http响应报头吧

httplib响应类是Responce,需要用户手动填补,httplib返回响应时,会将Responce组织成字符串返回给客户端

cpp 复制代码
struct Response {
    std::string version;   //协议版本号,默认时http1.1
    int status = -1;       //响应状态码,
    std::string reason;    
    Headers headers;       //响应报头
    std::string body;      //响应正文
    std::string location; // 重定向位置
//两个比较重要的API
//以key-val将相应的字段设定到响应报头中
void set_header(const char *key, const char *val);
//设置正文------可以设置类型
void set_content(const std::string &s, const char *content_type);
};

1.2.3.Server类

Server类就是httplib中用于搭建服务器的类

cpp 复制代码
class Server {
        //Handler一个函数指针,它的返回值为void,参数是Request,和Response
        using Handler = std::function<void(const Request &, Response &)>;
        //Handlers是一个映射表,将请求资源和处理函数映射在一起
        using Handlers = std::vector<std::pair<std::regex, Handler>>;
        //将Get方法的请求资源与处理函数加载到Handlers表中
        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 &Patch(const std::string &pattern, Handler handler);  
        Server &Delete(const std::string &pattern, Handler handler);
        Server &Options(const std::string &pattern, Handler handler);
        //线程池
        std::function<TaskQueue *(void)> new_task_queue;
        //搭建并启动http
        bool listen(const char *host, int port, int socket_flags = 0);
 };

首先,Handler是一个回调的函数指针。Handlers是一个映射表。std::regex是正则表达式,用于匹配请求资源,即URL中的path。Handlers将请求的资源和处理函数进行映射。当服务器收到一个请求,解析Request,获取客户端请求的资源路径,在Handlers表中找是否存在映射关系,有则调用对应的Handler回调函数处理请求,无则返回404(请求资源不存在)。

上面的Get,Post,Put,Patch,Delete,Options都是往Handlers表中设置映射关系

请求方法,请求资源和处理函数都是强相关的,如上表:

  1. 只有http请求的请求方法是GET,且请求的资源是/Hello/a,服务器才会调用echoHelloA处理请求
  2. 只要请求方法和请求资源在Handlers表中找不到映射的处理函数,http服务器就会返回404的http响应,表示请求资源不存在
  3. Get("/Hello/a", echoHelloA):将请求方法为GET,请求资源为/Hello/a,与函数echoHelloA注册到Handlers表中

线程池的工作:

  1. 当服务器接收到一个http请求,会将该http请求放入线程池,线程池的线程会调用相应的函数解析http请求,形成一个Request对象
  2. 在Handlers映射表中查找有无对应请求方法和请求资源的处理函数,有则调用处理函数,并构建http响应
  3. 处理函数调用完后,将Responce构建成http响应,并将构建好的http响应返回给客户端

1.2.4.Client类

cpp 复制代码
class Client {
    //构造一个客户端对象,传入服务器Ip地址和端口
    Client(const std::string &host, int port);
    //向服务器发送GET请求
    Result Get(const char *path, const Headers &headers);
    //向服务器发送Post请求
    //path是路径,body是request请求路径
    //content_length是正文大小
    //content_type是正文的类型
    Result Post(const char *path, const char *body, size_t content_length,
                const char *content_type);
        
    //以Post方法上传文件
    Result Post(const char *path, const MultipartFormDataItems &items);
}

1.3.httplib库搭建简单服务器

1.3.1.示例1

搭建服务器:

  1. 定义server对象
  2. 在Handlers表中注册相应请求方法和请求资源的处理函数
  3. listen绑定IP地址和端口号,启动服务器,监听连接和请求
cpp 复制代码
// 包含httplib库头文件,用于搭建HTTP服务器
#include <iostream>
#include "httplib.h"

// 使用httplib命名空间,简化代码
using namespace httplib;

// 处理GET /hi 请求的函数
// 参数req: 客户端请求对象,包含请求信息
// 参数rsp: 服务器响应对象,用于设置响应内容
void Hello(const Request &req, Response &rsp)
{   
    // 设置响应内容为 "Hello World!",内容类型为纯文本
    rsp.set_content("Hello World!", "text/plain");
    // 设置HTTP状态码为200(成功)
    rsp.status = 200;
}

// 处理GET /numbers/数字 请求的函数,使用正则表达式捕获路径中的数字
// 参数req: 请求对象,req.matches 保存正则捕获结果
// 参数rsp: 响应对象
void Numbers(const Request &req, Response &rsp)
{   
    // req.matches[0] 保存完整匹配的路径,req.matches[1] 保存第一个捕获组(即数字部分)
    auto num = req.matches[1];
    // 将捕获到的数字作为响应内容返回
    rsp.set_content(num, "text/plain");
    rsp.status = 200;
}

// 处理POST /multipart 请求的函数,用于处理文件上传(multipart/form-data)
void Multipart(const Request &req, Response &rsp)
{
    // 检查请求中是否包含名为 "file" 的文件字段
    auto ret = req.has_file("file");
    if(ret == false)
    {
        // 如果没有文件,输出提示信息,并返回400错误(客户端请求错误)
        std::cout << "not file upload\n";
        rsp.status = 400;
        return;
    }
    // 获取名为 "file" 的文件字段内容,返回一个 MultipartFile 对象
    const auto& file = req.get_file_value("file");
    // 清空响应正文
    rsp.body.clear();
    // 在响应正文中添加文件名和文件内容,用换行分隔
    rsp.body = file.filename; // 文件名称
    rsp.body += "\n";
    rsp.body += file.content; // 文件内容
    // 设置响应头 Content-Type 为纯文本
    rsp.set_header("Content-Type", "text/plain");
    rsp.status = 200;
    return;
}

int main()
{
    // 创建一个Server对象,用于搭建HTTP服务器
    httplib::Server sever;

    // 注册路由:GET /hi 映射到 Hello 函数
    sever.Get("/hi", Hello);

    // 注册路由:GET /numbers/数字 使用正则表达式匹配,映射到 Numbers 函数
    // R"(/numbers/(\d+))" 表示一个原始字符串,匹配 /numbers/ 后跟一个或多个数字
    sever.Get(R"(/numbers/(\d+))", Numbers);

    // 注册路由:POST /multipart 映射到 Multipart 函数,用于处理文件上传
    sever.Post("/multipart", Multipart);

    // 启动服务器,监听所有网络接口(0.0.0.0)的9090端口
    sever.listen("0.0.0.0", 9090);

    return 0;
}

以下是该程序的执行过程详解:

创建HTTP服务器:

  • 在main函数内部,首先创建了一个httplib::Server对象。这个对象代表了一个HTTP服务器,它将负责监听和处理来自客户端的请求。

设置路由和处理函数:程序通过调用server.Get和server.Post方法为HTTP服务器设置了路由和处理函数。

  • 对于/hello路由,当收到GET请求时,服务器将返回"Hello World!"作为响应。
  • 对于/number/(\d+)路由,当收到GET请求并匹配到一个数字时,服务器将返回该数字作为响应。这里使用了正则表达式\d+来匹配一个或多个数字。

启动服务器:

  • 程序通过调用server.listen("0.0.0.0", 8080)来启动服务器。这里,"0.0.0.0"表示服务器将监听所有可用的网络接口,8080是服务器监听的端口号。
  • listen函数将阻塞当前线程,等待客户端的连接和请求。这意味着在服务器运行期间,程序将停留在这一步,直到服务器被外部方式关闭(如通过发送终止信号或手动停止程序)。

处理客户端请求:

  • 当有客户端向服务器发送请求时**(例如,通过浏览器访问http://<服务器IP>:8080/hello)**,服务器将根据请求的URL和方法(GET、POST等)找到相应的处理函数。
  • 处理函数将被调用,并接收两个参数:一个表示请求信息的httplib::Request对象和一个用于构建响应的httplib::Response对象。
  • 处理函数根据请求的内容生成响应,并通过修改httplib::Response对象来设置响应的内容、状态码和头部信息。
  • 最后,服务器将响应发送回客户端。

编译

bash 复制代码
g++ -o server main.cc -std=c++14 -lpthread

接下来就能开始我们的测试了

我们可以来试一试效果

这个程序是一个基于httplib库的简单HTTP服务器,它能够处理特定路由上的GET和POST请求。以下是它能处理的客户端请求类型:

GET请求:

  1. /hello:当客户端向服务器发送GET请求到/hello路由时,服务器将返回文本内容"Hello World!"。
  2. /number/(\d+):这是一个带有正则表达式的路由,用于匹配形如/number/123的URL,其中123可以是任意数字。当匹配成功时,服务器将返回该数字作为文本内容。

需要注意的是,这个程序只能处理上述特定路由上的请求。如果客户端发送请求到未定义的路由,服务器将不会返回任何响应(或者根据httplib的默认行为,可能会返回一个404错误)。

1.3.2.示例2

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

// 程序入口函数
int main()
{
    // 1. 实例化服务器对象,创建一个HTTP服务器实例
    httplib::Server server;
    
    // 2. 注册回调函数,为GET请求的路径"/hi"设置处理逻辑
    // 回调函数类型为 void(const httplib::Request &, httplib::Response &)
    server.Get("/hi", [](const httplib::Request &req, httplib::Response &rsp){
        // 打印请求的方法(例如 GET、POST等)
        std::cout << req.method << std::endl;
        // 打印请求的路径(例如 /hi)
        std::cout << req.path << std::endl;
        // 遍历并打印所有的HTTP请求头
        for (auto it : req.headers) {
            std::cout << it.first << ": " << it.second << std::endl;
        }
        // 构造一个简单的HTML响应体
        std::string body = "<html><body><h1>Hello World</h1></body></html>";
        // 设置响应的内容,同时指定内容类型为 text/html
        rsp.set_content(body, "text/html");
        // 显式设置HTTP状态码为200(成功),虽然set_content可能已默认设置,但此处明确指定
        rsp.status = 200;
    });
    
    // 这行是注释,提示也可以注册POST请求的处理函数
    // server.Post()
    
    // 3. 启动服务器,监听所有网络接口(0.0.0.0)上的8070端口
    // 服务器将在此端口上等待客户端连接
    server.listen("0.0.0.0", 8070);
    
    // 服务器启动后,listen会阻塞当前线程,直到服务器关闭,因此正常情况下不会执行到这里
    return 0;
}

很完美

二.websocketpp

2.1.websocket协议

我们有了HTTP协议,为什么还需要websocket协议

HTTP协议和websocket协议的对比

HTTP 协议 ,就像寄信

  1. 你(客户端) 想问问朋友(服务器)今晚看不看电影。你写了一封信(HTTP 请求),寄出去。

  2. 朋友(服务器) 收到信后,给你回一封信(HTTP 响应),说"看,7点见"。

  3. 在这个过程里,朋友不能主动给你说话。如果朋友突然想起来要改时间到8点,他没法主动告诉你,必须等你再写一封信去问。

  4. 如果你特别想知道有没有新消息,就只能不停地写信去问(这就是轮询):"改时间了吗?""改时间了吗?"。这样很累(浪费资源),而且信在路上有时间差,你得到的信息也未必是实时的。

WebSocket 则更像是打电话

  1. 你先拨通朋友的电话,说"喂?"。

  2. 朋友接起来,说"喂,我在!"。

  3. 好了,电话打通了(连接建立)。接下来,你们俩谁都可以随时开口说话。

  4. 你可以说"今晚7点看电影?"

  5. 朋友可以马上回"好啊",过了几秒,朋友又主动说"哎,改8点吧,我加个班。"。

  6. 你听到后回复"没问题"。

你看,WebSocket 的核心就是:在同一个连接上,双方都可以随时、主动地向对方发送数据。这解决了 HTTP 协议"一来一回、服务器被动"的痛点。

2.2.安装websocketpp

websocketpp 是一个 C++ 的库,它的作用就是帮你用 C++ 语言快速实现 WebSocket 的功能。

我们刚才聊到 WebSocket 是一种"协议",就像是打电话的规则。而 websocketpp 就是一套已经写好的 C++ 工具,让你不用关心底层那些复杂的握手、数据帧解析、连接管理等细节,只需要调用它提供的简单接口,就能让你的 C++ 程序成为一个 WebSocket 服务器或者客户端。

用类比来理解

  • WebSocket 协议:就是"打电话"的规则(比如先拨号、接通后怎么说、怎么挂断)。

  • 浏览器中的 WebSocket API :浏览器已经内置了打电话的工具,你只要写几行 JavaScript(new WebSocket())就能用。

  • websocketpp:就是给 C++ 程序装上的"电话机"。你的 C++ 程序本来只能处理文件、计算数据,装上这个库之后,它就能和浏览器或者其他 WebSocket 客户端实时通话了。

websocketpp 的特点

  1. 为 C++ 而生:如果你用 C++ 写后端服务(比如游戏服务器、高性能推送服务),就可以用这个库来实现 WebSocket 通信。

  2. 跨平台:Windows、Linux、macOS 都能用。

  3. 仅头文件(Header-only):意思是大部分代码都写在头文件里,你只要在项目里包含它的头文件,就可以直接使用,不需要编译成单独的库(配置起来相对简单)。

  4. 支持 TLS/SSL :可以加密通信,就像 WebSocket 的安全版本 wss://

  5. 灵活 :它提供了不同的传输方式(比如基于 ASIO 或者自定义的),你可以根据需要选择。ASIO 是一个很流行的网络编程库,websocketpp 可以配合它使用。

我们来安装一下

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

安装完毕后,若在 /usr/include 下有了 websocketpp目录就表示安装成功了。

cpp 复制代码
ls /usr/include/websocketpp/

2.3.使用示例

2.3.1.示例1

server.cpp

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

// 定义一个 WebSocket 服务器类型,使用上述配置(无 TLS,基于 Asio)
typedef websocketpp::server<websocketpp::config::asio> server;

// 全局 server 指针,用于在回调函数中访问 server 对象
// 因为普通回调函数无法直接访问 main 中的局部变量,所以用全局指针传递
server* g_server = nullptr;

// 消息处理回调函数(普通函数)
// 当服务器收到客户端发来的消息时,该函数会被自动调用
// 参数 hdl 是连接句柄,用于标识具体的客户端连接
// 参数 msg 是指向消息对象的指针,包含消息内容、类型等信息
void on_message(websocketpp::connection_hdl hdl, server::message_ptr msg) 
{
    // 在控制台打印收到的消息内容(get_payload() 返回消息的文本内容)
    std::cout << "服务端收到消息: " << msg->get_payload() << std::endl;

    // 定义一个错误码变量,用于接收发送操作的结果状态
    websocketpp::lib::error_code ec;
    // 通过全局 server 指针调用 send 方法,将收到的消息原样发回给客户端
    // 参数:连接句柄、消息内容、消息类型(文本/二进制)、错误码引用
    g_server->send(hdl, msg->get_payload(), msg->get_opcode(), ec);
    // 检查发送是否出错
    if (ec) {
        // 如果出错,打印错误信息
        std::cout << "发送消息失败: " << ec.message() << std::endl;
    }
}

// 程序主函数
int main() {
    // 创建一个 server 对象(名为 echo_server),代表 WebSocket 服务器实例
    server echo_server;
    // 将全局指针指向这个 server 对象,以便回调函数能使用它
    g_server = &echo_server;  // 设置全局指针

    // 关闭日志输出(可选)
    // set_access_channels 设置要输出的日志类型,alevel::none 表示不输出任何访问日志
    echo_server.set_access_channels(websocketpp::log::alevel::none);
    // clear_access_channels 清除之前设置的日志类型,alevel::all 表示所有类型,这里全部清除
    echo_server.clear_access_channels(websocketpp::log::alevel::all);

    // 初始化 Asio 网络库
    // 定义一个错误码变量,用于接收初始化结果
    websocketpp::lib::error_code ec;
    // 调用 init_asio(ec) 进行初始化,错误信息会保存在 ec 中
    echo_server.init_asio(ec);
    // 检查初始化是否成功
    if (ec) {
        std::cout << "init_asio 失败: " << ec.message() << std::endl;
        return 1;  // 初始化失败,退出程序
    }

    // 设置消息处理函数,将上面定义的 on_message 函数注册为消息回调
    // 当有客户端消息到达时,会自动调用 on_message
    echo_server.set_message_handler(&on_message);

    // 让服务器监听 8002 端口
    // listen 第二个参数是错误码引用,用于返回监听操作的结果
    echo_server.listen(8002, ec);
    if (ec) 
    {
        std::cout << "listen 失败: " << ec.message() << std::endl;
        return 1;  // 监听失败,退出程序
    }

    // 开始接受客户端连接请求
    // start_accept 使服务器进入接受连接的状态,但不会阻塞,真正的等待在 run() 中进行
    echo_server.start_accept(ec);
    if (ec) 
    {
        std::cout << "start_accept 失败: " << ec.message() << std::endl;
        return 1;  // 启动接受失败,退出程序
    }

    // 打印提示信息,表示服务器已启动
    std::cout << "WebSocket 服务端启动,监听端口 8002" << std::endl;

    // 启动事件循环(阻塞)
    // run() 会一直运行,处理网络事件(连接、消息、关闭等),直到服务器被停止
    echo_server.run();

    // 程序正常退出(实际上 run() 通常不会返回,除非服务器主动停止)
    return 0;
}

client.cpp

cpp 复制代码
// 包含 websocketpp 库中基于 Asio 且不启用 TLS 的客户端配置文件
#include <websocketpp/config/asio_no_tls.hpp>
// 包含 websocketpp 的客户端核心头文件
#include <websocketpp/client.hpp>
// 包含标准输入输出流,用于打印信息
#include <iostream>

// 定义一个 WebSocket 客户端类型,使用上述配置(无 TLS,基于 Asio)
typedef websocketpp::client<websocketpp::config::asio> client;

// 全局 client 指针,用于在回调函数中访问 client 对象
// 因为普通回调函数无法直接访问 main 中的局部变量,所以用全局指针传递
client* g_client = nullptr;

// 连接打开回调函数
// 当客户端与服务器成功建立连接时,该函数会被自动调用
// 参数 hdl 是连接句柄,用于标识当前连接
void on_open(websocketpp::connection_hdl hdl) 
{
    // 在控制台打印提示信息
    std::cout << "连接已打开,发送消息..." << std::endl;
    // 定义一个错误码变量,用于接收发送操作的结果状态
    websocketpp::lib::error_code ec;
    // 通过全局 client 指针调用 send 方法,向服务器发送一条文本消息
    // 参数:连接句柄、消息内容、消息类型(text 表示文本)、错误码引用
    g_client->send(hdl, "Hello, server!", websocketpp::frame::opcode::text, ec);
    // 检查发送是否出错
    if (ec) {
        // 如果出错,打印错误信息
        std::cout << "发送失败: " << ec.message() << std::endl;
    }
}

// 收到消息回调函数
// 当客户端收到服务器发来的消息时,该函数会被自动调用
// 参数 hdl 是连接句柄,参数 msg 是指向消息对象的指针,包含消息内容、类型等信息
void on_message(websocketpp::connection_hdl hdl, client::message_ptr msg) 
{
    // 在控制台打印收到的回复消息内容
    std::cout << "客户端收到回复: " << msg->get_payload() << std::endl;
    // 定义一个错误码变量,用于接收关闭操作的结果状态
    websocketpp::lib::error_code ec;
    // 通过全局 client 指针调用 close 方法,主动关闭与服务器的连接
    // 参数:连接句柄、关闭状态码(normal 表示正常关闭)、关闭理由、错误码引用
    g_client->close(hdl, websocketpp::close::status::normal, "Goodbye", ec);
    // 检查关闭是否出错
    if (ec) 
    {
        std::cout << "关闭失败: " << ec.message() << std::endl;
    }
}

// 连接关闭回调函数
// 当连接被关闭(无论主动还是被动)时,该函数会被自动调用
// 参数 hdl 是已关闭的连接句柄
void on_close(websocketpp::connection_hdl hdl) 
{
    // 在控制台打印连接已关闭的提示
    std::cout << "连接已关闭" << std::endl;
    // 调用客户端的 stop 方法,通知事件循环可以停止运行
    // 这样 run() 函数就会返回,程序可以正常退出
    g_client->stop();  // 停止事件循环
}

// 程序主函数
int main() {
    // 创建一个 client 对象(名为 ws_client),代表 WebSocket 客户端实例
    client ws_client;
    // 将全局指针指向这个 client 对象,以便回调函数能使用它
    g_client = &ws_client;

    // 关闭日志输出(可选)
    // set_access_channels 设置要输出的日志类型,alevel::none 表示不输出任何访问日志
    ws_client.set_access_channels(websocketpp::log::alevel::none);
    // clear_access_channels 清除之前设置的日志类型,alevel::all 表示所有类型,这里全部清除
    ws_client.clear_access_channels(websocketpp::log::alevel::all);

    // 初始化 Asio 网络库
    // 定义一个错误码变量,用于接收初始化结果
    websocketpp::lib::error_code ec;
    // 调用 init_asio(ec) 进行初始化,错误信息会保存在 ec 中
    ws_client.init_asio(ec);
    // 检查初始化是否成功
    if (ec) {
        std::cout << "init_asio 失败: " << ec.message() << std::endl;
        return 1;  // 初始化失败,退出程序
    }

    // 设置回调函数,将上面定义的普通函数注册为相应事件的处理器
    ws_client.set_open_handler(&on_open);       // 连接打开时的回调
    ws_client.set_message_handler(&on_message); // 收到消息时的回调
    ws_client.set_close_handler(&on_close);     // 连接关闭时的回调

    // 创建一个连接到指定服务器的连接对象
    // get_connection 的第一个参数是服务器 URI(ws:// 表示 WebSocket 协议,localhost:8002 是地址和端口)
    // 第二个参数 ec 用于接收创建过程中的错误信息
    client::connection_ptr con = ws_client.get_connection("ws://localhost:8002", ec);
    // 检查连接对象创建是否成功
    if (ec) {
        std::cout << "连接创建失败: " << ec.message() << std::endl;
        return 1;  // 创建失败,退出程序
    }

    // 发起连接,将之前创建的连接对象加入客户端的连接管理
    ws_client.connect(con);

    // 启动客户端的事件循环(阻塞)
    // run() 会一直运行,处理网络事件(连接、消息、关闭等),直到 stop() 被调用或所有连接关闭
    ws_client.run();

    // 当 run() 返回时(例如连接关闭后调用了 stop()),打印退出信息
    std::cout << "客户端退出" << std::endl;
    return 0;
}

makefile

cpp 复制代码
all : server client
server : server.cpp
	g++ -std=c++17 $^ -o $@ -lpthread -lboost_system
client : client.cpp
	g++ -std=c++17 $^ -o $@ -lpthread -lboost_system
.PHONY : clean
clean :
	rm -rf server client

2.3.2.示例2

使用Websocketpp实现一个简单的http和websocket服务器

server.cpp

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

// 使用标准命名空间,避免每次输入 std::
using namespace std;

// 定义 WebSocket 服务器类型,基于 Asio 且无 TLS,并起别名 websocketsvr
typedef websocketpp::server<websocketpp::config::asio> websocketsvr;
// 定义消息指针类型别名,便于使用
typedef websocketsvr::message_ptr message_ptr;

// 引入 websocketpp 库中的占位符,用于绑定回调函数
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
// 引入绑定函数,用于将成员函数或普通函数与对象绑定
using websocketpp::lib::bind;

// WebSocket 连接成功时的回调函数
// 参数 server: 指向服务器对象的指针,hdl: 连接句柄,用于标识当前连接
void OnOpen(websocketsvr *server, websocketpp::connection_hdl hdl) {
    // 在控制台输出提示信息
    cout << "连接成功" << endl;
}

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

// WebSocket 收到消息时的回调函数
// 参数 server: 服务器指针,hdl: 连接句柄,msg: 收到的消息对象指针
void OnMessage(websocketsvr *server, websocketpp::connection_hdl hdl, message_ptr msg) {
    // 输出收到的消息内容(get_payload() 返回消息的文本内容)
    cout << "收到消息" << msg->get_payload() << endl;
    // 将收到的消息原样发回给客户端(回声)
    // 使用 send 方法,指定连接句柄、消息内容和消息类型(文本)
    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;
    // 通过连接句柄获取连接对象的指针,以便操作 HTTP 响应
    websocketsvr::connection_ptr con = server->get_con_from_hdl(hdl);
    // 使用 stringstream 构建 HTML 页面内容
    std::stringstream ss;
    ss << "<!doctype html><html><head>"
       << "<title>hello websocket</title><body>"
       << "<h1>hello websocketpp</h1>"
       << "</body></head></html>";
    // 设置 HTTP 响应的主体内容为上面构建的 HTML 字符串
    con->set_body(ss.str());
    // 设置 HTTP 响应状态码为 200 OK
    con->set_status(websocketpp::http::status_code::ok);
}

int main() {
    // 创建一个 WebSocket 服务器对象
    websocketsvr server;

    // 设置 websocketpp 的日志输出级别
    // alevel::none 表示关闭所有访问日志,避免控制台被大量信息刷屏
    server.set_access_channels(websocketpp::log::alevel::none);

    // 初始化 Asio 网络库,为后续的网络操作做准备
    server.init_asio();

    // 注册 HTTP 请求处理函数,将 OnHttp 绑定到服务器对象上
    // 使用 bind 将 server 指针作为第一个参数传递给 OnHttp,_1 是占位符,表示将来传入的 hdl
    server.set_http_handler(bind(&OnHttp, &server, ::_1));

    // 注册 WebSocket 相关事件的处理函数
    // 连接打开事件
    server.set_open_handler(bind(&OnOpen, &server, ::_1));
    // 连接关闭事件
    server.set_close_handler(bind(&OnClose, &server, _1));
    // 收到消息事件(注意:_1 是 hdl,_2 是 message_ptr)
    server.set_message_handler(bind(&OnMessage, &server, _1, _2));
    // (可选)连接异常事件,但代码中未注册 OnFail,如果需要可以取消注释下面一行
    // server.set_fail_handler(bind(&OnFail, &server, _1));

    // 让服务器监听 8888 端口
    server.listen(8888);

    // 开始接受客户端连接请求(进入接受状态,但真正的处理在 run 中)
    server.start_accept();

    // 启动服务器事件循环,该函数阻塞,直到服务器停止
    server.run();

    // 服务器停止后返回 0
    return 0;
}

这段代码确实实现了一个简单的 HTTP 和 WebSocket 服务器。

功能说明

HTTP 服务器功能

  • 通过 set_http_handler 注册了 OnHttp 函数,处理所有进入的 HTTP 请求。
  • 在 OnHttp 中,构建了一个简单的 HTML 页面,设置响应体并返回状态码 200 OK。
  • 因此,当你在浏览器中访问 http://localhost:8888 时,会看到一个标题为 "hello websocket" 的欢迎页面。

WebSocket 服务器功能

  • 注册了 OnOpen、OnClose、OnMessage 等回调,处理 WebSocket 连接的生命周期事件。
  • OnMessage 实现了 echo 服务:收到任何文本消息后,原样发送回客户端。
  • 同时支持 WebSocket 的握手(通过 HTTP 升级),因此 JavaScript 可以通过 new WebSocket("ws://localhost:8888") 连接并进行双向通信。

我们打开浏览器

用浏览器访问 http://你的主机IP:8888 可看到 HTML 页面。

然后我们需要去测试WebSocket部分

我们回到我们的Windows主机来,创建一个test.html,填充下面这段代码

cpp 复制代码
<!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://你的主机IP:8888
    // 类比http
    // ws 表示websocket 协议
    // 192.168.51.100 表示服务器地址
    // 8888 表示服务器绑定的端口
    let websocket = new WebSocket("ws://你的主机IP: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>

我们双击打开,进入浏览器

我们点击F12,打开浏览器的控制台

看看我们的服务器(注意从连接成功开始看)

输入你好

点击提交

怎么样?还是很不错的吧

2.3.3.示例3

test.cpp

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

// 0. 定义 server 类型:基于 Asio 且无 TLS 的 WebSocket 服务器类型,并起别名 server_t
typedef websocketpp::server<websocketpp::config::asio> server_t;

// 连接打开回调函数:当 WebSocket 长连接成功建立时调用
void onOpen(websocketpp::connection_hdl hdl) {
    // 在控制台输出提示信息
    std::cout << "websocket长连接建立成功!\n";
}

// 连接关闭回调函数:当 WebSocket 长连接断开时调用
void onClose(websocketpp::connection_hdl hdl) {
    std::cout << "websocket长连接断开!\n";
}

// 收到消息回调函数:当收到客户端发送的消息时调用
// 参数 server: 指向服务器对象的指针,用于在回调中调用服务器方法
// 参数 hdl: 连接句柄,用于标识当前连接
// 参数 msg: 消息对象指针,包含消息内容等信息
void onMessage(server_t *server, websocketpp::connection_hdl hdl, server_t::message_ptr msg) {
    // 1. 获取有效消息载荷数据,进行业务处理
    // get_payload() 返回消息的文本内容
    std::string body = msg->get_payload();
    std::cout << "收到消息:" << body << std::endl;

    // 2. 对客户端进行响应
    // 获取通信连接对象(通过连接句柄)
    auto conn = server->get_con_from_hdl(hdl);
    // 发送数据给客户端:在原消息后加上 "-Hello!",消息类型为文本
    conn->send(body + "-Hello!", websocketpp::frame::opcode::value::text);
}

int main()
{
    // 1. 实例化服务器对象
    server_t server;

    // 2. 初始化日志输出 --- 关闭日志输出,避免控制台被大量信息刷屏
    server.set_access_channels(websocketpp::log::alevel::none);

    // 3. 初始化 Asio 框架,为后续网络操作做准备
    server.init_asio();

    // 4. 设置消息处理/连接握手成功/连接关闭回调函数
    // 注册连接打开回调
    server.set_open_handler(onOpen);
    // 注册连接关闭回调
    server.set_close_handler(onClose);
    // 使用 std::bind 将服务器对象指针绑定到 onMessage 的第一个参数,
    // std::placeholders::_1 和 _2 分别对应后续传入的 hdl 和 msg
    auto msg_handler = std::bind(onMessage, &server, std::placeholders::_1, std::placeholders::_2);
    // 注册消息接收回调
    server.set_message_handler(msg_handler);

    // 5. 启用地址重用,允许在端口被占用时重新绑定(例如快速重启服务器)
    server.set_reuse_addr(true);

    // 5. 设置监听端口为 8080(此处步骤编号重复,但功能正确)
    server.listen(8080);

    // 6. 开始接受客户端连接请求(进入接受状态,但真正的处理在 run 中进行)
    server.start_accept();

    // 7. 启动服务器事件循环,该函数阻塞运行,直到服务器被停止
    server.run();

    return 0;
}

makefile

cpp 复制代码
test : test.cpp
	g++ -std=c++17 $^ -o $@ -lpthread -lboost_system
.PHONY : clean
clean :
	rm -rf test

我们编译运行一下

我们还是在我们的Windows主机创建一个test.html,然后存放下面这个内容

cpp 复制代码
<!DOCTYPE html>
<html>
<body>
    <input id="msg" placeholder="输入消息">
    <button onclick="send()">发送</button>
    <div id="log"></div>

    <script>
        const ws = new WebSocket('ws://你的主机IP:8080'); // 请根据实际服务器地址修改
        const log = document.getElementById('log');

        ws.onopen = () => log.innerHTML += '<div>✅ 连接成功</div>';
        ws.onmessage = (e) => log.innerHTML += '<div>📩 收到: ' + e.data + '</div>';
        ws.onclose = () => log.innerHTML += '<div>❌ 连接关闭</div>';

        function send() {
            const msg = document.getElementById('msg').value;
            if (msg) {
                ws.send(msg);
                log.innerHTML += '<div>📤 发送: ' + msg + '</div>';
                document.getElementById('msg').value = '';
            }
        }
    </script>
</body>
</html>

双击打开

输入你好

我们点击发送

我们输入我很好

非常完美的一个程序。

我们的服务端也会出现下面这个

相关推荐
小年糕是糕手2 小时前
【35天从0开始备战蓝桥杯 -- Day4】
数据结构·c++·算法·leetcode·蓝桥杯
xiaoye-duck2 小时前
《算法题讲解指南:递归,搜索与回溯算法--递归》--1.汉诺塔,2.合并两个有序链表
数据结构·c++·算法
故以往之不谏2 小时前
算法专题--数组二分查找--Leetcode704题
c语言·开发语言·c++·算法·新人首发
biter down2 小时前
C++ stringstream 简单介绍:告别字符数组,安全高效的字符串与数据转换利器
开发语言·c++
C+-C资深大佬2 小时前
C++ 模板进阶
开发语言·c++·算法
菜_小_白2 小时前
高并发定时任务调度系统
linux·c++
耶叶2 小时前
C++:拷贝构造函数
开发语言·c++
努力中的编程者2 小时前
栈和队列(C语言底层实现栈)
c语言·开发语言·数据结构·c++
前端不太难2 小时前
OpenClaw 如何运行 Claw 资源文件
c++·开源·游戏引擎