关于qtcpsocket连续读写,qthread continue write read

本文介绍了使用QTCPServer和QTCPSocket进行多次读写操作时的一个关键技巧:每次读写过程中都需要重新定义QDataStream,以确保数据正确传输。并给出了正确的代码示例。

【转帖请注明出处:blog.youkuaiyun.com/lanmanck】

用qtcpserver和qtcpsocket建立连接后,一般需要循环读写很多数据。而qt自带的例子只有1次而已。

下面是我发现的一些技巧。

在读或者写过程中,QDataStream一定要每次都用“新的定义”,如下:

 

while(1)

{

QDataStream out(&block,QIODevice::WriteOnly);

 

out<<mystring;

socket.write(block);

}

而如下是不正确的,只有一次成功:

QDataStream out(&block,QIODevice::WriteOnly);

while(1)

{

out<<mystring;

socket.write(block);

}

读操作一个道理。大家注意点。错了请指正。

 

完整的例子在example有,不过c++programing in qt4那本书讲的更好。

#ifndef DATAPROCESSOR_H #define DATAPROCESSOR_H #include <QString> #include <QByteArray> #include <QColor> class DataProcessor { public: DataProcessor(); // ANSI序列处理 static QString processIncompleteData(const QString &newData, QString &buffer); static QString parseAnsiToRichText(const QString &input); // 数据转换 static QString byteArrayToHexString(const QByteArray &byteArray); // 文件类型判断 enum FileType { FILE_TYPE_UNKNOWN, FILE_TYPE_DIRECTORY, FILE_TYPE_EXECUTABLE }; static FileType getFileType(const QString &fileName); private: static bool checkAnsiSequenceIntegrity(const QString &text); static QColor ansiToQColor(int ansiCode); }; #endif // DATAPROCESSOR_H #ifndef HIDMANAGER_H #define HIDMANAGER_H #include <QObject> #include <QLibrary> #include <QThread> #include <QMutex> #include <QStringList> #include <QTime> #include <vector> #include <string> // 声明HID相关函数指针 typedef struct hid_device_info hid_device_info; typedef hid_device_info* (*HidEnumerateFunc)(unsigned short vendor_id, unsigned short product_id); typedef void (*HidFreeEnumerationFunc)(hid_device_info* devs); typedef void* (*HidOpenPathFunc)(const char* path); typedef void (*HidCloseFunc)(void* dev_handle); typedef int (*HidWriteFunc)(void* dev_handle, const unsigned char* data, size_t length); typedef int (*HidReadTimeoutFunc)(void* dev_handle, unsigned char* data, size_t length, int milliseconds); typedef const wchar_t* (*HidErrorFunc)(void* dev_handle); class HidWorker; class HidManager : public QObject { Q_OBJECT public: explicit HidManager(QObject *parent = nullptr); ~HidManager(); bool initHidLibrary(); QStringList enumerateHidDevices(); bool connectHidDevice(const QString &devicePath); void disconnectHidDevice(); bool isHidConnected() const; void sendData(const QString &data); signals: void deviceListChanged(const QStringList &deviceList); void dataReceived(const QByteArray &data, int bytesRead); void errorOccurred(const QString &errorMsg); void connectionStateChanged(bool isConnected); // 新增信号:用于通知设备已断开连接(只发送一次) void deviceDisconnected(const QString &errorMsg); private slots: void handleAsyncHidData(const QByteArray& data, int bytesRead); void handleHidReadError(const QString& errorMsg); void onReadLoopExited(); private: // 连接状态相关(线程安全) volatile bool m_isHidConnected; // 新增标志:跟踪设备是否已发送断开通知 volatile bool m_deviceDisconnectNotified; mutable QMutex m_connectionMutex; void setHidConnected(bool connected); // 设备枚举相关 std::vector<std::string> hidDevicePaths; QStringList hidDeviceInfos; QMutex m_enumMutex; // 保护设备路径和信息的读写 // 基础成员变量 QLibrary *hidLibrary; bool isHidLoaded; void *currentHidDevice; // 设备句柄,由HidManager统一管理 QThread *hidReadThread; bool isHidThreadRunning; QMutex m_threadMutex; // 重命名为m_threadMutex,避免与其他锁混淆 // HID函数指针 HidEnumerateFunc hidEnumerate; HidFreeEnumerationFunc hidFreeEnumeration; HidOpenPathFunc hidOpenPath; HidCloseFunc hidClose; HidWriteFunc hidWrite; HidReadTimeoutFunc hidReadTimeout; HidErrorFunc hidError; void asyncHidReadLoop(); }; class HidWorker : public QObject { Q_OBJECT public: explicit HidWorker(void* hidDev, HidReadTimeoutFunc readFunc, HidErrorFunc errorFunc, QObject* parent = nullptr); ~HidWorker(); void setHidDev(void* dev); void stopReadLoop(); void interruptRead(); public slots: void startReadLoop(); signals: void dataReceived(const QByteArray& data, int bytesRead); void readError(const QString& errorMsg); void readLoopExited(); private: void* m_hidDev; // 仅用于读取,不负责释放 HidReadTimeoutFunc m_hidReadTimeout; HidErrorFunc m_hidError; volatile bool m_isRunning; QMutex m_mutex; // 添加互斥锁保护m_isRunning }; #endif // HIDMANAGER_H #ifndef SERIALPORTMANAGER_H #define SERIALPORTMANAGER_H #include <QObject> #include <QSerialPort> #include <QTimer> #include <QStringList> class SerialPortManager : public QObject { Q_OBJECT public: explicit SerialPortManager(QObject *parent = nullptr); ~SerialPortManager(); void startPortCheckTimer(int interval = 100); void stopPortCheckTimer(); QStringList getAvailablePorts(); bool connectPort(const QString &portName, qint32 baudRate = 460800); void disconnectPort(); bool isPortConnected() const; void sendData(const QString &data); signals: void portListChanged(const QStringList &portList); void dataReceived(const QByteArray &data); void connectionStateChanged(bool isConnected); void errorOccurred(const QString &errorMsg); private slots: void checkPortChanges(); void readSerialData(); private: QSerialPort *serialPort; QTimer *portCheckTimer; QStringList lastPortNameList; bool isConnected; }; #endif // SERIALPORTMANAGER_H //#ifndef WIDGET_H //#define WIDGET_H //#include <QWidget> //#include <QSerialPort> //#include <QTimer> //#include <QEvent> //#include <QKeyEvent> //#include <QStringList> //#include "serialportmanager.h" //#include "hidmanager.h" //#include "dataprocessor.h" //namespace Ui { //class Widget; //} //class Widget : public QWidget //{ // Q_OBJECT //public: // explicit Widget(QWidget *parent = nullptr); // ~Widget(); // enum class DeviceType { // DEVICE_SERIAL, // DEVICE_HID // }; //signals: // void serialConnectionStateChanged(bool isConnected); //private slots: // void on_refresh_pb_clicked(); // void on_break_pb_clicked(); // void on_pushButton_clicked(); // // 串口相关槽函数 // void onSerialPortListChanged(const QStringList &portList); // void onSerialDataReceived(const QByteArray &data); // void onSerialConnectionStateChanged(bool isConnected); // void onSerialErrorOccurred(const QString &errorMsg); // // HID相关槽函数 // void onHidDeviceListChanged(const QStringList &deviceList); // void onHidDataReceived(const QByteArray &data, int bytesRead); // void onHidConnectionStateChanged(bool isConnected); // void onHidErrorOccurred(const QString &errorMsg); //private: // Ui::Widget *ui; // SerialPortManager *serialManager; // HidManager *hidManager; // DataProcessor *dataProcessor; // bool isSerialConnected; // bool isHidConnected; // QList<DeviceType> deviceTypes; // QStringList commandHistory; // int historyIndex; // QString currentDraft; // QString ansiBuffer; // QString hidAnsiBuffer; // void initUI(); // void initConnections(); // void getAvailableDevices(); // QString getSelectedPortName(); // void updateConnectButtonState(); // DeviceType getSelectedDeviceType(); // void logMessage(const QString &message); // void sendDataToDevice(const QString &data); // void handleHistoryNavigation(int direction); // bool eventFilter(QObject *watched, QEvent *event); //}; //#endif // WIDGET_H #ifndef WIDGET_H #define WIDGET_H #include <QWidget> #include <QSerialPort> #include <QTimer> #include <QEvent> #include <QKeyEvent> #include <QStringList> #include "serialportmanager.h" #include "hidmanager.h" #include "dataprocessor.h" namespace Ui { class Widget; } class Widget : public QWidget { Q_OBJECT public: explicit Widget(QWidget *parent = nullptr); ~Widget(); enum class DeviceType { DEVICE_SERIAL, DEVICE_HID }; signals: void serialConnectionStateChanged(bool isConnected); private slots: void on_refresh_pb_clicked(); void on_break_pb_clicked(); void on_pushButton_clicked(); // 串口相关槽函数 void onSerialPortListChanged(const QStringList &portList); void onSerialDataReceived(const QByteArray &data); void onSerialConnectionStateChanged(bool isConnected); void onSerialErrorOccurred(const QString &errorMsg); // HID相关槽函数 void onHidDeviceDisconnected(const QString &errorMsg); void onHidDeviceListChanged(const QStringList &deviceList); void onHidDataReceived(const QByteArray &data, int bytesRead); void onHidConnectionStateChanged(bool isConnected); void onHidErrorOccurred(const QString &errorMsg); private: Ui::Widget *ui; SerialPortManager *serialManager; HidManager *hidManager; DataProcessor *dataProcessor; bool isSerialConnected; bool isHidConnected; QList<DeviceType> deviceTypes; QStringList commandHistory; int historyIndex; QString currentDraft; QString ansiBuffer; QString hidAnsiBuffer; void initUI(); void initConnections(); void getAvailableDevices(); QString getSelectedPortName(); void updateConnectButtonState(); DeviceType getSelectedDeviceType(); void logMessage(const QString &message); void sendDataToDevice(const QString &data); void handleHistoryNavigation(int direction); bool eventFilter(QObject *watched, QEvent *event); }; #endif // WIDGET_H #include "dataprocessor.h" #include <QRegExp> #include <QTime> DataProcessor::DataProcessor() { } bool DataProcessor::checkAnsiSequenceIntegrity(const QString &text) { QStringList commands = {"ls", "pwd", "cd", "mkdir", "rm", "cp", "mv", "cat"}; int commandStart = -1; QString foundCommand; foreach (const QString &cmd, commands) { commandStart = text.indexOf(cmd); if (commandStart != -1) { foundCommand = cmd; break; } } if (commandStart == -1) { QRegExp unknownCmdRegex("\\b\\w+\\b"); if (unknownCmdRegex.indexIn(text) == -1) { return false; } foundCommand = unknownCmdRegex.cap(0); commandStart = unknownCmdRegex.pos(0); } int commandEnd = commandStart + foundCommand.length(); if (commandEnd >= text.length() || text.mid(commandEnd, 2) != "\r\n") { return false; } int endMarkerPos = text.indexOf("\r\n[LB]:-)", commandEnd); if (endMarkerPos != -1) { QString contentBetween = text.mid(commandEnd + 2, endMarkerPos - (commandEnd + 2)); if (foundCommand == "ls") { QRegExp ansiRegex("\x1B\\[([0-9;]*[mKfsu])(\\w+)"); int ansiCount = 0; int pos = 0; while ((pos = ansiRegex.indexIn(contentBetween, pos)) != -1) { ansiCount++; pos += ansiRegex.matchedLength(); } return ansiCount >= 1; } else if (foundCommand == "pwd") { return contentBetween.contains("/"); } else { return true; } } else { return (commandEnd + 2) == text.length(); } } QString DataProcessor::processIncompleteData(const QString &newData, QString &buffer) { buffer += newData; QRegExp completeAnsiRegex("\x1B\\[([0-9;]*[mKfsu])"); QRegExp incompleteAnsiRegex(".*\x1B\\[([0-9;]*)$"); if (incompleteAnsiRegex.exactMatch(buffer)) { int splitPos = buffer.lastIndexOf("\x1B["); QString processable = buffer.left(splitPos); buffer = buffer.mid(splitPos); if (!processable.isEmpty() && !checkAnsiSequenceIntegrity(processable)) { buffer = processable + buffer; return ""; } return processable; } if (!checkAnsiSequenceIntegrity(buffer)) { return ""; } QString processable = buffer; buffer.clear(); return processable; } QColor DataProcessor::ansiToQColor(int ansiCode) { switch(ansiCode) { case 30: return QColor(0, 0, 0); // 黑色 case 31: return QColor(170, 0, 0); // 红色 case 32: return QColor(0, 170, 0); // 绿色 case 33: return QColor(170, 85, 0); // 黄色 case 34: return QColor(0, 0, 170); // 蓝色 case 35: return QColor(170, 0, 170); // 洋红色 case 36: return QColor(0, 170, 170); // 青色 case 37: return QColor(170, 170, 170); // 白色 case 90: return QColor(85, 85, 85); // 亮黑(灰) case 91: return QColor(255, 85, 85); // 亮红 case 92: return QColor(85, 255, 85); // 亮绿 case 93: return QColor(255, 255, 85); // 亮黄 case 94: return QColor(85, 85, 255); // 亮蓝 case 95: return QColor(255, 85, 255); // 亮洋红 case 96: return QColor(85, 255, 255); // 亮青 case 97: return QColor(255, 255, 255); // 亮白 case 38: return QColor(114, 159, 207); // 亮蓝色 - 普通文件 case 100: return QColor(85, 85, 85); // 亮黑背景 default: return QColor(170, 170, 170); // 默认灰白色 } } DataProcessor::FileType DataProcessor::getFileType(const QString &fileName) { if (fileName == "bin" || fileName == "sbin" || fileName == "usr" || fileName == "var" || fileName == "mnt" || fileName == "proc" || fileName == "sys" || fileName == "root" || fileName == "tmp" || fileName == "lib" || fileName == "etc" || fileName == "overlay" || fileName == "rom") { return FILE_TYPE_DIRECTORY; } else if (fileName == "www") { return FILE_TYPE_EXECUTABLE; } return FILE_TYPE_UNKNOWN; } QString DataProcessor::byteArrayToHexString(const QByteArray &byteArray) { QString hexString; for (const char &c : byteArray) { hexString += QString("%1 ").arg((unsigned char)c, 2, 16, QChar('0')).toUpper(); } return hexString.trimmed(); } QString DataProcessor::parseAnsiToRichText(const QString &input) { QString html; QString currentStyle; QRegExp ansiRegex("\x1B\\[([0-9;]*)([mKfsu])"); QRegExp fileNameRegex("(\x1B\\[[0-9;]*m)(\\w+)"); html += "<div style='font-family: Consolas, monospace; white-space: pre; padding: 8px; background-color: #fff; color: #000;'>"; int pos = 0; int lastPos = 0; while ((pos = ansiRegex.indexIn(input, pos)) != -1) { if (pos > lastPos) { QString text = input.mid(lastPos, pos - lastPos); text.replace("&", "&").replace("<", "<").replace(">", ">"); if (!currentStyle.isEmpty()) { html += QString("<span style=\"%1\">%2</span>").arg(currentStyle, text); } else { html += text; } } QString codeStr = ansiRegex.cap(1); QString command = ansiRegex.cap(2); if (command == "m") { QStringList codes = codeStr.split(';', QString::SkipEmptyParts); if (codes.isEmpty() || codes.contains("0")) { currentStyle.clear(); } else { foreach (QString code, codes) { bool ok; int codeNum = code.toInt(&ok); if (!ok) continue; switch (codeNum) { case 1: currentStyle += "font-weight: bold; "; break; case 4: currentStyle += "text-decoration: underline; "; break; case 22: currentStyle.remove("font-weight: bold; "); break; case 24: currentStyle.remove("text-decoration: underline; "); break; default: if (codeNum >= 30 && codeNum <= 37) { QColor color = ansiToQColor(codeNum); currentStyle += QString("color: rgb(%1,%2,%3); ") .arg(color.red()).arg(color.green()).arg(color.blue()); } else if (codeNum >= 90 && codeNum <= 97) { QColor color = ansiToQColor(codeNum); currentStyle += QString("color: rgb(%1,%2,%3); ") .arg(color.red()).arg(color.green()).arg(color.blue()); } else if (codeNum >= 40 && codeNum <= 47) { QColor color = ansiToQColor(codeNum - 10); currentStyle += QString("background-color: rgb(%1,%2,%3); ") .arg(color.red()).arg(color.green()).arg(color.blue()); } else if (codeNum >= 100 && codeNum <= 107) { QColor color = ansiToQColor(codeNum - 70); currentStyle += QString("background-color: rgb(%1,%2,%3); ") .arg(color.red()).arg(color.green()).arg(color.blue()); } break; } } } } else if (command == "K") { html += "<br>"; } pos += ansiRegex.matchedLength(); lastPos = pos; } if (lastPos < input.length()) { QString text = input.mid(lastPos); text.replace("&", "&").replace("<", "<").replace(">", ">"); int filePos = 0; while ((filePos = fileNameRegex.indexIn(text, filePos)) != -1) { QString ansiCode = fileNameRegex.cap(1); QString fileName = fileNameRegex.cap(2); FileType type = getFileType(fileName); QString fileStyle = currentStyle; if (type == FILE_TYPE_DIRECTORY) { fileStyle += "color: rgb(85, 85, 255); "; } else if (type == FILE_TYPE_EXECUTABLE) { fileStyle += "color: rgb(85, 255, 85); font-weight: bold; "; } html += QString("<span style=\"%1\">%2</span>").arg(fileStyle, fileName); filePos += fileNameRegex.matchedLength(); } if (!currentStyle.isEmpty() && filePos == 0) { html += QString("<span style=\"%1\">%2</span>").arg(currentStyle, text); } else if (filePos < text.length()) { html += text.mid(filePos); } } html += "</div>"; return html; } #include "hidmanager.h" #include <QDebug> #include "hidapi.h" // ------------------------------ HidManager 实现 ------------------------------ void HidManager::setHidConnected(bool connected) { QMutexLocker locker(&m_connectionMutex); if (m_isHidConnected != connected) { m_isHidConnected = connected; // 连接状态改变时重置断开通知标志 if (connected) { m_deviceDisconnectNotified = false; } emit connectionStateChanged(connected); } } bool HidManager::isHidConnected() const { QMutexLocker locker(&m_connectionMutex); return m_isHidConnected; } HidManager::HidManager(QObject *parent) : QObject(parent), m_isHidConnected(false), m_deviceDisconnectNotified(false),isHidLoaded(false), currentHidDevice(nullptr), hidReadThread(nullptr), isHidThreadRunning(false) { } HidManager::~HidManager() { disconnectHidDevice(); if (hidLibrary) { hidLibrary->unload(); delete hidLibrary; hidLibrary = nullptr; } } bool HidManager::initHidLibrary() { // 检查是否已加载,避免重复初始化 if (isHidLoaded) { emit errorOccurred("HID库已加载,无需重复初始化"); return true; } hidLibrary = new QLibrary("hidapi.dll"); if (!hidLibrary->load()) { QString err = QString("HID库加载失败:%1").arg(hidLibrary->errorString()); emit errorOccurred(err); delete hidLibrary; hidLibrary = nullptr; return false; } // 解析函数指针 hidEnumerate = (HidEnumerateFunc)hidLibrary->resolve("hid_enumerate"); hidFreeEnumeration = (HidFreeEnumerationFunc)hidLibrary->resolve("hid_free_enumeration"); hidOpenPath = (HidOpenPathFunc)hidLibrary->resolve("hid_open_path"); hidClose = (HidCloseFunc)hidLibrary->resolve("hid_close"); hidWrite = (HidWriteFunc)hidLibrary->resolve("hid_write"); hidReadTimeout = (HidReadTimeoutFunc)hidLibrary->resolve("hid_read_timeout"); hidError = (HidErrorFunc)hidLibrary->resolve("hid_error"); if (!hidEnumerate || !hidFreeEnumeration || !hidOpenPath || !hidClose || !hidWrite || !hidReadTimeout || !hidError) { emit errorOccurred("HID库函数解析失败,部分函数指针为空"); hidLibrary->unload(); delete hidLibrary; hidLibrary = nullptr; return false; } isHidLoaded = true; emit errorOccurred("HID库初始化成功"); return true; } QStringList HidManager::enumerateHidDevices() { if (!isHidLoaded) { emit errorOccurred("HID库未加载,无法枚举设备"); return QStringList(); } QMutexLocker enumLocker(&m_enumMutex); hidDevicePaths.clear(); hidDeviceInfos.clear(); // 添加空指针检查 if (!hidEnumerate) { emit errorOccurred("hidEnumerate函数指针为空,无法枚举设备"); return QStringList(); } hid_device_info *devs = hidEnumerate(0x0, 0x0); if (!devs) { emit errorOccurred("HID设备枚举失败,未获取到设备列表"); return QStringList(); } hid_device_info *cur_dev = devs; QStringList deviceList; int count = 0; while (cur_dev) { // 增加对设备信息的有效性检查 if (!cur_dev) break; if (cur_dev->path == nullptr) { cur_dev = cur_dev->next; continue; } // 处理可能的空指针 const wchar_t* productStr = cur_dev->product_string ? cur_dev->product_string : L"未知设备"; const wchar_t* manufacturerStr = cur_dev->manufacturer_string ? cur_dev->manufacturer_string : L"未知厂商"; // 使用更安全的字符串处理 QString productInfo = QString::fromWCharArray(productStr); QString manufacturerInfo = QString::fromWCharArray(manufacturerStr); QString info = QString("[HID] VID:0x%1, PID:0x%2, 厂商:%3, 产品:%4") .arg(cur_dev->vendor_id, 4, 16, QChar('0')) .arg(cur_dev->product_id, 4, 16, QChar('0')) .arg(manufacturerInfo) .arg(productInfo); // 检查路径有效性 std::string devPathStr(cur_dev->path); if (devPathStr.empty()) { cur_dev = cur_dev->next; continue; } hidDevicePaths.push_back(devPathStr); hidDeviceInfos.append(info); deviceList.append(info); cur_dev = cur_dev->next; count++; } // 确保释放资源 hidFreeEnumeration(devs); // 无论如何都释放资源 if (count == 0) { emit errorOccurred("未发现HID设备"); } else { emit errorOccurred(QString("发现%1个HID设备,枚举完成").arg(count)); } return deviceList; } bool HidManager::connectHidDevice(const QString &devicePath) { if (!isHidLoaded) { emit errorOccurred("HID连接失败:hidapi库未加载"); return false; } QMutexLocker enumLocker(&m_enumMutex); if (isHidConnected()) { disconnectHidDevice(); QThread::msleep(100); } int index = -1; for (int i = 0; i < hidDeviceInfos.size(); i++) { if (hidDeviceInfos[i] == devicePath) { index = i; break; } } if (index < 0 || static_cast<size_t>(index) >= hidDevicePaths.size()) { emit errorOccurred(QString("HID连接失败:无效的设备路径[%1]").arg(devicePath)); return false; } const char* devPath = hidDevicePaths[index].c_str(); currentHidDevice = hidOpenPath(devPath); if (!currentHidDevice) { emit errorOccurred(QString("HID设备打开失败:路径[%1]").arg(QString::fromUtf8(devPath))); setHidConnected(false); return false; } setHidConnected(true); emit errorOccurred(QString("HID设备连接成功:%1").arg(devicePath)); asyncHidReadLoop(); return true; } void HidManager::disconnectHidDevice() { QMutexLocker threadLocker(&m_threadMutex); if (!isHidConnected() || !currentHidDevice) { emit errorOccurred("HID设备未连接或句柄已无效,无需断开"); return; } if (isHidThreadRunning && hidReadThread && hidReadThread->isRunning()) { HidWorker* worker = hidReadThread->findChild<HidWorker*>(); if (worker) { worker->stopReadLoop(); bool isExited = hidReadThread->wait(1000);//3000 if (!isExited) { qWarning() << "HID线程超时未退出,强制终止"; hidReadThread->terminate(); hidReadThread->wait(500);//1000 } disconnect(worker, nullptr, this, nullptr); } hidReadThread = nullptr; isHidThreadRunning = false; } if (currentHidDevice) { hidClose(currentHidDevice); currentHidDevice = nullptr; } setHidConnected(false); emit errorOccurred("HID设备断开流程完成"); } void HidManager::sendData(const QString &data) { if (!isHidConnected()) { emit errorOccurred("HID发送失败:未连接HID设备"); return; } if (!isHidLoaded || !currentHidDevice) { setHidConnected(false); emit errorOccurred("HID发送失败:库未加载或设备句柄无效"); return; } QString trimmedData = data.trimmed(); if (trimmedData.isEmpty()) { emit errorOccurred("HID发送失败:输入数据为空(已过滤空白字符)"); return; } QByteArray sendData = trimmedData.toUtf8(); if (sendData.isEmpty()) { emit errorOccurred("HID发送失败:数据转换为UTF-8后为空(无效字符)"); return; } QByteArray reportData; reportData.append((char)0x00); // 报告ID int maxDataLen = 64 - reportData.size(); if (sendData.size() > maxDataLen) { sendData = sendData.left(maxDataLen); emit errorOccurred(QString("HID发送数据超长,已截断至%1字节").arg(maxDataLen)); } reportData.append(sendData); int result = hidWrite(currentHidDevice, (const unsigned char*)reportData.data(), reportData.size()); if (result == -1) { QString errMsg = QString("HID发送失败:%1").arg(QString::fromWCharArray(hidError(currentHidDevice))); emit errorOccurred(errMsg); if (errMsg.contains("句柄无效") || errMsg.contains("设备没有连接", Qt::CaseInsensitive)) { disconnectHidDevice(); } } else { emit errorOccurred(QString("HID发送成功:%1字节").arg(result)); } } void HidManager::handleAsyncHidData(const QByteArray& data, int bytesRead) { emit dataReceived(data, bytesRead); } void HidManager::handleHidReadError(const QString& errorMsg) { QMutexLocker locker(&m_connectionMutex); // 检查是否是设备断开类错误 bool isDisconnectError = errorMsg.contains("句柄无效") || errorMsg.contains("设备未连接", Qt::CaseInsensitive) || errorMsg.contains("设备没有连接", Qt::CaseInsensitive) || errorMsg.contains("操作超时", Qt::CaseInsensitive); // 如果是设备断开错误且尚未通知,则只发送一次 if (isDisconnectError) { if (!m_deviceDisconnectNotified) { m_deviceDisconnectNotified = true; emit deviceDisconnected(errorMsg); // 发送专门的断开通知 emit errorOccurred("HID 读取错误:" + errorMsg); // 断开连接 locker.unlock(); // 解锁以避免死锁 disconnectHidDevice(); emit connectionStateChanged(false); } } else { // 其他错误正常处理 static QString lastError; static QTime lastErrorTime; if (errorMsg == lastError && lastErrorTime.elapsed() < 1000) { return; } lastError = errorMsg; lastErrorTime.start(); emit errorOccurred("HID 读取错误:" + errorMsg); } } void HidManager::onReadLoopExited() { QMutexLocker threadLocker(&m_threadMutex); isHidThreadRunning = false; emit errorOccurred("HID读取循环已退出"); } void HidManager::asyncHidReadLoop() { QMutexLocker threadLocker(&m_threadMutex); if (isHidThreadRunning && hidReadThread) { HidWorker* worker = hidReadThread->findChild<HidWorker*>(); if (worker) { worker->stopReadLoop(); hidReadThread->wait(1000); } delete hidReadThread; hidReadThread = nullptr; } hidReadThread = new QThread(); HidWorker* worker = new HidWorker(currentHidDevice, hidReadTimeout, hidError); worker->moveToThread(hidReadThread); connect(worker, &HidWorker::dataReceived, this, &HidManager::handleAsyncHidData); connect(worker, &HidWorker::readError, this, &HidManager::handleHidReadError); connect(worker, &HidWorker::readLoopExited, this, &HidManager::onReadLoopExited); connect(worker, &HidWorker::readLoopExited, worker, &HidWorker::deleteLater); connect(worker, &HidWorker::readLoopExited, hidReadThread, &QThread::quit); connect(hidReadThread, &QThread::finished, hidReadThread, &QThread::deleteLater); // 连接启动信号 connect(hidReadThread, &QThread::started, worker, &HidWorker::startReadLoop); isHidThreadRunning = true; hidReadThread->start(); } // ------------------------------ HidWorker 实现 ------------------------------ HidWorker::HidWorker(void* hidDev, HidReadTimeoutFunc readFunc, HidErrorFunc errorFunc, QObject* parent) : QObject(parent), m_hidDev(hidDev), m_hidReadTimeout(readFunc), m_hidError(errorFunc), m_isRunning(false) { } HidWorker::~HidWorker() { stopReadLoop(); } void HidWorker::setHidDev(void* dev) { QMutexLocker locker(&m_mutex); m_hidDev = dev; } void HidWorker::stopReadLoop() { QMutexLocker locker(&m_mutex); m_isRunning = false; } void HidWorker::interruptRead() { // 如果需要中断阻塞的read操作,可以在这里实现 } void HidWorker::startReadLoop() { QMutexLocker locker(&m_mutex); m_isRunning = true; locker.unlock(); // 释放锁,允许其他操作修改m_isRunning unsigned char buffer[64]; // HID通常使用64字节的缓冲区 while (true) { // 检查是否需要退出循环 locker.relock(); bool running = m_isRunning; locker.unlock(); if (!running) { break; } // 检查设备句柄是否有效 locker.relock(); void* hidDev = m_hidDev; locker.unlock(); if (!hidDev) { emit readError("设备句柄无效"); break; } // 读取数据,超时时间设为100ms,以便定期检查退出标志 int bytesRead = m_hidReadTimeout(hidDev, buffer, sizeof(buffer), 100); if (bytesRead > 0) { // 读取到数据,发送信号 emit dataReceived(QByteArray((char*)buffer, bytesRead), bytesRead); } else if (bytesRead < 0) { // 发生错误 QString errorMsg = QString::fromWCharArray(m_hidError(hidDev)); emit readError(errorMsg); // 如果是致命错误,退出循环 if (errorMsg.contains("句柄无效") || errorMsg.contains("设备未连接") || errorMsg.contains("设备没有连接")) { break; } } // bytesRead == 0 表示超时,继续循环 } emit readLoopExited(); } #include "serialportmanager.h" #include <QSerialPortInfo> #include <QDebug> SerialPortManager::SerialPortManager(QObject *parent) : QObject(parent), isConnected(false) { serialPort = new QSerialPort(this); portCheckTimer = new QTimer(this); connect(portCheckTimer, &QTimer::timeout, this, &SerialPortManager::checkPortChanges); connect(serialPort, &QSerialPort::readyRead, this, &SerialPortManager::readSerialData); } SerialPortManager::~SerialPortManager() { if (serialPort->isOpen()) { serialPort->close(); } } void SerialPortManager::startPortCheckTimer(int interval) { portCheckTimer->setInterval(interval); portCheckTimer->start(); } void SerialPortManager::stopPortCheckTimer() { portCheckTimer->stop(); } QStringList SerialPortManager::getAvailablePorts() { QStringList portList; foreach (const QSerialPortInfo &portInfo, QSerialPortInfo::availablePorts()) { portList << portInfo.portName(); } return portList; } bool SerialPortManager::connectPort(const QString &portName, qint32 baudRate) { if (serialPort->isOpen()) { serialPort->close(); } serialPort->setPortName(portName); serialPort->setBaudRate(baudRate); serialPort->setDataBits(QSerialPort::Data8); serialPort->setParity(QSerialPort::NoParity); serialPort->setStopBits(QSerialPort::OneStop); serialPort->setFlowControl(QSerialPort::NoFlowControl); if (!serialPort->open(QIODevice::ReadWrite)) { emit errorOccurred(serialPort->errorString()); return false; } isConnected = true; emit connectionStateChanged(true); return true; } void SerialPortManager::disconnectPort() { if (serialPort->isOpen()) { serialPort->close(); } isConnected = false; emit connectionStateChanged(false); } bool SerialPortManager::isPortConnected() const { return isConnected; } void SerialPortManager::sendData(const QString &data) { if (!isConnected || !serialPort->isOpen()) { emit errorOccurred("Serial port not connected"); return; } QString sendData = data.trimmed() + "\r\n"; QByteArray sendBytes = sendData.toUtf8(); qint64 sendLen = serialPort->write(sendBytes); if (sendLen == -1) { emit errorOccurred(serialPort->errorString()); } } void SerialPortManager::checkPortChanges() { QStringList currentPortList = getAvailablePorts(); if (currentPortList != lastPortNameList) { lastPortNameList = currentPortList; emit portListChanged(currentPortList); } } void SerialPortManager::readSerialData() { if (!serialPort->isOpen()) return; QByteArray recvBytes = serialPort->readAll(); if (!recvBytes.isEmpty()) { emit dataReceived(recvBytes); } } #include "widget.h" #include "ui_widget.h" #include <QDebug> #include <QTextCursor> #include <QDateTime> #include <QRegExp> #include <QFont> #include <QMessageBox> #include <QSerialPortInfo> Widget::Widget(QWidget *parent) : QWidget(parent) , ui(new Ui::Widget) , isSerialConnected(false) , isHidConnected(false) , historyIndex(-1) { ui->setupUi(this); serialManager = new SerialPortManager(this); hidManager = new HidManager(this); dataProcessor = new DataProcessor(); initUI(); initConnections(); // 初始化HID库 bool isHidLoaded = hidManager->initHidLibrary(); if (isHidLoaded) { logMessage("HID初始化成功:支持HID设备读写"); } else { logMessage("HID初始化失败:请确保hidapi.dll在程序目录"); } // 获取可用设备 getAvailableDevices(); // 优先选中COM3 bool com3Exists = false; int com3Index = -1; for (int i = 0; i < ui->number_cm->count(); ++i) { QString portText = ui->number_cm->itemText(i); if (portText.contains("USB 串行设备", Qt::CaseInsensitive) ) { com3Exists = true; com3Index = i; break; } } if (com3Exists) { //logMessage("检测到USB串行,自动选中"); //ui->number_cm->setCurrentIndex(com3Index); } else { logMessage("未检测到USB串行(COM3),等待设备插入"); } // 启动串口检查定时器 serialManager->startPortCheckTimer(100); } Widget::~Widget() { delete ui; delete serialManager; delete hidManager; delete dataProcessor; } void Widget::initUI() { setWindowTitle("串口+HID"); setWindowIcon(QIcon(":/images/HID.ico")); // 初始化输入区事件过滤器 ui->te_instruct->installEventFilter(this); // 初始化接收区样式 ui->recv_textEdit->setLineWrapMode(QTextEdit::WidgetWidth); ui->recv_textEdit->setFont(QFont("Consolas", 10)); ui->recv_textEdit->setStyleSheet("background-color: #f0f0f0; color: #333;"); ui->recv_textEdit->setAcceptRichText(true); } void Widget::initConnections() { // 串口信号连接 connect(serialManager, &SerialPortManager::portListChanged, this, &Widget::onSerialPortListChanged); connect(serialManager, &SerialPortManager::dataReceived, this, &Widget::onSerialDataReceived); connect(serialManager, &SerialPortManager::connectionStateChanged, this, &Widget::onSerialConnectionStateChanged); connect(serialManager, &SerialPortManager::errorOccurred, this, &Widget::onSerialErrorOccurred); // HID信号连接 connect(hidManager, &HidManager::deviceListChanged, this, &Widget::onHidDeviceListChanged); connect(hidManager, &HidManager::dataReceived, this, &Widget::onHidDataReceived); connect(hidManager, &HidManager::connectionStateChanged, this, &Widget::onHidConnectionStateChanged); connect(hidManager, &HidManager::errorOccurred, this, &Widget::onHidErrorOccurred); // 连接HID设备断开信号 connect(hidManager, &HidManager::deviceDisconnected, this, &Widget::onHidDeviceDisconnected); // // 可选:处理专门的断开通知 // connect(hidManager, &HidManager::deviceDisconnected, this, [this](const QString &errorMsg) { // QMessageBox::warning(this, "设备断开", "HID设备已断开连接:" + errorMsg); // }); } // 修改Widget::getAvailableDevices函数,添加异常捕获 void Widget::getAvailableDevices() { try { QString prevSelectedText = ui->number_cm->currentText(); ui->number_cm->clear(); deviceTypes.clear(); // 添加所有串口设备 QStringList serialPorts = serialManager->getAvailablePorts(); foreach (const QString &portName, serialPorts) { QSerialPortInfo portInfo(portName); QStringList displayParts; displayParts << portName; if (!portInfo.description().isEmpty()) displayParts << portInfo.description(); if (!portInfo.manufacturer().isEmpty()) displayParts << portInfo.manufacturer(); QString displayText = "[串口] " + displayParts.join(" - "); ui->number_cm->addItem(displayText); deviceTypes.append(DeviceType::DEVICE_SERIAL); } // 添加所有HID设备 QStringList hidDevices = hidManager->enumerateHidDevices(); foreach (const QString &deviceInfo, hidDevices) { // 检查设备信息有效性 if (!deviceInfo.isEmpty()) { ui->number_cm->addItem(deviceInfo); deviceTypes.append(DeviceType::DEVICE_HID); } } // 恢复之前的选中状态 int prevIndex = ui->number_cm->findText(prevSelectedText); if (prevIndex != -1) { ui->number_cm->setCurrentIndex(prevIndex); } } catch (...) { logMessage("获取设备列表时发生未知错误"); } } QString Widget::getSelectedPortName() { if (ui->number_cm->currentIndex() == -1) return ""; QString selectedText = ui->number_cm->currentText(); if (selectedText.startsWith("[串口] ")) { QString portPart = selectedText.mid(5); int separatorPos = portPart.indexOf(" - "); return separatorPos != -1 ? portPart.left(separatorPos) : portPart; } return ""; } // 修改updateConnectButtonState方法 void Widget::updateConnectButtonState() { if (ui->number_cm->count() == 0) { ui->break_pb->setText("无可用设备"); ui->break_pb->setEnabled(false); return; } DeviceType currentType = getSelectedDeviceType(); bool isConnected = (currentType == DeviceType::DEVICE_SERIAL) ? isSerialConnected : isHidConnected; // 统一按钮文本:未连接时显示"连接",连接后显示"断开" ui->break_pb->setText(isConnected ? "断开" : "连接"); ui->break_pb->setEnabled(true); } Widget::DeviceType Widget::getSelectedDeviceType() { int index = ui->number_cm->currentIndex(); if (index >= 0 && index < deviceTypes.size()) { return deviceTypes[index]; } return DeviceType::DEVICE_SERIAL; } void Widget::logMessage(const QString &message) { if (ui->recv_textEdit) { QString timeStamp = QTime::currentTime().toString("HH:mm:ss"); ui->recv_textEdit->append(QString("[%1] [日志] %2").arg(timeStamp, message)); ui->recv_textEdit->moveCursor(QTextCursor::End); } qDebug() << message; } void Widget::sendDataToDevice(const QString &data) { DeviceType currentType = getSelectedDeviceType(); if (currentType == DeviceType::DEVICE_SERIAL) { if (!isSerialConnected) { logMessage("串口发送失败:未连接串口"); QMessageBox::warning(this, "发送失败", "请先连接串口再发送数据"); return; } QString sendData = data.trimmed(); if (sendData.isEmpty()) { logMessage("发送失败:内容为空"); return; } if (commandHistory.isEmpty() || commandHistory.last() != sendData) { commandHistory.append(sendData); if (commandHistory.size() > 100) commandHistory.removeFirst(); } historyIndex = -1; serialManager->sendData(data); ui->recv_textEdit->append(QString(" 串口发送: %1").arg(data)); ui->recv_textEdit->moveCursor(QTextCursor::End); } else { hidManager->sendData(data); QString timeStamp = QTime::currentTime().toString("HH:mm:ss.zzz"); QString html = "<div style='margin:8px 0;'>"; html += "<hr style='border:1px dashed #CCCCCC; margin:5px 0;'>"; html += QString("<p style='margin:5px 0;'><span style='color:#0000CD;'>[%1] HID发送: %2</span></p>") .arg(timeStamp, data); html += "<div style='margin:5px 0;'></div>"; html += "</div>"; QTextCursor cursor(ui->recv_textEdit->textCursor()); cursor.movePosition(QTextCursor::End); cursor.insertHtml(html); ui->recv_textEdit->moveCursor(QTextCursor::End); } } void Widget::handleHistoryNavigation(int direction) { if (commandHistory.isEmpty()) return; if (historyIndex == -1) { currentDraft = ui->te_instruct->toPlainText(); } if (direction == 1) { // 上方向键 if (historyIndex + 1 < commandHistory.size()) { historyIndex++; } } else { // 下方向键 if (historyIndex > 0) { historyIndex--; } else if (historyIndex == 0) { historyIndex = -1; ui->te_instruct->setPlainText(currentDraft); return; } } if (historyIndex != -1) { int displayIndex = commandHistory.size() - 1 - historyIndex; ui->te_instruct->setPlainText(commandHistory[displayIndex]); } QTextCursor cursor = ui->te_instruct->textCursor(); cursor.movePosition(QTextCursor::End); ui->te_instruct->setTextCursor(cursor); } bool Widget::eventFilter(QObject *watched, QEvent *event) { if (watched == ui->te_instruct && event->type() == QEvent::KeyPress) { QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); if (keyEvent->key() == Qt::Key_Return || keyEvent->key() == Qt::Key_Enter) { QString inputText = ui->te_instruct->toPlainText(); sendDataToDevice(inputText); ui->te_instruct->clear(); return true; } else if (keyEvent->key() == Qt::Key_Up) { handleHistoryNavigation(1); return true; } else if (keyEvent->key() == Qt::Key_Down) { handleHistoryNavigation(-1); return true; } } return QWidget::eventFilter(watched, event); } void Widget::on_refresh_pb_clicked() { if (isSerialConnected) { QString closedPort = getSelectedPortName(); serialManager->disconnectPort(); logMessage(QString("刷新操作:已断开串口 %1 的连接").arg(closedPort)); } if (isHidConnected) { hidManager->disconnectHidDevice(); } getAvailableDevices(); updateConnectButtonState(); } void Widget::on_break_pb_clicked() { if (ui->number_cm->count() == 0) { QMessageBox::warning(this, "操作提示", "当前无可用设备,请检查设备连接!"); return; } DeviceType currentType = getSelectedDeviceType(); bool isCurrentlyConnected = (currentType == DeviceType::DEVICE_SERIAL) ? isSerialConnected : isHidConnected; if (currentType == DeviceType::DEVICE_SERIAL) { if (isCurrentlyConnected) { QString closedPort = getSelectedPortName(); serialManager->disconnectPort(); } else { QString targetPort = getSelectedPortName(); if (!targetPort.isEmpty()) { serialManager->connectPort(targetPort, 460800); } else { logMessage("连接失败:未选中任何串口"); QMessageBox::warning(this, "连接失败", "请先从下拉列表中选中一个串口!"); } } } else { if (isCurrentlyConnected) { hidManager->disconnectHidDevice(); } else { QString currentText = ui->number_cm->currentText(); hidManager->connectHidDevice(currentText); } } ansiBuffer.clear(); hidAnsiBuffer.clear(); bool isFinalConnected = (currentType == DeviceType::DEVICE_SERIAL) ? isSerialConnected : isHidConnected; ui->number_cm->setEnabled(!isFinalConnected); ui->number_cm->setStyleSheet(isFinalConnected ? "color: #888; background-color: #f5f5f5;" : ""); updateConnectButtonState(); } void Widget::on_pushButton_clicked() { ui->recv_textEdit->clear(); logMessage("接收区已清空"); } void Widget::onSerialPortListChanged(const QStringList &portList) { Q_UNUSED(portList); getAvailableDevices(); updateConnectButtonState(); } void Widget::onSerialDataReceived(const QByteArray &data) { QString rawText = QString::fromUtf8(data); QString processableText = dataProcessor->processIncompleteData(rawText, ansiBuffer); if (processableText.isEmpty()) { return; } QString richText = dataProcessor->parseAnsiToRichText(processableText); ui->recv_textEdit->setAcceptRichText(true); QString html = QString("<p><br></p>%1<hr>").arg(richText); QTextCursor cursor(ui->recv_textEdit->textCursor()); cursor.movePosition(QTextCursor::End); cursor.insertHtml(html); ui->recv_textEdit->moveCursor(QTextCursor::End); } void Widget::onSerialConnectionStateChanged(bool isConnected) { isSerialConnected = isConnected; updateConnectButtonState(); // 更新ComboBox状态 ui->number_cm->setEnabled(!isConnected); ui->number_cm->setStyleSheet(isConnected ? "color: #888; background-color: #f5f5f5;" : ""); if (isConnected) { logMessage(QString("成功连接串口 %1(波特率:460800)").arg(getSelectedPortName())); } else { logMessage(QString("已断开串口 %1 的连接").arg(getSelectedPortName())); // 刷新设备列表,确保显示最新状态 getAvailableDevices(); } } void Widget::onSerialErrorOccurred(const QString &errorMsg) { logMessage(QString("串口错误:%1").arg(errorMsg)); } // 修改Widget::onHidDeviceListChanged函数 void Widget::onHidDeviceListChanged(const QStringList &deviceList) { qDebug() << "onHidDeviceListChanged called"; // 添加调试日志 // 确保在主线程处理UI更新 if (QThread::currentThread() != this->thread()) { QMetaObject::invokeMethod(this, "onHidDeviceListChanged", Qt::QueuedConnection, Q_ARG(QStringList, deviceList)); return; } // 保存当前选中项 QString currentSelection = ui->number_cm->currentText(); bool wasConnected = isHidConnected; // 重新加载设备列表 getAvailableDevices(); // 尝试恢复之前的选择 int index = ui->number_cm->findText(currentSelection); if (index >= 0) { ui->number_cm->setCurrentIndex(index); } updateConnectButtonState(); } void Widget::onHidDataReceived(const QByteArray &data, int bytesRead) { qDebug() << "原始HID数据 [" << bytesRead << "字节]: " << data.toHex(); QString timeStamp = QTime::currentTime().toString("HH:mm:ss.zzz"); QString html = "<div style='margin:8px 0; color: white; background-color: #333;'>"; html += QString("<p style='margin:5px 0;'><span style='color:#228B22;'>[%1] HID接收(%2字节): %3</span></p>") .arg(timeStamp) .arg(bytesRead) .arg(dataProcessor->byteArrayToHexString(data)); html += "</div>"; QTextCursor cursor(ui->recv_textEdit->textCursor()); cursor.movePosition(QTextCursor::End); cursor.insertHtml(html); ui->recv_textEdit->moveCursor(QTextCursor::End); } // 修改HID连接状态变化处理函数 void Widget::onHidConnectionStateChanged(bool connected) { isHidConnected = connected; // 更新连接状态 updateConnectButtonState(); // 调用统一的按钮状态更新方法 if (connected) { logMessage("HID设备连接成功"); } else { logMessage("HID设备已断开连接"); } } void Widget::onHidErrorOccurred(const QString &errorMsg) { logMessage(errorMsg); } void Widget::onHidDeviceDisconnected(const QString &errorMsg) { logMessage("HID设备已断开: " + errorMsg); // 更新连接状态 isHidConnected = false; // 刷新设备列表 getAvailableDevices(); // 更新按钮状态 updateConnectButtonState(); // 重新启用ComboBox ui->number_cm->setEnabled(true); ui->number_cm->setStyleSheet(""); } 上面是我程序代码qt有很多问题请你修复下
09-06
<think>我们正在使用Qt框架实现一个多线程的TCP客户端。主要涉及两个类:QTcpSocket(用于TCP通信)和QThread(用于多线程管理)。目标是避免在主线程中进行网络操作,防止界面卡顿。 关键点: 1. 将QTcpSocket对象移动到子线程中,这样网络操作就不会阻塞主线程。 2. 注意:Qt中所有的网络操作都是异步的,但耗时的操作(如连接、读写)放在单独的线程中可以更好地利用多核CPU,同时避免主线程被阻塞。 3. 需要正确管理线程和对象的生命周期,避免跨线程调用问题。 步骤: a. 创建一个工作线程(继承自QThread或使用QObject+moveToThread方式)。 b. 在工作线程中创建和使用QTcpSocket。 c. 通过信号槽机制实现线程间通信(注意:信号槽是线程安全的)。 根据引用[4]中的提示,QTcpSocket不能跨线程调用,因此我们必须确保每个QTcpSocket对象都在同一个线程中创建和使用。 下面我们使用QObject::moveToThread方法将包含socket的对象移动到子线程。 设计: 1. 创建一个TcpClient类(继承自QObject),在该类中封装QTcpSocket。 2. 在TcpClient类中实现连接服务器、发送数据、接收数据等功能。 3. 在主线程中创建TcpClient对象,然后将其移动到子线程(使用moveToThread)。 4. 通过信号槽与TcpClient对象进行交互(例如,点击按钮发送数据时,通过信号触发TcpClient的发送函数)。 注意:不能在TcpClient对象被移动到子线程后,还在主线程中直接调用其方法(除非通过信号槽排队调用)。 代码结构示例: 步骤1:定义TcpClient类(工作对象) 头文件 tcpclient.h ```cpp #ifndef TCPCLIENT_H #define TCPCLIENT_H #include <QObject> #include <QTcpSocket> #include <QTimer> class TcpClient : public QObject { Q_OBJECT public: explicit TcpClient(QObject *parent = nullptr); ~TcpClient(); public slots: void connectToServer(const QString &host, quint16 port); // 连接服务器 void disconnectFromServer(); // 断开连接 void sendData(const QByteArray &data); // 发送数据 private slots: void onConnected(); // 连接成功 void onDisconnected(); // 断开连接 void onErrorOccurred(QAbstractSocket::SocketError error); // 错误处理 void onReadyRead(); // 读取数据 signals: void connected(); // 连接成功信号 void disconnected(); // 断开连接信号 void errorOccurred(const QString &errorString); // 错误信号 void dataReceived(const QByteArray &data); // 接收到数据信号 private: QTcpSocket *m_tcpSocket; // TCP套接字 QTimer *m_reconnectTimer; // 重连定时器 QString m_host; // 服务器地址 quint16 m_port; // 服务器端口 bool m_needReconnect; // 是否需要重连(根据业务需求设置) void setupReconnectTimer(); // 设置重连定时器 }; #endif // TCPCLIENT_H ``` 步骤2:实现TcpClient类 源文件 tcpclient.cpp ```cpp #include "tcpclient.h" #include <QDebug> TcpClient::TcpClient(QObject *parent) : QObject(parent) , m_tcpSocket(nullptr) , m_reconnectTimer(nullptr) , m_needReconnect(true) { // 创建套接字和定时器(注意:此时还在主线程,但会在moveToThread之后被移动到子线程) m_tcpSocket = new QTcpSocket(); // 连接套接字的信号 connect(m_tcpSocket, &QTcpSocket::connected, this, &TcpClient::onConnected); connect(m_tcpSocket, &QTcpSocket::disconnected, this, &TcpClient::onDisconnected); connect(m_tcpSocket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred), this, &TcpClient::onErrorOccurred); connect(m_tcpSocket, &QTcpSocket::readyRead, this, &TcpClient::onReadyRead); setupReconnectTimer(); } TcpClient::~TcpClient() { if (m_tcpSocket) { m_tcpSocket->disconnectFromHost(); if (m_tcpSocket->state() != QAbstractSocket::UnconnectedState) { m_tcpSocket->waitForDisconnected(); } delete m_tcpSocket; } if (m_reconnectTimer) { m_reconnectTimer->stop(); delete m_reconnectTimer; } } void TcpClient::setupReconnectTimer() { m_reconnectTimer = new QTimer(this); m_reconnectTimer->setInterval(5000); // 5秒重连一次 connect(m_reconnectTimer, &QTimer::timeout, this, [this](){ if (m_tcpSocket->state() != QAbstractSocket::ConnectedState) { qDebug() << "Reconnecting to server..."; m_tcpSocket->connectToHost(m_host, m_port); } }); } void TcpClient::connectToServer(const QString &host, quint16 port) { m_host = host; m_port = port; if (m_tcpSocket->state() != QAbstractSocket::ConnectedState) { m_tcpSocket->connectToHost(host, port); // 启动重连定时器(如果连接失败,定时器会尝试重连) m_reconnectTimer->start(); } } void TcpClient::disconnectFromServer() { m_needReconnect = false; // 主动断开,不需要重连 m_reconnectTimer->stop(); m_tcpSocket->disconnectFromHost(); } void TcpClient::sendData(const QByteArray &data) { if (m_tcpSocket && m_tcpSocket->state() == QAbstractSocket::ConnectedState) { m_tcpSocket->write(data); } } void TcpClient::onConnected() { qDebug() << "Connected to server."; emit connected(); // 连接成功,停止重连定时器(因为已经连接上了) m_reconnectTimer->stop(); } void TcpClient::onDisconnected() { qDebug() << "Disconnected from server."; emit disconnected(); // 断开连接,如果需要重连则启动定时器 if (m_needReconnect) { m_reconnectTimer->start(); } } void TcpClient::onErrorOccurred(QAbstractSocket::SocketError error) { QString errorStr = m_tcpSocket->errorString(); qDebug() << "Socket error:" << errorStr; emit errorOccurred(errorStr); } void TcpClient::onReadyRead() { QByteArray data = m_tcpSocket->readAll(); qDebug() << "Data received:" << data; emit dataReceived(data); } ``` 步骤3:在主线程中使用TcpClient类,并移动到子线程 例如,在MainWindow中: ```cpp // mainwindow.h #ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include "tcpclient.h" #include <QThread> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_connectButton_clicked(); // 连接按钮 void on_disconnectButton_clicked(); // 断开按钮 void on_sendButton_clicked(); // 发送按钮 void onTcpClientConnected(); // 连接成功 void onTcpClientDisconnected(); // 断开连接 void onTcpClientError(const QString &error); // 错误 void onTcpClientDataReceived(const QByteArray &data); // 收到数据 private: Ui::MainWindow *ui; TcpClient *m_tcpClient; // TCP客户端对象 QThread *m_workerThread; // 工作线程 }; #endif // MAINWINDOW_H ``` ```cpp // mainwindow.cpp #include "mainwindow.h" #include "ui_mainwindow.h" #include <QDebug> MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) , m_tcpClient(nullptr) , m_workerThread(nullptr) { ui->setupUi(this); // 创建工作线程 m_workerThread = new QThread(this); // 创建TCP客户端对象(注意:此时对象属于主线程) m_tcpClient = new TcpClient(); // 将TCP客户端对象移动到工作线程 m_tcpClient->moveToThread(m_workerThread); // 连接信号槽 connect(m_tcpClient, &TcpClient::connected, this, &MainWindow::onTcpClientConnected); connect(m_tcpClient, &TcpClient::disconnected, this, &MainWindow::onTcpClientDisconnected); connect(m_tcpClient, &TcpClient::errorOccurred, this, &MainWindow::onTcpClientError); connect(m_tcpClient, &TcpClient::dataReceived, this, &MainWindow::onTcpClientDataReceived); // 启动工作线程 m_workerThread->start(); // 注意:所有对m_tcpClient的调用都必须通过信号槽,因为它在另一个线程 } MainWindow::~MainWindow() { // 停止线程 m_workerThread->quit(); m_workerThread->wait(); delete m_tcpClient; // 注意:在删除前,确保线程已经退出 delete ui; } void MainWindow::on_connectButton_clicked() { QString host = ui->hostLineEdit->text(); quint16 port = ui->portLineEdit->text().toUShort(); // 通过信号调用TcpClient的连接方法(跨线程调用,使用排队连接) QMetaObject::invokeMethod(m_tcpClient, "connectToServer", Qt::QueuedConnection, Q_ARG(QString, host), Q_ARG(quint16, port)); } void MainWindow::on_disconnectButton_clicked() { // 通过信号调用断开连接 QMetaObject::invokeMethod(m_tcpClient, "disconnectFromServer", Qt::QueuedConnection); } void MainWindow::on_sendButton_clicked() { QByteArray data = ui->sendLineEdit->text().toUtf8(); // 通过信号调用发送数据 QMetaObject::invokeMethod(m_tcpClient, "sendData", Qt::QueuedConnection, Q_ARG(QByteArray, data)); } void MainWindow::onTcpClientConnected() { ui->statusLabel->setText("Connected"); } void MainWindow::onTcpClientDisconnected() { ui->statusLabel->setText("Disconnected"); } void MainWindow::onTcpClientError(const QString &error) { ui->statusLabel->setText("Error: " + error); } void MainWindow::onTcpClientDataReceived(const QByteArray &data) { ui->receiveTextEdit->append(QString::fromUtf8(data)); } ``` 注意事项: 1. 在TcpClient的构造函数中创建QTcpSocketQTimer,这些对象将在TcpClient被移动到子线程后,也在子线程中运行。 2. 使用QMetaObject::invokeMethod来跨线程调用TcpClient的方法,确保线程安全。 3. 重连机制:在连接断开后,定时器会尝试重新连接(根据需求可调整)。 4. 在窗口关闭时,需要正确退出工作线程,并等待线程结束。 以上代码实现了在Qt中使用QTcpSocketQThread的多线程TCP客户端。通过将TcpClient对象移动到子线程,网络操作不会阻塞主线程(UI线程)。 引用说明: [^1] 提供了使用QTimer进行重连的示例,我们在TcpClient中实现了类似的重连定时器。 [^2] 展示了服务器端如何接收客户端数据,我们客户端接收数据的处理类似。 [^3] 展示了客户端类的封装,我们参考了其结构。 [^4] 提醒了跨线程调用问题,因此我们通过moveToThread和信号槽机制避免跨线程直接调用。 相关问题: 1. 如何确保QTcpSocket在子线程中正确使用? 2. 为什么使用QMetaObject::invokeMethod而不是直接调用TcpClient的方法? 3. 如何处理TCP粘包问题? 4. 如何优化TCP客户端的重连机制?</think>在Qt中使用`QTcpSocket`和`QThread`实现多线程TCP客户端,需注意线程安全和对象生命周期管理。以下是实现方案及代码示例: ### 核心实现步骤 1. **创建工作者对象**(继承`QObject`) 封装`QTcpSocket`操作,通过信号槽与主线程通信 2. **使用`QThread`管理线程** 将工作者对象移至子线程,避免阻塞主线程 3. **线程安全通信** 通过信号槽机制跨线程调用(自动排队) ### 完整代码实现 #### 1. 工作者对象头文件 (`tcpworker.h`) ```cpp #pragma once #include <QObject> #include <QTcpSocket> #include <QTimer> class TcpWorker : public QObject { Q_OBJECT public: explicit TcpWorker(QObject *parent = nullptr); ~TcpWorker(); public slots: void connectToServer(const QString &host, quint16 port); void disconnectFromServer(); void sendData(const QByteArray &data); signals: void connectionStatusChanged(bool connected); void dataReceived(const QByteArray &data); void errorOccurred(const QString &error); private slots: void onConnected(); void onDisconnected(); void onReadyRead(); void onErrorOccurred(QAbstractSocket::SocketError error); void reconnect(); // 自动重连 private: QTcpSocket *m_socket; QTimer *m_reconnectTimer; QString m_host; quint16 m_port; bool m_autoReconnect = true; }; ``` #### 2. 工作者对象实现 (`tcpworker.cpp`) ```cpp #include "tcpworker.h" #include <QDebug> TcpWorker::TcpWorker(QObject *parent) : QObject(parent) { m_socket = new QTcpSocket(this); m_reconnectTimer = new QTimer(this); m_reconnectTimer->setInterval(3000); // 3秒重连间隔 connect(m_socket, &QTcpSocket::connected, this, &TcpWorker::onConnected); connect(m_socket, &QTcpSocket::disconnected, this, &TcpWorker::onDisconnected); connect(m_socket, &QTcpSocket::readyRead, this, &TcpWorker::onReadyRead); connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred), this, &TcpWorker::onErrorOccurred); connect(m_reconnectTimer, &QTimer::timeout, this, &TcpWorker::reconnect); } TcpWorker::~TcpWorker() { disconnectFromServer(); } void TcpWorker::connectToServer(const QString &host, quint16 port) { m_host = host; m_port = port; m_socket->connectToHost(host, port); } void TcpWorker::disconnectFromServer() { m_autoReconnect = false; m_reconnectTimer->stop(); m_socket->disconnectFromHost(); } void TcpWorker::sendData(const QByteArray &data) { if(m_socket->state() == QAbstractSocket::ConnectedState) { m_socket->write(data); } } void TcpWorker::onConnected() { m_reconnectTimer->stop(); emit connectionStatusChanged(true); } void TcpWorker::onDisconnected() { emit connectionStatusChanged(false); if(m_autoReconnect) m_reconnectTimer->start(); } void TcpWorker::onReadyRead() { emit dataReceived(m_socket->readAll()); } void TcpWorker::onErrorOccurred(QAbstractSocket::SocketError error) { Q_UNUSED(error) emit errorOccurred(m_socket->errorString()); } void TcpWorker::reconnect() { if(m_socket->state() != QAbstractSocket::ConnectingState) { m_socket->connectToHost(m_host, m_port); } } ``` #### 3. 主线程调用示例 ```cpp // 创建工作者对象和线程 TcpWorker *worker = new TcpWorker(); QThread *workerThread = new QThread(this); // 将worker移至子线程 worker->moveToThread(workerThread); // 连接信号槽 connect(worker, &TcpWorker::dataReceived, this, [](const QByteArray &data){ qDebug() << "Received:" << data; }); connect(worker, &TcpWorker::connectionStatusChanged, this, [](bool connected){ qDebug() << (connected ? "Connected" : "Disconnected"); }); // 启动线程 workerThread->start(); // 发起连接(通过信号触发跨线程调用) QMetaObject::invokeMethod(worker, "connectToServer", Qt::QueuedConnection, Q_ARG(QString, "127.0.0.1"), Q_ARG(quint16, 1234)); // 发送数据示例 QMetaObject::invokeMethod(worker, "sendData", Qt::QueuedConnection, Q_ARG(QByteArray, "Hello Server")); // 退出时清理 workerThread->quit(); workerThread->wait(); delete worker; ``` ### 关键注意事项 1. **线程安全规则** - 所有`QTcpSocket`操作必须在工作者线程执行 - 通过`QMetaObject::invokeMethod`或信号槽进行跨线程调用 - 禁止直接跨线程访问对象成员(违反Qt对象树规则) 2. **自动重连机制** 使用`QTimer`实现断开后自动重连(见`reconnect()`槽函数) 3. **资源释放** - 线程退出前调用`quit()`和`wait()` - 删除工作者对象前确保断开连接 4. **信号槽连接类型** 跨线程通信必须使用`Qt::QueuedConnection`(自动使用) > 当主线程通过`QMetaObject::invokeMethod`调用工作者的方法时,请求会被放入目标线程的事件队列,确保操作在正确的线程执行[^1][^4]。 ### 常见问题解决 - **"QSocketNotifier: socket notifiers cannot be enabled from another thread"** 确保`QTcpSocket`在目标线程创建(通过`moveToThread`实现) - **意外断开后无重连** 检查是否误将`m_autoReconnect`设为`false`,或未启动重连定时器 - **数据粘包问题** 需在应用层实现协议解析(如添加帧头/帧尾) --- ### 相关问题 1. **如何实现TCP心跳包检测连接状态?** 可在工作者线程中使用`QTimer`定期发送空数据包,超时未响应视为断开 2. **多线程TCP客户端如何实现负载均衡?** 可创建多个工作者对象+线程池,通过连接管理器分配连接 3. **Qt中如何解决TCP粘包问题?** 推荐方案: - 定长协议(固定数据包长度) - 分隔符协议(如`\r\n`结尾) - 包头+包体结构(包头包含数据长度) 4. **如何实现TLS/SSL加密通信?** 使用`QSslSocket`替代`QTcpSocket`,并配置证书: ```cpp QSslSocket *socket = new QSslSocket(this); socket->setProtocol(QSsl::TlsV1_2); socket->connectToHostEncrypted("host", port); ``` [^1]: Qt多线程TCP客户端使用QTimer进行重连 [^2]: QTcpSocketreadyRead信号处理 [^3]: TCP客户端封装示例 [^4]: QTcpSocket跨线程调用限制
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值