WebSocket 服务器实现详解:ws_svr 模块深度剖析
前言
在当今的 Web 应用开发领域,WebSocket 协议凭借其建立持久化双向通信通道的独特优势,已经成为实时通信不可或缺的技术选择。无论是即时通讯、在线协作,还是实时数据监控,WebSocket 都扮演着至关重要的角色。
本文将带领读者深入探索 ws_svr 模块的内部实现,从架构设计到代码细节,全方位解析一个功能完善的 WebSocket 服务器是如何从零构建的。无论你是 WebSocket 技术的初学者,还是希望深入了解服务器实现细节的开发者,都能从本文中获得有价值的见解。
一、整体架构概览
ws_svr 模块建立在 hv(libhv)库的坚实基础之上,采用了经典的单例模式设计,为开发者提供了一套完整且易于使用的 WebSocket 服务功能。该模块的设计充分考虑了性能、安全性和可扩展性,使其能够胜任各种实时通信场景的需求。
整体架构如下图所示,清晰地展示了各个核心组件之间的协作关系:
#mermaid-svg-LLy8b9My3ogEvv3I{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-LLy8b9My3ogEvv3I .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-LLy8b9My3ogEvv3I .error-icon{fill:#552222;}#mermaid-svg-LLy8b9My3ogEvv3I .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-LLy8b9My3ogEvv3I .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-LLy8b9My3ogEvv3I .marker{fill:#333333;stroke:#333333;}#mermaid-svg-LLy8b9My3ogEvv3I .marker.cross{stroke:#333333;}#mermaid-svg-LLy8b9My3ogEvv3I svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-LLy8b9My3ogEvv3I p{margin:0;}#mermaid-svg-LLy8b9My3ogEvv3I .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-LLy8b9My3ogEvv3I .cluster-label text{fill:#333;}#mermaid-svg-LLy8b9My3ogEvv3I .cluster-label span{color:#333;}#mermaid-svg-LLy8b9My3ogEvv3I .cluster-label span p{background-color:transparent;}#mermaid-svg-LLy8b9My3ogEvv3I .label text,#mermaid-svg-LLy8b9My3ogEvv3I span{fill:#333;color:#333;}#mermaid-svg-LLy8b9My3ogEvv3I .node rect,#mermaid-svg-LLy8b9My3ogEvv3I .node circle,#mermaid-svg-LLy8b9My3ogEvv3I .node ellipse,#mermaid-svg-LLy8b9My3ogEvv3I .node polygon,#mermaid-svg-LLy8b9My3ogEvv3I .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-LLy8b9My3ogEvv3I .rough-node .label text,#mermaid-svg-LLy8b9My3ogEvv3I .node .label text,#mermaid-svg-LLy8b9My3ogEvv3I .image-shape .label,#mermaid-svg-LLy8b9My3ogEvv3I .icon-shape .label{text-anchor:middle;}#mermaid-svg-LLy8b9My3ogEvv3I .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-LLy8b9My3ogEvv3I .rough-node .label,#mermaid-svg-LLy8b9My3ogEvv3I .node .label,#mermaid-svg-LLy8b9My3ogEvv3I .image-shape .label,#mermaid-svg-LLy8b9My3ogEvv3I .icon-shape .label{text-align:center;}#mermaid-svg-LLy8b9My3ogEvv3I .node.clickable{cursor:pointer;}#mermaid-svg-LLy8b9My3ogEvv3I .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-LLy8b9My3ogEvv3I .arrowheadPath{fill:#333333;}#mermaid-svg-LLy8b9My3ogEvv3I .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-LLy8b9My3ogEvv3I .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-LLy8b9My3ogEvv3I .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLy8b9My3ogEvv3I .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-LLy8b9My3ogEvv3I .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLy8b9My3ogEvv3I .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-LLy8b9My3ogEvv3I .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-LLy8b9My3ogEvv3I .cluster text{fill:#333;}#mermaid-svg-LLy8b9My3ogEvv3I .cluster span{color:#333;}#mermaid-svg-LLy8b9My3ogEvv3I div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-LLy8b9My3ogEvv3I .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-LLy8b9My3ogEvv3I rect.text{fill:none;stroke-width:0;}#mermaid-svg-LLy8b9My3ogEvv3I .icon-shape,#mermaid-svg-LLy8b9My3ogEvv3I .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-LLy8b9My3ogEvv3I .icon-shape p,#mermaid-svg-LLy8b9My3ogEvv3I .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-LLy8b9My3ogEvv3I .icon-shape rect,#mermaid-svg-LLy8b9My3ogEvv3I .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-LLy8b9My3ogEvv3I .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-LLy8b9My3ogEvv3I .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-LLy8b9My3ogEvv3I :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
WebSocket 服务器架构
事件处理
连接管理
核心组件
onopen连接建立
webSocketSvr单例服务器
WebSocketServer底层服务器
WebSocketService服务处理器
HttpService心跳检测
连接映射表std::map
连接ID计数器atomic
Token认证器
onmessage消息接收
onclose连接断开
二、核心类设计
2.1 webSocketSvr 类
webSocketSvr 作为整个模块的核心类,承担着服务器初始化、连接管理和消息分发等重要职责。该类采用了单例模式,确保在整个应用程序的生命周期中只存在一个服务器实例,从而避免了资源浪费和状态冲突的问题。
class webSocketSvr : public wheels::dm::singleton< webSocketSvr >
{
private:
std::string m_listen_ip__; // 监听IP地址
int m_listen_port__; // 监听端口
std::shared_ptr<hv::WebSocketService > pt_ws_service__; // WebSocket服务
std::shared_ptr<hv::WebSocketServer> pt_ws_svr__; // WebSocket服务器
std::shared_ptr<hv::HttpService> pt_ping__; // HTTP心跳服务
std::map< uint32_t , std::shared_ptr< stConnection > > m_channels__; // 连接管理
token * p_token__; // Token认证器
std::atomic< uint32_t > m_conn_id__; // 连接ID计数器
};
设计亮点:
- 智能指针管理资源:通过 std::shared_ptr 管理核心组件的生命周期,有效避免了内存泄漏和悬垂指针等常见问题,确保了系统的稳定性和可靠性。
- 线程安全的 ID 生成:采用 std::atomic 实现连接 ID 的原子递增操作,在多线程环境下依然能够保证 ID 的唯一性和正确性,消除了竞态条件带来的潜在风险。
- 高效的连接管理:利用 std::map 数据结构存储所有活跃连接,不仅提供了 O(log n) 时间复杂度的查找性能,还支持自动排序和快速遍历,为后续的广播和消息分发功能奠定了坚实基础。
2.2 stConnection 类
stConnection 类封装了单个 WebSocket 连接的所有必要信息,是连接管理的最小单元。每个连接对象都包含一个唯一的标识符和对应的 WebSocket 通道,使得服务器能够精确地定位和管理每一个客户端连接。
class stConnection
{
private:
uint32_t m_id; // 连接唯一ID
WebSocketChannelPtr p_channel__; // WebSocket通道
};
三、初始化流程详解
3.1 构造函数与初始化
服务器的初始化过程被精心设计为三个紧密衔接的阶段,每个阶段都承担着特定的职责,确保服务器能够以最佳状态启动并运行。整个初始化流程如下图所示:
Token认证器
WebSocketService
WebSocketServer
webSocketSvr
客户端
Token认证器
WebSocketService
WebSocketServer
webSocketSvr
客户端
#mermaid-svg-7NHg39KrnrvDV3or{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-7NHg39KrnrvDV3or .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-7NHg39KrnrvDV3or .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-7NHg39KrnrvDV3or .error-icon{fill:#552222;}#mermaid-svg-7NHg39KrnrvDV3or .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-7NHg39KrnrvDV3or .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-7NHg39KrnrvDV3or .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-7NHg39KrnrvDV3or .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-7NHg39KrnrvDV3or .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-7NHg39KrnrvDV3or .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-7NHg39KrnrvDV3or .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-7NHg39KrnrvDV3or .marker{fill:#333333;stroke:#333333;}#mermaid-svg-7NHg39KrnrvDV3or .marker.cross{stroke:#333333;}#mermaid-svg-7NHg39KrnrvDV3or svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-7NHg39KrnrvDV3or p{margin:0;}#mermaid-svg-7NHg39KrnrvDV3or .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7NHg39KrnrvDV3or text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-7NHg39KrnrvDV3or .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7NHg39KrnrvDV3or .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-7NHg39KrnrvDV3or .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-7NHg39KrnrvDV3or .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-7NHg39KrnrvDV3or #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-7NHg39KrnrvDV3or .sequenceNumber{fill:white;}#mermaid-svg-7NHg39KrnrvDV3or #sequencenumber{fill:#333;}#mermaid-svg-7NHg39KrnrvDV3or #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-7NHg39KrnrvDV3or .messageText{fill:#333;stroke:none;}#mermaid-svg-7NHg39KrnrvDV3or .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7NHg39KrnrvDV3or .labelText,#mermaid-svg-7NHg39KrnrvDV3or .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-7NHg39KrnrvDV3or .loopText,#mermaid-svg-7NHg39KrnrvDV3or .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-7NHg39KrnrvDV3or .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-7NHg39KrnrvDV3or .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-7NHg39KrnrvDV3or .noteText,#mermaid-svg-7NHg39KrnrvDV3or .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-7NHg39KrnrvDV3or .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7NHg39KrnrvDV3or .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7NHg39KrnrvDV3or .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-7NHg39KrnrvDV3or .actorPopupMenu{position:absolute;}#mermaid-svg-7NHg39KrnrvDV3or .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-7NHg39KrnrvDV3or .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-7NHg39KrnrvDV3or .actor-man circle,#mermaid-svg-7NHg39KrnrvDV3or line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-7NHg39KrnrvDV3or :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
初始化阶段
创建服务器实例
注入Token认证器
init__() 初始化
创建WebSocketServer
创建WebSocketService
init_ws_service__() 注册事件处理器
init_ssl__() 配置SSL(可选)
注册WebSocketService
注册HttpService(ping)
初始化完成
关键代码分析:
webSocketSvr::webSocketSvr( token* p_token )
: p_token__( p_token ), m_conn_id__( 0 )
{
if( !p_token ){
ERROR_MSG( \”token不能为空\” );
throw std::runtime_error( \”token不能为空\” );
}
init__();
}
构造函数的设计体现了防御性编程的思想。首先,它会对传入的 Token 认证器进行严格的参数校验,确保其有效性。如果 Token 认证器为空,构造函数会立即抛出异常,阻止服务器进入不完整的状态。这种\”快速失败\”的策略有助于在问题发生的早期阶段就将其暴露出来,避免了后续可能出现的难以追踪的错误。通过参数校验后,构造函数会调用 init__() 方法,正式启动服务器的初始化流程。
3.2 WebSocket 服务初始化
init_ws_service__() 方法是整个初始化流程的核心,它负责注册三个关键的事件处理器,这些处理器将贯穿 WebSocket 连接的整个生命周期。事件处理器的注册流程如下图所示:
#mermaid-svg-HPqKJTA7MpgV2LRt{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HPqKJTA7MpgV2LRt .error-icon{fill:#552222;}#mermaid-svg-HPqKJTA7MpgV2LRt .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HPqKJTA7MpgV2LRt .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HPqKJTA7MpgV2LRt .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HPqKJTA7MpgV2LRt .marker.cross{stroke:#333333;}#mermaid-svg-HPqKJTA7MpgV2LRt svg{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HPqKJTA7MpgV2LRt p{margin:0;}#mermaid-svg-HPqKJTA7MpgV2LRt .label{font-family:\”trebuchet ms\”,verdana,arial,sans-serif;color:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster-label text{fill:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster-label span{color:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster-label span p{background-color:transparent;}#mermaid-svg-HPqKJTA7MpgV2LRt .label text,#mermaid-svg-HPqKJTA7MpgV2LRt span{fill:#333;color:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt .node rect,#mermaid-svg-HPqKJTA7MpgV2LRt .node circle,#mermaid-svg-HPqKJTA7MpgV2LRt .node ellipse,#mermaid-svg-HPqKJTA7MpgV2LRt .node polygon,#mermaid-svg-HPqKJTA7MpgV2LRt .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-HPqKJTA7MpgV2LRt .rough-node .label text,#mermaid-svg-HPqKJTA7MpgV2LRt .node .label text,#mermaid-svg-HPqKJTA7MpgV2LRt .image-shape .label,#mermaid-svg-HPqKJTA7MpgV2LRt .icon-shape .label{text-anchor:middle;}#mermaid-svg-HPqKJTA7MpgV2LRt .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-HPqKJTA7MpgV2LRt .rough-node .label,#mermaid-svg-HPqKJTA7MpgV2LRt .node .label,#mermaid-svg-HPqKJTA7MpgV2LRt .image-shape .label,#mermaid-svg-HPqKJTA7MpgV2LRt .icon-shape .label{text-align:center;}#mermaid-svg-HPqKJTA7MpgV2LRt .node.clickable{cursor:pointer;}#mermaid-svg-HPqKJTA7MpgV2LRt .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-HPqKJTA7MpgV2LRt .arrowheadPath{fill:#333333;}#mermaid-svg-HPqKJTA7MpgV2LRt .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-HPqKJTA7MpgV2LRt .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-HPqKJTA7MpgV2LRt .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HPqKJTA7MpgV2LRt .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-HPqKJTA7MpgV2LRt .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HPqKJTA7MpgV2LRt .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster text{fill:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt .cluster span{color:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:\”trebuchet ms\”,verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-HPqKJTA7MpgV2LRt .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-HPqKJTA7MpgV2LRt rect.text{fill:none;stroke-width:0;}#mermaid-svg-HPqKJTA7MpgV2LRt .icon-shape,#mermaid-svg-HPqKJTA7MpgV2LRt .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-HPqKJTA7MpgV2LRt .icon-shape p,#mermaid-svg-HPqKJTA7MpgV2LRt .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-HPqKJTA7MpgV2LRt .icon-shape rect,#mermaid-svg-HPqKJTA7MpgV2LRt .image-shape rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-HPqKJTA7MpgV2LRt .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-HPqKJTA7MpgV2LRt .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-HPqKJTA7MpgV2LRt :root{–mermaid-font-family:\”trebuchet ms\”,verdana,arial,sans-serif;}
网硕互联帮助中心





评论前必须登录!
注册