1. 重构了WebSocket类的实现,并且设置其为项目的核心通信协议
This commit is contained in:
@@ -94,6 +94,8 @@ bool LAppModel::IsPointOnModel(const csmFloat32 x, const csmFloat32 y)
|
||||
return false;
|
||||
}
|
||||
// 如果有命中区域,使用HitTest(是否存在命中区域,这取决于模型是否定义这两部分信息)
|
||||
// 不过不少模型不会写或者是不写入腿部信息,因此实际会出现点击腿部的时候判定为在模型外面,这并不是bug
|
||||
// 也就是尽量依赖HitAreas,以此获得最好的性能,但是如果模型没有定义,则使用IsPointOnDrawable
|
||||
/* 通常是这样的信息,在.model3.json当中
|
||||
"HitAreas": [
|
||||
{
|
||||
|
||||
@@ -30,6 +30,8 @@ file(GLOB_RECURSE YosugaSrc
|
||||
"src/AudioHandle/Inc/*.h"
|
||||
"src/Menu/Src/*.cpp"
|
||||
"src/Menu/Inc/*.h"
|
||||
"src/DAO/Inc/*.h"
|
||||
"src/DAO/Src/*.cpp"
|
||||
"src/NetWorkHandle/Src/*.cpp"
|
||||
"src/NetWorkHandle/Inc/*.h"
|
||||
"src/Setting/Src/*.cpp"
|
||||
@@ -193,6 +195,7 @@ target_include_directories(${PROJECT_NAME}
|
||||
src/Setting/Inc
|
||||
src/Render/TextRender/Inc
|
||||
src/Core/Inc
|
||||
src/DAO/Inc
|
||||
)
|
||||
|
||||
|
||||
|
||||
+33
-6
@@ -25,7 +25,7 @@ QMap<QString, double> GLCore::frameRateMap = {
|
||||
{"240", 240.0}
|
||||
};
|
||||
|
||||
GLCore::GLCore(int w, int h, QWidget *parent)
|
||||
GLCore::GLCore(const int width, const int height, QWidget *parent)
|
||||
: QOpenGLWidget(parent),
|
||||
isLeftPressed(false), // 显式初始化
|
||||
isRightPressed(false) // 显式初始化
|
||||
@@ -52,9 +52,9 @@ GLCore::GLCore(int w, int h, QWidget *parent)
|
||||
contextMenu = new Menu(this);
|
||||
|
||||
// 设置窗口大小
|
||||
setFixedSize(w, h);
|
||||
setFixedSize(width, height);
|
||||
// 设置文本渲染器窗口大小
|
||||
TextRenderer::getInstance()->setWindowSize(w, h);
|
||||
TextRenderer::getInstance()->setWindowSize(width, height);
|
||||
TextRenderer::getInstance()->setGlobalFont(QFont("Microsoft YaHei", 14, QFont::Bold));
|
||||
TextRenderer::getInstance()->setHoldDuration(1.0f); // 停留1.2秒
|
||||
TextRenderer::getInstance()->setGravity(600.0f); // 更快的下坠速度
|
||||
@@ -164,8 +164,7 @@ void GLCore::closeEvent(QCloseEvent* event)
|
||||
}
|
||||
|
||||
#ifdef Q_OS_WIN
|
||||
void GLCore::setWindowTransparentForMouse(bool transparent)
|
||||
{
|
||||
void GLCore::setWindowTransparentForMouse(const bool transparent) const {
|
||||
if (!hwnd) return;
|
||||
|
||||
LONG exStyle = GetWindowLong(hwnd, GWL_EXSTYLE);
|
||||
@@ -182,7 +181,7 @@ void GLCore::setWindowTransparentForMouse(bool transparent)
|
||||
|
||||
SetWindowLong(hwnd, GWL_EXSTYLE, exStyle);
|
||||
// 刷新窗口
|
||||
SetWindowPos(hwnd, 0, 0, 0, 0, 0,
|
||||
SetWindowPos(hwnd, nullptr, 0, 0, 0, 0,
|
||||
SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_FRAMECHANGED);
|
||||
}
|
||||
#endif
|
||||
@@ -250,9 +249,37 @@ void GLCore::mousePressEvent(QMouseEvent* event)
|
||||
// TODO: 右键菜单等
|
||||
if (event->button() == Qt::RightButton) {
|
||||
// 在鼠标右键点击的位置创建菜单,显示自定义右键菜单
|
||||
if (onModel) {
|
||||
contextMenu->showMenu(event->globalPos());
|
||||
this->isRightPressed = true;
|
||||
}
|
||||
else {
|
||||
#ifdef Q_OS_WIN
|
||||
// 设置窗口为鼠标穿透
|
||||
setWindowTransparentForMouse(true);
|
||||
|
||||
// 发送鼠标按下事件到底层窗口
|
||||
POINT pt = { event->globalPos().x(), event->globalPos().y() };
|
||||
HWND hWndBelow = WindowFromPoint(pt);
|
||||
if (hWndBelow && hWndBelow != hwnd) {
|
||||
// 转换坐标
|
||||
ScreenToClient(hWndBelow, &pt);
|
||||
|
||||
// 发送鼠标按下消息
|
||||
PostMessage(hWndBelow, WM_LBUTTONDOWN,
|
||||
MK_LBUTTON, MAKELPARAM(pt.x, pt.y));
|
||||
PostMessage(hWndBelow, WM_LBUTTONUP,
|
||||
0, MAKELPARAM(pt.x, pt.y));
|
||||
}
|
||||
|
||||
// 恢复窗口不穿透状态(下一次鼠标移动时会重新检测)
|
||||
QTimer::singleShot(100, this, [this]() {
|
||||
setWindowTransparentForMouse(false);
|
||||
});
|
||||
#endif
|
||||
this->isRightPressed = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCore::mouseReleaseEvent(QMouseEvent* event)
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
//
|
||||
// Created by misaki on 2025/12/29.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* 本类为网络数据流会使用到的数据访问对象封装
|
||||
* 目的是便于统一数据访问方式
|
||||
* 同时屏蔽了端到端数据交换格式,使得上层调用不再需要关心数据格式,而只需要填入数据即可
|
||||
*/
|
||||
|
||||
/**
|
||||
* 简单描述一下Yosuga客户端所需要使用到的数据
|
||||
* 主要为音频数据,控制信息,文本信息。
|
||||
* 其中文本信息与音频数据为捆绑收发,并且其中还包括了一些特别的信息,例如音频时长等
|
||||
* 控制信息与各种业务逻辑相关,例如模拟点击,模拟输入等
|
||||
*/
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonDocument>
|
||||
#include <QScopedPointer>
|
||||
#include <QMutex>
|
||||
#include <functional>
|
||||
|
||||
/**
|
||||
* 数据传输对象 (DTO) 定义
|
||||
*/
|
||||
// 音频文本捆绑数据结构
|
||||
struct AudioDataPacket {
|
||||
QString text; // 文本内容
|
||||
QByteArray audioData; // 音频原始数据 (二进制)
|
||||
int sampleRate; // 采样率
|
||||
int channels; // 通道数
|
||||
qint64 duration; // 时长 (ms)
|
||||
|
||||
AudioDataPacket() : sampleRate(16000), channels(1), duration(0) {}
|
||||
};
|
||||
|
||||
// 控制指令数据结构 (预留)
|
||||
enum class ControlType {
|
||||
Click,
|
||||
Input,
|
||||
Scroll
|
||||
};
|
||||
|
||||
struct ControlDataPacket {
|
||||
ControlType action;
|
||||
int x;
|
||||
int y;
|
||||
QString extraData;
|
||||
};
|
||||
|
||||
/**
|
||||
* NetworkDO
|
||||
*/
|
||||
class NetworkDO final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(NetworkDO) // 禁用拷贝
|
||||
|
||||
public:
|
||||
// 单例访问点
|
||||
static NetworkDO* getInstance();
|
||||
// 显式销毁
|
||||
static void destroy();
|
||||
|
||||
// 定义发送回调函数类型
|
||||
using SenderFunc = std::function<void(const QString& type, const QJsonObject& data)>;
|
||||
|
||||
public:
|
||||
// 注入发送接口
|
||||
void registerSender(SenderFunc sender);
|
||||
|
||||
// 业务发送函数
|
||||
void sendAudioPacket(const AudioDataPacket& packet);
|
||||
void sendControlPacket(const ControlDataPacket& packet);
|
||||
|
||||
signals:
|
||||
// 业务接收信号
|
||||
void audioPacketReceived(const AudioDataPacket& packet);
|
||||
void controlPacketReceived(const ControlDataPacket& packet);
|
||||
void errorOccurred(const QString& errorMsg);
|
||||
|
||||
public slots:
|
||||
// 接收底层 JSON 数据
|
||||
void onDataReceived(const QString& type, const QJsonObject& data);
|
||||
public:
|
||||
~NetworkDO() override;
|
||||
private:
|
||||
// 构造/析构函数私有化
|
||||
explicit NetworkDO(QObject *parent = nullptr);
|
||||
|
||||
// 内部处理逻辑
|
||||
void handleAudioMessage(const QJsonObject& data);
|
||||
|
||||
static QScopedPointer<NetworkDO> m_instance;
|
||||
static QMutex m_mutex;
|
||||
|
||||
SenderFunc m_sender; // 注入的发送器
|
||||
};
|
||||
@@ -0,0 +1,118 @@
|
||||
//
|
||||
// Created by misaki on 2025/12/29.
|
||||
//
|
||||
#include "NetWorkDO.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QMutexLocker>
|
||||
|
||||
// 初始化静态成员
|
||||
QScopedPointer<NetworkDO> NetworkDO::m_instance;
|
||||
QMutex NetworkDO::m_mutex;
|
||||
|
||||
// 单例实现 (QScopedPointer + Mutex)
|
||||
NetworkDO* NetworkDO::getInstance()
|
||||
{
|
||||
if (m_instance.isNull()) {
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_instance.isNull()) {
|
||||
// 使用 reset 创建实例,因为构造函数是私有的
|
||||
m_instance.reset(new NetworkDO());
|
||||
}
|
||||
}
|
||||
return m_instance.data();
|
||||
}
|
||||
|
||||
void NetworkDO::destroy()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_instance.isNull()) {
|
||||
m_instance.reset(); // 这会触发析构函数
|
||||
}
|
||||
}
|
||||
|
||||
NetworkDO::NetworkDO(QObject *parent) : QObject(parent)
|
||||
{
|
||||
qDebug() << "NetworkDO initialized";
|
||||
}
|
||||
|
||||
NetworkDO::~NetworkDO()
|
||||
{
|
||||
qDebug() << "NetworkDO destroyed";
|
||||
}
|
||||
|
||||
// 业务逻辑实现
|
||||
void NetworkDO::registerSender(SenderFunc sender)
|
||||
{
|
||||
QMutexLocker locker(&m_mutex); // 简单保护一下赋值
|
||||
m_sender = std::move(sender);
|
||||
}
|
||||
|
||||
void NetworkDO::sendAudioPacket(const AudioDataPacket& packet)
|
||||
{
|
||||
// 检查发送器是否已注入
|
||||
if (!m_sender) {
|
||||
emit errorOccurred("Sender not registered! Call registerSender() first.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 封装数据 (DTO -> JSON)
|
||||
QJsonObject dataObj;
|
||||
dataObj["text"] = packet.text;
|
||||
// 音频转 Base64 字符串传输
|
||||
dataObj["audio"] = QString::fromLatin1(packet.audioData.toBase64());
|
||||
dataObj["sampleRate"] = packet.sampleRate;
|
||||
dataObj["channels"] = packet.channels;
|
||||
dataObj["duration"] = packet.duration;
|
||||
|
||||
// 调用底层发送 (解耦)
|
||||
// "textAudio" 是与后端约定的协议类型
|
||||
m_sender("textAudio", dataObj);
|
||||
}
|
||||
|
||||
void NetworkDO::sendControlPacket(const ControlDataPacket& packet)
|
||||
{
|
||||
if (!m_sender) return;
|
||||
|
||||
QJsonObject dataObj;
|
||||
dataObj["action"] = static_cast<int>(packet.action);
|
||||
dataObj["x"] = packet.x;
|
||||
dataObj["y"] = packet.y;
|
||||
|
||||
m_sender("control", dataObj);
|
||||
}
|
||||
|
||||
void NetworkDO::onDataReceived(const QString& type, const QJsonObject& data)
|
||||
{
|
||||
// 根据类型分发
|
||||
if (type == "textAudio") {
|
||||
handleAudioMessage(data);
|
||||
}
|
||||
else if (type == "control") {
|
||||
// handleControlMessage(data);
|
||||
}
|
||||
else {
|
||||
qWarning() << "[NetworkDO] Received unknown type:" << type;
|
||||
}
|
||||
}
|
||||
|
||||
void NetworkDO::handleAudioMessage(const QJsonObject& data)
|
||||
{
|
||||
AudioDataPacket packet;
|
||||
|
||||
// 解析基础字段 (JSON -> DTO)
|
||||
packet.text = data.value("text").toString();
|
||||
packet.sampleRate = data.value("sampleRate").toInt(16000);
|
||||
packet.channels = data.value("channels").toInt(1);
|
||||
// 注意类型转换,确保 long long 精度
|
||||
packet.duration = static_cast<qint64>(data.value("duration").toDouble());
|
||||
|
||||
// 解析音频 (Base64 -> Binary)
|
||||
QString base64Audio = data.value("audio").toString();
|
||||
if (!base64Audio.isEmpty()) {
|
||||
packet.audioData = QByteArray::fromBase64(base64Audio.toLatin1());
|
||||
}
|
||||
|
||||
// 通知上层业务
|
||||
emit audioPacketReceived(packet);
|
||||
}
|
||||
@@ -1,59 +1,150 @@
|
||||
//
|
||||
// Created by Administrator on 2025/2/4.
|
||||
//
|
||||
#pragma once
|
||||
#include <QWebSocket>
|
||||
#include <QObject>
|
||||
#include <QThread>
|
||||
#include <QJsonDocument>
|
||||
#include <QTimer>
|
||||
#include <QQueue>
|
||||
#include <QAtomicPointer>
|
||||
#include <QMutexLocker>
|
||||
|
||||
/**
|
||||
* 已废弃
|
||||
* 2025.12.25重构 Misaki
|
||||
* 多线程websocket实现
|
||||
*/
|
||||
|
||||
#ifndef AIRI_DESKTOPGRIL_WEBSOCKETMANAGER_H
|
||||
#define AIRI_DESKTOPGRIL_WEBSOCKETMANAGER_H
|
||||
|
||||
|
||||
#include <QObject>
|
||||
#include <QtWebSockets/QWebSocket>
|
||||
|
||||
class WebSocketManager : public QObject
|
||||
class WebSocketManager final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WebSocketManager(QObject *parent = nullptr);
|
||||
|
||||
~WebSocketManager();
|
||||
|
||||
// 上传接口(传入本地文件路径)
|
||||
Q_INVOKABLE void uploadFile(const QString &filePath);
|
||||
|
||||
// 连接服务器
|
||||
void connectToServer();
|
||||
|
||||
// 断开服务器
|
||||
void disconnectFromServer();
|
||||
|
||||
// 设置服务器地址get&set方法
|
||||
void setUrl(const QString &url);
|
||||
QString getUrl();
|
||||
// 构造函数
|
||||
explicit WebSocketManager(QObject *parent = nullptr); // 不携带URL参数
|
||||
~WebSocketManager() override;
|
||||
// 删除拷贝构造函数和赋值操作符
|
||||
WebSocketManager(const WebSocketManager&) = delete;
|
||||
WebSocketManager& operator=(const WebSocketManager&) = delete;
|
||||
public:
|
||||
bool setRequestContent(const QString& requestToken); // 设置自定义websocket首次请求Token(鉴权用) 服务端应保持Token一致
|
||||
bool setSocketUrl(QUrl url); // 设置URL
|
||||
|
||||
signals:
|
||||
// 上传进度(0-100)
|
||||
void uploadProgressChanged(int percent);
|
||||
// 下载进度(0-100)
|
||||
void downloadProgressChanged(int percent);
|
||||
// 文件处理完成(返回保存路径)
|
||||
void fileProcessed(const QString &filePath);
|
||||
// 错误通知
|
||||
void errorOccurred(const QString &message);
|
||||
// 发给主线程的信号
|
||||
void connected(); // 连接
|
||||
void disconnected(); // 断开
|
||||
void textReceived(const QString &message); // 接收文本
|
||||
void jsonReceived(const QString &type, const QJsonObject &data); // 接收JSON
|
||||
void binaryReceived(const QByteArray &data); // 接收二进制数据
|
||||
void error(const QString &errorMsg); // 错误
|
||||
void log(const QString &msg); // 日志
|
||||
void reconnecting(int attempt); // 重连
|
||||
|
||||
public slots:
|
||||
// 主线程调用的槽
|
||||
bool connectToServer(); // 连接到服务器
|
||||
void disconnectFromServer(); // 断开连接
|
||||
void sendText(const QString &message); // 发送文本
|
||||
void sendJson(const QString &type, const QJsonObject &data); // 发送JSON
|
||||
void sendBinary(const QByteArray &data); // 发送二进制数据
|
||||
void setReconnectEnabled(bool enabled); // 设置重连
|
||||
void setRequestEnabled(bool enabled); // 设置自定义首次请求
|
||||
|
||||
[[nodiscard]] bool isConnected() const; // 是否已连接
|
||||
|
||||
private slots:
|
||||
void onConnected();
|
||||
void onBinaryMessageReceived(const QByteArray &message);
|
||||
void onError(QAbstractSocket::SocketError error);
|
||||
void onConnected(); // 连接成功
|
||||
void onDisconnected(); // 断开连接
|
||||
void onTextMessageReceived(const QString &message); // 接收到文本消息
|
||||
void onError(QAbstractSocket::SocketError socketError); // 错误
|
||||
void onSslErrors(const QList<QSslError> &errors); // SSL错误
|
||||
void onPong(quint64 elapsedTime, const QByteArray &payload); // Pong
|
||||
void sendPing() const; // 发送Ping
|
||||
void tryReconnect(); // 尝试重连
|
||||
|
||||
private:
|
||||
QWebSocket m_socket;
|
||||
QByteArray m_receivedData;
|
||||
qint64 m_totalFileSize = 0;
|
||||
QString url = "ws://localhost:8765";
|
||||
QWebSocket *m_socket; /// WebSocket对象
|
||||
QUrl m_url; /// 服务器地址
|
||||
QTimer *m_pingTimer; /// Ping定时器
|
||||
QTimer *m_reconnectTimer; /// 重连定时器
|
||||
int m_reconnectAttempts; /// 重连尝试次数
|
||||
bool m_isReconnectEnabled; /// 是否启用重连
|
||||
QNetworkRequest m_request; /// 自定义websocket首次请求(鉴权用)
|
||||
bool m_isRequest; /// 是否启用websocket首次请求
|
||||
};
|
||||
|
||||
#endif //AIRI_DESKTOPGRIL_WEBSOCKETMANAGER_H
|
||||
/**
|
||||
* WebSocket 客户端单例管理类
|
||||
* 负责管理 WebSocket 线程和全局访问
|
||||
*/
|
||||
class WebSocketClient final : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_DISABLE_COPY(WebSocketClient)
|
||||
|
||||
public:
|
||||
static WebSocketClient* getInstance();
|
||||
static void destroy();
|
||||
|
||||
// 初始化/重新配置 WebSocket
|
||||
bool setConfiguration(const QUrl& url, const QString& authToken = QString());
|
||||
|
||||
// WebSocket 操作
|
||||
void connectToServer();
|
||||
void disconnectFromServer();
|
||||
void reconnect();
|
||||
|
||||
void sendText(const QString& message);
|
||||
void sendJson(const QString& type, const QJsonObject& data);
|
||||
void sendBinary(const QByteArray& data);
|
||||
|
||||
[[nodiscard]] bool isConnected() const;
|
||||
[[nodiscard]] bool hasConfiguration() const { return m_url.isValid(); }
|
||||
|
||||
void setAutoReconnect(bool enabled);
|
||||
void setPingInterval(int milliseconds);
|
||||
|
||||
[[nodiscard]] QUrl currentUrl() const { return m_url; }
|
||||
[[nodiscard]] QString currentToken() const { return m_authToken; }
|
||||
|
||||
// 获取内部管理器(仅供高级使用)
|
||||
[[nodiscard]] WebSocketManager* manager() const { return m_webSocketManager; }
|
||||
|
||||
signals:
|
||||
// WebSocket 事件
|
||||
void connected();
|
||||
void disconnected();
|
||||
void textReceived(const QString &message);
|
||||
void jsonReceived(const QString &type, const QJsonObject &data);
|
||||
void binaryReceived(const QByteArray &data);
|
||||
void error(const QString &errorMsg);
|
||||
void log(const QString &msg);
|
||||
void reconnecting(int attempt);
|
||||
|
||||
// 配置变更
|
||||
void configurationChanged(const QUrl& oldUrl, const QUrl& newUrl);
|
||||
|
||||
// 内部信号(用于跨线程通信)
|
||||
void internalSetUrl(const QUrl& url);
|
||||
void internalSetAuthToken(const QString& token);
|
||||
void internalConnect();
|
||||
void internalDisconnect();
|
||||
void internalSendText(const QString& message);
|
||||
void internalSendJson(const QString& type, const QJsonObject& data);
|
||||
void internalSendBinary(const QByteArray& data);
|
||||
void internalSetAutoReconnect(bool enabled);
|
||||
void internalSetRequestEnabled(bool enabled);
|
||||
public:
|
||||
~WebSocketClient() override;
|
||||
private:
|
||||
explicit WebSocketClient(QObject *parent = nullptr);
|
||||
|
||||
static QMutex m_mutex;
|
||||
static QScopedPointer<WebSocketClient> m_instance;
|
||||
|
||||
QThread* m_workerThread;
|
||||
WebSocketManager* m_webSocketManager;
|
||||
QUrl m_url;
|
||||
QString m_authToken;
|
||||
bool m_hasAuthToken;
|
||||
};
|
||||
@@ -2,124 +2,473 @@
|
||||
// Created by Administrator on 2025/2/4.
|
||||
//
|
||||
|
||||
|
||||
#include "websocketmanager.h"
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <zlib.h>
|
||||
#include <QJsonObject>
|
||||
#include <utility>
|
||||
#include <QMutexLocker>
|
||||
|
||||
/// WebSocketManager
|
||||
WebSocketManager::WebSocketManager(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_socket(new QWebSocket) // 创建 WebSocket 对象
|
||||
, m_pingTimer(new QTimer(this))
|
||||
, m_reconnectTimer(new QTimer(this))
|
||||
, m_reconnectAttempts(0) // 重连尝试次数初始为0
|
||||
, m_isReconnectEnabled(false) // 默认不启用重连
|
||||
, m_isRequest(false) // 默认不启用自定义首次请求
|
||||
{
|
||||
// 连接信号槽
|
||||
connect(&m_socket, &QWebSocket::connected,
|
||||
// 配置 WebSocket
|
||||
m_socket->setParent(this); // 确保 socket 也在工作线程
|
||||
// 连接信号
|
||||
connect(m_socket, &QWebSocket::connected,
|
||||
this, &WebSocketManager::onConnected);
|
||||
connect(&m_socket, &QWebSocket::binaryMessageReceived,
|
||||
this, &WebSocketManager::onBinaryMessageReceived);
|
||||
connect(&m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::error),
|
||||
connect(m_socket, &QWebSocket::disconnected,
|
||||
this, &WebSocketManager::onDisconnected);
|
||||
connect(m_socket, &QWebSocket::textMessageReceived,
|
||||
this, &WebSocketManager::onTextMessageReceived);
|
||||
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QWebSocket::errorOccurred),
|
||||
this, &WebSocketManager::onError);
|
||||
connect(m_socket, &QWebSocket::sslErrors,
|
||||
this, &WebSocketManager::onSslErrors);
|
||||
connect(m_socket, &QWebSocket::pong,
|
||||
this, &WebSocketManager::onPong);
|
||||
// 心跳定时器
|
||||
m_pingTimer->setInterval(30000); // 30秒
|
||||
connect(m_pingTimer, &QTimer::timeout, this, &WebSocketManager::sendPing);
|
||||
|
||||
// 重连定时器
|
||||
m_reconnectTimer->setSingleShot(true);
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, &WebSocketManager::tryReconnect);
|
||||
}
|
||||
|
||||
WebSocketManager::~WebSocketManager()
|
||||
{
|
||||
m_socket.close();
|
||||
WebSocketManager::~WebSocketManager() {
|
||||
if (m_socket) {
|
||||
m_socket->close();
|
||||
m_socket->deleteLater();
|
||||
}
|
||||
}
|
||||
bool WebSocketManager::setRequestContent(const QString& requestToken) {
|
||||
if (this->m_url.isEmpty()) {
|
||||
emit error("URL is empty!");
|
||||
return false;
|
||||
}
|
||||
m_request.setUrl(this->m_url);
|
||||
m_request.setRawHeader("Authorization", requestToken.toUtf8()); // 设置请求头(包含鉴权Token)
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketManager::uploadFile(const QString &filePath)
|
||||
bool WebSocketManager::setSocketUrl(QUrl url) {
|
||||
if (this->m_url == url) {return true;} // 如果 URL 没有变化,直接返回成功
|
||||
// 判断URL是否合法,即是否符合websocket的格式
|
||||
if (!url.isValid() || url.scheme() != "ws" && url.scheme() != "wss") {
|
||||
emit error("Invalid URL!");
|
||||
return false;
|
||||
}
|
||||
this->m_url = std::move(url); // 设置 URL
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebSocketManager::connectToServer() {
|
||||
if (m_socket->state() != QAbstractSocket::UnconnectedState) { // 如果已经连接,则先断开连接重新连接
|
||||
m_socket->close();
|
||||
}
|
||||
if (m_url.isEmpty()) { // 如果 URL 为空
|
||||
emit error("URL is empty!");
|
||||
return false;
|
||||
}
|
||||
emit log(QString("Connecting to %1...").arg(m_url.toString()));
|
||||
|
||||
// SSL 配置
|
||||
if (m_url.scheme() == "wss") {
|
||||
QSslConfiguration sslConfig = m_socket->sslConfiguration();
|
||||
sslConfig.setPeerVerifyMode(QSslSocket::VerifyNone); // 开发环境
|
||||
m_socket->setSslConfiguration(sslConfig);
|
||||
}
|
||||
if (m_isRequest) { // 如果需要自定义首次请求
|
||||
m_socket->open(this->m_request); // 使用重载函数
|
||||
}
|
||||
else {
|
||||
m_socket->open(m_url);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketManager::disconnectFromServer() {
|
||||
m_isReconnectEnabled = false; // 手动断开不重连
|
||||
if (m_socket->state() != QAbstractSocket::UnconnectedState) {
|
||||
m_socket->close(QWebSocketProtocol::CloseCodeNormal, "Client closed");
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::sendText(const QString &message) {
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->sendTextMessage(message);
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::sendJson(const QString &type, const QJsonObject &data) {
|
||||
QJsonObject wrapper;
|
||||
wrapper["type"] = type;
|
||||
wrapper["data"] = data;
|
||||
wrapper["timestamp"] = QDateTime::currentMSecsSinceEpoch();
|
||||
|
||||
sendText(QJsonDocument(wrapper).toJson(QJsonDocument::Compact));
|
||||
}
|
||||
|
||||
void WebSocketManager::sendBinary(const QByteArray &data) {
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->sendBinaryMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketManager::isConnected() const {
|
||||
return m_socket->state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
void WebSocketManager::setReconnectEnabled(bool enabled) {
|
||||
m_isReconnectEnabled = enabled;
|
||||
if (!enabled) {
|
||||
m_reconnectTimer->stop();
|
||||
}
|
||||
}
|
||||
void WebSocketManager::setRequestEnabled(const bool enabled) {
|
||||
m_isRequest = enabled;
|
||||
}
|
||||
|
||||
// 私有槽
|
||||
void WebSocketManager::onConnected() {
|
||||
m_reconnectAttempts = 0;
|
||||
emit log("✅ WebSocket connected");
|
||||
emit connected();
|
||||
m_pingTimer->start();
|
||||
}
|
||||
|
||||
void WebSocketManager::onDisconnected() {
|
||||
emit log("⚠️ WebSocket disconnected");
|
||||
emit disconnected();
|
||||
m_pingTimer->stop();
|
||||
|
||||
// 自动重连
|
||||
if (m_isReconnectEnabled) {
|
||||
m_reconnectTimer->start(3000); // 3秒后重试
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::onTextMessageReceived(const QString &message) {
|
||||
emit textReceived(message);
|
||||
|
||||
// 自动解析 JSON
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(message.toUtf8());
|
||||
if (doc.isObject()) {
|
||||
const QJsonObject obj = doc.object();
|
||||
const QString type = obj.value("type").toString();
|
||||
const QJsonObject data = obj.value("data").toObject();
|
||||
if (!type.isEmpty()) {
|
||||
emit jsonReceived(type, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::onError(QAbstractSocket::SocketError socketError) {
|
||||
QString errorMsg;
|
||||
switch (socketError) {
|
||||
case QAbstractSocket::ConnectionRefusedError:
|
||||
errorMsg = "Connection refused";
|
||||
break;
|
||||
case QAbstractSocket::RemoteHostClosedError:
|
||||
errorMsg = "Remote host closed";
|
||||
break;
|
||||
case QAbstractSocket::HostNotFoundError:
|
||||
errorMsg = "Host not found";
|
||||
break;
|
||||
case QAbstractSocket::SocketTimeoutError:
|
||||
errorMsg = "Socket timeout";
|
||||
break;
|
||||
case QAbstractSocket::NetworkError:
|
||||
errorMsg = "Network error";
|
||||
break;
|
||||
case QAbstractSocket::SslHandshakeFailedError:
|
||||
errorMsg = "SSL handshake failed";
|
||||
break;
|
||||
default:
|
||||
errorMsg = m_socket->errorString();
|
||||
}
|
||||
emit error(QString("Socket error: %1").arg(errorMsg));
|
||||
}
|
||||
|
||||
void WebSocketManager::onSslErrors(const QList<QSslError> &errors) {
|
||||
foreach (const QSslError &err, errors) {
|
||||
emit log(QString("SSL error: %1").arg(err.errorString()));
|
||||
}
|
||||
#ifdef QT_DEBUG
|
||||
m_socket->ignoreSslErrors(); // 开发环境忽略
|
||||
#endif
|
||||
}
|
||||
|
||||
void WebSocketManager::sendPing() const {
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->ping();
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::onPong(quint64 elapsedTime, const QByteArray &) {
|
||||
emit log(QString("Pong received, latency: %1ms").arg(elapsedTime));
|
||||
}
|
||||
|
||||
void WebSocketManager::tryReconnect() {
|
||||
if (!m_isReconnectEnabled) return;
|
||||
|
||||
m_reconnectAttempts++;
|
||||
emit reconnecting(m_reconnectAttempts);
|
||||
emit log(QString("🔄 Reconnecting... (attempt %1)").arg(m_reconnectAttempts));
|
||||
|
||||
m_socket->open(m_url);
|
||||
}
|
||||
|
||||
#include <QScopedPointer>
|
||||
QMutex WebSocketClient::m_mutex;
|
||||
QScopedPointer<WebSocketClient> WebSocketClient::m_instance;
|
||||
|
||||
WebSocketClient* WebSocketClient::getInstance()
|
||||
{
|
||||
QFile file(filePath);
|
||||
if (!file.open(QIODevice::ReadOnly)) {
|
||||
emit errorOccurred(tr("无法打开文件: %1").arg(filePath));
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (m_instance.isNull()) {
|
||||
m_instance.reset(new WebSocketClient());
|
||||
}
|
||||
return m_instance.data();
|
||||
}
|
||||
|
||||
void WebSocketClient::destroy()
|
||||
{
|
||||
QMutexLocker locker(&m_mutex);
|
||||
if (!m_instance.isNull()) {
|
||||
m_instance.reset();
|
||||
}
|
||||
}
|
||||
|
||||
WebSocketClient::WebSocketClient(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_workerThread(nullptr)
|
||||
, m_webSocketManager(nullptr)
|
||||
, m_hasAuthToken(false)
|
||||
{
|
||||
// 创建工作线程
|
||||
m_workerThread = new QThread(this);
|
||||
m_workerThread->setObjectName("WebSocketWorkerThread");
|
||||
|
||||
// 创建 WebSocketManager
|
||||
m_webSocketManager = new WebSocketManager();
|
||||
m_webSocketManager->moveToThread(m_workerThread);
|
||||
|
||||
// 连接线程结束信号
|
||||
connect(m_workerThread, &QThread::finished,
|
||||
m_webSocketManager, &QObject::deleteLater);
|
||||
|
||||
// 连接 WebSocketManager 的信号到本类的信号(转发到主线程)
|
||||
connect(m_webSocketManager, &WebSocketManager::connected,
|
||||
this, &WebSocketClient::connected);
|
||||
connect(m_webSocketManager, &WebSocketManager::disconnected,
|
||||
this, &WebSocketClient::disconnected);
|
||||
connect(m_webSocketManager, &WebSocketManager::textReceived,
|
||||
this, &WebSocketClient::textReceived);
|
||||
connect(m_webSocketManager, &WebSocketManager::jsonReceived,
|
||||
this, &WebSocketClient::jsonReceived);
|
||||
connect(m_webSocketManager, &WebSocketManager::binaryReceived,
|
||||
this, &WebSocketClient::binaryReceived);
|
||||
connect(m_webSocketManager, &WebSocketManager::error,
|
||||
this, &WebSocketClient::error);
|
||||
connect(m_webSocketManager, &WebSocketManager::log,
|
||||
this, &WebSocketClient::log);
|
||||
connect(m_webSocketManager, &WebSocketManager::reconnecting,
|
||||
this, &WebSocketClient::reconnecting);
|
||||
|
||||
// 连接本类的内部信号到 WebSocketManager 的槽(跨线程调用)
|
||||
// 注意:由于 internal* 信号是私有信号,我们需要使用 lambda 包装器
|
||||
connect(this, &WebSocketClient::internalSetUrl,
|
||||
m_webSocketManager, [this](const QUrl& url) {
|
||||
bool success = m_webSocketManager->setSocketUrl(url);
|
||||
if (!success) {
|
||||
emit error("Failed to set WebSocket URL");
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalSetAuthToken,
|
||||
m_webSocketManager, [this](const QString& token) {
|
||||
if (!token.isEmpty()) {
|
||||
bool success = m_webSocketManager->setRequestContent(token);
|
||||
if (!success) {
|
||||
emit error("Failed to set authentication token");
|
||||
}
|
||||
m_webSocketManager->setRequestEnabled(true);
|
||||
} else {
|
||||
m_webSocketManager->setRequestEnabled(false);
|
||||
}
|
||||
}, Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalConnect,
|
||||
m_webSocketManager, &WebSocketManager::connectToServer,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalDisconnect,
|
||||
m_webSocketManager, &WebSocketManager::disconnectFromServer,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalSendText,
|
||||
m_webSocketManager, &WebSocketManager::sendText,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalSendJson,
|
||||
m_webSocketManager, &WebSocketManager::sendJson,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalSendBinary,
|
||||
m_webSocketManager, &WebSocketManager::sendBinary,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
connect(this, &WebSocketClient::internalSetAutoReconnect,
|
||||
m_webSocketManager, &WebSocketManager::setReconnectEnabled,
|
||||
Qt::QueuedConnection);
|
||||
|
||||
// 启动工作线程
|
||||
m_workerThread->start();
|
||||
|
||||
qDebug() << "WebSocketClient initialized, worker thread:" << m_workerThread;
|
||||
}
|
||||
|
||||
WebSocketClient::~WebSocketClient()
|
||||
{
|
||||
qDebug() << "Shutting down WebSocketClient...";
|
||||
|
||||
// 断开连接
|
||||
disconnectFromServer();
|
||||
|
||||
// 停止工作线程
|
||||
if (m_workerThread && m_workerThread->isRunning()) {
|
||||
m_workerThread->quit();
|
||||
if (!m_workerThread->wait(3000)) {
|
||||
m_workerThread->terminate();
|
||||
m_workerThread->wait();
|
||||
}
|
||||
delete m_workerThread;
|
||||
m_workerThread = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WebSocketClient::setConfiguration(const QUrl& url, const QString& authToken)
|
||||
{
|
||||
if (!url.isValid()) {
|
||||
emit error("Invalid URL provided");
|
||||
return false;
|
||||
}
|
||||
|
||||
QUrl oldUrl = m_url;
|
||||
m_url = url;
|
||||
m_authToken = authToken;
|
||||
m_hasAuthToken = !authToken.isEmpty();
|
||||
|
||||
// 发送到工作线程进行配置
|
||||
emit internalSetUrl(url);
|
||||
if (!authToken.isEmpty()) {
|
||||
emit internalSetAuthToken(authToken);
|
||||
}
|
||||
|
||||
// 通知配置变更
|
||||
if (oldUrl != url) {
|
||||
emit configurationChanged(oldUrl, url);
|
||||
}
|
||||
|
||||
emit log(QString("WebSocket configuration updated: %1").arg(url.toString()));
|
||||
return true;
|
||||
}
|
||||
|
||||
void WebSocketClient::connectToServer()
|
||||
{
|
||||
if (!m_url.isValid()) {
|
||||
emit error("WebSocket URL not configured. Call setConfiguration() first.");
|
||||
return;
|
||||
}
|
||||
|
||||
// 读取并压缩数据
|
||||
QByteArray rawData = file.readAll();
|
||||
QByteArray compressedData = qCompress(rawData, 9);
|
||||
|
||||
// 重置接收缓存
|
||||
m_receivedData.clear();
|
||||
m_totalFileSize = compressedData.size();
|
||||
|
||||
// 分块发送(每64KB一个块)
|
||||
const int chunkSize = 64 * 1024;
|
||||
int totalChunks = (compressedData.size() + chunkSize - 1) / chunkSize;
|
||||
|
||||
for (int i = 0; i < totalChunks; ++i) {
|
||||
QByteArray chunk = compressedData.mid(i * chunkSize, chunkSize);
|
||||
m_socket.sendBinaryMessage(chunk);
|
||||
|
||||
// 计算上传进度
|
||||
int progress = (i + 1) * 100 / totalChunks;
|
||||
emit uploadProgressChanged(progress);
|
||||
emit log(QString("Connecting to server: %1").arg(m_url.toString()));
|
||||
emit internalConnect();
|
||||
}
|
||||
|
||||
// 发送结束标记
|
||||
m_socket.sendBinaryMessage("END");
|
||||
}
|
||||
|
||||
void WebSocketManager::onBinaryMessageReceived(const QByteArray &message)
|
||||
void WebSocketClient::disconnectFromServer()
|
||||
{
|
||||
if (message == "END") {
|
||||
// 解压接收数据
|
||||
QByteArray decompressedData = qUncompress(m_receivedData);
|
||||
|
||||
// 生成保存路径
|
||||
QString savePath = QDir::tempPath() + "/processed_" +
|
||||
QDateTime::currentDateTime().toString("yyyyMMdd_hhmmss") + ".wav";
|
||||
|
||||
// 保存文件
|
||||
QFile file(savePath);
|
||||
if (file.open(QIODevice::WriteOnly)) {
|
||||
file.write(decompressedData);
|
||||
emit fileProcessed(savePath);
|
||||
} else {
|
||||
emit errorOccurred(tr("无法保存文件: %1").arg(savePath));
|
||||
emit log("Disconnecting from server...");
|
||||
emit internalDisconnect();
|
||||
}
|
||||
|
||||
m_receivedData.clear();
|
||||
} else {
|
||||
m_receivedData.append(message);
|
||||
|
||||
// 计算下载进度
|
||||
if (m_totalFileSize > 0) {
|
||||
int progress = m_receivedData.size() * 100 / m_totalFileSize;
|
||||
emit downloadProgressChanged(progress);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::connectToServer()
|
||||
void WebSocketClient::reconnect()
|
||||
{
|
||||
// 检查是否已经连接到服务器
|
||||
if (m_socket.state() == QAbstractSocket::ConnectedState) {
|
||||
emit errorOccurred(tr("已经连接到服务器"));
|
||||
if (!hasConfiguration()) {
|
||||
emit error("WebSocket not configured. Cannot reconnect.");
|
||||
return;
|
||||
}
|
||||
// 连接服务器
|
||||
m_socket.open(QUrl(this->url));
|
||||
|
||||
// 先断开,再连接
|
||||
disconnectFromServer();
|
||||
|
||||
// 短暂延迟后重新连接
|
||||
QTimer::singleShot(100, this, [this]() {
|
||||
emit log("Attempting to reconnect...");
|
||||
connectToServer();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void WebSocketManager::setUrl(const QString &url)
|
||||
void WebSocketClient::sendText(const QString& message)
|
||||
{
|
||||
this->url = url;
|
||||
if (isConnected()) {
|
||||
emit internalSendText(message);
|
||||
} else {
|
||||
emit error("Cannot send message: WebSocket is not connected");
|
||||
}
|
||||
}
|
||||
|
||||
QString WebSocketManager::getUrl()
|
||||
void WebSocketClient::sendJson(const QString& type, const QJsonObject& data)
|
||||
{
|
||||
return this->url;
|
||||
if (isConnected()) {
|
||||
emit internalSendJson(type, data);
|
||||
} else {
|
||||
emit error("Cannot send JSON: WebSocket is not connected");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void WebSocketManager::onConnected()
|
||||
void WebSocketClient::sendBinary(const QByteArray& data)
|
||||
{
|
||||
qDebug() << "Connected to server";
|
||||
if (isConnected()) {
|
||||
emit internalSendBinary(data);
|
||||
} else {
|
||||
emit error("Cannot send binary data: WebSocket is not connected");
|
||||
}
|
||||
}
|
||||
|
||||
void WebSocketManager::onError(QAbstractSocket::SocketError error)
|
||||
bool WebSocketClient::isConnected() const
|
||||
{
|
||||
emit errorOccurred(tr("网络错误: %1").arg(m_socket.errorString()));
|
||||
if (m_webSocketManager) {
|
||||
bool connected = false;
|
||||
// 使用阻塞调用获取连接状态
|
||||
QMetaObject::invokeMethod(const_cast<WebSocketManager*>(m_webSocketManager),
|
||||
[&connected, manager = m_webSocketManager]() {
|
||||
connected = manager->isConnected();
|
||||
},
|
||||
Qt::BlockingQueuedConnection);
|
||||
return connected;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void WebSocketClient::setAutoReconnect(bool enabled)
|
||||
{
|
||||
emit log(QString("Auto reconnect %1").arg(enabled ? "enabled" : "disabled"));
|
||||
emit internalSetAutoReconnect(enabled);
|
||||
}
|
||||
|
||||
void WebSocketClient::setPingInterval(int milliseconds)
|
||||
{
|
||||
// 注意:需要在 WebSocketManager 中添加相应的方法才能支持
|
||||
// 这里暂时记录日志,提醒需要实现
|
||||
emit log(QString("setPingInterval(%1) called, but not implemented in WebSocketManager")
|
||||
.arg(milliseconds));
|
||||
Q_UNUSED(milliseconds)
|
||||
}
|
||||
@@ -0,0 +1,236 @@
|
||||
当前项目中实现了tcp, socket, websocket三种通信方式
|
||||
项目只用到了了**websocket**方式,其他两种是历史遗留(Yosuga[Qt5]所使用)
|
||||
并且websocket经过了重构,交互数据也为自定义格式
|
||||
|
||||
这边顺便提供下WebSocket类的Mermaid图,帮助理解(将代码丢给AI生成出来的图,审阅了一下还是十分准确的)
|
||||
|
||||
## 1. 类图
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
class WebSocketManager {
|
||||
-QWebSocket* m_socket
|
||||
-QUrl m_url
|
||||
-QTimer* m_pingTimer
|
||||
-QTimer* m_reconnectTimer
|
||||
-int m_reconnectAttempts
|
||||
-bool m_isReconnectEnabled
|
||||
-QNetworkRequest m_request
|
||||
-bool m_isRequest
|
||||
+WebSocketManager(parent)
|
||||
~WebSocketManager()
|
||||
+setRequestContent(requestToken) bool
|
||||
+setSocketUrl(url) bool
|
||||
+connectToServer() bool
|
||||
+disconnectFromServer()
|
||||
+sendText(message)
|
||||
+sendJson(type, data)
|
||||
+sendBinary(data)
|
||||
+setReconnectEnabled(enabled)
|
||||
+setRequestEnabled(enabled)
|
||||
+isConnected() bool
|
||||
-onConnected()
|
||||
-onDisconnected()
|
||||
-onTextMessageReceived(message)
|
||||
-onError(socketError)
|
||||
-onSslErrors(errors)
|
||||
-onPong(elapsedTime, payload)
|
||||
-sendPing()
|
||||
-tryReconnect()
|
||||
-- signals --
|
||||
+connected()
|
||||
+disconnected()
|
||||
+textReceived(message)
|
||||
+jsonReceived(type, data)
|
||||
+binaryReceived(data)
|
||||
+error(errorMsg)
|
||||
+log(msg)
|
||||
+reconnecting(attempt)
|
||||
}
|
||||
|
||||
class WebSocketClient {
|
||||
-static QMutex m_mutex
|
||||
-static QScopedPointer~WebSocketClient~ m_instance
|
||||
-QThread* m_workerThread
|
||||
-WebSocketManager* m_webSocketManager
|
||||
-QUrl m_url
|
||||
-QString m_authToken
|
||||
-bool m_hasAuthToken
|
||||
+getInstance() WebSocketClient*
|
||||
+destroy()
|
||||
+setConfiguration(url, authToken) bool
|
||||
+connectToServer()
|
||||
+disconnectFromServer()
|
||||
+reconnect()
|
||||
+sendText(message)
|
||||
+sendJson(type, data)
|
||||
+sendBinary(data)
|
||||
+isConnected() bool
|
||||
+hasConfiguration() bool
|
||||
+setAutoReconnect(enabled)
|
||||
+setPingInterval(milliseconds)
|
||||
+currentUrl() QUrl
|
||||
+currentToken() QString
|
||||
+manager() WebSocketManager*
|
||||
-- signals --
|
||||
+connected()
|
||||
+disconnected()
|
||||
+textReceived(message)
|
||||
+jsonReceived(type, data)
|
||||
+binaryReceived(data)
|
||||
+error(errorMsg)
|
||||
+log(msg)
|
||||
+reconnecting(attempt)
|
||||
+configurationChanged(oldUrl, newUrl)
|
||||
-internalSetUrl(url)
|
||||
-internalSetAuthToken(token)
|
||||
-internalConnect()
|
||||
-internalDisconnect()
|
||||
-internalSendText(message)
|
||||
-internalSendJson(type, data)
|
||||
-internalSendBinary(data)
|
||||
-internalSetAutoReconnect(enabled)
|
||||
-internalSetRequestEnabled(enabled)
|
||||
}
|
||||
|
||||
class QWebSocket {
|
||||
+open(url)
|
||||
+close()
|
||||
+sendTextMessage(message)
|
||||
+sendBinaryMessage(data)
|
||||
+ping()
|
||||
+state() QAbstractSocket::SocketState
|
||||
}
|
||||
|
||||
class QThread {
|
||||
+start()
|
||||
+quit()
|
||||
+wait()
|
||||
+terminate()
|
||||
+isRunning() bool
|
||||
}
|
||||
|
||||
WebSocketClient "1" --> "1" WebSocketManager : 管理
|
||||
WebSocketClient "1" --> "1" QThread : 工作线程
|
||||
WebSocketManager "1" --> "1" QWebSocket : 封装
|
||||
WebSocketManager "1" --> "2" QTimer : 心跳/重连
|
||||
```
|
||||
|
||||
## 2. 连接时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant MainThread as 主线程
|
||||
participant Client as WebSocketClient
|
||||
participant WorkerThread as 工作线程
|
||||
participant Manager as WebSocketManager
|
||||
participant Socket as QWebSocket
|
||||
participant Server as WebSocket服务器
|
||||
|
||||
MainThread->>Client: getInstance()
|
||||
Client-->>MainThread: WebSocketClient实例
|
||||
|
||||
MainThread->>Client: setConfiguration(url, token)
|
||||
Client->>WorkerThread: 创建工作线程
|
||||
Client->>Manager: 创建WebSocketManager
|
||||
Manager->>Socket: 创建QWebSocket
|
||||
Client->>Manager: 信号连接配置
|
||||
Client->>Manager: 设置URL和Token
|
||||
|
||||
MainThread->>Client: connectToServer()
|
||||
Client->>Manager: emit internalConnect()
|
||||
Manager->>Socket: open(url/request)
|
||||
Socket->>Server: WebSocket握手
|
||||
Server-->>Socket: 连接成功
|
||||
Socket->>Manager: connected()
|
||||
Manager->>Manager: 启动心跳定时器
|
||||
Manager->>Client: emit connected()
|
||||
Client->>MainThread: emit connected()
|
||||
```
|
||||
|
||||
## 3. 状态图
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> 未配置
|
||||
|
||||
未配置 --> 已配置 : setConfiguration()
|
||||
已配置 --> 连接中 : connectToServer()
|
||||
|
||||
连接中 --> 已连接 : 连接成功
|
||||
连接中 --> 重连中 : 连接失败
|
||||
连接中 --> [*] : disconnectFromServer()
|
||||
|
||||
已连接 --> 已连接 : 发送/接收数据
|
||||
已连接 --> 已断开 : 连接断开
|
||||
已连接 --> [*] : disconnectFromServer()
|
||||
|
||||
已断开 --> 重连中 : 自动重连开启
|
||||
已断开 --> [*] : 自动重连关闭
|
||||
|
||||
重连中 --> 已连接 : 重连成功
|
||||
重连中 --> 重连中 : 重连失败(继续重试)
|
||||
重连中 --> [*] : disconnectFromServer()
|
||||
|
||||
state 重连中 {
|
||||
[*] --> 等待重试
|
||||
等待重试 --> 尝试连接 : 定时器触发
|
||||
尝试连接 --> 等待重试 : 连接失败
|
||||
尝试连接 --> [*] : 连接成功
|
||||
}
|
||||
```
|
||||
|
||||
## 4. 消息发送时序图
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant App as 应用程序
|
||||
participant Client as WebSocketClient
|
||||
participant Manager as WebSocketManager
|
||||
participant Socket as QWebSocket
|
||||
participant Server as WebSocket服务器
|
||||
|
||||
App->>Client: sendJson("message", data)
|
||||
alt 已连接
|
||||
Client->>Manager: emit internalSendJson("message", data)
|
||||
Manager->>Socket: sendTextMessage(json)
|
||||
Socket->>Server: 发送JSON数据
|
||||
Server-->>Socket: 响应(可选)
|
||||
Socket->>Manager: textMessageReceived()
|
||||
Manager->>Manager: 解析JSON
|
||||
Manager->>Client: emit jsonReceived()
|
||||
Client->>App: emit jsonReceived()
|
||||
else 未连接
|
||||
Client->>App: emit error("未连接")
|
||||
end
|
||||
```
|
||||
|
||||
## 5. 线程关系图
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
subgraph 主线程 [UI/主线程]
|
||||
direction LR
|
||||
App[应用程序]
|
||||
Client[WebSocketClient<br/>单例]
|
||||
end
|
||||
|
||||
subgraph 工作线程 [WebSocket工作线程]
|
||||
direction LR
|
||||
Manager[WebSocketManager]
|
||||
Socket[QWebSocket]
|
||||
end
|
||||
|
||||
subgraph 外部系统
|
||||
Server[WebSocket服务器]
|
||||
end
|
||||
|
||||
App -- 调用 --> Client
|
||||
Client -- 跨线程信号 --> Manager
|
||||
Manager -- Qt信号槽 --> Socket
|
||||
Socket -- TCP/WebSocket --> Server
|
||||
|
||||
Client -- 返回事件信号 --> App
|
||||
Socket -- 接收数据 --> Manager
|
||||
Manager -- 跨线程信号 --> Client
|
||||
```
|
||||
@@ -1,9 +1,7 @@
|
||||
//
|
||||
// Created by Administrator on 2025/3/2.
|
||||
//
|
||||
|
||||
#ifndef AIRI_DESKTOPGRIL_NETWORKPAGE_H
|
||||
#define AIRI_DESKTOPGRIL_NETWORKPAGE_H
|
||||
#pragma once
|
||||
|
||||
#include "BasePage.h"
|
||||
#include "ElaPushButton.h"
|
||||
@@ -11,23 +9,23 @@
|
||||
|
||||
class ElaPushButton;
|
||||
class ElaLineEdit;
|
||||
class NetWorkPage : public BasePage
|
||||
class NetWorkPage final : public BasePage
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
Q_INVOKABLE explicit NetWorkPage(QWidget* parent = nullptr);
|
||||
~NetWorkPage();
|
||||
~NetWorkPage() override;
|
||||
|
||||
private:
|
||||
void initUI();
|
||||
void initWebSocketClient();
|
||||
|
||||
|
||||
|
||||
private:
|
||||
// IP控件
|
||||
ElaPushButton* ipPushButton = nullptr;
|
||||
ElaLineEdit* ipLineEdit = nullptr;
|
||||
|
||||
// 端口控件
|
||||
ElaPushButton* portPushButton = nullptr;
|
||||
ElaLineEdit* portLineEdit = nullptr;
|
||||
// websocket 控件
|
||||
ElaPushButton* websocketPushButton = nullptr;
|
||||
ElaLineEdit* websocketLineEdit = nullptr;
|
||||
|
||||
// 连接测试
|
||||
ElaPushButton* connectTestPushButton = nullptr;
|
||||
@@ -36,7 +34,6 @@ private:
|
||||
// 断开
|
||||
ElaPushButton* disconnectPushButton = nullptr;
|
||||
|
||||
// 发送测试按钮
|
||||
ElaPushButton* sendTestPushButton = nullptr;
|
||||
};
|
||||
|
||||
|
||||
#endif //AIRI_DESKTOPGRIL_NETWORKPAGE_H
|
||||
|
||||
+154
-135
@@ -3,155 +3,174 @@
|
||||
//
|
||||
|
||||
#include "NetworkPage.h"
|
||||
|
||||
#include <QHBoxLayout>
|
||||
|
||||
#include "ElaComboBox.h"
|
||||
#include "ElaPlainTextEdit.h"
|
||||
#include "ElaScrollPageArea.h"
|
||||
#include "ElaSpinBox.h"
|
||||
#include "ElaText.h"
|
||||
|
||||
#include "socketmanager.h"
|
||||
#include <QHostAddress>
|
||||
#include "ElaMessageBar.h"
|
||||
#include "websocketmanager.h"
|
||||
#include "NetWorkDO.h"
|
||||
|
||||
NetWorkPage::NetWorkPage(QWidget* parent)
|
||||
: BasePage(parent)
|
||||
{
|
||||
// 预览窗口标题
|
||||
setWindowTitle("NetworkPage");
|
||||
|
||||
// ip
|
||||
ipPushButton = new ElaPushButton("设定",this);
|
||||
ipLineEdit = new ElaLineEdit(this);
|
||||
ElaScrollPageArea* ipToggleSwitchArea = new ElaScrollPageArea(this);
|
||||
QHBoxLayout* ipToggleSwitchLayout = new QHBoxLayout(ipToggleSwitchArea);
|
||||
ElaText* ipToggleSwitchText = new ElaText("服务端IP:", this);
|
||||
ipToggleSwitchText->setTextPixelSize(15);
|
||||
ipToggleSwitchLayout->addWidget(ipToggleSwitchText);
|
||||
ipToggleSwitchLayout->addWidget(ipLineEdit);
|
||||
ipToggleSwitchLayout->addStretch();
|
||||
connect(ipPushButton, &ElaPushButton::clicked, this, [=, this]() {
|
||||
// 我爱lambda函数
|
||||
QString ip_temp = ipLineEdit->text();
|
||||
auto f_temp = [=](const QString &ip) -> bool {
|
||||
QHostAddress addr;
|
||||
if (addr.setAddress(ip) && addr.protocol() == QAbstractSocket::IPv4Protocol) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
if(!f_temp(ip_temp)){
|
||||
ElaMessageBar::error(ElaMessageBarType::TopLeft, "连接设置", "服务端IP格式错误", 800.0, this);
|
||||
}
|
||||
else{
|
||||
SocketManager::getInstance()->setIp(ip_temp);
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连接设置", "服务端IP设置成功", 800.0, this);
|
||||
}
|
||||
});
|
||||
ipToggleSwitchLayout->addWidget(ipPushButton);
|
||||
ipToggleSwitchLayout->addSpacing(10);
|
||||
|
||||
// 端口
|
||||
portPushButton = new ElaPushButton("设定",this);
|
||||
portLineEdit = new ElaLineEdit(this);
|
||||
ElaScrollPageArea* portToggleSwitchArea = new ElaScrollPageArea(this);
|
||||
QHBoxLayout* portToggleSwitchLayout = new QHBoxLayout(portToggleSwitchArea);
|
||||
ElaText* portToggleSwitchText = new ElaText("服务端端口:", this);
|
||||
portToggleSwitchText->setTextPixelSize(15);
|
||||
portToggleSwitchLayout->addWidget(portToggleSwitchText);
|
||||
portToggleSwitchLayout->addWidget(portLineEdit);
|
||||
portToggleSwitchLayout->addStretch();
|
||||
connect(portPushButton, &ElaPushButton::clicked, this, [=, this]() {
|
||||
QString port_temp = portLineEdit->text();
|
||||
auto f_temp = [=](const QString &port) -> bool {
|
||||
return port.toInt() > 0 && port.toInt() < 65535;
|
||||
};
|
||||
if(!f_temp(port_temp)){
|
||||
ElaMessageBar::error(ElaMessageBarType::TopLeft, "连接设置", "服务端端口格式错误", 800.0, this);
|
||||
}
|
||||
else{
|
||||
SocketManager::getInstance()->setPort(port_temp.toInt());
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连接设置", "服务端端口设置成功", 800.0, this);
|
||||
}
|
||||
});
|
||||
portToggleSwitchLayout->addWidget(portPushButton);
|
||||
portToggleSwitchLayout->addSpacing(10);
|
||||
|
||||
connectTestPushButton = new ElaPushButton("连通测试",this);
|
||||
connectTestPushButton->setToolTip("测试与服务器连通性(如果成功连通会自动连上服务器)");
|
||||
connectPushButton = new ElaPushButton("连接",this);
|
||||
disconnectPushButton = new ElaPushButton("断开",this);
|
||||
ElaScrollPageArea* connectTestArea = new ElaScrollPageArea(this);
|
||||
QHBoxLayout* connectTestLayout = new QHBoxLayout(connectTestArea);
|
||||
connectTestLayout->addWidget(connectTestPushButton);
|
||||
connectTestLayout->addStretch();
|
||||
connect(connectTestPushButton, &ElaPushButton::clicked, this, [=, this]() {
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::ConnectedState){
|
||||
// 如果已连接
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试", "已连通", 800.0, this);
|
||||
}
|
||||
else{
|
||||
SocketManager::getInstance()->connectToServer();
|
||||
// TODO:等待很短的时间,等tcp握手完成
|
||||
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::ConnectedState){
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试", "已连通", 800.0, this);
|
||||
}
|
||||
else{
|
||||
ElaMessageBar::error(ElaMessageBarType::TopLeft, "连通测试", "未连通,请检查服务器是否开启或IP和端口信息是否正确", 1500.0, this);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
connect(connectPushButton, &ElaPushButton::clicked, this, [=, this]() {
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::ConnectedState){
|
||||
// 如果已连接
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试", "已连接", 800.0, this);
|
||||
}
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::UnconnectedState){
|
||||
// 如果未连接
|
||||
SocketManager::getInstance()->connectToServer();
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::ConnectedState){
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试", "连接成功", 800.0, this);
|
||||
}
|
||||
else{
|
||||
ElaMessageBar::error(ElaMessageBarType::TopLeft, "连通测试", "连接失败,请检查服务器是否开启或IP和端口信息是否正确", 1500.0, this);
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(disconnectPushButton, &ElaPushButton::clicked, this, [=, this]() {
|
||||
if(SocketManager::getInstance()->state() == QAbstractSocket::ConnectedState){
|
||||
// 如果已连接,则断开
|
||||
SocketManager::getInstance()->disconnectFromServer();
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试", "断开成功", 800.0, this);
|
||||
}
|
||||
else{
|
||||
// 如果未连接,则提示
|
||||
ElaMessageBar::information(ElaMessageBarType::BottomRight, "连通测试", "似乎并没有连接到服务器", 800.0, this);
|
||||
}
|
||||
});
|
||||
connectTestLayout->addWidget(disconnectPushButton);
|
||||
connectTestLayout->addWidget(connectPushButton);
|
||||
connectTestLayout->addSpacing(10);
|
||||
|
||||
|
||||
|
||||
QWidget* centralWidget = new QWidget(this);
|
||||
centralWidget->setWindowTitle("连接设置");
|
||||
QVBoxLayout* centerLayout = new QVBoxLayout(centralWidget);
|
||||
centerLayout->addWidget(ipToggleSwitchArea);
|
||||
centerLayout->addWidget(portToggleSwitchArea);
|
||||
centerLayout->addWidget(connectTestArea);
|
||||
|
||||
centerLayout->addStretch();
|
||||
centerLayout->setContentsMargins(0, 0, 0, 0);
|
||||
addCentralWidget(centralWidget, true, true, 0);
|
||||
|
||||
this->initUI(); // 初始化UI
|
||||
this->initWebSocketClient(); // 初始化websocket客户端(主要是相关的信号与槽)
|
||||
}
|
||||
|
||||
NetWorkPage::~NetWorkPage()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void NetWorkPage::initUI() {
|
||||
// websocket UI
|
||||
websocketPushButton = new ElaPushButton("设定",this);
|
||||
websocketPushButton->setToolTip("设定服务端WebSocket地址");
|
||||
websocketLineEdit = new ElaLineEdit(this);
|
||||
websocketLineEdit->setPlaceholderText("请输入服务端WebSocket地址");
|
||||
websocketLineEdit->setFixedWidth(300); // 设置websocketLineEdit框的宽度
|
||||
|
||||
ElaScrollPageArea* websocketToggleSwitchArea = new ElaScrollPageArea(this);
|
||||
QHBoxLayout* websocketToggleSwitchLayout = new QHBoxLayout(websocketToggleSwitchArea);
|
||||
ElaText* websocketToggleSwitchText = new ElaText("服务端WebSocket地址:", this);
|
||||
websocketToggleSwitchText->setTextPixelSize(15);
|
||||
websocketToggleSwitchLayout->addWidget(websocketToggleSwitchText);
|
||||
websocketToggleSwitchLayout->addWidget(websocketLineEdit);
|
||||
websocketToggleSwitchLayout->addStretch();
|
||||
websocketToggleSwitchLayout->addWidget(websocketPushButton);
|
||||
websocketToggleSwitchLayout->addSpacing(10);
|
||||
// 连通测试按钮
|
||||
connectTestPushButton = new ElaPushButton("连通测试",this);
|
||||
connectTestPushButton->setToolTip("测试与服务器连通性(如果成功连通会自动连上服务器)");
|
||||
connectPushButton = new ElaPushButton("连接",this);
|
||||
disconnectPushButton = new ElaPushButton("断开",this);
|
||||
sendTestPushButton = new ElaPushButton("发送测试",this);
|
||||
ElaScrollPageArea* connectTestArea = new ElaScrollPageArea(this); // 创建一个滚动页面
|
||||
QHBoxLayout* connectTestLayout = new QHBoxLayout(connectTestArea);
|
||||
connectTestLayout->addWidget(connectTestPushButton); // 将连通测试按钮添加到布局中
|
||||
connectTestLayout->addStretch(); // 添加一个空格
|
||||
connectTestLayout->addWidget(sendTestPushButton); // 将发送测试按钮添加到布局中
|
||||
connectTestLayout->addWidget(disconnectPushButton); // 将断开按钮添加到布局中
|
||||
connectTestLayout->addWidget(connectPushButton); // 将连接按钮添加到布局中
|
||||
connectTestLayout->addSpacing(10);
|
||||
// 添加到布局
|
||||
QWidget* centralWidget = new QWidget(this); // 中心部件
|
||||
centralWidget->setWindowTitle("连接设置"); // 中心部件标题
|
||||
QVBoxLayout* centerLayout = new QVBoxLayout(centralWidget); // 中心部件布局
|
||||
centerLayout->addWidget(websocketToggleSwitchArea); // 将websocketToggleSwitchArea添加到布局中
|
||||
centerLayout->addWidget(connectTestArea); // 将connectTestArea添加到布局中
|
||||
|
||||
centerLayout->addStretch();
|
||||
centerLayout->setContentsMargins(0, 0, 0, 0); // 设置布局的边距
|
||||
addCentralWidget(centralWidget, true, true, 0); // 添加中心部件
|
||||
}
|
||||
|
||||
void NetWorkPage::initWebSocketClient() {
|
||||
auto* client = WebSocketClient::getInstance(); // 获取单例实例(设置一个默认地址)
|
||||
auto* netDO = NetworkDO::getInstance();
|
||||
// 注入:将底层发送能力赋予 NetworkDO
|
||||
netDO->registerSender([client](const QString& type, const QJsonObject& data){
|
||||
client->sendJson(type, data);
|
||||
});
|
||||
// 监听:底层收到数据 -> NetworkDO 解析
|
||||
connect(client, &WebSocketClient::jsonReceived,
|
||||
netDO, &NetworkDO::onDataReceived);
|
||||
|
||||
// 连接成功的处理
|
||||
connect(client, &WebSocketClient::connected, this, [this]() {
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "WebSocket", "连接成功", 800.0, this);
|
||||
});
|
||||
// 连接失败的处理
|
||||
connect(client, &WebSocketClient::error, this, [this](const QString& errorMsg) {
|
||||
ElaMessageBar::error(ElaMessageBarType::TopLeft, "WebSocket错误", errorMsg, 1500.0, this);
|
||||
});
|
||||
// 断开连接的处理
|
||||
connect(client, &WebSocketClient::disconnected, this, [this]() {
|
||||
ElaMessageBar::information(ElaMessageBarType::BottomRight, "WebSocket", "连接已断开", 800.0, this);
|
||||
});
|
||||
// 接收数据处理
|
||||
connect(client, &WebSocketClient::jsonReceived, this, [](const QString &type, const QJsonObject &data) {
|
||||
qDebug() << "Received JSON data: " << type << " " << data;
|
||||
});
|
||||
|
||||
connect(websocketPushButton, &ElaPushButton::clicked, this, [this, client]() { // 设置服务端websocket地址
|
||||
const QUrl url(websocketLineEdit->text().trimmed()); // 从LineEdit中获取服务端websocket地址
|
||||
// 初始化客户端
|
||||
if (client->setConfiguration(url)) {
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连接设置",
|
||||
QString("服务器地址已设置为: %1").arg(url.toString()), 800.0, this);
|
||||
return;
|
||||
}
|
||||
ElaMessageBar::warning(ElaMessageBarType::TopLeft, "连接设置",
|
||||
QString("服务器地址存在问题"), 800.0, this);
|
||||
});
|
||||
|
||||
connect(connectTestPushButton, &ElaPushButton::clicked, this, [this, client]() {
|
||||
if (client->isConnected()) {
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试",
|
||||
"当前已连通", 800.0, this);
|
||||
return;
|
||||
}
|
||||
client->connectToServer(); // 连接
|
||||
// 使用定时器延迟检查连接状态
|
||||
QTimer::singleShot(1000, this, [this, client]() {
|
||||
if (client->isConnected()) {
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "连通测试",
|
||||
"连通测试成功", 800.0, this);
|
||||
} else {
|
||||
ElaMessageBar::warning(ElaMessageBarType::TopLeft, "连通测试",
|
||||
"无法连接到服务器,请检查地址和服务器状态", 1500.0, this);
|
||||
}
|
||||
});
|
||||
});
|
||||
connect(connectPushButton, &ElaPushButton::clicked, this, [this, client]() {
|
||||
if (client->isConnected()) {
|
||||
ElaMessageBar::information(ElaMessageBarType::TopRight, "连接状态",
|
||||
"已连接,无需重复连接", 800.0, this);
|
||||
return;
|
||||
}
|
||||
const QString urlStr = websocketLineEdit->text().trimmed();
|
||||
if (urlStr.isEmpty()) {
|
||||
ElaMessageBar::warning(ElaMessageBarType::TopLeft, "连接",
|
||||
"请先设置服务器地址", 800.0, this);
|
||||
return;
|
||||
}
|
||||
// 确保使用正确的地址
|
||||
client->setConfiguration(QUrl(urlStr));
|
||||
client->connectToServer();
|
||||
// 连接结果会在 connected/error 信号中处理
|
||||
ElaMessageBar::information(ElaMessageBarType::TopRight, "连接",
|
||||
"正在连接服务器...", 800.0, this);
|
||||
});
|
||||
connect(disconnectPushButton, &ElaPushButton::clicked, this, [this, client]() {
|
||||
if (client->isConnected()) {
|
||||
client->disconnectFromServer();
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "断开连接",
|
||||
"已断开连接", 800.0, this);
|
||||
} else {
|
||||
ElaMessageBar::information(ElaMessageBarType::BottomRight, "断开连接",
|
||||
"当前未连接", 800.0, this);
|
||||
}
|
||||
});
|
||||
connect(sendTestPushButton, &ElaPushButton::clicked, this, [this, netDO]() {
|
||||
// 创建数据包
|
||||
AudioDataPacket packet;
|
||||
packet.text = "Hello, World!";
|
||||
// 填入元数据
|
||||
packet.sampleRate = 16000;
|
||||
packet.channels = 1;
|
||||
packet.duration = 2500; // 2.5秒
|
||||
// 填入音频数据 (模拟从文件或录音设备读取的二进制数据)
|
||||
QByteArray rawAudioData;
|
||||
rawAudioData.append("...这里是真实的PCM或WAV二进制数据...");
|
||||
packet.audioData = rawAudioData;
|
||||
netDO->sendAudioPacket(packet);
|
||||
ElaMessageBar::success(ElaMessageBarType::TopRight, "发送测试",
|
||||
"已成功发送数据包", 1000.0, this);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user