Qt中的QWebSocket 和 QWebSocketServer详解:从协议说明到实际应用解析

前言

本篇围绕 QWebSocket 和 QWebSocketServer,从协议基础、通信模式、数据传输特点等方面展开,结合具体接口应用与实战案例进行说明。
在实时网络通信领域,WebSocket 技术以其独特的全双工通信能力,成为连接客户端与服务器的重要桥梁。Qt 框架中的 QWebSocket 和 QWebSocketServer 类,封装了 WebSocket 协议的复杂细节,为开发者提供了简洁高效的接口。本文将从 WebSocket 协议基础出发,全面解析 QWebSocket 与 QWebSocketServer 的通信模式、数据传输特点、应用场景、核心接口及实战案例,帮助开发者快速掌握这一技术,轻松构建实时通信应用。

一、WebSocket 协议基础:实时通信的 “高速公路”

在了解 QWebSocket 和 QWebSocketServer 之前,我们需要先认识它们所基于的 WebSocket 协议。WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议,由 IETF 在 RFC 6455 中定义,它解决了传统 HTTP 协议在实时通信场景中的局限性。

1.1 为什么需要 WebSocket?

传统的 HTTP 协议是一种 “请求 - 响应” 模式的无状态协议,客户端只能主动向服务器发送请求,服务器无法主动向客户端推送数据。在实时通信场景(如在线聊天、实时数据监控)中,这种模式会导致以下问题:

  • 轮询效率低下:客户端需要不断发送 HTTP 请求询问服务器是否有新数据(如每隔 1 秒发送一次请求),即使没有新数据,也会产生大量无效请求,浪费带宽和服务器资源。
    延迟较高:轮询的间隔时间决定了数据更新的延迟,间隔太长会导致数据滞后,间隔太短则会增加服务器负担。
  • 连接开销大:每次 HTTP 请求都需要建立 TCP 连接(三次握手),并携带大量头部信息,增加了通信成本。

WebSocket 协议的出现正是为了解决这些问题,它提供了一种持久化的连接,允许客户端和服务器之间进行双向实时通信。

1.2 WebSocket 协议的核心特性

WebSocket 协议具有以下核心特性,使其成为实时通信的理想选择:

  • 全双工通信:一旦建立连接,客户端和服务器可以同时向对方发送数据,就像一条双向车道,数据可以双向流动。
  • 持久连接:连接建立后会一直保持,直到客户端或服务器主动关闭,避免了频繁建立连接的开销。
  • 低开销:WebSocket 连接建立后,数据传输的头部信息非常精简(仅 2-10 字节),远低于 HTTP 请求的头部开销。
  • 基于 TCP:WebSocket 建立在 TCP 协议之上,继承了 TCP 的可靠性(数据有序、无丢失、无重复)。
  • 与 HTTP 兼容:WebSocket 连接的建立过程(握手)使用 HTTP 协议,因此可以与现有 HTTP 服务器和网络基础设施兼容,通过 80(ws)和 443(wss)端口通信,避免被防火墙拦截。

1.3 WebSocket 握手过程:从 HTTP 到 WebSocket 的 “变身”

WebSocket 连接的建立需要经过一次特殊的 HTTP 握手过程,具体步骤如下:

  • 客户端发送握手请求:客户端向服务器发送一个 HTTP GET 请求,包含特殊的头部信息,表明想要升级到 WebSocket 协议。
GET /chat HTTP/1.1
Host: example.com:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
  • Upgrade: websocket和Connection: Upgrade头部告诉服务器,客户端希望将连接升级到 WebSocket 协议。
    Sec-WebSocket-Key是一个 Base64 编码的随机字符串,用于服务器验证和生成响应密钥。
    Sec-WebSocket-Version指定 WebSocket 协议版本(通常为 13)。
  • 服务器响应握手:如果服务器支持 WebSocket 协议,会返回一个 HTTP 101(Switching Protocols)响应,表明连接已成功升级。
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Sec-WebSocket-Accept是服务器通过Sec-WebSocket-Key计算得到的密钥(将Sec-WebSocket-Key与固定字符串258EAFA5-E914-47DA-95CA-C5AB0DC85B11拼接后,计算 SHA-1 哈希并进行 Base64 编码),客户端会验证该密钥以确认握手成功。
连接建立:握手成功后,HTTP 连接正式升级为 WebSocket 连接,双方可以开始双向数据传输。

1.4 WebSocket 数据帧:数据传输的 “包装盒”

WebSocket 协议中,数据通过 “帧”(Frame)的形式传输。帧是数据传输的基本单位,包含以下关键字段:

  • FIN:1 位,表示当前帧是否为消息的最后一帧(1 表示最后一帧,0 表示后续还有帧)。
  • Opcode:4 位,表示帧的类型,常见值包括:
  • 0x00:延续帧(用于拼接大数据消息)
  • 0x01:文本帧(UTF-8 编码的文本数据)
  • 0x02:二进制帧(二进制数据)
  • 0x08:连接关闭帧
  • 0x09:Ping 帧(心跳检测)
  • 0x0A:Pong 帧(Ping 响应)
  • Mask:1 位,表示数据是否经过掩码处理(客户端发送给服务器的帧必须掩码,服务器发送给客户端的帧不能掩码)。
  • Payload Length:7 位、7+16 位或 7+64 位,表示数据载荷的长度。
  • Masking-Key:32 位(仅当 Mask 为 1 时存在),用于对数据载荷进行解掩码。
  • Payload Data:实际传输的数据内容。

这种帧结构设计既保证了数据传输的灵活性(支持文本和二进制数据),又通过掩码机制提供了基本的安全性,同时精简的头部设计降低了传输开销。

二、QWebSocket 与 QWebSocketServer:Qt 中的 WebSocket 实现

Qt 框架通过 QWebSocket(客户端)和 QWebSocketServer(服务器)两个类,对 WebSocket 协议进行了封装,屏蔽了底层协议的复杂性,让开发者可以专注于业务逻辑实现。

2.1 QWebSocket:WebSocket 客户端的 在Qt中“代言人”

QWebSocket 类用于实现 WebSocket 客户端功能,它继承自 QAbstractSocket,提供了连接服务器、发送数据、接收数据、关闭连接等接口。QWebSocket 的核心特点包括:

  • 自动处理握手:无需手动构造握手请求,调用connectToUrl()方法即可完成与服务器的握手过程。
  • 支持文本和二进制数据:通过sendTextMessage()发送文本数据,sendBinaryMessage()发送二进制数据。
  • 信号驱动:通过信号(如connected()、disconnected()、textMessageReceived())通知连接状态变化和数据到达,符合 Qt 的信号 - 槽机制。
  • 错误处理:提供errorOccurred()信号和errorString()方法,方便处理连接和传输过程中的错误。

2.2 QWebSocketServer:WebSocket 服务器的 “指挥官”

QWebSocketServer 类用于实现 WebSocket 服务器功能,它负责监听端口、接受客户端连接、管理连接列表等。QWebSocketServer 的核心特点包括:

  • 多客户端支持:可以同时接受多个客户端的连接,每个客户端连接对应一个 QWebSocket 对象。
  • 连接管理:通过newConnection()信号通知有新客户端连接,通过close()方法关闭服务器。
  • 协议协商:支持设置子协议(Subprotocol)和扩展(Extension),与客户端进行协商。
  • 安全支持:支持 wss(WebSocket Secure)协议,通过 SSL/TLS 加密传输数据。

2.3 两者关系:客户端与服务器的 “对话桥梁”

QWebSocket 和 QWebSocketServer 的关系就像电话和总机:QWebSocket 是 “电话机”,负责与对方(服务器)建立连接并通话;QWebSocketServer 是 “总机”,负责监听来电(客户端连接请求),并为每个来电分配一个 “电话机”(QWebSocket 对象)进行单独通话。
具体交互流程如下:

  1. 服务器通过 QWebSocketServer 监听指定端口(如 8080)。
  2. 客户端通过 QWebSocket 调用connectToUrl(),向服务器发送连接请求。
  3. 服务器收到请求后,通过newConnection()信号通知,调用nextPendingConnection()获取新连接的 QWebSocket 对象。
  4. 客户端和服务器通过各自的 QWebSocket 对象进行双向数据传输。
  5. 通信结束后,客户端或服务器调用close()关闭连接。

三、通信模式:全双工实时交互的 “运作机制”

WebSocket(及 QWebSocket/QWebSocketServer)的通信模式基于持久连接的全双工交互,与传统 HTTP 的 “请求 - 响应” 模式有本质区别,具体体现在以下几个方面:

3.1 连接建立:“一次握手,长期对话”

WebSocket 的连接建立是 “一次性” 的,一旦通过握手建立连接,就会保持打开状态,直到被主动关闭。这种模式避免了 HTTP 每次通信都需要建立连接的开销,特别适合频繁交互的场景。
例如,在在线聊天应用中,用户打开聊天窗口时,客户端与服务器建立一次 WebSocket 连接,之后所有的消息发送和接收都通过这个连接完成,直到用户关闭聊天窗口。

3.2 数据传输:“双向自由流动”

全双工通信意味着客户端和服务器可以随时向对方发送数据,无需等待对方的响应。这种 “双向自由流动” 的特性让实时通信成为可能:

  • 客户端主动发送:如用户在聊天窗口输入消息并发送,客户端通过 QWebSocket 的sendTextMessage()将消息发送给服务器。
  • 服务器主动推送:如其他用户发送了新消息,服务器通过对应的 QWebSocket 对象将消息推送给客户端,客户端无需主动请求。

这种模式与 HTTP 形成鲜明对比:在 HTTP 中,服务器永远是被动响应者,无法主动向客户端发送数据,除非客户端先发送请求。

3.3 连接关闭:“友好告别”

WebSocket 连接的关闭是 “协商式” 的,任何一方都可以发起关闭请求,双方通过交换关闭帧完成优雅关闭:

  • 发起方发送一个 opcode 为 0x08 的关闭帧,包含关闭状态码和原因。
  • 接收方收到关闭帧后,返回一个相同的关闭帧作为确认。
  • 双方确认后,关闭 TCP 连接。

QWebSocket 提供了close()方法(可指定状态码和原因)来触发关闭流程,通过disconnected()信号通知连接已关闭。

3.4 心跳机制:“保持连接活性”

由于网络设备(如路由器、防火墙)可能会关闭长时间没有数据传输的连接,WebSocket 提供了 Ping/Pong 机制来保持连接活性:

  • Ping 帧:一方发送 Ping 帧(opcode=0x09)给另一方,用于检测连接是否有效。
  • Pong 帧:收到 Ping 帧的一方必须立即返回 Pong 帧(opcode=0x0A)作为响应。
  • QWebSocket 自动处理 Ping/Pong 机制:当收到 Ping 帧时,会自动发送 Pong 帧响应;我们也可以通过ping()方法主动发送 Ping 帧,通过pongReceived()信号确认对方在线。

四、数据传输:文本与二进制的 “双车道”

QWebSocket 和 QWebSocketServer 支持两种数据传输类型:文本数据和二进制数据,满足不同场景的需求。

4.1 文本数据传输:适合字符型信息

文本数据传输适用于传输字符串形式的信息,如 JSON、XML、纯文本等,采用 UTF-8 编码。

4.1.1 发送文本数据
  • 客户端(QWebSocket)通过sendTextMessage()方法发送文本数据:
// 客户端发送文本消息
QWebSocket *clientSocket = new QWebSocket;
clientSocket->connectToUrl(QUrl("ws://localhost:8080/chat"));
// 连接成功后发送消息
connect(clientSocket, &QWebSocket::connected, [=]() {clientSocket->sendTextMessage("Hello, Server!");
});
  • 服务器(QWebSocketServer)通过客户端对应的 QWebSocket 对象发送文本数据:
// 服务器向客户端发送文本消息
void Server::onNewConnection() {QWebSocket *clientSocket = webSocketServer->nextPendingConnection();// 存储客户端连接(实际应用中需管理连接列表)clientSockets.append(clientSocket);// 向客户端发送欢迎消息clientSocket->sendTextMessage("Welcome to Chat Server!");
}
4.1.2 接收文本数据
  • 客户端和服务器通过textMessageReceived()信号接收文本数据:
// 客户端接收文本消息
connect(clientSocket, &QWebSocket::textMessageReceived, [=](const QString &message) {qDebug() << "Client received: " << message;
});// 服务器接收客户端文本消息
connect(clientSocket, &QWebSocket::textMessageReceived, [=](const QString &message) {qDebug() << "Server received from client: " << message;// 转发消息给其他客户端(群聊功能)foreach (QWebSocket *socket, clientSockets) {if (socket != clientSocket) {socket->sendTextMessage(message);}}
});

4.2 二进制数据传输:适合非字符型信息

二进制数据传输适用于传输图片、音频、视频、文件等非字符型数据,直接传输字节流,无需编码转换。

4.2.1 发送二进制数据
  • 客户端和服务器通过sendBinaryMessage()方法发送二进制数据:
// 客户端发送图片(二进制数据)
void Client::sendImage(const QString &imagePath) {QFile file(imagePath);if (file.open(QIODevice::ReadOnly)) {QByteArray imageData = file.readAll();clientSocket->sendBinaryMessage(imageData);file.close();}
}// 服务器发送二进制数据(如文件片段)
void Server::sendFileFragment(QWebSocket *client, const QByteArray &fragment) {client->sendBinaryMessage(fragment);
}
4.2.2 接收二进制数据
  • 客户端和服务器通过binaryMessageReceived()信号接收二进制数据:
// 客户端接收二进制数据(如图片)
connect(clientSocket, &QWebSocket::binaryMessageReceived, [=](const QByteArray &data) {qDebug() << "Client received binary data, size: " << data.size() << "bytes";// 保存为图片QFile file("received_image.png");if (file.open(QIODevice::WriteOnly)) {file.write(data);file.close();}
});// 服务器接收二进制数据
connect(clientSocket, &QWebSocket::binaryMessageReceived, [=](const QByteArray &data) {qDebug() << "Server received binary data from client, size: " << data.size() << "bytes";// 处理二进制数据(如存储文件)
});

4.3 大数据传输:分片与重组

当传输的数据量较大(如大文件、高清图片)时,WebSocket 会自动将数据拆分为多个帧进行传输,接收方会自动重组这些帧,还原为完整数据。开发者无需手动处理分片,只需关注完整数据的发送和接收。
例如,发送一个 10MB 的文件:

// 发送方(客户端或服务器)
QFile file("large_file.zip");
if (file.open(QIODevice::ReadOnly)) {QByteArray data = file.readAll(); // 读取10MB数据webSocket->sendBinaryMessage(data); // 自动分片传输file.close();
}// 接收方
connect(webSocket, &QWebSocket::binaryMessageReceived, [=](const QByteArray &data) {// 直接收到完整的10MB数据(自动重组)qDebug() << "Received total size: " << data.size() << "bytes";
});

4.4 数据格式建议:结构化与扩展性

在实际应用中,建议对传输的数据进行结构化处理,方便解析和扩展。例如,使用 JSON 格式封装消息类型、内容、时间戳等信息:

// 发送结构化文本消息
QJsonObject messageObj;
messageObj["type"] = "chat"; // 消息类型:聊天
messageObj["sender"] = "Alice"; // 发送者
messageObj["content"] = "Hello, everyone!"; // 内容
messageObj["timestamp"] = QDateTime::currentDateTime().toString(); // 时间戳
QString jsonMessage = QJsonDocument(messageObj).toJson(QJsonDocument::Compact);
webSocket->sendTextMessage(jsonMessage);// 接收方解析
connect(webSocket, &QWebSocket::textMessageReceived, [=](const QString &jsonMessage) {QJsonObject messageObj = QJsonDocument::fromJson(jsonMessage.toUtf8()).object();QString type = messageObj["type"].toString();if (type == "chat") {QString sender = messageObj["sender"].toString();QString content = messageObj["content"].toString();// 处理聊天消息} else if (type == "image") {// 处理图片消息(可能包含Base64编码的图片数据)}
});

五、应用场景:实时交互的典型应用

WebSocket(及 QWebSocket/QWebSocketServer)的实时双向通信特性,使其在众多领域中发挥重要作用,以下是一些典型应用场景:

5.1 即时通讯应用:聊天与消息推送

即时通讯是 WebSocket 最经典的应用场景,包括一对一聊天、群聊、系统消息推送等。

  • 场景特点:需要低延迟、高频次的双向数据传输,消息实时性要求高。
  • 优势体现:相比轮询,WebSocket 能显著减少网络流量和服务器负载,同时保证消息即时送达。
  • 实例:在线客服系统、团队协作工具(如 Slack)、社交软件的实时聊天功能。

使用 QWebSocket 和 QWebSocketServer 实现即时通讯的核心流程:

  1. 客户端(如聊天窗口)连接到服务器的 WebSocket 端点。
  2. 用户发送消息时,客户端将消息(含发送者、接收者、内容等)通过 QWebSocket 发送给服务器。
  3. 服务器接收到消息后,根据接收者信息,通过对应的 QWebSocket 对象将消息推送给目标客户端。
  4. 目标客户端收到消息后,在界面上显示。

5.2 实时数据监控:动态数据展示

实时数据监控系统(如股票行情、物联网传感器数据、服务器性能监控)需要将实时变化的数据及时展示给用户。

  • 场景特点:数据更新频繁(可能每秒多次),以服务器推送为主,客户端主要负责展示。
  • 优势体现:服务器可以在数据变化时立即推送,无需客户端不断查询,降低延迟和带宽消耗。
  • 实例:股票交易软件的实时行情、工厂的设备状态监控、智能家居的环境数据展示。
  • 实现思路:
  1. 客户端连接到监控服务器的 WebSocket 接口。
  2. 服务器定期(或数据变化时)采集数据(如传感器读数、股票价格)。
  3. 服务器将格式化后的数据(如 JSON)通过 QWebSocket 推送给所有连接的客户端。
  4. 客户端接收到数据后,实时更新界面(如 charts、仪表盘)。

5.3 在线游戏:实时交互与状态同步

多人在线游戏(如实时对战游戏、协作游戏)需要实时同步玩家的位置、动作、状态等信息。

  • 场景特点:低延迟要求极高(通常需低于 100ms),数据量小但频率高,需要双向交互。
  • 优势体现:WebSocket 的低延迟和全双工特性,能满足游戏状态实时同步的需求。
  • 实例:网页版多人贪吃蛇、在线棋牌游戏、实时战略游戏。
  • 实现要点:
  1. 每个玩家客户端通过 WebSocket 连接到游戏服务器。
  2. 玩家操作(如移动、攻击)通过 QWebSocket 实时发送给服务器。
  3. 服务器处理所有玩家的操作,更新游戏状态(如位置、血量)。
  4. 服务器将更新后的游戏状态广播给所有玩家客户端。
  5. 客户端根据收到的状态更新游戏画面。

5.4 协同编辑:多用户实时协作

协同编辑工具(如在线文档、思维导图)允许多个用户同时编辑同一文件,需要实时同步每个人的修改。

  • 场景特点:多用户并发操作,修改需即时可见,冲突需处理。
  • 优势体现:用户的每一次修改(如输入文字、插入图片)都能实时同步给其他用户,提升协作效率。
  • 实例:Google Docs、腾讯文档、在线白板工具。
  • 实现逻辑:
  1. 多个用户客户端连接到文档服务器的 WebSocket 端点。
  2. 用户在文档中进行修改(如输入字符、删除内容)。
  3. 客户端将修改操作(含位置、内容、用户 ID 等)通过 QWebSocket 发送给服务器。
  4. 服务器验证操作合法性,处理冲突(如同一位置的并发修改),更新文档状态。
  5. 服务器将处理后的修改操作广播给其他用户客户端。
  6. 其他客户端应用该修改,更新本地文档显示。

5.5 实时通知:事件驱动的信息推送

实时通知系统用于向用户推送重要事件(如订单状态更新、新邮件提醒、系统告警)。

  • 场景特点:事件触发式推送,数据量小,对实时性有一定要求。
  • 优势体现:相比邮件、短信等方式,WebSocket 通知更及时且成本更低。
  • 实例:电商平台的订单发货通知、社交媒体的点赞提醒、运维系统的故障告警。
  • 实现方式:
  1. 客户端登录后,通过 WebSocket 连接到通知服务器,关联用户 ID。
  2. 当事件发生时(如订单状态变化),业务系统通知通知服务器。
  3. 通知服务器根据用户 ID 找到对应的 WebSocket 连接,推送通知消息。
  4. 客户端收到通知后,通过弹窗、声音等方式提醒用户。

六、核心接口:QWebSocket 与 QWebSocketServer 的 操作说明

掌握 QWebSocket 和 QWebSocketServer 的核心接口,是实现 WebSocket 通信的关键。本节将详细介绍这两个类的常用接口、信号和使用方法。

6.1 QWebSocket 核心接口

QWebSocket 类提供了连接管理、数据传输、状态查询等功能,以下是其核心接口:

6.1.1 连接管理
  • void connectToUrl(const QUrl &url, const QByteArray &origin = QByteArray())
    功能:连接到指定的 WebSocket 服务器 URL。
    参数:url为服务器地址(格式为ws://host:port/path或wss://host:port/path);origin可选,指定请求的源地址(用于服务器验证)。
    代码如下:
QWebSocket *webSocket = new QWebSocket;
webSocket->connectToUrl(QUrl("ws://localhost:8080/chat"));
  • void close(CloseCode closeCode = CloseCodeNormal, const QString &reason = QString())
    功能:关闭 WebSocket 连接。
    参数:closeCode为关闭状态码(如CloseCodeNormal表示正常关闭);reason为关闭原因描述。
    示例:
webSocket->close(QWebSocket::CloseCodeNormal, "User disconnected");
  • bool isValid() const
    功能:判断连接是否有效(已建立且未关闭)。
    返回值:true表示连接有效,false表示无效。
6.1.2 数据传输
  • void sendTextMessage(const QString &message)
    功能:发送文本消息(UTF-8 编码)。
    参数:message为要发送的文本内容。
webSocket->sendTextMessage("Hello, WebSocket!");
  • void sendBinaryMessage(const QByteArray &data)
    功能:发送二进制数据。
    参数:data为要发送的二进制字节流。
QByteArray imageData = ...; // 二进制图片数据
webSocket->sendBinaryMessage(imageData);
  • void ping(const QByteArray &payload = QByteArray())
    功能:发送 Ping 帧(心跳检测)。
    参数:payload为可选的附加数据(最长 125 字节)。
// 定期发送Ping帧保持连接
QTimer *pingTimer = new QTimer(this);
connect(pingTimer, &QTimer::timeout, [=]() {webSocket->ping();
});
pingTimer->start(30000); // 每30秒发送一次
6.1.3 信号
  • void connected()
    触发时机:WebSocket 连接成功建立后。
    用途:连接成功后执行初始化操作(如发送登录信息)。
  • void disconnected()
    触发时机:WebSocket 连接关闭后。
    用途:处理连接关闭后的清理工作(如重连逻辑)。
  • void textMessageReceived(const QString &message)
    触发时机:收到文本消息时。
    用途:处理接收到的文本数据(如解析消息内容)。
  • void binaryMessageReceived(const QByteArray &message)
    触发时机:收到二进制消息时。
    用途:处理接收到的二进制数据(如保存文件)。
  • void errorOccurred(QAbstractSocket::SocketError error)
    触发时机:发生错误时。
    用途:错误处理(如输出错误信息、尝试重连)。
    示例:
connect(webSocket, &QWebSocket::errorOccurred, [=](QAbstractSocket::SocketError error) {qDebug() << "WebSocket error:" << webSocket->errorString();// 连接失败后重试if (error == QAbstractSocket::ConnectionRefusedError) {QTimer::singleShot(5000, [=]() {webSocket->connectToUrl(QUrl("ws://localhost:8080/chat"));});}
});
  • void pongReceived(const QByteArray &payload)
    触发时机:收到 Pong 帧响应时。
    用途:确认服务器在线,更新连接状态。
6.1.4 状态与信息查询
  • QAbstractSocket::SocketState state() const
    功能:返回当前连接状态。
    可能值:UnconnectedState(未连接)、ConnectingState(连接中)、ConnectedState(已连接)等。
  • QUrl requestUrl() const
    功能:返回连接的服务器 URL。
  • QHostAddress peerAddress() const
    功能:返回服务器的 IP 地址。
  • quint16 peerPort() const
    功能:返回服务器的端口号。
  • QString errorString() const
    功能:返回最近一次错误的描述信息。

6.2 QWebSocketServer 核心接口

QWebSocketServer 类用于创建 WebSocket 服务器,管理客户端连接,以下是其核心接口:

6.2.1 服务器启动与停止
  • QWebSocketServer(const QString &serverName, SslMode secureMode, QObject *parent = nullptr)
    构造函数:创建 WebSocketServer 实例。
    参数:serverName为服务器名称(用于握手信息);secureMode指定是否使用 SSL(NonSecureMode为 ws,SecureMode为 wss)。
// 创建非加密服务器
QWebSocketServer *server = new QWebSocketServer("Chat Server", QWebSocketServer::NonSecureMode, this);
  • bool listen(const QHostAddress &address = QHostAddress::Any, quint16 port = 0)
    功能:开始监听指定地址和端口的连接请求。
    参数:address为监听地址(QHostAddress::Any表示监听所有网络接口);port为端口号(0 表示随机端口)。
    返回值:true表示监听成功,false表示失败。
if (server->listen(QHostAddress::Any, 8080)) {qDebug() << "Server listening on port 8080";
} else {qDebug() << "Server failed to listen:" << server->errorString();
}
  • void close()
    功能:停止监听,关闭所有客户端连接。
6.2.2 客户端连接管理

QWebSocket *nextPendingConnection()
功能:获取下一个待处理的客户端连接。
返回值:指向新连接的 QWebSocket 对象(需手动管理生命周期)。

connect(server, &QWebSocketServer::newConnection, [=]() {QWebSocket *client = server->nextPendingConnection();qDebug() << "New client connected:" << client->peerAddress().toString();// 存储客户端连接clients.insert(client);// 连接客户端的信号connect(client, &QWebSocket::textMessageReceived, this, &Server::onClientMessage);connect(client, &QWebSocket::disconnected, this, &Server::onClientDisconnected);
});
  • QList<QWebSocket *> clients() const
    功能:返回当前所有已连接的客户端列表(Qt 5.10+)。
  • void removeClient(QWebSocket *client)
    功能:从服务器客户端列表中移除指定客户端(Qt 5.10+)。
6.2.3 信号
  • void newConnection()
    触发时机:有新的客户端连接请求并完成握手后。
    用途:获取新连接的客户端对象,进行后续处理。
  • void closed()
    触发时机:服务器关闭后。
    用途:处理服务器关闭后的清理工作。
  • void serverError(QWebSocketServer::Error error)
    触发时机:服务器发生错误时。
    用途:处理服务器错误(如监听失败)。
    void peerVerifyError(const QSslError &error)
    触发时机:SSL 握手时客户端证书验证失败(仅用于 wss)。
6.2.4 服务器信息查询
  • quint16 serverPort() const
    功能:返回服务器当前监听的端口号。
  • QHostAddress serverAddress() const
    功能:返回服务器监听的地址。
  • QString errorString() const
    功能:返回最近一次服务器错误的描述信息。
  • bool isListening() const
    功能:判断服务器是否正在监听连接请求。

6.3 SSL/TLS 加密(wss 协议)

对于需要安全传输的场景(如涉及用户隐私、支付信息),可以使用 wss 协议(WebSocket Secure),通过 SSL/TLS 加密数据。

6.3.1 服务器配置 SSL
// 创建支持SSL的服务器
QWebSocketServer *secureServer = new QWebSocketServer("Secure Chat Server", QWebSocketServer::SecureMode, this);// 加载SSL证书和私钥
QSslConfiguration sslConfig = QSslConfiguration::defaultConfiguration();
QSslCertificate cert = QSslCertificate::fromPath("server.crt").first(); // 服务器证书
QSslKey key = QSslKey::fromPath("server.key", QSsl::Rsa); // 私钥
sslConfig.setLocalCertificate(cert);
sslConfig.setPrivateKey(key);
secureServer->setSslConfiguration(sslConfig);// 监听443端口(wss默认端口)
if (secureServer->listen(QHostAddress::Any, 443)) {qDebug() << "Secure server listening on port 443";
}
//注意:生产环境中一般使用权威机构签名的证书,并启用证书验证,避免安全风险。
6.3.2 客户端连接 wss 服务器

七、案例:基于 QWebSocket 的实时聊天系统

通过一个完整的案例 —— 实时聊天系统,展示 QWebSocket 和 QWebSocketServer 的具体应用。该系统包含服务器和客户端两部分,支持多人聊天、发送文本消息和图片。

7.1 系统设计

7.1.1 功能需求

服务器:监听客户端连接,转发客户端消息给其他用户,管理在线用户。
客户端:连接服务器,发送文本消息和图片,接收并显示其他用户的消息。

7.1.2 消息格式

采用 JSON 格式封装消息,包含以下类型:
登录消息:{“type”:“login”,“username”:“Admin”}
文本消息:{“type”:“text”,“username”:“Admin”,“content”:“Hello”,“timestamp”:“2025-08-05 12:00:00”}
图片消息:{“type”:“image”,“username”:“Admin”,“filename”:“photo.png”,“data”:“base64编码的图片数据”,“timestamp”:“2023-10-01 12:01:00”}
用户列表更新:{“type”:“userList”,“users”:[“Admin”,“Bob”]}

7.2 服务器实现

7.2.1 服务器类定义(Server.h)
#ifndef SERVER_H
#define SERVER_H#include <QObject>
#include <QWebSocketServer>
#include <QWebSocket>
#include <QSet>
#include <QDateTime>
#include <QJsonDocument>
#include <QJsonObject>class Server : public QObject
{Q_OBJECT
public:explicit Server(quint16 port, QObject *parent = nullptr);~Server();private slots:void onNewConnection();void onTextMessageReceived(const QString &message);void onBinaryMessageReceived(const QByteArray &data);void onClientDisconnected();private:void sendUserList();QJsonObject createMessage(const QString &type, const QString &username, const QString &content = QString());QWebSocketServer *m_webSocketServer;QSet<QWebSocket *> m_clients; // 存储所有客户端连接QMap<QWebSocket *, QString> m_userNames; // 客户端与用户名的映射
};#endif // SERVER_H
7.2.2 服务器实现(Server.cpp)
#include "Server.h"
#include <QDebug>Server::Server(quint16 port, QObject *parent) : QObject(parent)
{// 创建WebSocket服务器m_webSocketServer = new QWebSocketServer("Chat Server", QWebSocketServer::NonSecureMode, this);// 监听指定端口if (m_webSocketServer->listen(QHostAddress::Any, port)) {qDebug() << "Server started on port" << port;// 连接新客户端信号connect(m_webSocketServer, &QWebSocketServer::newConnection, this, &Server::onNewConnection);} else {qDebug() << "Server failed to start:" << m_webSocketServer->errorString();}
}Server::~Server()
{m_webSocketServer->close();qDeleteAll(m_clients.begin(), m_clients.end());
}void Server::onNewConnection()
{// 获取新连接的客户端QWebSocket *client = m_webSocketServer->nextPendingConnection();if (!client) return;qDebug() << "New client connected:" << client->peerAddress().toString();// 存储客户端连接m_clients.insert(client);// 连接客户端的信号connect(client, &QWebSocket::textMessageReceived, this, &Server::onTextMessageReceived);connect(client, &QWebSocket::binaryMessageReceived, this, &Server::onBinaryMessageReceived);connect(client, &QWebSocket::disconnected, this, &Server::onClientDisconnected);connect(client, &QWebSocket::disconnected, client, &QWebSocket::deleteLater);
}void Server::onTextMessageReceived(const QString &message)
{QWebSocket *senderClient = qobject_cast<QWebSocket *>(sender());if (!senderClient) return;// 解析JSON消息QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());if (doc.isNull()) {qDebug() << "Invalid JSON message:" << message;return;}QJsonObject obj = doc.object();QString type = obj["type"].toString();if (type == "login") {// 处理登录消息QString username = obj["username"].toString();if (!username.isEmpty()) {m_userNames[senderClient] = username;qDebug() << "User logged in:" << username;// 广播用户列表更新sendUserList();// 发送欢迎消息QJsonObject welcomeMsg = createMessage("system", "Server", "Welcome, " + username + "!");senderClient->sendTextMessage(QJsonDocument(welcomeMsg).toJson(QJsonDocument::Compact));}} else if (type == "text") {// 处理文本消息,转发给其他客户端if (m_userNames.contains(senderClient)) {QString username = m_userNames[senderClient];QString content = obj["content"].toString();QJsonObject forwardMsg = createMessage("text", username, content);QString forwardStr = QJsonDocument(forwardMsg).toJson(QJsonDocument::Compact);// 转发给所有其他客户端foreach (QWebSocket *client, m_clients) {if (client != senderClient) {client->sendTextMessage(forwardStr);}}}}
}void Server::onBinaryMessageReceived(const QByteArray &data)
{// 本案例中二进制数据主要用于图片,通过文本消息中的Base64编码传输// 此处可扩展为直接传输二进制文件QWebSocket *senderClient = qobject_cast<QWebSocket *>(sender());if (senderClient) {qDebug() << "Received binary data from" << m_userNames.value(senderClient, "unknown") << "size:" << data.size();}
}void Server::onClientDisconnected()
{QWebSocket *client = qobject_cast<QWebSocket *>(sender());if (client && m_clients.contains(client)) {QString username = m_userNames.value(client, "unknown");qDebug() << "Client disconnected:" << username;// 移除客户端m_clients.remove(client);m_userNames.remove(client);// 广播用户列表更新sendUserList();// 广播用户离开消息QJsonObject leaveMsg = createMessage("system", "Server", username + " has left the chat.");QString leaveStr = QJsonDocument(leaveMsg).toJson(QJsonDocument::Compact);foreach (QWebSocket *c, m_clients) {c->sendTextMessage(leaveStr);}}
}void Server::sendUserList()
{// 构建用户列表消息QJsonObject userListMsg;userListMsg["type"] = "userList";QJsonArray usersArray;foreach (const QString &username, m_userNames.values()) {usersArray.append(username);}userListMsg["users"] = usersArray;// 发送给所有客户端QString userListStr = QJsonDocument(userListMsg).toJson(QJsonDocument::Compact);foreach (QWebSocket *client, m_clients) {client->sendTextMessage(userListStr);}
}QJsonObject Server::createMessage(const QString &type, const QString &username, const QString &content)
{QJsonObject msg;msg["type"] = type;msg["username"] = username;msg["timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");if (!content.isEmpty()) {msg["content"] = content;}return msg;
}
7.2.3 服务器主函数(main.cpp)
#include <QCoreApplication>
#include "Server.h"int main(int argc, char *argv[])
{QCoreApplication a(argc, argv);// 创建服务器,监听8080端口Server server(8080);return a.exec();
}

7.3 客户端实现

客户端使用 Qt Widgets 构建界面,包含登录窗口、聊天窗口(消息显示区、输入区、发送按钮、发送图片按钮)。

7.3.1 客户端类定义(Client.h)
#ifndef CLIENT_H
#define CLIENT_H#include <QWidget>
#include <QWebSocket>
#include <QFileDialog>
#include <QJsonObject>
#include <QJsonDocument>QT_BEGIN_NAMESPACE
namespace Ui { class Client; }
QT_END_NAMESPACEclass Client : public QWidget
{Q_OBJECTpublic:Client(QWidget *parent = nullptr);~Client();private slots:void on_loginButton_clicked();void on_sendButton_clicked();void on_sendImageButton_clicked();void onConnected();void onDisconnected();void onTextMessageReceived(const QString &message);void onBinaryMessageReceived(const QByteArray &data);void onErrorOccurred(QAbstractSocket::SocketError error);private:void connectToServer();void sendLoginMessage();void sendTextMessage(const QString &content);void sendImageMessage(const QString &imagePath);void addMessageToDisplay(const QString &username, const QString &content, const QString &timestamp, bool isImage = false, const QString &imagePath = QString());Ui::Client *ui;QWebSocket *m_webSocket;QString m_username;QString m_serverUrl;
};
#endif // CLIENT_H
7.3.2 客户端实现(Client.cpp)
#include "Client.h"
#include "ui_Client.h"
#include <QDebug>
#include <QPixmap>
#include <QDateTime>
#include <QScrollBar>Client::Client(QWidget *parent): QWidget(parent), ui(new Ui::Client), m_webSocket(nullptr)
{ui->setupUi(this);setWindowTitle("Chat Client");// 初始化UIui->chatWidget->setEnabled(false);ui->serverUrlEdit->setText("ws://localhost:8080");// 创建WebSocket对象m_webSocket = new QWebSocket;// 连接信号connect(m_webSocket, &QWebSocket::connected, this, &Client::onConnected);connect(m_webSocket, &QWebSocket::disconnected, this, &Client::onDisconnected);connect(m_webSocket, &QWebSocket::textMessageReceived, this, &Client::onTextMessageReceived);connect(m_webSocket, &QWebSocket::binaryMessageReceived, this, &Client::onBinaryMessageReceived);connect(m_webSocket, &QWebSocket::errorOccurred, this, &Client::onErrorOccurred);
}Client::~Client()
{if (m_webSocket) {m_webSocket->close();delete m_webSocket;}delete ui;
}void Client::on_loginButton_clicked()
{m_username = ui->usernameEdit->text().trimmed();m_serverUrl = ui->serverUrlEdit->text().trimmed();if (m_username.isEmpty()) {ui->statusLabel->setText("请输入用户名");return;}if (m_serverUrl.isEmpty()) {ui->statusLabel->setText("请输入服务器地址");return;}// 禁用登录控件,显示连接中ui->loginWidget->setEnabled(false);ui->statusLabel->setText("连接中...");// 连接服务器connectToServer();
}void Client::on_sendButton_clicked()
{QString content = ui->messageEdit->text().trimmed();if (content.isEmpty()) return;// 发送文本消息sendTextMessage(content);// 清空输入框ui->messageEdit->clear();
}void Client::on_sendImageButton_clicked()
{// 打开文件选择对话框QString imagePath = QFileDialog::getOpenFileName(this, "选择图片", "", "图片文件 (*.png *.jpg *.jpeg *.bmp)");if (imagePath.isEmpty()) return;// 发送图片消息sendImageMessage(imagePath);
}void Client::onConnected()
{ui->statusLabel->setText("已连接到服务器");ui->chatWidget->setEnabled(true);// 发送登录消息sendLoginMessage();
}void Client::onDisconnected()
{ui->statusLabel->setText("与服务器断开连接");ui->chatWidget->setEnabled(false);ui->loginWidget->setEnabled(true);
}void Client::onTextMessageReceived(const QString &message)
{QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());if (doc.isNull()) {qDebug() << "Invalid JSON message:" << message;return;}QJsonObject obj = doc.object();QString type = obj["type"].toString();if (type == "text" || type == "system") {// 处理文本消息QString username = obj["username"].toString();QString content = obj["content"].toString();QString timestamp = obj["timestamp"].toString();addMessageToDisplay(username, content, timestamp);} else if (type == "userList") {// 处理用户列表更新QJsonArray usersArray = obj["users"].toArray();QString userListStr = "在线用户: ";for (int i = 0; i < usersArray.size(); ++i) {if (i > 0) userListStr += ", ";userListStr += usersArray[i].toString();}ui->userListLabel->setText(userListStr);} else if (type == "image") {// 处理图片消息QString username = obj["username"].toString();QString filename = obj["filename"].toString();QString base64Data = obj["data"].toString();QString timestamp = obj["timestamp"].toString();// 将Base64数据转换为图片并保存QByteArray imageData = QByteArray::fromBase64(base64Data.toUtf8());QString tempPath = QDir::tempPath() + "/" + filename;QFile file(tempPath);if (file.open(QIODevice::WriteOnly)) {file.write(imageData);file.close();addMessageToDisplay(username, filename, timestamp, true, tempPath);}}
}void Client::onBinaryMessageReceived(const QByteArray &data)
{qDebug() << "Received binary data, size:" << data.size();// 可扩展为直接处理二进制图片
}void Client::onErrorOccurred(QAbstractSocket::SocketError error)
{ui->statusLabel->setText("错误: " + m_webSocket->errorString());ui->loginWidget->setEnabled(true);
}void Client::connectToServer()
{if (m_webSocket->state() == QAbstractSocket::ConnectedState) {m_webSocket->close();}m_webSocket->connectToUrl(QUrl(m_serverUrl));
}void Client::sendLoginMessage()
{QJsonObject loginMsg;loginMsg["type"] = "login";loginMsg["username"] = m_username;m_webSocket->sendTextMessage(QJsonDocument(loginMsg).toJson(QJsonDocument::Compact));
}void Client::sendTextMessage(const QString &content)
{QJsonObject textMsg;textMsg["type"] = "text";textMsg["username"] = m_username;textMsg["content"] = content;textMsg["timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");m_webSocket->sendTextMessage(QJsonDocument(textMsg).toJson(QJsonDocument::Compact));// 在本地显示自己发送的消息addMessageToDisplay(m_username, content, textMsg["timestamp"].toString());
}void Client::sendImageMessage(const QString &imagePath)
{QFile file(imagePath);if (!file.open(QIODevice::ReadOnly)) {ui->statusLabel->setText("无法打开图片文件");return;}// 读取图片数据并编码为Base64QByteArray imageData = file.readAll();file.close();QString base64Data = imageData.toBase64();// 构建图片消息QJsonObject imageMsg;imageMsg["type"] = "image";imageMsg["username"] = m_username;imageMsg["filename"] = QFileInfo(imagePath).fileName();imageMsg["data"] = base64Data;imageMsg["timestamp"] = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");m_webSocket->sendTextMessage(QJsonDocument(imageMsg).toJson(QJsonDocument::Compact));// 在本地显示自己发送的图片addMessageToDisplay(m_username, imageMsg["filename"].toString(), imageMsg["timestamp"].toString(), true, imagePath);
}void Client::addMessageToDisplay(const QString &username, const QString &content, const QString &timestamp, bool isImage, const QString &imagePath)
{// 构建消息显示HTMLQString html;html += QString("<p><strong>%1</strong> <span style='color: #666; font-size: 8pt;'>%2</span></p>").arg(username).arg(timestamp);if (isImage) {// 显示图片QPixmap pixmap(imagePath);if (!pixmap.isNull()) {// 缩放图片适应显示区域pixmap = pixmap.scaled(300, 300, Qt::KeepAspectRatio, Qt::SmoothTransformation);// 保存缩放后的图片到临时文件(用于HTML显示)QString tempImgPath = QDir::tempPath() + "/chat_" + QDateTime::currentDateTime().toString("yyyyMMddhhmmss") + ".png";pixmap.save(tempImgPath);html += QString("<p><img src='file:///%1' /></p>").arg(tempImgPath);} else {html += QString("<p>[无法显示图片: %1]</p>").arg(content);}} else {// 显示文本html += QString("<p>%1</p>").arg(content);}// 添加分隔线html += "<hr />";// 追加到显示区域ui->messageDisplay->insertHtml(html);// 滚动到底部QScrollBar *scrollBar = ui->messageDisplay->verticalScrollBar();scrollBar->setValue(scrollBar->maximum());
}
7.3.3 客户端 UI 设计(client.ui)

UI 设计使用 Qt Designer 完成,主要包含:

  • 登录区域(loginWidget):包含用户名输入框(usernameEdit)、服务器地址输入框(serverUrlEdit)、登录按钮(loginButton)。
  • 聊天区域(chatWidget):包含消息显示文本框(messageDisplay,设置为只读)、用户列表标签(userListLabel)、消息输入框(messageEdit)、发送按钮(sendButton)、发送图片按钮(sendImageButton)。
  • 状态标签(statusLabel):显示连接状态和错误信息。

7.4 系统测试与运行

  • 编译服务器:将服务器代码编译为控制台应用程序,运行后显示 “Server started on port 8080”。
  • 编译客户端:将客户端代码编译为带界面的应用程序,运行后显示登录窗口。
  • 多客户端连接:启动多个客户端,输入不同用户名(如 “Alice”、“Bob”)和服务器地址 “ws://localhost:8080”,点击登录。
  • 发送文本消息:在客户端输入框中输入文字,点击 “发送”,其他客户端应能收到并显示消息。
  • 发送图片:点击 “发送图片”,选择一张图片,其他客户端应能收到并显示图片。
  • 用户列表更新:新用户登录或用户退出时,所有客户端的在线用户列表应实时更新。

八、总结

掌握了 WebSocket 协议以及 Qt 中的 QWebSocket 和 QWebSocketServer 类,并且通过QWebSocket 实现客户端连接服务器进行数据传输后,可以进一步探索以下进阶内容:

  • 负载均衡:对于高并发场景,单台 WebSocket 服务器可能无法处理大量连接,可使用负载均衡器(如 Nginx)分发连接到多台服务器,并通过共享内存或消息队列实现服务器间的消息同步。
    断线重连:实现智能重连机制,根据网络状况调整重连间隔,重连时恢复会话状态(如用户登录信息)。
  • 消息确认与重试:在可靠性要求高的场景(如金融交易),实现消息确认机制,发送方未收到确认时自动重试。
  • WebSocket 子协议:通过setSubprotocol()方法协商子协议(如 JSON-RPC、STOMP),规范客户端与服务器的交互格式。

最后,QWebSocket 和 QWebSocketServer 为 Qt 开发者提供了强大的实时通信工具,通过封装 WebSocket 协议的复杂性,让开发者能够轻松构建高效、实时的网络应用。无论是简单的聊天工具,还是复杂的实时监控系统,掌握这两个类的使用都能为项目开发带来极大便利。

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

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

相关文章

机器学习 —— 决策树

机器学习 —— 决策树&#xff08;Decision Tree&#xff09;详细介绍决策树是一种直观且易于解释的监督学习算法&#xff0c;广泛应用于分类和回归任务。它通过模拟人类决策过程&#xff0c;将复杂问题拆解为一系列简单的判断规则&#xff0c;最终形成类似 “树” 状的结构。以…

车规MCU软错误防护技术的多维度分析与优化路径

摘要&#xff1a;随着汽车电子技术的飞速发展&#xff0c;微控制单元&#xff08;MCU&#xff09;在汽车电子系统中的应用日益广泛。然而&#xff0c;大气中子诱发的单粒子效应&#xff08;SEE&#xff09;对MCU的可靠性构成了严重威胁。本文深入探讨了软错误防护技术在车规MCU…

原生微信小程序实现语音转文字搜索---同声传译

效果展示 ![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/23257ce3b6c149a1bb54fd8bc2a05c68.png#pic_center 注意&#xff1a;引入同声传译组件请看这篇文章 1.search.wxml <view class"search-page"><navigation-bar title"搜索" …

Wireshark安装过程缺失vc_runtimeMinimum_x64.msi文件,安装 Visual C++ Redistributable

一、我大意了 一开始是Npcap装不上。 在这个网站下的&#xff1a; Wireshark (kafan58.com) 安装程序&#xff1a; 安装过程&#xff1a; 无语死了&#xff0c;感觉被骗了......外网下的才是最正版的。 二、外网正版 下载最新的4.4.8版本Wireshark重新安装 2.1 vc_runtime…

高通平台Wi-Fi Display学习-- 调试 Wi-Fi Display 问题

4.1 调试 WFD 性能 4.1.1 通过启用调节器模式验证 WFD 当系统设为调节器模式时,设备的运行时钟将达到峰值。要在系统中启用调节器模式,应 在序列中输入以下命令: 1. adb shell stop mpdecision 2. adb shell echo 1→/sys/devices/system/cpu/cpu1/online 3. adb shell…

5G专网与SD-WAN技术融合:某饮料智能工厂网络架构深度解析

随着工业互联网的快速发展&#xff0c;制造业正从传统的生产模式向智能化、数字化方向转型。某饮料智能工厂项目创新性地引入了5G专网与SD-WAN技术&#xff0c;形成了“连接-计算-应用-安全”的全链条网络架构。本文将深入剖析这两种技术在智能工厂中的应用场景、部署架构&…

Java项目:基于SSM框架实现的公益网站管理系统【ssm+B/S架构+源码+数据库+毕业论文+答辩PPT+远程部署】

摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本公益网站就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&#x…

向华为学习——IPD流程体系之IPD术语

第一章 IPD体系 1.1集成产品开发IPD Integrated Product Development,IPD是一种领先的、成熟的产品开发的管理思想和管理模式。它是根据大量成功的产品开发管理实践总结出来的,并被大量实践证明的高效的产品开发模式。通过IPD,可建立起基于市场和客户需求驱动的集成产品开…

落霞归雁:从自然之道到“存内计算”——用算法思维在芯片里开一条“数据高速航道”

作者 落霞归雁&#xff08;CSDN首发&#xff0c;转载请注明&#xff09; 段落一 现象&#xff1a;当“摩尔”老去&#xff0c;数据却在狂奔 过去 30 年&#xff0c;CPU 频率翻了 60 倍&#xff0c;而 DRAM 带宽只翻了 20 倍。算力与带宽的剪刀差&#xff0c;让“计算”变成“等…

StyleX:Meta推出的高性能零运行时CSS-in-JS解决方案

简介 StyleX 是由 Meta 开发的零运行时 CSS-in-JS 解决方案&#xff0c;在构建时将样式编译为静态 CSS&#xff0c;消除运行时开销。 核心特性 零运行时开销 – 构建时编译为静态 CSS类型安全 – 完整的 TypeScript 支持原子化 CSS – 自动生成原子化类名&#xff0c;最小化…

LINUX 85 SHElL if else 前瞻 实例

问题 判断用户是否存在 id user id $user变量判断vsftpd软件包被安装 rpm -q vsftpd rpm -ql vsftpd >& null[rootweb ~]# rpm -ql vsftpd >/dev/null 2>&1 您在 /var/spool/mail/root 中有邮件yum install vsftpd 内核主版本判断 uname -rcut -d[rootweb ~]#…

2025 年非关系型数据库全面指南:类型、优势

非关系型数据库的分类与特点随着数据量呈指数级增长和数据类型日益多样化&#xff0c;传统关系型数据库在处理海量非结构化数据时面临着严峻挑战。非关系型数据库&#xff08;NoSQL&#xff09;应运而生&#xff0c;它摒弃了传统关系模型的约束&#xff0c;采用更灵活的数据存储…

深度残差网络ResNet结构

Deep Residual Learning for Image Recognition&#xff0c;由Kaiming He、Xiangyu Zhang、Shaoqing Ren和Jian Sun于2016年发表在CVPR上 1512.03385 (arxiv.org)https://arxiv.org/pdf/1512.03385 下图中&#xff0c;左侧为VGG19网络&#xff0c;中间为34层的普通网络&#xf…

python笔记--socket_TCP模拟浏览器实现

""" 1,导包 2,创建TCP套接字 3,建立连接 4,拼接客户端请求报文 5,发送请求报文 6,接收响应报文 7,过滤出html页面 8,保存为html文件 9,关闭套接字 """ # 1,导包 import socket # 2,创建TCP套接字 tcp_socketsocket.socket(socket.AF_INET,socket…

西门子PLC基础指令4:置位指令 S、复位指令 R

布尔指令 1、置位指令 S Setbit 是要进行置位操作的地址的首地址&#xff0c;N 是从该首地址开始连续置位的位数 。 LD I0.0 // 装载输入继电器I0.0的状态&#xff08;当I0.0为ON时&#xff0c;执行后续指令&#xff09; S Q0.0, 3 // 从Q0.0开始&#xff0c;连续置位3…

2.3 子组件样式冲突详解

Vue2组件样式冲突的成因与解决方案组件样式冲突的根本原因在Vue单页面应用中&#xff0c;所有组件的DOM结构最终都会合并到同一个index.html 页面中。若子组件未使用scoped属性&#xff0c;其样式会默认全局生效&#xff0c;导致不同组件中相同选择器&#xff08;如h1、.contai…

26-数据仓库与Apache Hive

1.数据仓库 是什么&#xff1f;解决什么&#xff1f;1.1 数据仓库Data Warehouse 数仓 / DW 是一个用于存储、分析、报告的数据系统.目的&#xff1a;构建面向分析的集成数据环境&#xff0c;分析结构为企业提供决策支持。数仓专注于分析数仓本身不“”生产“”数据&#xff0c…

前端開發技術教學(二)

書接上回&#xff1a;前端開發技術教學(一) -CSDN博客 必要資源&#xff1a;TRAE - The Real AI Engineer 目录 一) 自定義函數 - function 二) DOM操控 DOM事件 a.) onclick b.) onkeydown 三) AI寫代碼 書接上回說到的前端3種主語言以及其用法&#xff0c;這期我們…

设计模式 - 组合模式:用树形结构处理对象之间的复杂关系

文章目录一、引言二、模式原理分析三、代码示例四、核心要点五、使用场景分析六、案例七、为何使用组合模式&#xff1f;八、优缺点剖析九、最佳实践建议十、总结一、引言 “组合模式”&#xff08;Composite Pattern&#xff09;常被误解为“组合关系”。前者专注于将对象组合…

STM32U575低功耗调试

开启了MSIK时钟导致功耗变高在stop2模式下, 整体板子25.41uA; 如果在standby模式, 整体板子5uA;如果在stop2模式, 并且把LPTIM3,4选择的时钟是MSIK, 整体功耗53.59uA2.stanby模式板子整体5uA调试的时候, 可以让板子进入standby模式, 如果电流很小, 可以证明板子没有漏电(图画错…