首页 > 其他分享 >WebSocket入门到精通,就差这一篇了

WebSocket入门到精通,就差这一篇了

时间:2024-11-23 23:59:45浏览次数:12  
标签:std 精通 WebSocket 入门 服务器 HTTP 连接 客户端

目录

一、概述

二、对比Http(介绍特点)

1. 通信模式对比

2. 连接机制

3. 数据传输效率

4. 实时性

5. 使用场景

三、websocket通信原理

1、建立连接连接

1. 客户端发起 HTTP 请求

2. 服务器处理请求并响应

3. 升级到 WebSocket 协议

4.双向通信

2、通信内容(websocket帧)

1. Header(帧头部,2 字节)

2.Extend Payload (8字节,可选)

2. Masking Key(4 字节,可选)

4. Payload Data(实际数据)

3、帧的处理流程与模拟

处理流程

模拟流程

为什么WebSocket 设置掩码且为异或?

4、持久心跳机制

5、 关闭会话

1. 关闭流程

2. 关闭帧结构

3.自定义错误码

四、WebSocket基本框架

服务端框架

1. 头文件

2. WebSocket 服务器类型定义

3. 回调函数的实现

4、主函数逻辑

测试方法

客户端框架

测试方法

结束语


一、概述

        WebSocket是从HTML5开始⽀持的⼀种⽹⻚端和服务端保持⻓连接的消息推送机制。 传统的web程序都是属于"⼀问⼀答"的形式,即客⼾端给服务器发送了⼀个HTTP请求,服务器 给客⼾端返回⼀个HTTP响应。这种情况下服务器是属于被动的⼀⽅,如果客⼾端不主动发起请求 服务器就⽆法主动给客⼾端响应 ,但像⽹⻚即时聊天这样的程序都是⾮常依赖"消息推送"的,即需要服务器 主动推动消息到客⼾端。如果只是使⽤原⽣的HTTP协议,要想实现消息推送⼀般需要通过"轮 询"的⽅式实现,⽽轮询的成本⽐较⾼并且也不能及时的获取到消息的响应。 基于上述两个问题,就产⽣了WebSocket协议。WebSocket更接近于TCP这种级别的通信⽅式,⼀ 旦连接建⽴完成客⼾端或者服务器都可以主动的向对⽅发送数据。

二、对比Http(介绍特点)

        websocket的特点在概述当中我们大概可以寻得一些蛛丝马迹,但还需要更加系统的来阐述。如果直接表述websocketpp的特性,或者说特点,是很枯燥的。对于想入门的同学来说会有一些不知所云,可能看过就过去了,并不能很好的理解和感受,最终留不下什么印象;这里我们用对比http的方法来进行阐述。如果你准备开始了解websocket,那么你在网络部分不可能是一个一问三不知的小白,都是向进阶学习,了解新技术的人群。那么你对http再熟悉不过了,对比着来看,你会更加理解与加深印象;话不多说直接开始......

1. 通信模式对比

HTTP:基于请求-响应模式,客户端主动发送请求,服务端被动响应。每次通信需要重新建立连接(无状态)。

WebSocket:双向通信协议,建立连接后,客户端和服务端都可以主动发送消息(全双工)。连接建立后保持持续状态,不需要重复握手。

  • HTTP 更像是 “我问,你答” 的客服系统。
  • WebSocket 更像是电话通信,双方可以随时说话,不需要每次重新拨号。

2. 连接机制

HTTP:每次请求都会创建一个新的连接,完成后立即关闭。这种模式虽然简单,但对于频繁通信的场景效率较低。

WebSocket:只需一次握手,即可建立长连接。后续的通信都通过这个连接完成,避免了反复的连接开销,尤其适合实时性要求高的场景。

优势分析: WebSocket 的连接机制更节省资源,特别是在需要频繁交互的场景(如聊天、游戏、实时更新数据等)。

3. 数据传输效率

HTTP:每次请求都需要带上完整的请求头部信息,造成较大的额外开销。

WebSocket:初次握手后,后续传输只需少量的帧头信息,数据包更加紧凑,减少了网络带宽的浪费。

  • HTTP 就像每次寄快递都需要填完整的收寄信息。
  • WebSocket 就像直接搭建了一条输送带,传输效率更高。

4. 实时性

HTTP:对于实时性要求高的场景(如股票行情推送、在线聊天),需要不断轮询服务器获取最新数据,导致高延迟和资源浪费。

WebSocket:支持服务器主动推送消息,无需轮询,实现毫秒级的低延迟实时通信。

优势分析: WebSocket 的实时推送特性,使其成为需要即时响应场景的最佳选择(如在线游戏、协同编辑、物联网设备监控等)。


5. 使用场景

HTTP:适用于大多数传统的 Web 应用场景,如网页浏览、表单提交、文件下载等。WebSocket:**专为需要双向实时通信的场景设计,如:

  • 在线聊天(如微信、Slack 等)
  • 实时游戏(如在线多人竞技游戏)
  • 实时更新(如股票行情、新闻推送)
  • 物联网通信(如设备状态实时监控)

        在实现 WebSocket 功能时,C++ 开发者可以选择使用 WebSocket++,它是一款高效且易用的 C++ WebSocket 库。相比原生实现,WebSocket++ 提供了更高层次的封装,简化了开发流程。通过对比理解 HTTP 和 WebSocket 的特性后,你会更清楚 WebSocket++ 能为你的项目带来什么样的优势。

三、websocket通信原理

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

1、建立连接连接

1. 客户端发起 HTTP 请求

        客户端向服务器发起一个标准的 HTTP 请求,并在请求头中包含特定的 WebSocket 升级字段,以表明希望建立 WebSocket 连接。下面是一个http请求示例:

GET /chat HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Version: 13

Upgrade: websocket:请求升级到 WebSocket 协议。

Connection: Upgrade:明确表示连接需要升级。

Sec-WebSocket-Key:客户端生成的随机字符串,服务器通过它生成校验值。

Sec-WebSocket-Version:协议版本(目前通常是 13)。

2. 服务器处理请求并响应

        服务器收到 HTTP 请求后,验证请求是否满足 WebSocket 协议要求。若成功,服务器发送 HTTP 101 状态码作为响应,并包含必要的头部字段。下面是一个http应答示例:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=

101 Switching Protocols:表示协议已成功升级。

Upgrade: websocket:确认协议升级到 WebSocket。

Connection: Upgrade:确认连接升级。

Sec-WebSocket-Accept:基于客户端的 Sec-WebSocket-Key 和一个固定 GUID,(258EAFA5-E914-47DA-95CA-C5AB0DC85B11) 计算的校验值,用于确认连接的安全性。

校验值计算过程:

1,将客户端的 Sec-WebSocket-Key 与固定 GUID 拼接。

2,对拼接后的字符串进行 SHA-1 哈希。

3,将哈希结果进行 Base64 编码,得到 Sec-WebSocket-Accept。

3. 升级到 WebSocket 协议

        握手成功后,客户端和服务器之间的连接从 HTTP 协议切换为 WebSocket 协议,开始使用 WebSocket 帧进行双向通信。连接保持打开状态,直到显式关闭。通信过程中无需再发送 HTTP 头部,仅传输 WebSocket 帧,效率更高。

4.双向通信

        握手完成后,双方可以通过 WebSocket 数据帧进行双向实时通信:客户端可以发送消息到服务器。服务器可以主动推送消息到客户端。如果连接失败情况,握手请求无效(如缺少必要头部字段),服务器会返回错误响应(如 400 Bad Request)。如果服务器不支持 WebSocket 协议,则不会进行升级。

2、通信内容(websocket帧)

        上面的图片就是websocket通信的具体内容格式,也就是websocket帧,我们把上述很多小的区域总结为五个部分来依次介绍:

1. Header(帧头部,2 字节)

        帧的头部包含控制和标志信息,用于定义帧类型和消息状态。

第 1 字节

FIN(1 比特):表示是否是消息的最后一个帧。值为 1 表示这是最后一个帧;为 0 表示后续还有帧。

RSV1, RSV2, RSV3(各 1 比特):保留字段,通常为 0,用于未来扩展。

Opcode(4 比特):指定帧的类型(如文本、二进制、关闭等)。主要取值:0x0:延续帧(Continuation Frame)。0x1:文本帧(Text Frame)。0x2:二进制帧(Binary Frame)。0x8:关闭帧(Connection Close Frame)。0x9:Ping 帧(心跳检测)。0xA:Pong 帧(心跳响应)。

第 2 字节

MASK(1 比特):指示是否使用掩码(Masking Key)。客户端发送给服务器时,必须设置为 1,服务器返回时通常为 0。

Payload Length(7 比特):指定帧的数据部分长度:

值为 0-125:直接表示负载数据的长度。

值为 126:后续 2 字节扩展,表示负载数据长度。

值为 127:后续 8 字节扩展,表示负载数据长度。

2.Extend Payload (8字节,可选)

        消息长度由应用消息长度和扩展数据长度组成,<=125 字节仅使用Payload len,126 至2^16-1 字节Payload len值为126 Extended payload length 16 位表示长度。2^16 至 2^64-1 Payload len 值为127 Extended payload length 共8字节64位表示长度。

2. Masking Key(4 字节,可选)

        作用:WebSocket 要求客户端向服务器发送的每个帧必须使用掩码(Masking Key)对数据进行简单加密,防止恶意数据注入或缓存污染攻击。

        掩码处理:数据与 4 字节掩码通过按位异或操作进行加密。解密时,同样使用掩码按位异或还原。

4. Payload Data(实际数据)

        包含实际传输的数据内容,可以是文本(UTF-8 编码)或二进制数据。客户端发送时,数据会被 Masking Key 掩码处理。

3、帧的处理流程与模拟

处理流程

        客户端发送帧,当数据长度小于等于 125 字节时,直接用 Header 标注长度。数据超过 125 字节时,用扩展的 Length 字段表示长度。客户端使用 Masking Key 加密数据。服务器接收帧:检查 Header,解析 Opcode,确认帧类型。解码 Masking Key,恢复原始 Payload 数据。根据 Opcode 执行业务逻辑(如返回响应、处理数据)。

        服务器发送帧:不使用 Masking Key,直接发送明文数据帧。


模拟流程

客户端发送:

        客户端发送帧("Hello" 文本消息)假设 Payload 是字符串 "Hello":我们先来个根据要求写一个帧头部:

Header:
FIN = 1,Opcode = 0x1(文本帧)。
Mask = 1,Payload Length = 5。
Masking Key:ABCD(示例)。

        再进行加密数据:原始数据:"Hello" 的 ASCII 编码为 [72, 101, 108, 108, 111]。Masking Key 为 [65, 66, 67, 68]。加密后数据为:[72^65, 101^66, 108^67, 108^68, 111^65] => [9, 39, 47, 40, 46],之后就得到了最终帧

Header: FIN=1, Opcode=0x1, Mask=1, Payload Length=5
Masking Key: ABCD
Payload: [9, 39, 47, 40, 46]

服务器接收并响应:

        解析 Header:确定为文本帧。解码 Masking Key:使用相同的 Masking Key 还原数据。[9^65, 39^66, 47^67, 40^68, 46^65] => [72, 101, 108, 108, 111]恢复原始数据:"Hello"。服务器发送帧(明文帧)服务器向客户端发送 "Hello":

Header:
FIN = 1,Opcode = 0x1,Mask = 0。
Payload Length = 5。
Payload 数据:直接为 "Hello" 明文,无需加密。

为什么WebSocket 设置掩码且为异或?

        防止恶意客户端注入数据到代理缓存,避免缓存污染攻击(Cache Poisoning)。避免传输的明文数据直接暴露,提供一种基础的防护。并非强加密,WebSocket 的安全性依赖于底层的 TLS/SSL(如 wss://),而不是掩码算法本身。选择异或的原因如下:

1 简单高效

        异或操作计算非常简单,只需一条 CPU 指令即可完成,处理速度快。WebSocket 是实时通信协议,使用异或操作可以最大程度减少计算开销,确保高性能。

2 可逆性

        异或操作是对称的,加密和解密使用相同的算法:加密:Encrypted = Original XOR Key解密:Original = Encrypted XOR Key这种特性简化了掩码处理逻辑,尤其是对于服务器端处理大量帧时。

3 防止直接暴露数据

        虽然异或并不提供强加密,但掩码的目的是避免明文数据直接被拦截和使用,而不是提供完全的安全性。结合掩码键(随机生成的 4 字节),数据在传输中变得不易直接解读。

4、持久心跳机制

为了保证连接的存活,WebSocket 使用 心跳检测 来维持连接的状态。

心跳帧(Ping/Pong):

Ping 帧:由客户端或服务器发送,用于检测对方的状态。

Pong 帧:接收到 Ping 帧时,响应一个 Pong 帧,表示连接正常。

如果一方发送了 Ping,但未在指定时间内收到 Pong,通常认为连接已断开。

客户端周期性发送 Ping 帧,确保服务器在线。根据未收到的 Pong 帧判断是否断开连接。服务器可以主动向客户端发送 Ping 帧,检测客户端的状态。

5、 关闭会话

1. 关闭流程

客户端或服务器发起关闭:任意一方可以通过发送 关闭帧 主动关闭连接。关闭帧的 Opcode 为 0x8,可包含一个 2 字节的状态码和可选的原因描述。

对方响应关闭:收到关闭帧的一方应立即返回一个关闭帧作为响应,并释放资源。如果未正确响应关闭帧,发起方会在超时时强制关闭连接。

释放底层资源:双方完成关闭帧的交换后,底层的 TCP 连接将被断开,释放所有相关资源。

2. 关闭帧结构

        关闭帧包含以下信息:Opcode:0x8(关闭帧)。Payload:状态码(可选):2 字节,表示关闭原因。原因描述(可选):状态码后可附加 UTF-8 编码的字符串,进一步解释关闭原因。下图展示部分常见错误码,及错误码信息:

3.自定义错误码

        WebSocket 协议允许用户定义状态码,但范围必须在 3000-4999。例如:3000:自定义状态码,用于某个特定应用的关闭原因。4000:表示客户端违反了应用层协议规则。

四、WebSocket基本框架

        知道websocket是什么后,我们也要进一步的学习怎么来使用,看看它的框架是什么样的,这里我们着重介绍服务端简单框架,也提供客户端框架不做解释,大家自行研究。

        声明:基于c++的websocket框架,如果你也使用c++语言,那么可以直接拿来使用并且额外拓展;如果是其他语言,那么你也可以学习思路,下去之后用你的语言来实现。

服务端框架

        实现一个简单的客户端发送消息,服务器返回消息的功能

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



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




//自定义函数回调处理

//参数
// typedef lib::weak_ptr<void> connection_hdl;  
//  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)>  

//  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 http_callback(wsserver_t* srv,websocketpp::connection_hdl hdl)
{
    wsserver_t::connection_ptr conn=srv->get_con_from_hdl(hdl);
    std::cout<<"body:"<<conn->get_request_body()<<std::endl;
    websocketpp::http::parser::request req=conn->get_request();//http中request的解析对象
    //打印关键信息
     std::cout<<"method:"<<req.get_method()<<std::endl;
     std::cout<<"url:"<<req.get_uri()<<std::endl;
    //创建响应html
    std::string body="<html><body><h1>hello<html><body><h1>";
    //添加响应内容
    conn->set_body(body);
    //添加响应头部,k,v
    conn->append_header("Content-Type","text/html");
    //设置响应状态码
    conn->set_status(websocketpp::http::status_code::ok);
}


void wsopen_callback(wsserver_t* srv,websocketpp::connection_hdl hdl)
{
    //http响应,成功升级后触发
    std::cout<<"WebSocket 握手成功"<<std::endl;
    
}
void wsclose_callback(wsserver_t* srv,websocketpp::connection_hdl hdl)
{
    std::cout<<"WebSocket 连接断开"<<std::endl;
}
void wsmessage_callback(wsserver_t* srv,websocketpp::connection_hdl hdl,wsserver_t::message_ptr msg)
{
    wsserver_t::connection_ptr conn=srv->get_con_from_hdl(hdl);
    //打印收到的信息,通过message对象指针
    std::cout<<"wsmsg:"<<msg->get_payload()<<std::endl;
    //发送消息,连接类调用send函数,默认发送类型为text
    std::string rsp="client say: "+msg->get_payload();
    conn->send(rsp,websocketpp::frame::opcode::text);
}


int main()
{

    //1,实例化server对象
    wsserver_t wssrv;
    //2,设置日志输出等级
    wssrv.set_access_channels(websocketpp::log::alevel::none);
    //3,初始化asio框架中的调度器
    //	管理异步事件,它负责监听端口、接收新连接、读取和写入数据,
    //  并调度回调函数来处理这些事件。
    //  无需为每个连接创建线程
    wssrv.init_asio();
    wssrv.set_reuse_addr(true);
    //4,设置业务回调函数
    wssrv.set_open_handler(std::bind(wsopen_callback,&wssrv,std::placeholders::_1));
    wssrv.set_close_handler(std::bind(wsclose_callback,&wssrv,std::placeholders::_1));
    wssrv.set_message_handler(std::bind(wsmessage_callback,&wssrv,std::placeholders::_1,std::placeholders::_2));
    wssrv.set_http_handler(std::bind(http_callback,&wssrv,std::placeholders::_1));

    //5,设置服务器监听端口
    wssrv.listen(8085);
    //6,开始获取新的连接
    wssrv.start_accept();
    //7,启动服务
    wssrv.run();

    return 0;
}

1. 头文件

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

iostreamstring: 用于标准输入输出和字符串操作。

websocketpp/server.hpp: WebSocket++ 的服务器类,提供 WebSocket 和 HTTP 功能。

websocketpp/config/asio_no_tls.hpp: 使用 WebSocket++ 的无 TLS 配置,适用于未加密的连接。

2. WebSocket 服务器类型定义

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

websocketpp::server<websocketpp::config::asio>:

基于 ASIO(Boost Asio)的 WebSocket 服务器类。

asio_no_tls 表示不使用加密(TLS)。

wsserver_t: 定义了一个 WebSocket 服务器类型的别名,便于后续使用。

3. 回调函数的实现

        上图是库中提供的设置回调函数的方法,作用以及,回调参数;

  http_handler 是 WebSocket++ 提供的用于处理 HTTP 请求的回调函数。当客户端通过 HTTP 协议向服务器发送请求时,WebSocket++ 调用此回调函数处理请求的内容。这通常在 WebSocket 握手之前触发,用于响应普通 HTTP 请求或初始化 WebSocket 握手。回调函数会接收当前的连接句柄,允许开发者从中获取请求数据(如方法、URI 和请求体),并设置响应内容、头部信息以及 HTTP 状态码。通过 http_handler,服务器可以灵活地提供静态资源(如 HTML、JSON)或根据业务逻辑动态响应请求,从而支持混合 HTTP 和 WebSocket 的应用场景。

  open_handler 是在客户端成功完成 WebSocket 协议握手并建立连接后触发的回调函数。它的主要作用是处理连接建立的事件,例如记录日志、初始化与该连接相关的资源(如用户会话数据),或发送初始消息通知客户端连接已成功。通过 open_handler,服务器可以对新连接进行相关的设置和响应,确保后续通信的顺畅。这个回调仅在 WebSocket 协议握手成功后触发,与普通 HTTP 请求无关,是 WebSocket 连接生命周期中的第一个关键事件。

        close_handler 在 WebSocket 连接断开时触发,用于处理连接关闭的事件。无论是客户端主动关闭连接还是服务器端关闭连接,此回调都会被触发。开发者可以利用该回调清理与连接相关的资源(如缓存数据、会话状态),或者记录连接关闭的原因和时间。close_handler 的作用是确保连接断开时的后续处理逻辑得以正确执行,从而避免资源泄露或其他问题。它是 WebSocket 连接生命周期中的终止点,确保断开连接的过程是可控且安全的。

        message_handler 是处理客户端通过 WebSocket 发送消息的回调函数。在服务器收到客户端的消息后,WebSocket++ 调用此回调来处理具体的消息内容。通过回调参数,开发者可以访问消息的内容(文本或二进制),并根据业务逻辑进行响应,例如发送回执、广播消息或处理命令。message_handler 是 WebSocket 通信中的核心部分,它支持实时双向数据交换,使得服务器能够根据客户端发送的内容动态响应或执行相关操作,是实现实时交互的基础。

        对于这个connection_hdl是什么我们也来解释一下:

其实本质是一个弱指针也可以说是句柄,通常用来作为参数获取一个连接对象的指针,连接对象是什么呢?

        

        在 WebSocket++ 中,连接对象(connection object) 是一个关键的数据结构,表示客户端与服务器之间的单个连接。它封装了连接的所有信息,并提供接口与该连接进行交互。连接对象提供了一组方法,用于与客户端的连接进行交互,包括:

  • 发送消息:向客户端发送 WebSocket 消息或 HTTP 响应。
  • 获取信息:如请求头、请求体、远程 IP 地址等。
  • 控制状态:设置连接状态,断开连接等。

        在 WebSocket++ 的事件处理(如消息接收、连接关闭)中,连接对象被传递到回调函数,用于访问和操作当前连接。

4、主函数逻辑

 //1,实例化server对象
    wsserver_t wssrv;
    //2,设置日志输出等级
    wssrv.set_access_channels(websocketpp::log::alevel::none);
    //3,初始化asio框架中的调度器
    //	管理异步事件,它负责监听端口、接收新连接、读取和写入数据,
    //  并调度回调函数来处理这些事件。
    //  无需为每个连接创建线程
    wssrv.init_asio();
    wssrv.set_reuse_addr(true);
    //4,设置业务回调函数
    wssrv.set_open_handler(std::bind(wsopen_callback,&wssrv,std::placeholders::_1));
    wssrv.set_close_handler(std::bind(wsclose_callback,&wssrv,std::placeholders::_1));
    wssrv.set_message_handler(std::bind(wsmessage_callback,&wssrv,std::placeholders::_1,std::placeholders::_2));
    wssrv.set_http_handler(std::bind(http_callback,&wssrv,std::placeholders::_1));

    //5,设置服务器监听端口
    wssrv.listen(8085);
    //6,开始获取新的连接
    wssrv.start_accept();
    //7,启动服务
    wssrv.run();

1、创建一个 WebSocket++ 服务器对象 wssrv,其类型为websocketpp::server<websocketpp::config::asio>。该对象负责管理 WebSocket 和 HTTP 请求的处理。它是服务器运行的核心,集成了 Boost Asio 的异步 I/O 功能,用于处理连接、消息传递和断开等事件。

2、配置服务器的日志记录级别。websocketpp::log::alevel::none 表示关闭所有日志输出,避免不必要的控制台信息干扰。如果需要调试,可以开启特定日志级别,如错误日志或调试信息。这有助于监控服务器的运行状态或定位问题。

3、初始化 WebSocket++ 内部使用的 Boost Asio 调度器。调度器负责管理异步事件,包括监听端口、接收新连接、处理消息、读取和写入数据等。它采用事件驱动模式,无需为每个连接单独创建线程,大大提高了服务器的性能和可扩展性。设置服务器支持端口复用。启用后,服务器在关闭后可以快速重新绑定到同一端口,避免“地址已被使用”的错误。这在服务器重启或崩溃后重启时尤为重要,可以减少服务恢复的时间。

4、通过回调函数处理特定事件。

5、指定服务器监听的端口为 8085。此端口用于接收客户端的 HTTP 和 WebSocket 请求。设置监听端口是启动服务器的关键步骤,确保服务器可以接收到客户端的连接请求。

6、开始监听端口并接受新连接。调用后,服务器进入事件循环模式,随时准备接收来自客户端的连接请求,并触发相关的回调函数来处理连接。

7、启动 WebSocket++ 的主事件循环。服务器开始处理所有事件,包括连接、消息、断开等。事件循环保持服务器运行直到显式停止,是服务器正常运行的核心机制。

测试方法

        运行程序,可以通过网页访问ip,端口号;也可以通过已经写好的网页html(跟换为运行程序机器的ip以及端口号),直接访问就可以

效果如下:

<!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" placeholder="请输入消息">
     <button id="submit">提交</button>
 
     <script>
         // 创建 WebSocket 实例
         let websocket = new WebSocket("ws://122.51.138.185:8085/");
 
         // WebSocket 连接打开的回调
         websocket.onopen = function () {
             console.log("WebSocket 连接成功");
             alert("WebSocket 连接成功");
         };
 
         // WebSocket 收到消息的回调
         websocket.onmessage = function (e) {
             console.log("收到消息:", e.data);
             alert("收到消息: " + e.data);
         };
 
         // WebSocket 连接错误的回调
         websocket.onerror = function (error) {
             console.error("WebSocket 连接异常:", error);
             alert("WebSocket 连接异常");
         };
 
         // WebSocket 连接关闭的回调
         websocket.onclose = function () {
             console.log("WebSocket 连接关闭");
             alert("WebSocket 连接关闭");
         };
 
         // 等待 DOM 加载完成后绑定按钮点击事件
         window.onload = function () {
             console.log("DOM 加载完成");
             let input = document.querySelector("#message");
             let button = document.querySelector("#submit");
 
             // 按钮点击事件
             button.onclick = function () {
                 console.log("按钮点击事件触发");
                 alert("按钮点击事件触发");
 
                 try {
                     if (websocket.readyState === WebSocket.OPEN) {
                         console.log("准备发送消息");
                         alert("准备发送消息");
                         websocket.send("Send begin");
                         console.log("发送消息:", input.value);
                         alert("发送消息: " + input.value);
                         websocket.send(input.value);
                         input.value = ""; // 清空输入框
                     } else {
                         console.error("WebSocket 未连接,无法发送消息");
                         alert("WebSocket 未连接,请检查服务器或刷新页面重试");
                     }
                 } catch (error) {
                     console.error("发送消息时发生错误:", error);
                     alert("发送消息时发生错误: " + error.message);
                 }
             };
         };
     </script>
 </body>
 
 </html>
 

客户端框架

        简单功能:连接到 WebSocket 服务器。发送文本消息到服务器。接收服务器的响应。正常关闭连接。

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

#include <iostream>
#include <string>
#include <functional>
#include <memory>
#include <thread>

// 定义 WebSocket 客户端类型
typedef websocketpp::client<websocketpp::config::asio_client> client;
//websocketpp::config::asio_client 是 WebSocket++ 提供的配置,用于不加密的 WebSocket 客户端。


class WebSocketClient {
public:
    WebSocketClient() {
        // 初始化 WebSocket++ 客户端
        ws_client.init_asio();

        // 设置消息处理回调函数
        ws_client.set_message_handler(std::bind(
            &WebSocketClient::onMessage, this, std::placeholders::_1, std::placeholders::_2
        ));

        // 设置打开连接的回调函数
        ws_client.set_open_handler(std::bind(
            &WebSocketClient::onOpen, this, std::placeholders::_1
        ));

        // 设置关闭连接的回调函数
        ws_client.set_close_handler(std::bind(
            &WebSocketClient::onClose, this, std::placeholders::_1
        ));

        // 设置错误处理回调函数
        ws_client.set_fail_handler(std::bind(
            &WebSocketClient::onFail, this, std::placeholders::_1
        ));
    }

    // 连接到 WebSocket 服务器
    void connect(const std::string& uri) {
        websocketpp::lib::error_code ec;

        // 创建连接
        client::connection_ptr conn = ws_client.get_connection(uri, ec);
        if (ec) {
            std::cerr << "Error creating connection: " << ec.message() << std::endl;
            return;
        }

        // 保存连接句柄
        handle = conn->get_handle();

        // 启动连接
        ws_client.connect(conn);

        // 运行 ASIO 服务
        ws_thread = std::thread([this]() { ws_client.run(); });
    }

    // 发送消息
    void sendMessage(const std::string& message) {
        websocketpp::lib::error_code ec;
        ws_client.send(handle, message, websocketpp::frame::opcode::text, ec);
        if (ec) {
            std::cerr << "Send failed: " << ec.message() << std::endl;
        }
    }

    // 关闭连接
    void close() {
        websocketpp::lib::error_code ec;
        ws_client.close(handle, websocketpp::close::status::normal, "Closing connection", ec);
        if (ec) {
            std::cerr << "Close failed: " << ec.message() << std::endl;
        }

        if (ws_thread.joinable()) {
            ws_thread.join();
        }
    }

private:
    client ws_client;
    websocketpp::connection_hdl handle;
    std::thread ws_thread;

    // 处理打开连接事件
    void onOpen(websocketpp::connection_hdl hdl) {
        std::cout << "Connection opened!" << std::endl;
    }

    // 处理消息事件
    void onMessage(websocketpp::connection_hdl hdl, client::message_ptr msg) {
        std::cout << "Message received: " << msg->get_payload() << std::endl;
    }

    // 处理关闭连接事件
    void onClose(websocketpp::connection_hdl hdl) {
        std::cout << "Connection closed!" << std::endl;
    }

    // 处理连接失败事件
    void onFail(websocketpp::connection_hdl hdl) {
        std::cerr << "Connection failed!" << std::endl;
    }
};

// 主程序
int main() {
    // 创建客户端对象
    WebSocketClient ws_client;

    // 连接到 WebSocket 服务器
    std::string uri = "ws://echo.websocket.events";
    ws_client.connect(uri);

    // 等待连接建立
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 发送消息
    ws_client.sendMessage("Hello, WebSocket!");

    // 等待响应
    std::this_thread::sleep_for(std::chrono::seconds(2));

    // 关闭连接
    ws_client.close();

    return 0;
}

测试方法

        启动代码后,客户端将连接到 ws://echo.websocket.events(一个公共 WebSocket 回显测试服务器)。客户端发送 "Hello, WebSocket!",服务器会回显该消息。程序会接收到响应并输出消息,然后正常关闭连接。

结束语

     希望通过这篇WebSocket 的介绍后,能够对websocket有一个深刻印象,希望你会更清楚 WebSocket++ 能为你的项目带来什么样的优势。

标签:std,精通,WebSocket,入门,服务器,HTTP,连接,客户端
From: https://blog.csdn.net/2201_75373858/article/details/143985359

相关文章

  • RabbitMQ 入门(七)SpringAMQP五种消息类型(Direct Exchange)
    一、发布订阅-DirectExchange(路由模式)在Fanout模式中,一条消息,会被所有订阅的队列都消费。但是,在某些场景下,我们希望不同的消息被不同的队列消费。这时就要用到Direct类型的Exchange。DirectExchange会将接收到的消息根据规则路由到指定queue,因此称为路由模式(r......
  • RabbitMQ 入门(四)SpringAMQP五种消息类型(Basic Queue)
    一、SpringAMQP简介SpringAMQP是基于RabbitMQ封装的一套模板,并且还利用SpringBoot对其实现了自动装配,使用起来非常方便。SpringAmqp的官方地址:https://spring.io/projects/spring-amqpSpringAMQP提供了三个功能:-自动声明队列、交换机及其绑定关系-基于注解的......
  • RabbitMQ 入门(五)SpringAMQP五种消息类型(Work Queue)
    一、WorkQueue(工作消息队列)Workqueues,也被称为(Taskqueues),任务模型。简单来说就是让多个消费者绑定到一个队列,共同消费队列中的消息。当消息处理比较耗时的时候,可能生产消息的速度会远远大于消息的消费速度。长此以往,消息就会堆积越来越多,无法及时处理。此时就可以......
  • Ollydbg的入门使用
    修改完一个文件之后,点击保存到可执行文件就可以保存。 1.各窗口简单介绍B:即Breakpointswindow,断点窗口,用来管理和查看用户的断点。L:logwindow,日志窗口,用来显示程序的输入输出和一些断点之类的日志信息E:Executablemoduleswindow,也就是可执行模块窗口,列出当前进程......
  • jvm入门
    jvm入门一:jvm的介绍定义jvm的全称是Javavirtualmachine(java虚拟机)-java的运行环境(java二进制字节码的运行环境)作用:1:wirteonce,runanywhere。一次编译可以在任意平台运行2:自动内存管理:垃圾回收机制3:数组下标越界检查(对比c)4:多太jvm,jre,jdk的关系:jdk包......
  • 【数据库入门】关系型数据库入门及SQL语句的编写
    1.数据库的类型:数据库分为网状数据库,层次数据库,关系型数据库和非关系型数据库四种。目前市场上比较主流的是:关系型数据库和非关系型数据库。关系型数据库使用结构化查询语句(SQL)对关系型数据库进行操作。2.关系型数据库数据以二维表的形式进行存储,表和表之间可以建立关......
  • MySQL入门学习(五)
      在本篇文章中,我们会将MySQL剩余的基础知识讲解完毕,这也就是我们MySQL专栏里的收官之作辣~~~事务  在MySQL当中,什么叫做事务呢?一个事务其实就是一个完整的业务逻辑,可以举个生活中常见的例子:  假设A要向B转账1000元,那么我们需要在数据库中做以下几个步骤:①将A账户......
  • 密码学入门总结&古典密码&现代密码学&RSA入门原理
    密码学入门总结&古典密码&现代密码学&RSA入门原理文章目录密码学入门总结&古典密码&现代密码学&RSA入门原理前言一、古典密码1.单表替换密码1.1凯撒密码1.2Atbash密码1.3摩斯密码1.4仿射密码2.多表替换密码2.1维吉尼亚密码2.2自动密钥密码3.其他类型密码3.1栅栏密......
  • DFS入门笔记
    DFS深度优先搜索-入门笔记DFS依靠递归的思想,总是往更远的方向行进,直到达到边界,再返回到上一步考虑另外的方向(递归-回溯)递归实现指数型枚举从\(1\)~\(n\)这\(n\)个整数中随机选取任意多个,输出所有可能的选择方案,即计算\(2^n\)我们考虑有\(n\)个空位,每次都选择填......
  • RabbitMQ2:介绍、安装、快速入门、数据隔离
    欢迎来到“雪碧聊技术”CSDN博客!在这里,您将踏入一个专注于Java开发技术的知识殿堂。无论您是Java编程的初学者,还是具有一定经验的开发者,相信我的博客都能为您提供宝贵的学习资源和实用技巧。作为您的技术向导,我将不断探索Java的深邃世界,分享最新的技术动态、实战经验以及项目......