【websocket】安装与使用

websocket安装与使用

  • [1. 介绍](#1. 介绍)
  • [2. 安装](#2. 安装)
  • [3. websocketpp常用接口](#3. websocketpp常用接口)
  • [4. Websocketpp使用](#4. Websocketpp使用)
    • [4.1 服务端](#4.1 服务端)
    • [4.2 客户端](#4.2 客户端)

1. 介绍

WebSocket 是从 HTML5 开始支持的一种网页端和服务端保持长连接的 消息推送机制。

  • 传统的 web 程序都是属于 "一问一答" 的形式,即客户端给服务器发送了一个
    HTTP 请求,服务器给客户端返回一个 HTTP 响应。这种情况下服务器是属于被动的一方,如果客户端不主动发起请求服务器就无法主动给客户端响应
  • 像网页即时聊天这样的程序非常依赖 "消息推送" 的,即需要服务器主动推动消息到客户端。如果只是使用原生的 HTTP 协议,要想实现消息推送一般需要通过 "轮询" 的方式实现, 而轮询的成本比较高并且也不能及时的获取到消息的响应。

基于上述两个问题, 就产生了 WebSocket 协议。WebSocket 更接近于 TCP 这种级别的通信方式,一旦连接建立完成客户端或者服务器都可以主动的向对方发送数据。

原理解析

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

具体协议升级的过程如下:

报文格式

报文字段比较多,我们重点关注这几个字段:

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

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

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

    • 0x0: 表示这是个延续帧,当 opcode 为 0 表示本次数据传输采用了数据分片,当前收到的帧为其中一个分片
    • 0x1: 表示这是文本帧
    • 0x2: 表示这是二进制帧
    • 0x3-0x7: 保留,暂未使用
    • 0x8: 表示连接断开
    • 0x9: 表示 ping 帧
    • 0xa: 表示 pong 帧
    • 0xb-0xf: 保留,暂未使用
  • mask:表示 Payload 数据是否被编码,若为 1 则必有 Mask-Key,用于解码

    Payload 数据。仅客户端发送给服务端的消息需要设置。

  • Payload length:数据载荷的长度,单位是字节, 有可能为 7 位、7+16 位、7+64位。假设 Payload length = x

    • x 为 0~126:数据的长度为 x 字节
    • x 为 126:后续 2 个字节代表一个 16 位的无符号整数,该无符号整数的值为数据的长度
    • x 为 127:后续 8 个字节代表一个 64 位的无符号整数(最高位为 0),该无符号整数的值为数据的长度
  • Mask-Key:当 mask 为 1 时存在,长度为 4 字节,解码规则: DECODED[i] =

    ENCODED[i] ^ MASK[i % 4]

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

Websocketpp 介绍

WebSocketpp 是一个跨平台的开源(BSD 许可证)头部专用 C++库,它实现了

RFC6455(WebSocket 协议)和 RFC7692(WebSocketCompression Extensions)。它允许将 WebSocket 客户端和服务器功能集成到 C++程序中。在最常见的配置中,全功能网络 I/O 由 Asio 网络库提供。

WebSocketpp 的主要特性包括:

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

WebSocketpp 同时支持 HTTP 和 Websocket 两种网络协议,可以该库作为项目的依赖库用来搭建 HTTP 和 WebSocket 服务器。

总结

websocket是一个应用层的tcp长连接协议。搭建一个websocket服务器其实就是搭建一个tcp服务器,只不过应用层使用websocket协议格式进行数据处理。

与此对比的就是httplib,它是一个短连接,只是让我快速搭建一个Http服务器,让我们重点关注业务处理。假如在一个项目中,不单单是 请求 - 响应 的业务处理,还包含了数据的主动推送,而这种消息数据的主动推送,是Http协议无法实现的。它只能是客户端发起请求,然后服务器收到给一个响应。因此需要搭建一个长连接的服务器,用于服务端主动向客户端推送数据。

选择websocket协议的考虑:因为Http通信支持websocket协议的切换。

websocket通信框架的选择:websocketpp 即支持websocket通信,也支持http通信。

2. 安装

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

3. websocketpp常用接口

cpp 复制代码
namespace websocketpp
{
    typedef lib::weak_ptr<void> connection_hdl;

    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;
        typedef typename connection_type::message_ptr message_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*/
        //weak_ptr无法对对象直接操作,必须要获得对应的shared_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_handlercallback);
    };


	//继承endpoint
    template <typename config>
    class server : public endpoint<connection<config>, config>
    {
        /*初始化并启动服务端监听连接的 accept 事件处理*/
        void start_accept();
    };

    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 http
    {
        namespace parser
        {
            class parser
            {
                std::string const &get_header(std::string const &key)
                std::string const &get_body() typedef std::map<std::string, std::string,utility::ci_less> header_list;
                header_list const &get_headers()
            }; 
            class request : public parser
            {
                /*获取请求方法*/
                std::string const &get_method()
                /*获取请求 uri 接口*/
                std::string const &get_uri()
            };
        }
    };

    class 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;
        };
    }

    namespace http
    {
        namespace status_code
        {
            enum value
            {
                uninitialized = 0,
                continue_code = 100,
                switching_protocols = 101,
                ok = 200,
                created = 201,
                accepted = 202,
                non_authoritative_information = 203,
                no_content = 204,
                reset_content = 205,
                partial_content = 206,
                multiple_choices = 300,
                moved_permanently = 301,
                found = 302,
                see_other = 303,
                not_modified = 304,
                use_proxy = 305,
                temporary_redirect = 307,
                bad_request = 400,
                unauthorized = 401,
                payment_required = 402,
                forbidden = 403,
                not_found = 404,
                method_not_allowed = 405,
                not_acceptable = 406,
                proxy_authentication_required = 407,
                request_timeout = 408,
                conflict = 409,
                gone = 410,
                length_required = 411,
                precondition_failed = 412,
                request_entity_too_large = 413,
                request_uri_too_long = 414,
                unsupported_media_type = 415,
                request_range_not_satisfiable = 416,
                expectation_failed = 417,
                im_a_teapot = 418,
                upgrade_required = 426,
                precondition_required = 428,
                too_many_requests = 429,
                request_header_fields_too_large = 431,
                internal_server_error = 500,
                not_implemented = 501,
                bad_gateway = 502,
                service_unavailable = 503,
                gateway_timeout = 504,
                http_version_not_supported = 505,
                not_extended = 510,
                network_authentication_required = 511
            };
        }
    }
    namespace frame
    {
        namespace opcode
        {
            enum value
            {
                continuation = 0x0,
                text = 0x1,
                binary = 0x2,
                rsv3 = 0x3,
                rsv4 = 0x4,
                rsv5 = 0x5,
                rsv6 = 0x6,
                rsv7 = 0x7,
                close = 0x8,
                ping = 0x9,
                pong = 0xA,
                control_rsvb = 0xB,
                control_rsvc = 0xC,
                control_rsvd = 0xD,
                control_rsve = 0xE,
                control_rsvf = 0xF,
            };
        }
    }
}

4. Websocketpp使用

4.1 服务端

websocketpp搭建服务器流程:

  1. 定义server类型
  2. 实例化服务器对象
  3. 初始化日志输出 -- 关闭日志输出
  4. 初始化asio框架
  5. 设置消息处理/连接握手成功/连接关闭回调函数/连接异常回调函数
  6. 启动地址重用
  7. 设置监听窗口
  8. 开始监听
  9. 启动服务器
cpp 复制代码
#include<iostream>
#include<websocketpp/config/asio_no_tls.hpp>
#include<websocketpp/server.hpp>
#include<sstream>

// 1. 定义server类型
typedef websocketpp::server<websocketpp::config::asio> websocketsvr;

//websocket握手成功回调函数
void onopen(websocketpp::connection_hdl hdl)
{
    std::cout<<"websocket长连接建立成功"<<std::endl;
}

//websocket 连接关闭回调处理函数
void onclose(websocketpp::connection_hdl hdl)
{
    std::cout<<"websocket关闭连接"<<std::endl;
}

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

//websocket 消息回调处理函数
void onmessage(websocketsvr* server,websocketpp::connection_hdl hdl,websocketsvr::message_ptr msg)
{
    //1. 获取有效消息载荷数据,进行业务处理
    std::string body = msg->get_payload();
    std::cout<<"收到客户端消息: "<<body<<std::endl;
    //2. 对客户端进行响应
    //获取通信连接
    auto conn = server->get_con_from_hdl(hdl);
    //发送数据
    conn->send(body + "服务器回复",websocketpp::frame::opcode::text);
}

//http 请求回调处理函数
void onhttp(websocketsvr* server,websocketpp::connection_hdl hdl)
{
    auto conn = 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>";

    conn->set_body(ss.str());
    conn->set_status(websocketpp::http::status_code::ok);
}


int main()
{
    // 2. 实例化服务器对象
    websocketsvr server;
    // 3. 初始化日志输出 -- 关闭日志输出
    // all 表示打印全部级别日志
    // none 表示什么日志都不打印
    server.set_access_channels(websocketpp::log::alevel::none);
    // 4. 初始化asio框架
    server.init_asio();
    // 5. 设置消息处理/连接握手成功/连接关闭回调函数/连接异常回调函数
    server.set_open_handler(onopen);
    server.set_close_handler(onclose);
    server.set_fail_handler(std::bind(onfail,&server,std::placeholders::_1));
    server.set_message_handler(std::bind(onmessage,&server,std::placeholders::_1,std::placeholders::_2));
    server.set_http_handler(std::bind(onhttp,&server,std::placeholders::_1));
    // 6. 启动地址重用
    server.set_reuse_addr(true);
    // 7. 设置监听窗口
    server.listen(8080);
    // 8. 开始监听
    server.start_accept();
    // 9. 启动服务器
    server.run();


    return 0;
}
cpp 复制代码
server:server.cc
	g++ -o $@ $^ -std=c++17 -lpthread -lboost_system

4.2 客户端

Http 客户端

使用浏览器作为 http 客户端即可, 访问服务器的 8080 端口。

WS 客户端

html 复制代码
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://124.223.54.148:8080
 // 类比 http
 // ws 表示 websocket 协议
 // 192.168.51.100 表示服务器地址
 // 8888 表示服务器绑定的端口
 let websocket = new WebSocket("ws://124.223.54.148:8080");
 
 // 处理连接打开的回调函数
 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>
相关推荐
网硕互联的小客服3 小时前
如何利用Elastic Stack(ELK)进行安全日志分析
linux·服务器·网络·安全
浩浩测试一下4 小时前
Authpf(OpenBSD)认证防火墙到ssh连接到SSH端口转发技术栈 与渗透网络安全的关联 (RED Team Technique )
网络·网络协议·tcp/ip·安全·网络安全·php
leagsoft_10034 小时前
联软NSPM自动化策略管理 助力上交所加速国产化替代提升运维效率
运维·网络·自动化
Think Spatial 空间思维5 小时前
【实施指南】Android客户端HTTPS双向认证实施指南
android·网络协议·https·ssl
昔我往昔6 小时前
https和http有什么区别-http各个版本有什么区别
网络协议·http·https
漫步者TZ6 小时前
【Netty系列】解决TCP粘包和拆包:LengthFieldBasedFrameDecoder
java·网络协议·tcp/ip·netty
leagsoft_10036 小时前
筑牢企业网管域安全防线,守护数字核心——联软网管域安全建设解决方案
网络·安全·网络安全
苦学编程的谢7 小时前
Java网络编程API 1
java·开发语言·网络
alien爱吃蛋挞7 小时前
【JavaEE】万字详解HTTP协议
网络·网络协议·http
一只帆記9 小时前
HTTP、WebSocket、SSE 对比
websocket·http