目录
一、概述
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>
iostream
和 string
: 用于标准输入输出和字符串操作。
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