C/C++编程:WebSocketpp(Linux + Clion + boostAsio)

  • WebSocket ++是一个C ++库,可用于实现WebSocket功能。该项目的目标是提供一种可移植,灵活,轻量级,低级和高性能的WebSocket实现。
  • WebSocket ++并不打算单独用作Web应用程序框架或功能全面的Web服务平台。这样的组件,示例和性能调整都适合作为WebSocket客户端或服务器进行操作。有一些最不方便的功能(例如,响应WebSocket升级以外的HTTP请求的能力)偏离了这些,但这些并不是项目的重点。特别是WebSocket ++并不打算实现任何与WebSocket不相关的后备选项(ajax /长轮询/彗星/等)。
  • 为了保持紧凑并提高可移植性,WebSocket ++项目致力于在可能和适当的情况下减少或消除外部依赖性。WebSocket ++核心除了C ++ 11标准库外没有其他依赖项。对于非C ++ 11编译器,Boost库为使用的C ++ 11功能提供了polyfill。
  • WebSocket ++实现了可插拔数据传输组件(使用两个可以相互替换的网络传输模块,其中一个基于C++ I/O流,另一个基于Asio)。
  • 默认组件允许通过使用STL iostream或通过读取和写入char缓冲区的原始字节改组来减少功能。该组件没有非STL依赖关系,可以在没有Boost的C ++ 11环境中使用。
  • 基于Asio的传输组件提供了功能齐全的网络客户端/服务器功能。该组件需要Boost Asio或C ++ 11编译器以及独立的Asio。作为高级选项,如果您想使用另一个库提供自己的库,则WebSocket ++支持自定义传输层。

资源:

仓库包含如下几个目录:

  • docs: 文档
  • examples: 示例程序演示如何为WebSocket客户端和服务器构建一些常用模式的基本版本。
  • test: 单元测试确认您的代码正常工作,并帮助检测平台特定的问题。
  • tutorials: 一组示例程序的详细演练。
  • websocketpp: 所有库代码和默认配置文件。

WebSocket++的主要特性包括:

  • 事件驱动的接口
  • 支持WSS、IPv6
  • 灵活的依赖管理 —— Boost或者C++ 11标准库
  • 可移植性:Posix/Windows、32/64bit、Intel/ARM/PPC
  • 线程安全

系统环境:centos 7
编译器: clion
当前电脑已经安装boost, 具体可以参考boost:从0到1开发boost(linux、clion)

编译

$ git clone https://github.com/zaphoyd/websocketpp.git
$ cd websocketpp #进入目录
$ mkdir build
$ cd build
$ cmake ..
$ sudo make

-- ENABLE_CPP11        = ON
-- BUILD_EXAMPLES      = OFF
-- BUILD_TESTS         = OFF

-- WEBSOCKETPP_ROOT    = /home/oceanstar/workspace/cpp/websocketpp
-- WEBSOCKETPP_BIN     = /home/oceanstar/workspace/cpp/websocketpp/build/bin
-- WEBSOCKETPP_LIB     = /home/oceanstar/workspace/cpp/websocketpp/build/lib
-- Install prefix      = /usr/local

-- WEBSOCKETPP_BOOST_LIBS        = 
-- WEBSOCKETPP_PLATFORM_LIBS     = 
-- WEBSOCKETPP_PLATFORM_TLS_LIBS = 

-- OPENSSL_FOUND        = 
-- OPENSSL_INCLUDE_DIR     = 
-- OPENSSL_LIBRARIES = 

$ sudo make install
Install the project...
-- Install configuration: ""
-- Installing: /usr/local/lib/cmake/websocketpp/websocketpp-config.cmake
-- Installing: /usr/local/lib/cmake/websocketpp/websocketpp-configVersion.cmake
-- Installing: /usr/local/include//websocketpp
-- Installing: /usr/local/include//websocketpp/base64
-- Installing: /usr/local/include//websocketpp/base64/base64.hpp
-- Installing: /usr/local/include//websocketpp/client.hpp
-- Installing: /usr/local/include//websocketpp/close.hpp
-- Installing: /usr/local/include//websocketpp/common

测试官方demo

echo_server

$ cd websocketpp/examples/echo_server
$ g++ -o echo_server echo_server.cpp -lboost_system -lpthread   -std=c++11
$ ./echo_server

在这里插入图片描述

echo_client

$ cd websocketpp/examples/echo_client 
$ g++ -o echo_client echo_client.cpp -lboost_system -lpthread   -std=c++11

在这里插入图片描述
当然,我们也可以使用在线测试工具测试: http://coolaf.com/tool/chattest
在这里插入图片描述

构建说明

要使用webSocket++,必须要加入一些依赖项

  • WebSocket++库头文件必须在构建系统的include搜索路径中。
  • 为了使用Asio传输配置,我们需要引入Asio库。这里有两种选择:
    • 如果您可以访问c++ 11构建环境,来自http://think-async.com的独立版本是一个很好的选择。
      • 这个header only库不会带来任何特殊的依赖,并确保你拥有最新版本的Asio。
      • 要使用独立Asio,请确保Asio头文件在include路径中,并定义ASIO_STANDALONE
      • c++ -std=c++11 step1.cpp (Asio Standalone)
    • 如果您没有c++ 11构建环境,或者已经引入了Boost库,您也可以使用Boost附带的Asio版本。
      • 要使用Boost Asio,请确保Boost头文件在包含路径中,并且正在链接到boost_system库。
      • c++ -std=c++11 step1.cpp -lboost_system (Boost Asio)

示例学习

log

websocketpp::log::basic




#include <string>
#include <assert.h>
#include <websocketpp/logger/basic.hpp>
#include <websocketpp/concurrency/none.hpp>
#include <websocketpp/concurrency/basic.hpp>
#include <sstream>

void test0(){
    typedef websocketpp::log::basic<websocketpp::concurrency::none,websocketpp::log::alevel> access_log;

    std::stringstream out;
    access_log logger(0xffffffff,&out);

    logger.clear_channels(0xffffffff);

    logger.write(websocketpp::log::alevel::devel,"devel");
   // std::cout << "|" << out.str() << "|" << std::endl;
    assert( out.str().size() == 0 );
}
void test1(){
    typedef websocketpp::log::basic<websocketpp::concurrency::none,websocketpp::log::alevel> access_log;

    std::stringstream out;
    access_log logger(0xffffffff,&out);

    logger.set_channels(0xffffffff);

    logger.write(websocketpp::log::alevel::devel,"devel");
   // std::cout << "|" << out.str() << "|" << std::endl;
    assert( out.str().size() > 0 );
}

void test_copy_constructor1 (){
    typedef websocketpp::log::basic<websocketpp::concurrency::basic,websocketpp::log::alevel> basic_access_log_type;

    std::stringstream out;
    basic_access_log_type logger1(0xffffffff,&out);
    basic_access_log_type logger2(logger1);
    //basic_access_log_type logger2(std::move(logger1));  // move_constructor

    logger2.set_channels(0xffffffff);
    logger2.write(websocketpp::log::alevel::devel,"devel");
    assert( out.str().size() > 0 );
}
void test_copy_constructor2 (){
    typedef websocketpp::log::basic<websocketpp::concurrency::basic,websocketpp::log::alevel> basic_access_log_type;

    std::stringstream out;
    basic_access_log_type logger1(0xffffffff,&out);
    basic_access_log_type logger2(logger1);

    logger2.set_channels(0xffffffff);
    logger1.write(websocketpp::log::alevel::devel,"devel");
    assert( out.str().size() == 0 );
}


int main(){
    test0();
    test1();
    test_copy_constructor1();
    test_copy_constructor2();
}


endpoint

#include <iostream>
#include <sstream>
#include <assert.h>
#include <websocketpp/config/asio.hpp>
#include <websocketpp/server.hpp>
// ----- -lssl -lcrypto   ----------------
void test_construct_server_asio_tls (){
    websocketpp::server<websocketpp::config::asio_tls> s;
    s.init_asio();
}

void test_test_construct_server_asio1(){
    websocketpp::server<websocketpp::config::asio> s;
    s.init_asio();
}
void test_test_construct_server_asio2(){
    websocketpp::server<websocketpp::config::asio> s;
    boost::asio::io_service ios;
    s.init_asio(&ios);
}

void test_move_construct_server_core(){
    websocketpp::server<websocketpp::config::core> s1;
    websocketpp::server<websocketpp::config::core> s2(std::move(s1));
}


void test_listen_after_listen_failure (){
    using websocketpp::transport::asio::error::make_error_code;
    using websocketpp::transport::asio::error::pass_through;

    websocketpp::server<websocketpp::config::asio> server1;
    websocketpp::server<websocketpp::config::asio> server2;

    websocketpp::lib::error_code ec;

    server1.init_asio();
    server2.init_asio();

    boost::asio::ip::tcp::endpoint ep1(boost::asio::ip::address::from_string("127.0.0.1"), 12345);
    boost::asio::ip::tcp::endpoint ep2(boost::asio::ip::address::from_string("127.0.0.1"), 23456);

    server1.listen(ep1, ec);
    assert(!ec);

    server2.listen(ep1, ec);  // error: [2021-02-04 10:40:35] [info] asio listen error: system:98 (Address already in use)
    assert(ec);


    server2.listen(ep2, ec);
    assert(!ec);
}



struct endpoint_extension{
    endpoint_extension() : extension_value(5) {}

    int extension_method() {
        return extension_value;
    }

    bool is_server() const {
        return false;
    }

    int extension_value;
};
struct stub_config : public websocketpp::config::core {
    typedef core::concurrency_type concurrency_type;

    typedef core::request_type request_type;
    typedef core::response_type response_type;

    typedef core::message_type message_type;
    typedef core::con_msg_manager_type con_msg_manager_type;
    typedef core::endpoint_msg_manager_type endpoint_msg_manager_type;

    typedef core::alog_type alog_type;
    typedef core::elog_type elog_type;

    typedef core::rng_type rng_type;

    typedef core::transport_type transport_type;

    typedef endpoint_extension endpoint_base;
};

void test_endpoint_extensions (){
    websocketpp::server<stub_config> s;

    assert(s.extension_value == 5);
    assert(s.extension_method() == 5);

    assert(s.is_server());
}
int main(){
    test_construct_server_asio_tls();
    test_test_construct_server_asio1();
    test_test_construct_server_asio2();
    test_move_construct_server_core();
    test_listen_after_listen_failure();

}

工具包学习

URI

#include <iostream>
#include <string>
#include <assert.h>
#include <websocketpp/uri.hpp>

void test_uri_valid(){
    websocketpp::uri uri("ws://localhost:9000/chat");

    assert(uri.get_valid());
    assert(!uri.get_secure());
    assert(uri.get_scheme() == "ws");
    assert(uri.get_host() == "localhost");
    assert(uri.get_port() == 9000);
    assert(uri.get_port_str() == "9000");
    assert(uri.get_resource() ==  "/chat");
    assert(uri.get_query().empty());
    assert(uri.str() == "ws://localhost:9000/chat");
    assert(uri.get_authority() == "localhost:9000");
    assert(uri.get_host_port() == "localhost:9000");

    websocketpp::uri uri1("ws://127.0.0.1/chat");
    assert(uri1.get_valid());
    assert(!uri.get_secure());
    assert(uri1.get_host() == "127.0.0.1");
    assert(uri1.get_port_str() == "80");
    assert(uri1.get_resource() ==  "/chat");

    websocketpp::uri uri2("wss://localhost/chat");
    assert(uri2.get_valid());
    assert(uri2.get_secure());
    assert(uri2.get_scheme() == "wss");
    assert(uri2.get_port() == 443);
    assert(uri2.get_resource() ==  "/chat");

    websocketpp::uri uri3("wss://localhost:9000");
    assert(uri3.get_port() == 9000);
    assert(uri3.get_resource() ==  "/");

    // Valid URI IPv6 Literal
    websocketpp::uri uri4("wss://[::1]:9000/chat");
    assert(uri4.get_host() == "::1");

    websocketpp::uri uri5("wss://thor-websocket.zaphoyd.net:88/");
    assert(uri5.get_host() == "thor-websocket.zaphoyd.net");

    // Invalid URI (port too long)
    websocketpp::uri uri6("wss://localhost:900000/chat");
    assert(!uri6.get_valid());

    websocketpp::uri uri7("http://localhost:9000/chat");
    assert(uri7.get_valid());


    websocketpp::uri uri8("wss://localhost:9000/chat/foo/bar");
    assert(uri8.get_resource() ==  "/chat/foo/bar");

    // Invalid URI includes uri fragment
    websocketpp::uri uri9("wss:/localhost:9002/chat#foo");
    assert(!uri9.get_valid());
}

int main(){
    test_uri_valid();
}

Unitily Server

此处为[md_tutorials_utility_server_utility_server](https://docs.websocketpp.org/
md_tutorials_utility_server_utility_server.html)翻译

本教程将逐步讨论构建基本的WebSocket++服务器。本教程的最终产品是示例部分中的utility_server示例应用程序。该服务器演示以下功能:

  • 使用Asio Transport进行网络连接
  • 一次接受多个WebSocket连接
  • 阅读传入消息并根据路径执行一些基本操作(回声、广播、遥测、服务器命令)

本教程是该库的0.6.x版本的最新版本。

第1步:添加WebSocket++包括并设置服务器端点类型

WebSocket++包括两种主要的对象类型:端点和连接。

  • 端点创建并启动新的连接,并维护这些连接的默认设置。端点还管理任何共享的网络资源

  • 一旦启动了连接,端点和连接之间就不存在链接。端点将所有默认设置复制到新连接中。更改端点上的默认设置只会影响未来的连接。

  • 连接存储特定于每个WebSocket会话的信息。

  • 连接不维护指向其关联端点的链接。

  • 端点不维护未完成连接的列表。如果你的应用程序需要便利所有的连接,则需要维护它们自身的列表

WebSocket++端点是通过结合端点角色和端点配置来构建的。

有两种不同类型的端点角色:

  • WebSocket会话中的客户端角色
  • WebSocket会话中的服务器角色websocketpp::server, 需要用到头文件<websocketpp/server.hpp>

术语:端点配置

  • webSocket++端点有一组配置,可以在编译时通过config template参数进行配置
  • 配置是struct类型的,它包含了用于生成具有特定属性的断点的类型和静态常量
  • 根据使用的配置,端点将具有不同的可用方法,并且可能具有其他第三方依赖项

endpoint角色接受一个名为config的模板参数,该参数用于在编译时配置endpoint的行为。

本教程我们将使用一个默认配置,

  • 该配置有一个叫做asio的库提供, 使用到了<websocketpp/config/asio_no_tls.hpp>.
  • asio库提供网络传送的服务器配置,但不支持基于TLS的安全性。

将配置与端点角色结合起来生成一个完全配置的端点。这种类型将经常被使用,所以建议在这里使用typedef。

typedef websocketpp::server<websocketpp::config::asio> server
  • utility_server构造函数

此端点类型将是跟踪服务器状态的实用工具服务器对象的基础。在utility_server构造函数中,会发生以下几种情况

  • 首先,我们将端点日志记录行为调整为包括除帧负载之外的所有错误日志记录通道和所有访问日志记录通道,一般来讲,帧负载通常只用于调试。
m_endpoint.set_error_channels(websocketpp::log::elevel::all);
m_endpoint.set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload);
  • 接下来,我们初始化端点下面的传输系统。接下来,我们初始化端点下面的传输系统。
m_endpoint.init_asio();
  • utility_server :: run方法

除了构造函数之外,我们还添加了一个run方法,该方法设置侦听套接字,开始接受连接,启动Asio io_service事件循环。

//监听端口9002
m_endpoint.listen(9002;
 
//排队连接接受操作
m_endpoint.start_accept();
 
//启动Asio io_service运行循环
m_endpoint.run();

最后一行m_endpoint.run(); 将阻塞,直到指示端点停止侦听新链接为止。在运行时,它将侦听和处理新的连接,并接受和处理现有连接的新数据和控制消息。websocket++在异步模式下使用Asio,在这种模式下,可以在单个线程内同时有效的服务多个连接

目前为止,代码是这样的:

//要使用独立版本的Asio,必须定义ASIO_STANDALONE。
//如果使用的是Boost Asio,则将其删除。
//#define ASIO_STANDALONE

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

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



class utility_server {
public:
    utility_server() {
         //设置日志记录设置
        m_endpoint.set_error_channels(websocketpp::log::elevel::all);
        m_endpoint.set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload);

        //初始化Asio
        m_endpoint.init_asio();
    }

    void run() {
        //监听端口9002
        m_endpoint.listen(9002);

        //排队连接接受操作
        m_endpoint.start_accept();

        //启动Asio io_service运行循环
        m_endpoint.run();
    }
private:
    server m_endpoint;
};


int main(){
    utility_server s;
    s.run();
}

cmakelist.txt是这样写的:

cmake_minimum_required(VERSION 3.16)
project(boosttest)

set(CMAKE_CXX_STANDARD 14)
set(SOURCE_FILES main.cpp)
#add_executable(myboost ${SOURCE_FILES})

set(BOOST_ROOT "/usr/local/boost")

#添加头文件搜索路径
include_directories(/usr/local/boost/include)

#添加库文件搜索路径
link_directories(/usr/local/boost/lib)

#用于将当前目录下的所有源文件的名字保存在变量 DIR_SRCS 中
aux_source_directory(. DIR_SRCS)

add_executable(boosttest ${DIR_SRCS} )

#在这里根据名字boost_thread去寻找libboost_thread.a文件
target_link_libraries(boosttest -lpthread boost_system )



测试:

1、浏览器连接:
在这里插入图片描述
效果:
在这里插入图片描述
结论:当前必须是websocket连接

2、在线测试工具测试: http://coolaf.com/tool/chattest
在这里插入图片描述
效果:
在这里插入图片描述

结论:
当前已经完成了ws连接,但是还不能接收客户端发来的消息

第2步:设置消息处理程序以将所有答复回显给原始用户

  • WebSocket ++提供了许多执行点,您可以在其中注册以运行处理程序。这些端点中哪些可用,将取决于其配置。
  • 可以在端点级别和连接级别注册处理程序。端点处理程序在创建时被复制到新的连接中。更改端点处理程序将仅影响将来的连接。在连接级别注册的处理程序将仅绑定到该特定连接。
  • 处理程序绑定方法的签名对于端点和连接是相同的。格式为:set__handler(…)。其中是处理程序的名称。例如,set_open_handler(…)将在打开新连接时将处理程序设置为被调用。set_fail_handler(…)将设置连接失败时调用的处理程序。
  • 所有处理程序都采用一个参数,这是一种可调用的类型,可以将其转换为std::function具有正确数量和参数类型的。您可以传递带有匹配参数列表的自由函数,函子和Lambda作为处理程序。另外,您可以使用std::bind(或boost::bind)向不匹配的参数列表注册函数。这对于传递在处理程序签名或成员函数中不存在的需要携带’this’指针的其他参数很有用。
#include <websocketpp/config/asio_no_tls.hpp>
#include <websocketpp/server.hpp>
#include <functional>

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



class utility_server {
public:
    utility_server() {
        //设置日志记录设置
        m_endpoint.set_error_channels(websocketpp::log::elevel::all);
        m_endpoint.set_access_channels(websocketpp::log::alevel::all ^ websocketpp::log::alevel::frame_payload);

        m_endpoint.init_asio();

        // Set the default open handler to the echo handler
        m_endpoint.set_open_handler( bind(&utility_server::open_handler, this, std::placeholders::_1 ));
        // Set the default message handler to the echo handler
        m_endpoint.set_message_handler(bind(&utility_server::message_handler, this,std::placeholders::_1, std::placeholders::_2));
        
    }

    void open_handler(websocketpp::connection_hdl hdl) {
        server::connection_ptr con = m_endpoint.get_con_from_hdl( hdl );      // 根据连接句柄获得连接对象
        std::string path = con->get_resource();    // 获得URL路径
        m_endpoint.get_alog().write( websocketpp::log::alevel::app, "Connected to path " + path );
    }
    
    
    
    void message_handler(websocketpp::connection_hdl hdl, server::message_ptr msg) {
        m_endpoint.send(hdl, msg->get_payload(), msg->get_opcode());
    }
    
    
    void run(int port){
        m_endpoint.set_reuse_addr(true);
        m_endpoint.listen(port);
        m_endpoint.start_accept();
        m_endpoint.run();
    }
private:
    server m_endpoint;
};


int main(){
    utility_server s;
    s.run(9002);
}

在这里插入图片描述
在这里插入图片描述

#include <iostream>

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

typedef websocketpp::server<websocketpp::config::asio> server;
typedef websocketpp::config::asio::message_type::ptr message_ptr;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;
using websocketpp::lib::bind;

void on_open( server *s, websocketpp::connection_hdl hdl ) {
    // 根据连接句柄获得连接对象
    server::connection_ptr con = s->get_con_from_hdl( hdl );
    // 获得URL路径
    std::string path = con->get_resource();
    s->get_alog().write( websocketpp::log::alevel::app, "Connected to path " + path );
}

void on_message( server *s, websocketpp::connection_hdl hdl, message_ptr msg ) {
    s->send( hdl, msg->get_payload(), websocketpp::frame::opcode::text );
}

int main() {
    server echo_server;
    // 调整日志策略
    echo_server.set_access_channels( websocketpp::log::alevel::all );
    echo_server.clear_access_channels( websocketpp::log::alevel::frame_payload );

    try {
        echo_server.init_asio();

        echo_server.set_open_handler( bind( &on_open, &echo_server, ::_1 ));
        echo_server.set_message_handler( bind( &on_message, &echo_server, ::_1, ::_2 ));
        // 在所有网络接口的9002上监听
        echo_server.listen( 9002);

        // 启动服务器端Accept事件循环
        echo_server.start_accept();

        // 启动事件循环(ASIO的io_service),当前线程阻塞
        echo_server.run();
    } catch ( websocketpp::exception const &e ) {
        std::cout << e.what() << std::endl;
    } catch ( ... ) {
        std::cout << "other exception" << std::endl;
    }
}

print_server

#include <iostream>

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

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

void on_message(websocketpp::connection_hdl, server::message_ptr msg) {
    std::cout << msg->get_payload() << std::endl;
}

int main() {
    server print_server;

    print_server.set_message_handler(&on_message);
    print_server.set_access_channels(websocketpp::log::alevel::all);
    print_server.set_error_channels(websocketpp::log::elevel::all);

    print_server.init_asio();
    print_server.listen(9002);
    print_server.start_accept();

    print_server.run();
}

在这里插入图片描述

效果:将发送的消息直接打印出来

broadcast_server

#include <set>

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

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

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

class broadcast_server{
public:
    broadcast_server(){
        m_server.init_asio();

        m_server.set_open_handler(bind(&broadcast_server::on_open,this,::_1));
        m_server.set_close_handler(bind(&broadcast_server::on_close,this,::_1));
        m_server.set_message_handler(bind(&broadcast_server::on_message,this,::_1,::_2));
    }

    void on_open(connection_hdl hdl) {
        m_connections.insert(hdl);
    }

    void on_close(connection_hdl hdl) {
        m_connections.erase(hdl);
    }

    void on_message(connection_hdl hdl, server::message_ptr msg) {
        for (auto it : m_connections) {
            m_server.send(it,msg);
        }
    }

    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::set<connection_hdl,std::owner_less<connection_hdl>> con_list;

    server  m_server;
    con_list  m_connections;
};

int main(){
    broadcast_server server;
    server.run(9002);
}

测试:
1、使用在线测试工具: http://coolaf.com/tool/chattest开两个连接
在这里插入图片描述
2、在其中的某一个客户端上发送一条消息
在这里插入图片描述

效果:可以看到另外一个服务器上被广播了消息

服务端主动给客户端发送消息

示例一

#include <functional>
#include <mutex>
#include <set>
#include <thread>

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

typedef websocketpp::server<websocketpp::config::asio> server;
using websocketpp::connection_hdl;
using websocketpp::lib::placeholders::_1;
using websocketpp::lib::placeholders::_2;

class count_server{
public:
    count_server() : m_count(0){
        m_server.init_asio();

        m_server.set_open_handler(bind(&count_server::on_open,this,_1));
        m_server.set_close_handler(bind(&count_server::on_close,this,_1));
    }

    void on_open(connection_hdl hdl) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_connections.insert(hdl);
    }

    void on_close(connection_hdl hdl) {
        std::lock_guard<std::mutex> lock(m_mutex);
        m_connections.erase(hdl);
    }

    void count(){
        while (1){
            sleep(1);
            m_count++;

            std::stringstream  ss;
            ss << m_count;
            std::lock_guard<std::mutex> lock(m_mutex);
            for (auto it : m_connections) {
                m_server.send(it,ss.str(),websocketpp::frame::opcode::text);
            }
        }
    }

    void run(uint16_t port) {
        m_server.listen(port);
        m_server.start_accept();
        m_server.run();
    }
private:
    typedef std::set<connection_hdl,std::owner_less<connection_hdl>> con_list;

    int m_count;
    server m_server;
    con_list  m_connections;
    std::mutex  m_mutex;
};

int main(){
    count_server server;
    std::thread t(std::bind(&count_server::count,&server));
    server.run(9002);
}

效果:当客户端连上来之后,服务器会 客户端发送消息,美中不足的是,发送的消息一模一样
在这里插入图片描述

https://docs.websocketpp.org/simple__count__server__thread_8cpp_source.html









## Utility Client

第一步:基础代码

功能:提示用户输入命令,然后对其进行处理

#include <iostream>
#include <string>
 
int main() {
    bool done = false;
    std::string input;
 
    while (!done) {
        std::cout << "Enter Command: ";
        std::getline(std::cin, input);
 
        if (input == "quit") {
            done = true;
        } else if (input == "help") {
            std::cout
                << "\nCommand List:\n"
                << "help: Display this help text\n"
                << "quit: Exit the program\n"
                << std::endl;
        } else {
            std::cout << "Unrecognized Command" << std::endl;
        }
    }
 
    return 0;
}

效果:
在这里插入图片描述

第2步:添加websocket++并设置一个endpoint type

websocket++包括两种主要的对象类型。端点和连接

  • 端点创建并启动新的连接,并维护这些连接的默认设置
  • 连接存储特定于每个WebSocket会话的信息。

WebSocket++端点是通过结合端点角色和端点配置来构建的。

有两种不同类型的端点角色:

  • WebSocket会话中的客户端角色websocketpp::client,需要用到头文件<websocketpp/client.hpp>
  • WebSocket会话中的服务器角色websocketpp::server, 需要用到头文件<websocketpp/server.hpp>

此处我们是客户端角色

术语:端点配置

  • webSocket++端点有一组配置,可以在编译时通过config template参数进行配置
  • 配置是struct类型的,它包含了用于生成具有特定属性的断点的类型和静态常量
  • 根据使用的配置,端点将具有不同的可用方法,并且可能具有其他第三方依赖项

endpoint角色接受一个名为config的模板参数,该参数用于在编译时配置endpoint的行为。

本教程我们将使用一个默认配置,

  • 该配置有一个叫做asio_client的库提供, 使用到了<websocketpp/config/asio_no_tls_client.hpp>.
  • asioasio_client提供网络传送的服务器配置,但不支持基于TLS的安全性。

将配置与端点角色结合起来生成一个完全配置的端点。这种类型将经常被使用,所以建议在这里使用typedef。

typedef websocketpp::client<websocketpp::config::asio_client> client

至此,我们的代码为:

#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>

#include <iostream>
#include <string>

typedef websocketpp::client<websocketpp::config::asio_client> client;

int main() {
    bool done = false;
    std::string input;

    while (!done) {
        std::cout << "Enter Command: ";
        std::getline(std::cin, input);

        if (input == "quit") {
            done = true;
        } else if (input == "help") {
            std::cout
                    << "\nCommand List:\n"
                    << "help: Display this help text\n"
                    << "quit: Exit the program\n"
                    << std::endl;
        } else {
            std::cout << "Unrecognized Command" << std::endl;
        }
    }

    return 0;
}

第3步:创建端点包装器对象(endpoint wrapper object),用来处理初始化和设置后台线程

为了使网络能够即使在后台运行也能够处理用户输入,我们将为WebScoket++处理循环使用一个单独的线程,从而可以令主线程可以自由的处理前台用户的输入。

为了为线程和端点启用简单的RAII风格的资源管理,我们将使用一个包装器对象,在其构造函数中配置它们

有关于: websocketpp :: lib命名空间

  • websocket++被设计为与c++ 11标准库一起使用。
  • 由于在流行的构建系统中并非普遍可用,因此Boost库可以用作C ++ 98构建环境中C ++ 11标准库的polyfill
  • 该websocketpp::lib命名空间是由库及其相关联的实施例以抽象掉两者之间的区别使用
  • websocketpp::lib::shared_ptr将std::shared_ptr在C ++ 11环境中评估为boost::shared_ptr

本教程使用websocketpp::lib包装器,因为它不知道阅读器的构建环境是什么。对于您的应用程序,除非您对类似的可移植性感兴趣,否则可以直接直接使用这些类型的boost或std版本。

websocket_endpoint构造函数中,发生了几件事:

  • 首先,通过清除所有访问和错误日​​志记录通道,将端点日志记录行为设置为静默
m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
m_endpoint.clear_error_channels(websocketpp::log::elevel::all);
  • 接下来,我们初始化端点下面的传输系统,并将其设置为永久模式。
    • 在永久模式下,当没有连接时,端点的处理循环不会自动退出。
    • 这一点很重要,因为我们希望这个端点在应用程序运行时保持活动状态,并在需要时处理对新的WebSocket连接的请求。
    • 这两种方法都是asio传输特有的。它们在使用非asio配置的终端中是不必要的,也不存在的。
m_endpoint.init_asio();
m_endpoint.start_perpetual();
  • 最后,我们启动一个线程开运行客户端端点的run方法
    • 当端点运行时,它将处理连接任务(读取并发送传入消息,帧并发送传出消息,等等)。
    • 因为它以永久模式运行,所以当没有激活的连接时,它将等待新的连接。
m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint));

目前位置,代码如下:

#include <websocketpp/config/asio_no_tls_client.hpp>
#include <websocketpp/client.hpp>

#include <websocketpp/common/thread.hpp>
#include <websocketpp/common/memory.hpp>

#include <iostream>
#include <string>

typedef websocketpp::client<websocketpp::config::asio_client> client;

class websocket_endpoint {
public:
    websocket_endpoint () {
        m_endpoint.clear_access_channels(websocketpp::log::alevel::all);
        m_endpoint.clear_error_channels(websocketpp::log::elevel::all);

        m_endpoint.init_asio();
        m_endpoint.start_perpetual();

        m_thread.reset(new websocketpp::lib::thread(&client::run, &m_endpoint));
    }
private:
    client m_endpoint;
    websocketpp::lib::shared_ptr<websocketpp::lib::thread> m_thread;
};

int main() {
    bool done = false;
    std::string input;
    websocket_endpoint endpoint;

    while (!done) {
        std::cout << "Enter Command: ";
        std::getline(std::cin, input);

        if (input == "quit") {
            done = true;
        } else if (input == "help") {
            std::cout
                    << "\nCommand List:\n"
                    << "help: Display this help text\n"
                    << "quit: Exit the program\n"
                    << std::endl;
        } else {
            std::cout << "Unrecognized Command" << std::endl;
        }
    }

    return 0;
}

第4步:打开socket连接

这一步向utility_client添加了两个新命令

  • 打开新连接的能力以及查看有关先前打开的连接的信息的能力
  • 每次打开的连接都会被分配一个整数连接id,程序的用户可以使用这个id与该连接进行交互

新的连接元数据对象:

  • 为了跟踪每个连接的信息,connection_metadata定义了一个对象
  • 该对象存储数字连接ID和在处理连接时将填写的许多字段。 包括连接状态(打开,打开,失败,关闭等),连接到的原始URI,来自服务器的标识值以及连接失败/关闭原因的说明

更新 websocket_endpoint :

  • websocket_endpoint对象已经获得一些新的数据成员和方法。现在它跟踪连接ID及其关联的元数据之间的映射以及要分发的下一个顺序ID号。
    • connect()方法启动一个新的连接
    • get_metadata方法检索给定ID的元数据。

连接方法: 新的WebSocket连接通过三步过程启动

  • 首先,由创建连接请求endpoint::get_connection(uri)
  • 接下来,配置连接请求。
  • 最后,将连接请求提交回端点,通过endpoint::connect()该端点将其添加到要建立的新连接队列中。

connection_ptr: websocket++使用引用计数的共享指针来跟踪与连接有关的资源。该指针的类型为endpoint::connection_ptr

  • connection_ptr 允许直接访问有关连接的信息,并允许更改连接设置
  • 由于这种直接访问以及在库中的内部资源管理角色,因此connection_prt除非在以下特定情况下,一般都是不安全的
    • endpoint::get_connection(...)endpoint::connect(): get_connection返回的connection_ptr。使用这个指针配置新连接是安全的。一旦您提交了要连接的连接,您可能就不再使用connection_ptr,并且应该立即丢弃它,以获得最佳的内存管理。
    • 在处理程序期间:WebSocket ++允许您为连接生命周期中发生的特定事件注册钩子/回调/事件处理程序。在调用其中一个处理程序期间,库保证可以安全地将aconnection_ptr用于与当前正在运行的处理程序关联的连接

connection_hdl: 由于connection_ptr库的线程安全性有限,该库还提供了更灵活的连接标识符,connection_hdl

  • connection_hdl的类型是websocketpp::connection_hdl,在<websocketpp/common/connection_hdl.hpp>中定义
    • connection_hdl保证在程序中是唯一的。单个程序中的多个端点将始终创建具有唯一句柄的连接。
    • connection_hdl与创建关联连接的端点使用不同的端点将导致不确定的行为。
    • 使用connection_hdl已关闭或删除其关联连接的连接是安全的。端点将返回一个特定错误,指出操作无法完成,因为关联的连接不存在。
  • 注意,与connection_ptr不同,这并不依赖于端点的类型或配置
  • connection_hdl是线程安全的,可以随时从任何线程使用。可以将它们复制并存储在容器中。删除hdl不会以任何方式影响连接。
  • 使用可以connection_ptr在处理程序调用期间将句柄升级为endpoint::get_con_from_hdl()。在connection_ptr该处理程序调用期间,可以安全地使用结果。

如果连接创建成功,将生成下一个连续的连接ID,并将connection_metadata对象插入到该ID下的连接列表中。

  • 首先,元数据对象存储连接ID、connection_hdl和打开连接的URI。
int new_id = m_next_id++;
metadata_ptr metadata(new connection_metadata(new_id, con->get_handle(), uri));
m_connection_list[new_id] = metadata;
  • 接下来,配置连接请求。对于此步骤,我们要做的唯一配置是设置一些默认处理程序。稍后,我们将返回并演示此处可能发生的一些更详细的配置(设置用户代理,源,代理,自定义标头,子协议等)。

注册处理程序

  • WebSocket ++提供了许多执行点,您可以在其中注册以运行处理程序。这些端点中哪些可用,将取决于其配置。
  • 可以在端点级别和连接级别注册处理程序。端点处理程序在创建时被复制到新的连接中。更改端点处理程序将仅影响将来的连接。在连接级别注册的处理程序将仅绑定到该特定连接。
  • 处理程序绑定方法的签名对于端点和连接是相同的。格式为:set__handler(…)。其中是处理程序的名称。例如,set_open_handler(…)将在打开新连接时将处理程序设置为被调用。set_fail_handler(…)将设置连接失败时调用的处理程序。
  • 所有处理程序都采用一个参数,这是一种可调用的类型,可以将其转换为std::function具有正确数量和参数类型的。您可以传递带有匹配参数列表的自由函数,函子和Lambda作为处理程序。另外,您可以使用std::bind(或boost::bind)向不匹配的参数列表注册函数。这对于传递在处理程序签名或成员函数中不存在的需要携带’this’指针的其他参数很有用。
  • 通常,所有处理程序都将connection_hdl标识此连接与哪个连接相关联作为第一个参数。一些处理程序(例如消息处理程序)包括其他参数。大多数处理器有一个void返回值,但一些(validate,ping,tls_init)没有

utility_client注册一个打开和失败处理程序。我们将使用它们来跟踪每个连接是成功打开还是失败。如果成功打开,我们将从打开握手中收集一些信息,并将其与我们的连接元数据一起存储。

  • 我们注册的Open处理程序connection_metadata::on_open将状态元数据字段设置为“ Open”,并从远程端点的HTTP响应中检索“ Server”标头的值,并将其存储在元数据对象中。服务器通常在此标头中设置标识字符串。
  • 我们注册的Open处理程序connection_metadata::on_open将状态元数据字段设置为“ Open”,并从远程端点的HTTP响应中检索“ Server”标头的值,并将其存储在元数据对象中。服务器通常在此标头中设置标识字符串。

在此示例中,我们将设置特定于连接的处理程序,这些处理程序直接绑定到与我们的连接关联的元数据对象。这使我们可以避免在每个处理程序中执行查找来查找我们计划更新的元数据对象,这效率更高一些。

让我们详细查看要发送的绑定参数:

con->set_open_handler(websocketpp::lib::bind(
    &connection_metadata::on_open,
    metadata,
    &m_endpoint,
    websocketpp::lib::placeholders::_1
));
  • &connection_metadata::on_open是该connection_metadata的on_open成员函数的地址。
  • metadata_ptr是指向与connection_metadata类关联的对象的指针。它将用作在其on_open上调用成员函数的对象。
  • &m_endpoint是正在使用的端点的地址。此参数将按原样传递给on_open方法。
  • 最后,websocketpp::lib::placeholders::_1是一个占位符,指示绑定的函数应在以后添加一个附加参数。WebSocket ++在connection_hdl调用处理程序时将使用填充此占位符。

最后,我们调用已endpoint::connect()配置的连接请求并返回新的连接ID。

新命令

  • connect [uri]:把uri传递给websocket_endpoint连接方法,并报告一个错误或新连接的连接ID。
  • show [connection id]:将检索并打印与该连接关联的元数据
} else if (input.substr(0,7) == "connect") {
    int id = endpoint.connect(input.substr(8));
    if (id != -1) {
        std::cout << "> Created connection with id " << id << std::endl;
    }
} else if (input.substr(0,4) == "show") {
    int id = atoi(input.substr(5).c_str());
 
    connection_metadata::ptr metadata = endpoint.get_metadata(id);
    if (metadata) {
        std::cout << *metadata << std::endl;
    } else {
        std::cout << "> Unknown connection id " << id << std::endl;
    }
}