基于WebSocket++开发服务端客户端实例

4,487 阅读3分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

一、WebSocket++

WebSocket++是一个用于实现WebSocket功能的C++库。该库具有易于移植、轻量级和高性能的特点。

主要特点:

  • 完整支持 RFC6455
  • 部分支持 Hixie 76 / Hybi 00, 07-17规格草案(仅服务器)
  • 基于消息/事件的接口
  • 支持安全的WebSockets(TLS)、IPv6和显式代理
  • 灵活的依赖性管理(C++11标准库或Boost)
  • 可互换的网络传输模块(raw、iostream、Asio或自定义)
  • 便携/跨平台(Posix/Windows,32/64位,Intel/ARM/PPC)
  • 线程安全

官方仓库

官方帮助文档

二、WebSocket++使用

2.1 准备环境

windows下编译boost 1.79

1、下载boost源码解压

2、进入vs命令行 x64 Native Tools Command Pro...

3、进入源码目录 cd boost_1_79_0

4、生成 b2.exe bootstrap.bat

5、编译全部 b2 -a

6、安装 b2 install

  • 不安装也行,注意引用的位置
  • (会安装到默认目录c:\Boost\,如果不想安装到默认目录可以用–prefix指定,具体可以用 b2 --help 查看)

注:对于windows平台,boost官方也提供编译好的二进制包

2.2 websocketpp库

1、websocketpp是一个c++的websocket库,下载解压后得到一下目录结构,其中websocketpp就是我们所要用到的库文件,examples下有一些websocket的基本使用案例。

2、配置vs项目 boost与websocketpp两个库都是hpp的源码,只需要在附加包含目录里引入两个路径即可(确保include可以正常导入库文件)。一定要切记:boost要最先导入,否则,会有各种莫名其妙的报错。

image.png

2.3 一些概念

从Websocketpp的例子中,拷贝需要用到的头文件包含和一些类型重定义

#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>

using websocketpp::connection_hdl;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;
typedef websocketpp::server<websocketpp::config::asio> server;
typedef server::message_ptr message_ptr;
// 生成server对象
server m_server; 

// 初始化m_server
m_server.set_access_channels(websocketpp::log::alevel::none); // 设置打印的日志等级

m_server.init_asio(); // 初始化asio

m_server.set_open_handler(bind(&XXXClass::on_open_func_ptr, this, ::_1)); // 绑定websocket连接到来时的回调函数
m_server.set_close_handler(bind(&XXXClass::on_close_func_ptr, this, ::_1)); // 绑定websocket连接断开时的回调函数
m_server.set_message_handler(bind(&XXXClass::on_message_func_ptr, this, ::_1, ::_2)); // 绑定websocket连接有消息到来时的回调函数

上面三个回调函数的原型如下,我是将server对象直接封装到一个类里面使用的,因此这里绑定的回调函数可以直接使用类的方法。Websocketpp也提供了其他很多环节上的回调函数设定,可自行查看其源代码进行了解。

class XXXClass
{
public :
    void on_open_func_ptr(connection_hdl hdl);
    void on_close_func_ptr(connection_hdl hdl);
    void on_message_func_ptr(connection_hdl hdl, message_ptr msg);
}

回调参数中的connection_hdl是一个weak_ptr,如果需要将连接存到容器中以便管理,则不能使用传入的hdl,需要使用

server::connection_ptr con = m_server.get_con_from_hdl(hdl);

获得的con是个shared_ptr,再调用

void *con_ptr = con->get();

可以得到这个连接的实际对象的地址,但websocketpp对我们隐藏了其对象的结构,只给了我们一个void*,不过用于在每次回调时区分不同的客户端足够了。 当websocket有新消息到来时,我们可以通过

std::string msg_str = msg->get_payload();

直接获取到传输的内容。

以上,就是websocketpp集成后的简单使用流程,server对象其实还提供了很多的使用方法以及回调绑定功能,等有时间我再详细研究,现在这几个已经能满足我的需求了。

2.4 展示

image.png

三、源码

客户端(命令窗口)

#include <websocketpp/config/asio_no_tls.hpp>

#include <websocketpp/server.hpp>

#include <iostream>

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

using websocketpp::lib::bind;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;

// pull out the type of messages sent by our config
typedef server::message_ptr message_ptr;

// Define a callback to handle incoming messages
void on_message(server *s, websocketpp::connection_hdl hdl, message_ptr msg)
{
    std::string cmd = msg->get_payload();
    std::string res = "fu wu qi de xin xi! \" ";

    std::cout << "on_message called with hdl: " << hdl.lock().get()
              << " and message: " << msg->get_payload()
              << std::endl;

    // check for a special command to instruct the server to stop listening so
    // it can be cleanly exited.
    if (msg->get_payload() == "stop-listening")
    {
        s->stop_listening();
        return;
    }

    try
    {
        s->send(hdl, res, msg->get_opcode());
    }
    catch (websocketpp::exception const &e)
    {
        std::cout << "Echo failed because: "
                  << "(" << e.what() << ")" << std::endl;
    }
}

int main()
{
    // Create a server endpoint
    server echo_server;

    try
    {
        // Set logging settings
        echo_server.set_access_channels(websocketpp::log::alevel::all);
        echo_server.clear_access_channels(websocketpp::log::alevel::frame_payload);

        // Initialize Asio
        echo_server.init_asio();

        // Register our message handler
        echo_server.set_message_handler(bind(&on_message, &echo_server, ::_1, ::_2));

        // Listen on port 9002
        echo_server.listen(9002);

        // Start the server accept loop
        echo_server.start_accept();

        // Start the ASIO io_service run loop
        echo_server.run();
    }
    catch (websocketpp::exception const &e)
    {
        std::cout << e.what() << std::endl;
    }
    catch (...)
    {
        std::cout << "other exception" << std::endl;
    }
}

客户端(网页)

<!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>Document</title>
</head>

<body>
    <script src="https://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    <script>
        function send(msg) {
            var ws = new WebSocket("ws://localhost:9002");

            ws.onopen = function (evt) {
                console.log("Connection open ...");
                ws.send(msg);
            };

            ws.onmessage = function (evt) {
                console.log("Received Message: " + evt.data);
                $("#show_txt").html(evt.data);
                ws.close();
            };

            ws.onclose = function (evt) {
                console.log("Connection closed.");
            };
        }
        function send_msg() {
            var msg = $("#in_msg").val();
            send(msg);
        }
    </script>
    <input type=text id="in_msg" style="width:800px;height:30px;" /><br />
    <button onclick="send_msg()">发送信息</button><br />
    输出:<div id="show_txt"></div>
</body>

</html>