我们来谈谈websocket

news2024/11/13 15:26:10

 "你一无所有地闯荡。"


一、初始WebSocket

(1) 什么是websocket

        WebSocket是一种在单个TCP连接上进行全双工通信的协议。

        WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。

                                                                                                                        WebSocket介绍

        websocket是从HTML5开始⽀持的⼀种⽹⻚端和服务端保持 "⻓连接" 的消息推送机制。可是,我们了解到,我们使用中最广泛的 就是http\https,为什么突然又 像 "一拍脑袋般” 地搞出另一套协议呢? websocket协议又和http\https协议有何关系呢?

(2) 为什么已经有了http协议,却仍然需要websocket?

        我们传统的web程序交互,基本属于 " 一问一答方式 ", 譬如这里有很多商品,你看上了哪一个,直接点击该商品周围位置,即可向服务器发送跳转请求。           此时,客户端向服务器发送一个Http跳转请求,服务器那么也就会想当然地返回一个跳转后的Http响应结果。

        从该举的例子开看,

        "服务端永远是被动的一方",如果客户端不主动如果客⼾端不主动发起请求服务器就⽆法主动给客⼾端响应。

        这种感觉像什么?就像你心爱的女生,从不会主动发消息找你一样。

        可是,现实生活中也有这样的场景,你打开一个页面游戏,一进去此时就算你什么也不干,会看到一个 小卡拉咪 开始疯狂攻击你,像这样……

         可是你知道,你什么也没干,按以往理解的传统web交互方式是,“一问一答”。但对于这种场景而言,非常依赖 "服务器"消息推送,告知客户端一些"状态"(例如: 你现在被打掉血了)。此时,就即需要服务器主动推动消息到客⼾端。

        原生的http能实现吗? 当然!客户端显然可以采用一种 "轮询"的⽅式,不停向服务器发送http报头,得到结果反馈。但这其实是一种 "伪服务器推"的方式,并且 轮询的成本⽐较⾼,服务端也不能及时的获取到消息的响应。因此,对于网页游戏这种在同一时间会产生大量的数据推送,这种方式一定是不高效的。

        

        我们都知道tcp是全双工的(即两端都可以互发数据,互接收数据)。可是,按照http的发送方式,"一问一答",好好的全双工机制,反而成了半双工了! 当然,这也不能怪http,因为很早网络没那么发达时,对于http应用层协议设计考虑之初,仅仅着眼于,看网页文本资源,压根没考虑网页游戏等,需要大量服务器向客户端推送数据的场景。

        由此,新的应该层协议,websocket被设计出来。

    

(3) websocket与http有什么区别?

websocket协议切换(升级)

        我们大概会使用网页做如下的三种场景: 看文本信息、刷视频、玩玩页游。

         三次请求的首先都是向服务器发送http请求,进行第一次通信。对于client1、client2而言,使用普通的http请求就能完成正常的数据交互过程,那么这它们就会继续保持用http协议进行交互。可是,对于client3而言,使用http协议并不能满足数据交互的需求,因此对它而言,就需要建立websocket来与服务器进行数据交互。

        此时,第二次http请求会在请求报头中携带一些特殊的报头信息:

        

响应报头:

        

        

也许你会看到这样的言论,

websocket是基于http的协议,这对吗?

        这其实是不对的,因为websocket是在建立连接时,才进行切换的协议。

        

        而websocket是需要http来进行协议切换的,升级完成之后和http就没有任何关系了。 

        就像你喜欢的女神,借你搭线要到了你室友的联系方式。接下来之后他们就开始聊了起来,把你晾在了一边。

        websocket完美继承了tcp的全双工能力,适用于服务器和客户端需要大量数据交互的场景,            如:网页游戏、小程序游戏、网路聊天室……

(4) websocket协议格式

        在websocket这一层,数据包按照 "帧"称呼。 

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

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

• opcode(0000~1111):标志当前数据帧的类型
0x0:表⽰这是个延续帧,当opcode为0表⽰本次数据传输采⽤了数据分⽚,当前收到的帧为其中⼀个分⽚
0x1:表⽰这是⽂本帧
0x2:表⽰这是⼆进制帧
0x3-0x7:保留,暂未使⽤
0x8:表⽰连接断开
0x9:表⽰ping帧
0xA:表⽰pong帧
0xB-0xF:保留,暂未使⽤

• mask:表⽰Payload数据是否被编码,若为1则必有Mask-Key,⽤于解码Payload数据。仅 "客⼾端发送给服务端" 的消息需要设置。 这也是秘钥:

• Payloadlength:数据载荷的⻓度,单位是字节,有可能为”7位、7+16位、7+64位”。假设Payloadlength=x
x为0~125:表示payload的总共长度
x为126: 表示该范围在(126~ 7位+16位(65,535) ) 这个时候需要读16位,表示payload的真实长度。

 x为127: 表示该范围为(127,7位+64位) 按照这里获取payload的数据字段大小。

• Payloaddata:报⽂携带的载荷数据


二、  Websocketpp介绍

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

        

 WebSocketpp的主要特性包括:

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

下面是一些websocketpp库介绍网站:

⽤⼾⼿册:WebSocket++: Main Page

官⽹:WebSocket++ | Zaphoyd Studios

(1) websocket相关接口的介绍

① 服务器初始化函数:

namespace websocketpp
{
        template <typename config>
        class server : public endpoint<connection<config>,config> {
            /*websocketpp基于asio框架实现,init_asio⽤于初始化asio框架中的io_service调度器*/
            void init_asio();

            /*设置是否启⽤地址重⽤ == setsockopt*/
            void set_reuse_addr(bool value);

            /*设置endpoint的绑定监听端⼝ == listen(sockfd,backlog)*/
            void listen(uint16_t port);

            /*初始化并启动服务端监听连接的accept事件处理 == accept*/
            void start_accept();

            /*对io_service对象的run接⼝封装,⽤于启动服务器*/
            std::size_t run();
    };
}

② 连接句柄:

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

 ③ 相关函数设置:

namespace websocketpp
{
    // 设置⽇志打印等级
    void set_access_channels(log::level channels);
    // 清除指定等级的⽇志
    void clear_access_channels(log::level channels);
    
    // 设置bind回调函数
    void set_open_handler(open_handler h);
    void set_close_handler(close_handler h);
    void set_message_handler(message_handler h);
    void set_http_handler(http_handler h);


    // websocket发送数据接口
    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);
}

④ http相关:

报文设置

namespace websocketpp
{

    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 websocketpp
{
    namespace http {
        namespace parser {
                class parser {
                    // 取得报头
                    std::string const & get_header(std::string const & key)
                }
                class request : public parser {
                    /*获取请求⽅法*/
                    std::string const & get_method()
                    /*获取请求uri接⼝*/
                    std::string const & get_uri()
             };
         }
    }

}

状态码:

/* http 状态码 */
namespace http {
    namespace status_code {
        enum value {
        uninitialized = 0,
        continue_code = 100,
        switching_protocols = 101,

        /* 200 */
        ok = 200,
        created = 201,
        accepted = 202,
        non_authoritative_information = 203,
        no_content = 204,
        reset_content = 205,
        partial_content = 206,
        
        /* 300 */
        multiple_choices = 300,
        moved_permanently = 301,
        found = 302,
        see_other = 303,
        not_modified = 304,
        use_proxy = 305,
        temporary_redirect = 307


        //....
}

 格式类型:

namespace frame {
    namespace opcode {
        enum value {
            continuation = 0x0,
            text = 0x1,
            binary = 0x2,
            rsv3 = 0x3,
            rsv4 = 0x4,
            rsv5 = 0x5,
            rsv6 = 0x6,
            rsv7 = 0x7,
            close = 0x8,    
            // .... 
        }
    }
}

⑤ 日志等级:

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


三、 搭建简易的基于websocket的服务器

(1) 初始化服务器     

① 头文件包含

        因为需要初始化服务器,因此我们得首先找到websocket库路径下的server.hpp,以及需要配置的调度器asio文件。

       创建的是server,并且不采用加密tls。因此,我们的头文件就需要包含这两个文件。

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

int main()
{
    return 0;
}

② 基本websocket信息 

 // 配置调度器
 // 鉴于" websocketpp::server<websocketpp::config::asio> "这个类型太长了 我们进行typedef一下
typedef websocketpp::server<websocketpp::config::asio> wsser_t;
int main()
{
    wsser_t wsser;
    // 1. 设置日志等级
    wsser.set_access_channels(websocketpp::log::alevel::none);
    
    // 2. 初始化调度器
    wsser.init_asio();

    // 3. 设置地址重用
    wsser.set_reuse_addr(true);
    return 0;
}

③ 回调函数设置:

        根据库中提供的回调函数类型:

    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;

         仅仅这样设置,对于回调函数而言,只是拿到了一个未初始化的connection_hd1, 我们需要用wsser_t 中的 方法 get_conn_from_hdl(), 才能真正拿到连接句柄。因此,我们必须要把wsser传入到函数中。可是,该函数已经设置,并且typedef好了,怎么才能给该函数增加参数呢?这里,我们就需要用到C++提供的bind函数。

 

④ 服务器正常启动:

(2) http报头解析

         搭建完了简易的服务器,我们接下来通过编写回调函数,来控制我们的行为。

void http_callback(wsser_t *srv, websocketpp::connection_hdl hd1)
{
    // 交由智能指针管理
    wsser_t::connection_ptr conn = srv->get_con_from_hdl(hd1);
    // 获取正文
    std::string req_body = conn->get_request_body();
    std::cout << "req_body: " << req_body << std::endl;

    // 获取方法和uri = 解析http
    websocketpp::http::parser::request req = conn->get_request();
    std::cout << "method: " << req.get_method() << std::endl;
    std::cout << "uri: " << req.get_uri() << std::endl;

    // 构建响应
    std::string resp_body = "<html><body><h1>Hello World</h1></body></html>";
    conn->set_body(resp_body);
    conn->append_header("Content-Type","text/html");
    conn->set_status(websocketpp::http::status_code::ok);
}

        这个函数是针对处理http请求的报文,并返回一个 "Hello World" 的标签页面。

测试:
        

        这就是一个普通的http请求与响应。 

        

(3) websocket主动向客户端发消息

        这里,我们采取客户端向服务端 提交 "信息",服务端通过websocket进行将消息再继续回显到客户端上。

        在开始之前,我们得有一个前端交互的页面:

<!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 实例 
    // 这里专门连接 服务器主机
	let websocket = new WebSocket("ws://47.115.203.63:8801");
	// 处理连接打开的回调函数
	websocket.onopen = function() {
		alert("连接建立");
	}
	
	// 处理收到消息的回调函数
	// 控制台打印消息
	websocket.onmessage = function(e) {
		alert("收到消息: " + e.data);
	}
	
	// 处理连接异常的回调函数
	websocket.onerror = function() {
		alert("连接异常");
	}
	
	// 处理连接关闭的回调函数
	websocket.onclose = function() {
		alert("连接关闭");
	}
	
	// 实现点击按钮后, 通过 websocket实例 向服务器发送请求
	let input = document.querySelector('#message');
	let button = document.querySelector('#submit');
	button.onclick = function() {
		alert("发送消息: " + input.value);
		websocket.send(input.value);
		input.value = ""; // 清空消息
	}
	
</script>
</body>
</html>

测试:

走的是 websocket协议层。 

 

        最终,我们的websocket简易服务器也就搭建好了。


 

总结:

        websocket无论是在学习、还是使用上肯定都简单于手搓一个 系统层面上的建立连接和http报文Parse。它通过http进行协议切换(升级),保证服务端可以主动向客户端推送消息。

本篇到此结束,感谢你的阅读。

祝你好运,向阳而生~

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/690843.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自学黑客(网络安全),一般人我劝你还是算了吧(自学网络安全学习路线--第九章 Internet安全协议)【建议收藏】

文章目录 一、自学网络安全学习的误区和陷阱二、学习网络安全的一些前期准备三、自学网络安全学习路线一、安全协议概述二、IPSec协议1、概述2、IP封装过程3、IPSec不安全性4、IPSec的功能5、IPSec体系结构6、IPSec的AH7、IPSec的AH8、IPSec的ESP9、IPSec的ESP10、ISAKMP11、IK…

分析油烟污染的危害及其控制防治对策 安科瑞 许敏

摘 要&#xff1a;介绍了烹饪油烟的组成及危害&#xff0c;着重概述了家庭烹饪油烟污染特点以及净化技术的研究进展&#xff0c;对各技术特点及存在的问题进行了分析&#xff0c;初步探讨了新近发展的静电催化耦合技术在烹饪排放污染控制中的应用&#xff0c;分析了现行的吸油烟…

全志V3S嵌入式驱动开发(spi-nand image制作和烧入)

【 声明:版权所有,欢迎转载,请勿用于商业用途。 联系信箱:feixiaoxing @163.com】 上一篇文章,我们说到了spi-nor image的制作和输入。相比较spi-nor,spi-nand虽然在稳定性上面差一点,但是价格上面有很大的优势。举例来说,一般32M的spi-nor大约在6-7元左右,但…

软件外包开发在线监控工具

软件系统上线后需要实时监控&#xff0c;这样在系统出现问题后可以及时发现问题并解决问题。今天和大家分享常见的软件系统监控工具&#xff0c;这些工具功能强大且成熟稳定&#xff0c;熟练的应用可以帮助运维人员解决很多项目中的实际问题。北京木奇移动技术有限公司&#xf…

ubuntu dlib 编译 人脸检测

编译&#xff1a; ubuntu14.04 dlib19.2【 C 】Face Landmark Detection_FR-0912的博客-CSDN博客 也可这样 cmake .. -DUSE_AVX_INSTRUCTIONS1 cmake --build .测试代码 linux安装dlib&#xff0c;关键点检测_dlib linux_Peanut_范的博客-CSDN博客 CmakeList.txt cmake_mini…

QtCreator工具下载链接

QT工具下载链接&#xff1a; 离线安装的安装包下载链接&#xff1a; Index of /archive/qthttp://download.qt.io/archive/qt/ 在线安装的安装包下载链接&#xff1a; Index of /archive/online_installershttps://download.qt.io/archive/online_installers/

第八十六天学习记录:Linux基础:基础指令Ⅰ

Linux系统的目录结构 Linux的目录结构是一个树型结构 Linux没有盘符的概念&#xff0c;只有一个根目录/&#xff0c;所有文件都在根目录下面 Linux系统的路径表达形式 在Linux系统中&#xff0c;路径之间的层级关系使用&#xff1a;/来表示。&#xff08;windows系统中用\&a…

ecology9-导出流程上图片附件的方案

ecology9 导出流程上图片附件 方案一方案二√方案三 ecology9 把图片名称位置等信息存储在imagefile中&#xff0c;实际文件以zip压缩包的形式存储在服务器上。需求是提供导出历史流程上的图片附件&#xff0c;方便新系统导入 方案一 编写图片附件下载接口&#xff0c;查询到图…

Java双指针专题——1.移动0 2.复写0 3.快乐数4.盛最多水的容器5.有效三角形的个数 6.和为s的两个数字7.三数之和8.四数之和

目录 1.移动0 2.复写0 3.快乐数 4.盛最多水的容器 5.有效三角形的个数 6.和为s的两个数字 7.三数之和 8.四数之和 1.移动0 给定一个数组 nums&#xff0c;编写一个函数将所有 0 移动到数组的末尾&#xff0c;同时保持非零元素的相对顺序。 请注意 &#xff0c;必须在不复制数组…

实施预测性维护的策略和实践探讨

随着工业设备复杂性和关键性的增加&#xff0c;传统的计划性维护方法已经无法满足现代工业的需求。预测性维护作为一种基于设备状态和数据分析的维护策略&#xff0c;能够准确预测设备故障&#xff0c;降低停机时间&#xff0c;提高生产效率和设备可靠性。在实施预测性维护之前…

初出茅庐的小李博客之RTC时间设置

串口上位机设置RTC时间进行校准 方式1&#xff1a;发送固定格式时间解析 代码&#xff1a; #include <stdio.h> #include <string.h> /* 当在格式字符串中使用 %*[^:] 时&#xff0c;它表示在读取输入时跳过冒号 : 之前的任何字符。 %*&#xff1a;星号 * 表示读…

DeepMind发布多任务机器人RoboCat;沧海拾珍之LLM、GPT

&#x1f989; AI新闻 &#x1f680; DeepMind发布多任务机器人控制AI模型RoboCat 摘要&#xff1a;谷歌旗下DeepMind发布了名为RoboCat的AI模型&#xff0c;该模型可以控制不同机器人手臂执行多项任务。RoboCat是第一个能够解决和适应多种任务的模型&#xff0c;并且使用真实…

过滤器-filter

1 Servlet规范中的过滤器-Filter 1.1 过滤器入门 1.1.1 过滤器概念及作用 过滤器——Filter&#xff0c;它是JavaWeb三大组件之一。另外两个是Servlet和Listener。 它是在2000年发布的Servlet2.3规范中加入的一个接口。是Servlet规范中非常实用的技术。 它可以对web应用中…

CSS3新增了哪些新特性?

一、是什么 css&#xff0c;即层叠样式表&#xff08;Cascading Style Sheets&#xff09;的简称&#xff0c;是一种标记语言&#xff0c;由浏览器解释执行用来使页面变得更美观 css3是css的最新标准&#xff0c;是向后兼容的&#xff0c;CSS1/2的特性在CSS3 里都是可以使用的…

最优化方法(基于lingo)之 求解线性规划问题(1/6)

一、实验目的&#xff1a; 1. 会建立合理的规划模型&#xff1b; 2. 学习掌握Matlab中求解线性规划的命令&#xff1b; [x,fval]linprog(f,A,b); [x,fval]linprog(f,A,b,Aeq,beq); [x,fval]linprog(f,A,b,Aeq,beq,lb;ub); 3. 要求学生能在计算机上应用各种优化软件包熟练地操作…

音视频流媒体开发工作机会占80%的市场份额

音视频流媒体开发领域的工作机会在过去几年中确实呈现出了快速增长的趋势。随着互联网的普及和网络带宽的提高&#xff0c;人们对音视频内容的需求也越来越大&#xff0c;这导致了许多公司和组织寻求音视频流媒体开发人员来满足市场需求。 音视频流媒体开发工作的主要流程可以概…

对MVVM和MVC开发模式的理解

对MVVM和MVC开发模式的理解 1、MVVM2、MVC3、MVVM与MVC的区别 1、MVVM MVVM最早由微软提出来&#xff0c;它借鉴了桌面应用程序的MVC思想&#xff0c;在前端页面中&#xff0c;把Model用纯JavaScript对象表示&#xff0c;View负责显示&#xff0c;两者做到了最大限度的分离&am…

基于 SpringBoot 2.X 框架的智能制造云办公系统,已开源

简介 基于SpringBoot 2.X框架的智能制造云办公系统&#xff0c;立志打造ERP生产功能的软件&#xff0c;专注进销存财务功能生产管理&#xff0c;适合各行业。主要完成从下单->进货->生产->出库的过程&#xff0c;涉及到领料&#xff0c;出入库&#xff0c;工序&#…

Kotlin Multiplatform项目探索之KMChat

Kotlin Multiplatform项目探索之KMChat 这是当前在 Kotlin Multiplatform 官方文档中提议的用例结构。Kotlin Multiplatform 中包含的许多子组件已经发布了稳定版本&#xff0c;即使目前不稳定的组件也在迅速更新。 本文中的演示项目包括 Compose Multiplatform - Web (Experi…

【微服务架构设计和实现】4.9 微服务测试和部署最佳实践

往期回顾&#xff1a; 第一章&#xff1a;【云原生概念和技术】 第二章&#xff1a;【容器化应用程序设计和开发】 第三章&#xff1a;【基于容器的部署、管理和扩展】 第四章&#xff1a;【4.1 微服务架构概述和设计原则】 第四章&#xff1a;【4.2 服务边界的定义和划分…