diff --git a/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro b/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro index 2017be7..a7035b5 100644 --- a/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro +++ b/App/BeltTearing/BeltTearingApp/BeltTearingApp.pro @@ -10,6 +10,11 @@ win32-msvc { QMAKE_CXXFLAGS += /utf-8 } +# For Windows crash dump generation and MessageBox functions (only for MSVC) +win32-msvc { + LIBS += -lDbgHelp -luser32 +} + # Include paths INCLUDEPATH += $$PWD/Presenter/Inc INCLUDEPATH += ../BeltTearingConfig/Inc diff --git a/App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h b/App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h index b8f5ce4..31703b5 100644 --- a/App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h +++ b/App/BeltTearing/BeltTearingApp/Presenter/Inc/BeltTearingPresenter.h @@ -10,6 +10,7 @@ #include #include #include +#include class BeltTearingPresenter : public QWidget @@ -19,6 +20,7 @@ class BeltTearingPresenter : public QWidget signals: void tearingDataReceived(const QJsonObject &data); void serverDataReceived(const QString &serverName, const QJsonObject &data); + void tcpDataReceivedAsync(const QString &aliasName, const QByteArray &data); public: @@ -61,6 +63,7 @@ private slots: void onConnected(const QString &serverName); void onDisconnected(const QString &serverName); void onTcpError(const QString &serverName, const QString &error); + void onTcpDataReceivedAsync(const QString &aliasName, const QByteArray &data); private: // 静态回调函数(用于C接口)- 已弃用,使用lambda替代 diff --git a/App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp b/App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp index 2272b68..bb4b5c1 100644 --- a/App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp +++ b/App/BeltTearing/BeltTearingApp/Presenter/Src/BeltTearingPresenter.cpp @@ -24,6 +24,11 @@ BeltTearingPresenter::BeltTearingPresenter(QWidget* parent) { // 创建配置实例 IVrBeltTearingConfig::CreateInstance(&m_config); + + // 连接异步数据处理信号和槽 + connect(this, &BeltTearingPresenter::tcpDataReceivedAsync, + this, &BeltTearingPresenter::onTcpDataReceivedAsync, + Qt::QueuedConnection); } BeltTearingPresenter::~BeltTearingPresenter() @@ -167,11 +172,15 @@ void BeltTearingPresenter::disconnectFromServer(const QString &serverName) it.value()->CloseDevice(); m_connectionStatus[it.key()] = false; } + // 清空所有数据缓存 + m_dataBuffers.clear(); } else { // 断开指定服务器连接 if (m_tcpClients.contains(serverName)) { m_tcpClients[serverName]->CloseDevice(); m_connectionStatus[serverName] = false; + // 清空该服务器的数据缓存 + m_dataBuffers.remove(serverName); } } } @@ -277,34 +286,11 @@ void BeltTearingPresenter::handleTcpDataReceived(const QString &aliasName, const { if (!pData || nLen <= 0) return; - // 将新数据添加到缓存 - m_dataBuffers[aliasName].append(pData, nLen); + // LOG_DEBUG("Received data from %s: size=%d\n", aliasName.toStdString().c_str(), nLen); - // 检查是否有完整的数据包 - QByteArray &buffer = m_dataBuffers[aliasName]; - const QByteArray endMarker = "___END___\r\n"; - - int endPos = buffer.indexOf(endMarker); - while (endPos != -1) { - // 找到完整数据包 - QByteArray completePacket = buffer.left(endPos); - // LOG_DEBUG("Found complete packet for %s: size=%d\n", serverName.toStdString().c_str(), completePacket.size()); - - // 处理完整数据包 - processCompletePacket(aliasName, completePacket); - - // 从缓存中移除已处理的数据(包括结束标记) - buffer.remove(0, endPos + endMarker.length()); - - // 检查缓存中是否还有其他完整数据包 - endPos = buffer.indexOf(endMarker); - } - - // 如果缓存过大,清空以避免内存问题 - if (buffer.size() > 1024 * 1024 * 2) { // 2MB 限制 - LOG_WARNING("Buffer too large for %s, clearing buffer\n", aliasName.toStdString().c_str()); - buffer.clear(); - } + // 将接收到的数据转换为QByteArray并通过信号发射到主线程异步处理 + QByteArray dataBuffer(pData, nLen); + emit tcpDataReceivedAsync(aliasName, dataBuffer); } void BeltTearingPresenter::processCompletePacket(const QString &aliasName, const QByteArray &completeData) @@ -528,7 +514,7 @@ void BeltTearingPresenter::handleServerInfoResponse(const QString& serverName, c void BeltTearingPresenter::ResetDetect(const QString& targetServerAlias) { LOG_INFO("Resetting detection for server: %s\n", targetServerAlias.toStdString().c_str()); - + // 检查目标服务端是否存在且已连接 if (!m_serverInfos.contains(targetServerAlias)) { LOG_ERROR("Target server not found: %s\n", targetServerAlias.toStdString().c_str()); @@ -537,7 +523,7 @@ void BeltTearingPresenter::ResetDetect(const QString& targetServerAlias) } return; } - + if (!isConnected(targetServerAlias)) { LOG_ERROR("Target server not connected: %s\n", targetServerAlias.toStdString().c_str()); if (m_statusUpdate) { @@ -545,21 +531,21 @@ void BeltTearingPresenter::ResetDetect(const QString& targetServerAlias) } return; } - + // 通知UI清空数据 if (m_statusUpdate) { m_statusUpdate->OnStatusUpdate(QString("正在重新检测服务器: %1...").arg(targetServerAlias)); // 清空数据的操作应该由UI层处理 } - + // 向指定的服务端发送重新检测指令 QJsonObject resetCommand; resetCommand["command"] = "resetDetect"; resetCommand["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate); - + QJsonDocument doc(resetCommand); QByteArray commandData = doc.toJson(QJsonDocument::Compact); - + // 只向指定的服务器发送重新检测指令 bool success = sendParametersToServer(ByteDataType::Text, targetServerAlias, commandData); if (success) { @@ -575,3 +561,39 @@ void BeltTearingPresenter::ResetDetect(const QString& targetServerAlias) } } +void BeltTearingPresenter::onTcpDataReceivedAsync(const QString &aliasName, const QByteArray &data) +{ + // 此槽函数在主线程中执行,避免阻塞TCP接收线程 + if (data.isEmpty()) return; + + // LOG_DEBUG("Processing async data from %s: size=%d\n", aliasName.toStdString().c_str(), data.size()); + + // 将新数据添加到缓存 + m_dataBuffers[aliasName].append(data); + + // 检查是否有完整的数据包 + QByteArray &buffer = m_dataBuffers[aliasName]; + const QByteArray endMarker = "___END___\r\n"; + + int endPos = buffer.indexOf(endMarker); + while (endPos != -1) { + // 找到完整数据包 + QByteArray completePacket = buffer.left(endPos); + // LOG_DEBUG("Found complete packet for %s: size=%d\n", aliasName.toStdString().c_str(), completePacket.size()); + + // 处理完整数据包 + processCompletePacket(aliasName, completePacket); + + // 从缓存中移除已处理的数据(包括结束标记) + buffer.remove(0, endPos + endMarker.length()); + + // 检查缓存中是否还有其他完整数据包 + endPos = buffer.indexOf(endMarker); + } + + // 如果缓存过大,清空以避免内存问题 + if (buffer.size() > 1024 * 1024 * 2) { // 2MB 限制 + LOG_WARNING("Buffer too large for %s, clearing buffer\n", aliasName.toStdString().c_str()); + buffer.clear(); + } +} diff --git a/App/BeltTearing/BeltTearingApp/main.cpp b/App/BeltTearing/BeltTearingApp/main.cpp index 5907ad7..989b9cc 100644 --- a/App/BeltTearing/BeltTearingApp/main.cpp +++ b/App/BeltTearing/BeltTearingApp/main.cpp @@ -2,6 +2,7 @@ #include "IStatusUpdate.h" #include "IVrBeltTearingConfig.h" #include "widgets/TearingDataTableWidget.h" +#include "CrashHandler.h" #include #include @@ -9,9 +10,18 @@ #include #include #include +#include +#include +#include +#include +#include int main(int argc, char *argv[]) { + // 初始化崩溃处理程序 + // CrashHandler::setDumpPath("c:/crash_dumps"); + // CrashHandler::registerCrashHandler(); + QApplication a(argc, argv); // Register meta types for signal-slot connections @@ -29,6 +39,43 @@ int main(int argc, char *argv[]) qRegisterMetaType("BeltTearingConfigResult"); qRegisterMetaType("NumericTableWidgetItem"); + + // 使用应用程序名称作为唯一标识符 + const QString appKey = "BeltTearingApp_SingleInstance_Key"; + + // 创建系统信号量,用于同步访问共享内存 + QSystemSemaphore semaphore(appKey + "_semaphore", 1); + semaphore.acquire(); // 获取信号量 + + // 创建共享内存对象 + QSharedMemory sharedMemory(appKey + "_memory"); + + bool isRunning = false; + + // 尝试附加到现有的共享内存 + if (sharedMemory.attach()) { + // 如果能够附加,说明已有实例在运行 + isRunning = true; + } else { + // 尝试创建新的共享内存 + if (!sharedMemory.create(1)) { + // 创建失败,可能是因为已经存在 + qDebug() << "Unable to create shared memory segment:" << sharedMemory.errorString(); + isRunning = true; + } + } + + semaphore.release(); // 释放信号量 + + if (isRunning) { + // 已有实例在运行,显示提示信息并退出 + QMessageBox::information(nullptr, + QObject::tr("应用程序已运行"), + QObject::tr("撕裂应用程序已经在运行中,请勿重复启动!"), + QMessageBox::Ok); + return 0; + } + MainWindow w; w.show(); return a.exec(); diff --git a/App/BeltTearing/BeltTearingApp/mainwindow.cpp b/App/BeltTearing/BeltTearingApp/mainwindow.cpp index 63ab1f5..e457b2e 100644 --- a/App/BeltTearing/BeltTearingApp/mainwindow.cpp +++ b/App/BeltTearing/BeltTearingApp/mainwindow.cpp @@ -82,7 +82,7 @@ MainWindow::MainWindow(QWidget *parent) // 设置表格最大行数限制,防止内存占用过大 if (m_tearingDataTableWidget) { - m_tearingDataTableWidget->setMaximumRows(500); + m_tearingDataTableWidget->setMaximumRows(100); } m_presenter->Init(); diff --git a/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp b/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp index 2df5a26..7fb4ec5 100644 --- a/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp +++ b/App/BeltTearing/BeltTearingApp/widgets/TearingDataTableWidget.cpp @@ -264,6 +264,9 @@ void TearingDataTableWidget::addDataBatch(const QString devName, const std::vect // 手动触发一次按ID列倒序排序 m_tableWidget->sortItems(1, Qt::DescendingOrder); + + // 检查是否需要限制行数 + limitRowsIfNeeded(); } void TearingDataTableWidget::clearData() @@ -289,7 +292,7 @@ void TearingDataTableWidget::clearData() void TearingDataTableWidget::setMaximumRows(int maxRows) { // 设置表格最大行数,防止内存占用过大 - // m_maxRows = maxRows; + m_maxRows = maxRows; } void TearingDataTableWidget::removeRowFromSet(int row) @@ -319,11 +322,12 @@ void TearingDataTableWidget::limitRowsIfNeeded() // 禁用更新以提高性能 m_tableWidget->setUpdatesEnabled(false); - // 删除最旧的行(假设新数据在后面) + // 从后往前删除多余的行(假设新数据在后面) for (int i = 0; i < rowsToDelete; i++) { + int rowToRemove = m_tableWidget->rowCount() - 1 - i; // 从集合中移除 - removeRowFromSet(0); - m_tableWidget->removeRow(0); + removeRowFromSet(rowToRemove); + m_tableWidget->removeRow(rowToRemove); } // 重新启用更新 diff --git a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp index 00812ba..cba5c42 100644 --- a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp +++ b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.cpp @@ -17,6 +17,8 @@ #include #include #include +#include +#include #include "VrLog.h" #include "VrSimpleLog.h" @@ -252,24 +254,16 @@ void BeltTearingPresenter::sendTestData(std::string fileName){ float lx, ly, rx, ry; SVzNL3DPosition pos; sscanf(line.c_str(), "{ %lf, %lf, %lf }-{ %f, %f}-{ %f, %f }", - &pos.pt3D.x, &pos.pt3D.y, &pos.pt3D.z, - &lx, &ly, &rx, &ry); - + &pos.pt3D.x, &pos.pt3D.y, &pos.pt3D.z, &lx, &ly, &rx, &ry); sVzNLPostion.push_back(pos); } - - // if(nIndex > 500){ - // break; - // } - - // std::this_thread::sleep_for(std::chrono::milliseconds(2)); } inputFile.close(); } -// 发送模拟数据 +// 发送模拟撕裂结果数据 void BeltTearingPresenter::sendSimulationData() { LOG_INFO("Sending simulation data\n"); @@ -278,7 +272,7 @@ void BeltTearingPresenter::sendSimulationData() m_pRobotProtocol->SetWorkStatus(RobotProtocol::WORK_STATUS_WORKING); } - // 创建模拟的皮带撕裂检测结果 + // 创建模拟的撕裂结果数据 std::vector simulationResults; // 创建第一个模拟撕裂结果 @@ -301,40 +295,31 @@ void BeltTearingPresenter::sendSimulationData() simulationResults.push_back(tear2); - // 创建第三个模拟撕裂结果 - SSG_beltTearingInfo tear3; - tear3.tearID = 13; - tear3.tearStatus = keSG_tearStatus_Ended; // 假设3表示无效撕裂 - tear3.tearWidth = 18.7f; // 撕裂宽度 18.7mm - tear3.tearDepth = 9.5f; // 撕裂深度 9.5mm - // 其他字段由于结构定义不明确,暂时不填充 + // 发送撕裂结果到所有客户端 + sendTearingResults(simulationResults); + SendDetectionResultToRobot(simulationResults); // 发送检测结果到机械臂 - simulationResults.push_back(tear3); + LOG_INFO("Simulation tearing data sent successfully\n"); +} + +// 发送模拟图像数据 +void BeltTearingPresenter::sendSimulationImageData() +{ + LOG_INFO("Sending simulation image data\n"); + + // 创建一个模拟的图像 + QImage simulationImage(800, 600, QImage::Format_RGB32); - // 创建第四个模拟撕裂结果 - SSG_beltTearingInfo tear4; - tear4.tearID = 14; - tear4.tearStatus = keSG_tearStatus_Ended; // 假设1表示有效撕裂 - tear4.tearWidth = 30.1f; // 撕裂宽度 30.1mm - tear4.tearDepth = 15.8f; // 撕裂深度 15.8mm - // 其他字段由于结构定义不明确,暂时不填充 + // 填充背景色 + simulationImage.fill(Qt::white); - simulationResults.push_back(tear4); + // 创建 QPainter 对象用于绘制 + QPainter painter(&simulationImage); - // 创建第五个模拟撕裂结果 - SSG_beltTearingInfo tear5; - tear5.tearID = 15; - tear5.tearStatus = keSG_tearStatus_Ended; // 假设1表示有效撕裂 - tear5.tearWidth = 25.4f; // 撕裂宽度 25.4mm - tear5.tearDepth = 11.3f; // 撕裂深度 11.3mm - // 其他字段由于结构定义不明确,暂时不填充 + // 发送图像到所有客户端 + sendImageToClients(simulationImage); - simulationResults.push_back(tear5); - - // 发送检测结果到机械臂 - SendDetectionResultToRobot(simulationResults); - - LOG_INFO("Simulation data sent successfully\n"); + LOG_INFO("Simulation image data sent successfully\n"); } bool BeltTearingPresenter::initializeCamera() @@ -783,7 +768,7 @@ void BeltTearingPresenter::sendImageToClients(const QImage& image) buffer.open(QIODevice::WriteOnly); // 保存为JPEG格式以减少数据大小 - if (!image.save(&buffer, "JPEG", 75)) { + if (!image.save(&buffer, "JPEG", 50)) { LOG_ERROR("Failed to convert image to JPEG format\n"); return; } @@ -809,6 +794,7 @@ void BeltTearingPresenter::sendImageToClients(const QImage& image) if (!success) { LOG_WARNING("Failed to send image data to all clients\n"); } + // LOG_DEBUG("Sent image data to %d clients datalen %d\n", m_clients.size(), imageData.size()); } catch (const std::exception& e) { LOG_ERROR("Error sending image to clients: %s\n", e.what()); diff --git a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h index 9d58d0b..0fd7088 100644 --- a/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h +++ b/App/BeltTearing/BeltTearingServer/BeltTearingPresenter.h @@ -11,7 +11,7 @@ #include #include #include "IVrEyeDevice.h" -#include "../../SDK/beltTearing/Inc/beltTearingDetection_Export.h" +#include "beltTearingDetection_Export.h" #include "IVrBeltTearingConfig.h" #include "VZNL_Common.h" #include "VZNL_Types.h" @@ -55,6 +55,9 @@ public: void sendTestData(std::string fileName); void sendSimulationData(); + // 发送模拟图像数据 + void sendSimulationImageData(); + private slots: void onCameraInitTimer(); diff --git a/App/BeltTearing/BeltTearingServer/main.cpp b/App/BeltTearing/BeltTearingServer/main.cpp index e8aabf8..88bc819 100644 --- a/App/BeltTearing/BeltTearingServer/main.cpp +++ b/App/BeltTearing/BeltTearingServer/main.cpp @@ -64,7 +64,7 @@ int main(int argc, char *argv[]) presenter.startCamera(); // 输出系统信息 - LOG_INFO("=== BeltTearing Server Started ===\n"); + LOG_INFO("=== BeltTearing Server Started %s===\n", BELT_TEARING_SERVER_VERSION_STRING); LOG_DEBUG("===================================\n"); LOG_INFO("Server is running on port %d\n", serverPort); LOG_INFO("Press Ctrl+C to exit.\n"); @@ -73,14 +73,16 @@ int main(int argc, char *argv[]) LOG_INFO("Simulation thread started - sending test data every 5 seconds\n"); // 处理应用程序退出 std::thread testThread([&presenter]() { - QThread::msleep(5000); // 等待5秒 + QThread::msleep(10); // 等待5秒 while (true) { - presenter.sendTestData("C:\\tmp\\8_LazerData_Je08A052.txt"); - // presenter.sendSimulationData(); + // presenter.sendTestData("C:\\tmp\\8_LazerData_Je08A052.txt"); + presenter.sendSimulationData(); // 启用发送模拟撕裂数据 + presenter.sendSimulationImageData(); } }); testThread.detach(); + #endif return app.exec(); diff --git a/GrabBagPrj/GrabBagPrj.pro b/GrabBagPrj/GrabBagPrj.pro index 1274247..6529c35 100644 --- a/GrabBagPrj/GrabBagPrj.pro +++ b/GrabBagPrj/GrabBagPrj.pro @@ -25,3 +25,6 @@ SUBDIRS += ../App/BeltTearing/BeltTearing.pro #焊接 SUBDIRS += ../App/LapWeld/LapWeld.pro +# Test 测试 +# SUBDIRS += ../Test/Test.pro + diff --git a/QtUtils/Inc/CrashHandler.h b/QtUtils/Inc/CrashHandler.h new file mode 100644 index 0000000..d7818b8 --- /dev/null +++ b/QtUtils/Inc/CrashHandler.h @@ -0,0 +1,29 @@ +#ifndef CRASHHANDLER_H +#define CRASHHANDLER_H + +#include + +#ifdef Q_OS_WIN +#include +#ifdef _MSC_VER +#include +#endif +#endif + +class CrashHandler +{ +public: + static void registerCrashHandler(); + static void setDumpPath(const QString& path); + static QString getDumpPath(); + +private: + static QString dumpPath; + static void crashHandlerFunction(int sig); + +#ifdef Q_OS_WIN + static LONG WINAPI windowsCrashHandler(struct _EXCEPTION_POINTERS* exceptionInfo); +#endif +}; + +#endif // CRASHHANDLER_H \ No newline at end of file diff --git a/QtUtils/QtUtils.pro b/QtUtils/QtUtils.pro index 6930dd9..8441b07 100644 --- a/QtUtils/QtUtils.pro +++ b/QtUtils/QtUtils.pro @@ -8,13 +8,20 @@ win32-msvc { QMAKE_CXXFLAGS += /utf-8 } +# For Windows crash dump generation and MessageBox functions +win32-msvc { + LIBS += -lDbgHelp -luser32 +} + INCLUDEPATH += $$PWD/Inc HEADERS += \ - Inc/StyledMessageBox.h + Inc/StyledMessageBox.h \ + Inc/CrashHandler.h SOURCES += \ - Src/StyledMessageBox.cpp + Src/StyledMessageBox.cpp \ + Src/CrashHandler.cpp # Default rules for deployment. unix { diff --git a/QtUtils/Src/CrashHandler.cpp b/QtUtils/Src/CrashHandler.cpp new file mode 100644 index 0000000..c5c154f --- /dev/null +++ b/QtUtils/Src/CrashHandler.cpp @@ -0,0 +1,199 @@ +#include "CrashHandler.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef Q_OS_WIN +#include +#ifdef _MSC_VER +#include +#pragma comment(lib, "dbghelp.lib") +#endif +#endif + +QString CrashHandler::dumpPath; + +void CrashHandler::registerCrashHandler() +{ + signal(SIGSEGV, crashHandlerFunction); // 段错误 + signal(SIGABRT, crashHandlerFunction); // abort调用 + signal(SIGFPE, crashHandlerFunction); // 浮点异常 + signal(SIGILL, crashHandlerFunction); // 非法指令 + +#ifdef Q_OS_WIN + SetUnhandledExceptionFilter(windowsCrashHandler); +#endif +} + +void CrashHandler::setDumpPath(const QString& path) +{ + dumpPath = path; + QDir dir(dumpPath); + if (!dir.exists()) { + dir.mkpath(dumpPath); + } +} + +QString CrashHandler::getDumpPath() +{ + if (dumpPath.isEmpty()) { + dumpPath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/crash_dumps"; + QDir dir(dumpPath); + if (!dir.exists()) { + dir.mkpath(dumpPath); + } + } + return dumpPath; +} + +#ifdef Q_OS_WIN +LONG WINAPI CrashHandler::windowsCrashHandler(struct _EXCEPTION_POINTERS* exceptionInfo) +{ + // 使用 C 函数生成时间戳,避免依赖 Qt(可能已损坏) + time_t now = time(NULL); + struct tm timeinfo; + localtime_s(&timeinfo, &now); + + char timestamp[64]; + strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &timeinfo); + + // 获取dump路径 + QString dumpDir = CrashHandler::getDumpPath(); + + // 创建目录(如果不存在) + CreateDirectoryW((WCHAR*)dumpDir.utf16(), NULL); + + // 构建文件路径 + QString dumpFilePath = dumpDir + "/crash_dump_" + QString(timestamp) + ".dmp"; + QString logFilePath = dumpDir + "/crash_log_" + QString(timestamp) + ".txt"; + + // 先写日志文件 + FILE* logFile = _wfopen((WCHAR*)logFilePath.utf16(), L"w"); + if (logFile) { + fprintf(logFile, "Application crashed at: %s\n", timestamp); + fprintf(logFile, "Exception Code: 0x%08X\n", exceptionInfo->ExceptionRecord->ExceptionCode); + fprintf(logFile, "Exception Address: 0x%p\n", exceptionInfo->ExceptionRecord->ExceptionAddress); + fprintf(logFile, "Dump file: %s\n", dumpFilePath.toLocal8Bit().constData()); + fclose(logFile); + } + +#ifdef _MSC_VER + // 创建 dump 文件 + HANDLE hFile = CreateFileW((WCHAR*)dumpFilePath.utf16(), + GENERIC_WRITE, + 0, + NULL, + CREATE_ALWAYS, + FILE_ATTRIBUTE_NORMAL, + NULL); + + if (hFile != INVALID_HANDLE_VALUE) { + MINIDUMP_EXCEPTION_INFORMATION exceptionInfoEx; + exceptionInfoEx.ThreadId = GetCurrentThreadId(); + exceptionInfoEx.ExceptionPointers = exceptionInfo; + exceptionInfoEx.ClientPointers = FALSE; + + // 使用更详细的 dump 类型 + MINIDUMP_TYPE dumpType = (MINIDUMP_TYPE)( + MiniDumpWithDataSegs | + MiniDumpWithHandleData | + MiniDumpWithThreadInfo); + + BOOL success = MiniDumpWriteDump( + GetCurrentProcess(), + GetCurrentProcessId(), + hFile, + dumpType, + &exceptionInfoEx, + NULL, + NULL); + + CloseHandle(hFile); + + if (success) { + // 成功写入,显示消息框 + wchar_t msg[512]; + swprintf_s(msg, 512, L"程序崩溃!\n\nDump文件已保存至:\n%s\n\n请联系技术支持。", + (WCHAR*)dumpFilePath.utf16()); + MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST); + } else { + DWORD error = GetLastError(); + wchar_t msg[256]; + swprintf_s(msg, 256, L"程序崩溃!\n\n无法创建dump文件(错误代码: %d)\n路径: %s", + error, (WCHAR*)dumpFilePath.utf16()); + MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST); + } + } else { + DWORD error = GetLastError(); + wchar_t msg[256]; + swprintf_s(msg, 256, L"程序崩溃!\n\n无法创建dump文件(文件创建失败,错误代码: %d)\n路径: %s", + error, (WCHAR*)dumpFilePath.utf16()); + MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST); + } +#else + // 非 MSVC 编译器,只显示崩溃信息 + wchar_t msg[256]; + swprintf_s(msg, 256, L"程序崩溃!\n\n日志文件: %s", (WCHAR*)logFilePath.utf16()); + MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST); +#endif + + return EXCEPTION_EXECUTE_HANDLER; +} +#endif + +void CrashHandler::crashHandlerFunction(int sig) +{ + // 使用 C 函数生成时间戳,避免依赖可能已损坏的 Qt + time_t now = time(NULL); + struct tm timeinfo; +#ifdef Q_OS_WIN + localtime_s(&timeinfo, &now); +#else + localtime_r(&now, &timeinfo); +#endif + + char timestamp[64]; + strftime(timestamp, sizeof(timestamp), "%Y%m%d_%H%M%S", &timeinfo); + + QString dumpDir = getDumpPath(); + QString dumpFilePath = dumpDir + "/crash_signal_" + QString(timestamp) + ".txt"; + + FILE* file = fopen(dumpFilePath.toLocal8Bit().constData(), "w"); + if (file) { + fprintf(file, "Crash occurred at: %s\n", timestamp); + fprintf(file, "Signal: %d (", sig); + switch(sig) { + case SIGSEGV: fprintf(file, "SIGSEGV - Segmentation fault"); break; + case SIGABRT: fprintf(file, "SIGABRT - Abort signal"); break; + case SIGFPE: fprintf(file, "SIGFPE - Floating point exception"); break; + case SIGILL: fprintf(file, "SIGILL - Illegal instruction"); break; + default: fprintf(file, "Unknown signal"); break; + } + fprintf(file, ")\n"); + + if (qApp) { + fprintf(file, "Application: %s\n", qApp->applicationName().toLocal8Bit().constData()); + fprintf(file, "Version: %s\n", qApp->applicationVersion().toLocal8Bit().constData()); + } + + fclose(file); + +#ifdef Q_OS_WIN + wchar_t msg[512]; + swprintf_s(msg, 512, L"程序崩溃(信号 %d)!\n\n日志文件已保存至:\n%s", + sig, (WCHAR*)dumpFilePath.utf16()); + MessageBoxW(NULL, msg, L"程序崩溃", MB_OK | MB_ICONERROR | MB_TOPMOST); +#endif + } + + // 调用默认的信号处理函数来终止程序 + signal(sig, SIG_DFL); + raise(sig); +} \ No newline at end of file diff --git a/Test/Test.pro b/Test/Test.pro new file mode 100644 index 0000000..ad4b33b --- /dev/null +++ b/Test/Test.pro @@ -0,0 +1,5 @@ +TEMPLATE = subdirs + +# 撕裂项目 +SUBDIRS += tcpclient/tcpclient_test.pro +SUBDIRS += tcpserver/tcpserver_test.pro \ No newline at end of file diff --git a/Test/camera/CMakeLists.txt b/Test/camera/CMakeLists.txt new file mode 100644 index 0000000..69f00d4 --- /dev/null +++ b/Test/camera/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_minimum_required(VERSION 3.10) +project(CameraDemoTest) + +# Set C++ standard +set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD_REQUIRED ON) + +set(CMAKE_C_COMPILER aarch64-linux-gnu-gcc) +set(CMAKE_CXX_COMPILER aarch64-linux-gnu-g++) + +# Include directories +include_directories( + ${CMAKE_SOURCE_DIR}/../../VrCommon/Inc + ${CMAKE_SOURCE_DIR}/../../SDK/VzNLSDK/Inc + ${CMAKE_SOURCE_DIR}/../../SDK/VzNLSDK/_Inc +) +LINK_DIRECTORIES( + ${CMAKE_SOURCE_DIR}/../../SDK/VzNLSDK/Arm/aarch64 +) + + +# Add source files +set(SOURCES + test.cpp +) + +# Create executable +add_executable(CameraDemoTest ${SOURCES} ) + +# Link libraries (you may need to adjust these paths based on your actual library locations) +target_link_libraries(CameraDemoTest + VzKernel + VzNLDetect + VzNLGraphics +) diff --git a/Test/camera/test.cpp b/Test/camera/test.cpp new file mode 100644 index 0000000..6e69c2a --- /dev/null +++ b/Test/camera/test.cpp @@ -0,0 +1,300 @@ +#include +#include +#include +#include +#include +#include + +// VzNLSDK includes +#include "../../SDK/VzNLSDK/Inc/VZNL_Common.h" +#include "../../SDK/VzNLSDK/Inc/VZNL_Types.h" +#include "../../SDK/VzNLSDK/Inc/VZNL_DetectLaser.h" +#include "../../SDK/VzNLSDK/Inc/VZNL_RGBConfig.h" +#include "../../SDK/VzNLSDK/Inc/VZNL_SwingMotor.h" + +class CameraDemoTest +{ +private: + VZNLHANDLE m_hDevice; + std::atomic m_bScanning; + std::atomic m_nScanDataCount; + bool m_isRGBD; + +public: + CameraDemoTest() + : m_hDevice(nullptr) + , m_bScanning(false) + , m_nScanDataCount(0) + , m_isRGBD(false) + { + } + + ~CameraDemoTest() + { + Cleanup(); + } + + // Initialize camera + bool InitializeCamera(bool isRGBD) + { + m_isRGBD = isRGBD; + int nErrCode = 0; + + // 初始化SDK + SVzNLConfigParam sVzNlConfigParam; + sVzNlConfigParam.nDeviceTimeOut = 5000; // 5 seconds timeout + nErrCode = VzNL_Init(&sVzNlConfigParam); + + // 搜索设备 + VzNL_ResearchDevice(keSearchDeviceFlag_EthLaserRobotEye); + + // 获取搜索到的设备 + SVzNLEyeCBInfo* pCBInfo = nullptr; + int nCount = 0; + nErrCode = VzNL_GetEyeCBDeviceInfo(nullptr, &nCount); + if (0 != nErrCode) + { + return false; + } + + if(0 == nCount) + { + return false; + } + + pCBInfo = new SVzNLEyeCBInfo[nCount]; + if (!pCBInfo) { + return false; + } + + nErrCode = VzNL_GetEyeCBDeviceInfo(pCBInfo, &nCount); + if (0 != nErrCode) { + delete[] pCBInfo; + return false; + } + + SVzNLOpenDeviceParam sVzNlOpenDeviceParam; + sVzNlOpenDeviceParam.eEyeResolution = keVzNLEyeResolution_Unknown; + sVzNlOpenDeviceParam.eSDKType = keSDKType_DetectLaser; + sVzNlOpenDeviceParam.bStopStream = VzTrue; + + m_hDevice = VzNL_OpenDevice(&pCBInfo[0], &sVzNlOpenDeviceParam, &nErrCode); + + + // 清理pCBInfo数组 + delete[] pCBInfo; + pCBInfo = nullptr; + + + if(nullptr == m_hDevice) + { + return false; + } + + if(0 != nErrCode) + { + return false; + } + + SVzNLEyeDeviceInfoEx m_sEeyCBDeviceInfo; + m_sEeyCBDeviceInfo.sEyeCBInfo.nSize = sizeof(SVzNLEyeDeviceInfoEx); + VzNL_GetDeviceInfo(m_hDevice, (SVzNLEyeCBInfo *)(&m_sEeyCBDeviceInfo)); + + VzNL_BeginDetectLaser(m_hDevice); + VzNL_EnableSwingMotor(m_hDevice, VzTrue); + + SVzNLVersionInfo sVersionInfo; + VzNL_GetVersion(m_hDevice, &sVersionInfo); + std::cout << "version : " << sVersionInfo.szSDKVersion << std::endl; + + // int nnnRet = VzNL_SetEthSendDataLength(m_pHandle, 1024); + // LOG_DEBUG("SenddataLen ret : %d\n", nnnRet); + + //启用RGBD + int nRGBRet = VzNL_EnableRGB(m_hDevice, isRGBD ? VzTrue : VzFalse); + std::cout << "Enable RGB " << (isRGBD ? "true" : "false") << " ret : " << nRGBRet << std::endl; + + // 填充 + VzNL_EnableFillLaserPoint(m_hDevice, VzTrue); + + VzNL_SetDeviceStatusNotifyEx(m_hDevice, StatusCallback, this); + + return true; + } + + // Start scanning + bool StartScanning() + { + std::cout << "\n--- Starting Scan ---" << std::endl; + + if (!m_hDevice) + { + std::cout << "Error: Device not opened" << std::endl; + return false; + } + + // Clear previous data + m_nScanDataCount = 0; + + // Select data type based on scan mode + EVzResultDataType dataType; + dataType = m_isRGBD ? keResultDataType_PointXYZRGBA : keResultDataType_Position; + + m_bScanning = true; + + // Start automatic detection + int ret = VzNL_StartAutoDetectEx(m_hDevice, dataType, keFlipType_None, ScanDataCallback, this); + if (ret != 0) + { + m_bScanning = false; + std::cout << "Error: Failed to start scanning, error code: " << ret << std::endl; + return false; + } + + std::cout << "✓ Scanning started, data type: " << (m_isRGBD ? "RGBD" : "Position") << std::endl; + + return true; + } + + // Stop scanning + bool StopScanning() + { + std::cout << "\n--- Stopping Scan ---" << std::endl; + + if (!m_hDevice) + { + std::cout << "Error: Device not opened" << std::endl; + return false; + } + + m_bScanning = false; + + int ret = VzNL_StopAutoDetect(m_hDevice); + if (ret != 0) + { + std::cout << "Error: Failed to stop scanning, error code: " << ret << std::endl; + return false; + } + + VzNL_EndDetectLaser(m_hDevice); + + std::cout << "✓ Scanning stopped" << std::endl; + return true; + } + + bool IsScanning() + { + return m_bScanning.load(); + } + + // Get scan data count + int GetScanDataCount() + { + return m_nScanDataCount.load(); + } + + // Cleanup resources + void Cleanup() + { + if (m_hDevice) + { + if (m_bScanning) + { + VzNL_StopAutoDetect(m_hDevice); + } + VzNL_CloseDevice(m_hDevice); + m_hDevice = nullptr; + } + + // Destroy SDK + VzNL_Destroy(); + } + +private: + // Status callback function + static void StatusCallback(EVzDeviceWorkStatus eStatus, void* pExtData, unsigned int nDataLength, void* pParam) + { + CameraDemoTest* pThis = static_cast(pParam); + if (!pThis) return; + + std::cout << "Device status change: "; + switch (eStatus) + { + case keDeviceWorkStatus_Eye_Comming: + std::cout << "Eye connected" << std::endl; + break; + case keDeviceWorkStatus_Eye_Reconnect: + std::cout << "Eye reconnected" << std::endl; + break; + case keDeviceWorkStatus_Working: + std::cout << "Working" << std::endl; + break; + case keDeviceWorkStatus_Offline: + std::cout << "Offline" << std::endl; + break; + case keDeviceWorkStatus_Device_Swing_Finish: + std::cout << "Swing module completed" << std::endl; + break; + case keDeviceWorkStatus_Device_Auto_Stop: + std::cout << "Device auto stopped, total scan data: " << pThis->GetScanDataCount() << std::endl; + pThis->m_bScanning = false; + break; + default: + std::cout << "Unknown status: " << eStatus << std::endl; + break; + } + } + + // Scan data callback function + static void ScanDataCallback(EVzResultDataType eDataType, SVzLaserLineData* pLaserLinePoint, void* pParam) + { + CameraDemoTest* pThis = static_cast(pParam); + if (!pThis || !pLaserLinePoint) return; + + // Increment data count + pThis->m_nScanDataCount++; + } +}; + +int main(int argc, char* argv[]) +{ + std::cout << "Camera Demo Test Program (Direct VzNLSDK)" << std::endl; + std::cout << "=========================================" << std::endl; + + CameraDemoTest cameraTest; + + // Check command line argument for RGBD mode + bool isRGBD = (argc > 1 && std::string(argv[1]) == "rgbd"); + std::cout << "Scan mode: " << (isRGBD ? "RGBD" : "Position") << std::endl; + + // Initialize camera + if (!cameraTest.InitializeCamera(isRGBD)) + { + std::cout << "Failed to initialize camera" << std::endl; + return -1; + } + + // Start scanning + if (!cameraTest.StartScanning()) + { + std::cout << "Failed to start scanning" << std::endl; + return -1; + } + + // Wait for scanning to complete + std::cout << "Scanning in progress..." << std::endl; + while (cameraTest.IsScanning()) + { + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + } + + // Stop scanning + cameraTest.StopScanning(); + + // Display final results + std::cout << "\n=== Final Results ===" << std::endl; + std::cout << "Total scan data received: " << cameraTest.GetScanDataCount() << std::endl; + + std::cout << "\nAll tests completed!" << std::endl; + return 0; +} diff --git a/Test/crash/test_crash.cpp b/Test/crash/test_crash.cpp new file mode 100644 index 0000000..4ab82e3 --- /dev/null +++ b/Test/crash/test_crash.cpp @@ -0,0 +1,64 @@ +#include +#include +#include +#include +#include +#include "../QtUtils/Inc/CrashHandler.h" + +void testNullPointerCrash() { + int* p = nullptr; + *p = 42; // 触发访问违规异常 +} + +void testDivisionByZero() { + volatile int a = 10; + volatile int b = 0; + volatile int c = a / b; // 浮点异常 + qDebug() << c; +} + +void testAbort() { + abort(); // 触发 SIGABRT +} + +int main(int argc, char *argv[]) +{ + QApplication app(argc, argv); + app.setApplicationName("CrashTest"); + app.setApplicationVersion("1.0"); + + // 注册崩溃处理器 + CrashHandler::setDumpPath("D:/crash_dumps"); + CrashHandler::registerCrashHandler(); + + QWidget window; + QVBoxLayout* layout = new QVBoxLayout(&window); + + QPushButton* btnNullPtr = new QPushButton("测试空指针崩溃(访问违规)"); + QPushButton* btnAbort = new QPushButton("测试 Abort 崩溃"); + QPushButton* btnNormal = new QPushButton("正常退出"); + + layout->addWidget(btnNullPtr); + layout->addWidget(btnAbort); + layout->addWidget(btnNormal); + + QObject::connect(btnNullPtr, &QPushButton::clicked, []() { + qDebug() << "触发空指针崩溃..."; + testNullPointerCrash(); + }); + + QObject::connect(btnAbort, &QPushButton::clicked, []() { + qDebug() << "触发 abort 崩溃..."; + testAbort(); + }); + + QObject::connect(btnNormal, &QPushButton::clicked, &app, &QApplication::quit); + + window.setWindowTitle("Crash Handler 测试"); + window.resize(300, 150); + window.show(); + + qDebug() << "崩溃处理器已注册,dump路径:" << CrashHandler::getDumpPath(); + + return app.exec(); +} diff --git a/Test/crash/test_crash.pro b/Test/crash/test_crash.pro new file mode 100644 index 0000000..7ba3bb4 --- /dev/null +++ b/Test/crash/test_crash.pro @@ -0,0 +1,26 @@ +QT += core gui widgets + +TEMPLATE = app +CONFIG += c++17 + +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} + +# For Windows crash dump generation (only for MSVC) +win32-msvc { + LIBS += -lDbgHelp +} + +INCLUDEPATH += ../QtUtils/Inc + +# Link QtUtils library +win32:CONFIG(debug, debug|release) { + LIBS += -L../QtUtils/debug -lQtUtils +} else:win32:CONFIG(release, debug|release) { + LIBS += -L../QtUtils/release -lQtUtils +} + +SOURCES += test_crash.cpp + +TARGET = test_crash diff --git a/Test/tcpclient/tcp_client_test.cpp b/Test/tcpclient/tcp_client_test.cpp new file mode 100644 index 0000000..3b12489 --- /dev/null +++ b/Test/tcpclient/tcp_client_test.cpp @@ -0,0 +1,391 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// 包含TCPClient头文件 +#include "IVrTCPClient.h" + +class TCPClientTest +{ +private: + IVrTCPClient* m_pClient; + std::atomic m_bRunning; + std::atomic m_bConnected; + std::string m_sServerIP; + int m_nPort; + std::atomic m_nRecvCount; + std::atomic m_nSendCount; + std::atomic m_nTotalRecvBytes; + std::thread m_sendThread; + std::thread m_statsThread; + std::mutex m_consoleMutex; + std::chrono::steady_clock::time_point m_startTime; + + // 测试配置 + bool m_bAutoSend; // 是否自动发送测试数据 + int m_nSendInterval; // 发送间隔(毫秒) + int m_nSendSize; // 每次发送的数据大小(字节) + +public: + /** + * @brief 构造函数 + * @param serverIP 服务器IP地址 + * @param port 服务器端口 + * @param autoSend 是否自动发送测试数据 + * @param sendInterval 发送间隔(毫秒) + * @param sendSize 每次发送的数据大小(字节) + */ + TCPClientTest(const std::string& serverIP, int port, bool autoSend = false, + int sendInterval = 1000, int sendSize = 64) + : m_pClient(nullptr), m_bRunning(false), m_bConnected(false), + m_sServerIP(serverIP), m_nPort(port), m_nRecvCount(0), + m_nSendCount(0), m_nTotalRecvBytes(0), + m_bAutoSend(autoSend), m_nSendInterval(sendInterval), m_nSendSize(sendSize) + { + // 创建TCPClient实例 + m_pClient = IVrTCPClient::CreateInstance(); + if (!m_pClient) { + std::cerr << "❌ Failed to create TCPClient instance" << std::endl; + } + } + + /** + * @brief 析构函数 + */ + ~TCPClientTest() + { + Stop(); + + // 等待线程结束 + if (m_sendThread.joinable()) { + m_sendThread.join(); + } + if (m_statsThread.joinable()) { + m_statsThread.join(); + } + + if (m_pClient) { + m_pClient->CloseDevice(); + IVrTCPClient::DestroyInstance(m_pClient); + m_pClient = nullptr; + } + } + + /** + * @brief 初始化客户端 + * @return true表示成功,false表示失败 + */ + bool Initialize() + { + if (!m_pClient) { + return false; + } + + PrintLog("🔌 Connecting to " + m_sServerIP + ":" + std::to_string(m_nPort)); + + // 连接设备 + int result = m_pClient->LinkDevice(m_sServerIP, m_nPort, true, + [this](IVrTCPClient* pClient, bool connected, void* pParam) { + this->OnConnectionEvent(pClient, connected, pParam); + }, nullptr); + + if (result != 0) { + PrintLog("❌ Failed to connect to server, error code: " + std::to_string(result)); + return false; + } + + // 启动工作线程 + result = m_pClient->StartWork( + [this](IVrTCPClient* pClient, const char* pData, const int nLen, void* pParam) { + this->OnDataReceived(pClient, pData, nLen, pParam); + }, nullptr); + + if (result != 0) { + PrintLog("❌ Failed to start client work thread, error code: " + std::to_string(result)); + return false; + } + + PrintLog("✅ Client initialized successfully"); + return true; + } + + /** + * @brief 启动客户端 + * @return true表示成功,false表示失败 + */ + bool Start() + { + m_bRunning = true; + m_startTime = std::chrono::steady_clock::now(); + + // 启动统计线程 + m_statsThread = std::thread(&TCPClientTest::StatsLoop, this); + + // 如果启用自动发送,启动发送线程 + if (m_bAutoSend) { + m_sendThread = std::thread(&TCPClientTest::SendDataLoop, this); + PrintLog("📤 Auto-send enabled: " + std::to_string(m_nSendSize) + + " bytes every " + std::to_string(m_nSendInterval) + " ms"); + } + + PrintLog("🚀 Client started, waiting for data..."); + return true; + } + + /** + * @brief 停止客户端 + */ + void Stop() + { + m_bRunning = false; + + // 等待线程结束 + if (m_sendThread.joinable()) { + m_sendThread.join(); + } + if (m_statsThread.joinable()) { + m_statsThread.join(); + } + + PrintLog("🛑 Client stopped"); + PrintFinalStats(); + } + + /** + * @brief 获取接收计数 + * @return 接收的消息数量 + */ + int GetRecvCount() const + { + return m_nRecvCount.load(); + } + + /** + * @brief 获取发送计数 + * @return 发送的消息数量 + */ + int GetSendCount() const + { + return m_nSendCount.load(); + } + + /** + * @brief 获取连接状态 + * @return true表示已连接,false表示未连接 + */ + bool IsConnected() const + { + return m_bConnected.load(); + } + + /** + * @brief 手动发送测试数据 + * @param message 要发送的消息 + * @return true表示成功,false表示失败 + */ + bool SendTestData(const std::string& message) + { + if (!m_pClient || !m_bConnected) { + PrintLog("⚠️ Cannot send: not connected"); + return false; + } + + bool result = m_pClient->SendData(message.c_str(), static_cast(message.length())); + if (result) { + m_nSendCount++; + PrintLog("📤 Sent: " + message + " (" + std::to_string(message.length()) + " bytes)"); + } else { + PrintLog("❌ Failed to send data"); + } + return result; + } + +private: + /** + * @brief 连接状态回调函数 + * @param pClient 客户端指针 + * @param connected 是否连接 + * @param pParam 参数指针 + */ + void OnConnectionEvent(IVrTCPClient* pClient, bool connected, void* pParam) + { + m_bConnected = connected; + if (connected) { + PrintLog("✅ Connected to server"); + } else { + PrintLog("❌ Disconnected from server (Auto-reconnect enabled)"); + } + } + + /** + * @brief 数据接收回调函数 + * @param pClient 客户端指针 + * @param pData 数据指针 + * @param nLen 数据长度 + * @param pParam 参数指针 + */ + void OnDataReceived(IVrTCPClient* pClient, const char* pData, const int nLen, void* pParam) + { + m_nRecvCount++; + m_nTotalRecvBytes += nLen; + + // 打印接收到的数据(限制长度) + std::string data(pData, std::min(nLen, 100)); + if (nLen > 100) { + data += "..."; + } + PrintLog("📥 Received #" + std::to_string(m_nRecvCount.load()) + ": [" + std::to_string(nLen) + " bytes] " + data); + } + + /** + * @brief 自动发送数据循环 + */ + void SendDataLoop() + { + int counter = 0; + while (m_bRunning) { + if (m_bConnected && m_pClient) { + // 构造测试数据 + std::string message = "Client-Test-" + std::to_string(++counter); + // 填充到指定大小 + if (message.length() < static_cast(m_nSendSize)) { + message.append(m_nSendSize - message.length(), 'X'); + } else if (message.length() > static_cast(m_nSendSize)) { + message.resize(m_nSendSize); + } + + bool result = m_pClient->SendData(message.c_str(), static_cast(message.length())); + if (result) { + m_nSendCount++; + } else { + PrintLog("❌ Failed to send data"); + } + } + + std::this_thread::sleep_for(std::chrono::milliseconds(m_nSendInterval)); + } + } + + /** + * @brief 统计信息循环打印 + */ + void StatsLoop() + { + while (m_bRunning) { + std::this_thread::sleep_for(std::chrono::seconds(5)); + if (m_bRunning) { + PrintStats(); + } + } + } + + /** + * @brief 打印当前统计信息 + */ + void PrintStats() + { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - m_startTime).count(); + if (duration == 0) duration = 1; // 避免除零 + + double recvRate = static_cast(m_nRecvCount.load()) / duration; + double sendRate = static_cast(m_nSendCount.load()) / duration; + double bandwidth = static_cast(m_nTotalRecvBytes.load()) / duration / 1024.0; // KB/s + + std::ostringstream oss; + oss << "\n📊 Statistics (Running " << duration << "s)" + << "\n Connected: " << (m_bConnected ? "Yes" : "No") + << "\n Received: " << m_nRecvCount.load() << " msgs (" << std::fixed << std::setprecision(2) << recvRate << " msg/s)" + << "\n Sent: " << m_nSendCount.load() << " msgs (" << std::fixed << std::setprecision(2) << sendRate << " msg/s)" + << "\n Bandwidth: " << std::fixed << std::setprecision(2) << bandwidth << " KB/s" + << "\n Total Recv: " << m_nTotalRecvBytes.load() << " bytes"; + + PrintLog(oss.str()); + } + + /** + * @brief 打印最终统计信息 + */ + void PrintFinalStats() + { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast(now - m_startTime).count(); + if (duration == 0) duration = 1; + + std::ostringstream oss; + oss << "\n📈 Final Statistics:" + << "\n Total Runtime: " << duration << " seconds" + << "\n Total Received: " << m_nRecvCount.load() << " messages" + << "\n Total Sent: " << m_nSendCount.load() << " messages" + << "\n Total Bytes Received: " << m_nTotalRecvBytes.load() << " bytes" + << "\n Average Receive Rate: " << std::fixed << std::setprecision(2) + << (static_cast(m_nRecvCount.load()) / duration) << " msg/s" + << "\n Average Send Rate: " << std::fixed << std::setprecision(2) + << (static_cast(m_nSendCount.load()) / duration) << " msg/s"; + + PrintLog(oss.str()); + } + + /** + * @brief 线程安全的日志打印 + * @param message 日志消息 + */ + void PrintLog(const std::string& message) + { + std::lock_guard lock(m_consoleMutex); + auto now = std::time(nullptr); + char timeStr[20]; + std::strftime(timeStr, sizeof(timeStr), "%H:%M:%S", std::localtime(&now)); + std::cout << "[" << timeStr << "] " << message << std::endl; + } +}; + +int main(int argc, char* argv[]) +{ + std::cout << "TCP Client Test Program" << std::endl; + std::cout << "=======================" << std::endl; + + // 默认连接参数 + std::string serverIP = "127.0.0.1"; + int port = 8080; + + if (argc > 1) { + serverIP = argv[1]; + } + if (argc > 2) { + port = std::stoi(argv[2]); + } + + TCPClientTest clientTest(serverIP, port); + + // 初始化客户端 + if (!clientTest.Initialize()) { + std::cerr << "Failed to initialize client" << std::endl; + return -1; + } + + // 启动客户端 + if (!clientTest.Start()) { + std::cerr << "Failed to start client" << std::endl; + return -1; + } + + // 运行一段时间或等待用户输入 + std::cout << "Client is running. Press Enter to stop..." << std::endl; + while(true){ + std::cin.get(); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + // 停止客户端 + clientTest.Stop(); + + std::cout << "TCP Client Test completed!" << std::endl; + return 0; +} diff --git a/Test/tcpclient/tcpclient_test.pro b/Test/tcpclient/tcpclient_test.pro new file mode 100644 index 0000000..32e77b1 --- /dev/null +++ b/Test/tcpclient/tcpclient_test.pro @@ -0,0 +1,39 @@ +# TCP Client Test Project + +TEMPLATE = app +TARGET = tcpclient_test + +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} + +# C++14标准 +CONFIG += c++14 + +# 源文件 +SOURCES += \ + tcp_client_test.cpp + +# 头文件路径 +INCLUDEPATH += ../../VrNets/TCPClient/Inc + +# Windows平台特定配置 +win32 { + LIBS += -lws2_32 +} + +# 目标文件输出路径 +DESTDIR = ./ + + +# Link libraries +win32:CONFIG(debug, debug|release) { + LIBS += -L../../VrNets/debug -lVrTcpClient + LIBS += -L../../VrUtils/debug -lVrUtils +} else:win32:CONFIG(release, debug|release) { + LIBS += -L../../VrNets/release -lVrTcpClient + LIBS += -L../../VrUtils/release -lVrUtils +}else:unix:!macx { + LIBS += -L../../VrNets -lVrTcpClient + LIBS += -L../../VrUtils -lVrUtils +} diff --git a/Test/tcpserver/tcp_server_test.cpp b/Test/tcpserver/tcp_server_test.cpp new file mode 100644 index 0000000..6eee578 --- /dev/null +++ b/Test/tcpserver/tcp_server_test.cpp @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include +#include + +// 包含TCPServer头文件 +#include "IYTCPServer.h" + +class TCPServerTest +{ +private: + IYTCPServer* m_pServer; + std::atomic m_bRunning; + int m_nPort; + std::thread m_sendThread; + +public: + /** + * @brief 构造函数 + * @param port 服务器监听端口 + */ + TCPServerTest(int port) : m_pServer(nullptr), m_bRunning(false), m_nPort(port) + { + // 创建TCPServer实例 + if (!VrCreatYTCPServer(&m_pServer)) { + std::cerr << "Failed to create TCPServer instance" << std::endl; + } + } + + /** + * @brief 析构函数 + */ + ~TCPServerTest() + { + Stop(); + if (m_pServer) { + m_pServer->Close(); + delete m_pServer; + m_pServer = nullptr; + } + } + + /** + * @brief 初始化服务器 + * @return true表示成功,false表示失败 + */ + bool Initialize() + { + if (!m_pServer) { + return false; + } + + // 初始化socket + if (!m_pServer->Init(m_nPort, false)) { + std::cerr << "Failed to initialize server socket" << std::endl; + return false; + } + + // 设置事件回调 + m_pServer->SetEventCallback([this](const TCPClient* pClient, TCPServerEventType eventType) { + this->OnServerEvent(pClient, eventType); + }); + + // 启动服务器线程 + if (!m_pServer->Start([this](const TCPClient* pClient, const char* pData, const unsigned int nLen) { + this->OnDataReceived(pClient, pData, nLen); + }, false)) { + std::cerr << "Failed to start server" << std::endl; + return false; + } + + std::cout << "Server initialized successfully on port " << m_nPort << std::endl; + return true; + } + + /** + * @brief 启动数据发送线程 + * @return true表示成功,false表示失败 + */ + bool Start() + { + if (!m_pServer) { + return false; + } + + m_bRunning = true; + m_sendThread = std::thread(&TCPServerTest::SendDataLoop, this); + std::cout << "Server started, sending data to clients..." << std::endl; + return true; + } + + /** + * @brief 停止服务器 + */ + void Stop() + { + m_bRunning = false; + + if (m_sendThread.joinable()) { + m_sendThread.join(); + } + + if (m_pServer) { + m_pServer->Stop(); + } + + std::cout << "Server stopped" << std::endl; + } + +private: + /** + * @brief 服务器事件回调函数 + * @param pClient 客户端指针 + * @param eventType 事件类型 + */ + void OnServerEvent(const TCPClient* pClient, TCPServerEventType eventType) + { + switch (eventType) { + case TCP_EVENT_CLIENT_CONNECTED: + std::cout << "Client connected" << std::endl; + break; + case TCP_EVENT_CLIENT_DISCONNECTED: + std::cout << "Client disconnected" << std::endl; + break; + case TCP_EVENT_CLIENT_EXCEPTION: + std::cout << "Client exception" << std::endl; + break; + default: + std::cout << "Unknown event" << std::endl; + break; + } + } + + /** + * @brief 数据接收回调函数 + * @param pClient 客户端指针 + * @param pData 数据指针 + * @param nLen 数据长度 + */ + void OnDataReceived(const TCPClient* pClient, const char* pData, const unsigned int nLen) + { + // 服务器测试程序只需要发送数据,不需要处理接收的数据 + std::cout << "Received " << nLen << " bytes from client" << std::endl; + } + + /** + * @brief 持续发送数据的循环 + */ + void SendDataLoop() + { + int counter = 0; + char pData[1024]; + while (m_bRunning) { + // 构造要发送的数据 + *(int *)(pData) = ++counter; + + // 发送给所有连接的客户端 + if (m_pServer) { + m_pServer->SendAllData(pData, sizeof(pData)); + } + + // 每秒发送一次 + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + } +}; + +int main(int argc, char* argv[]) +{ + std::cout << "TCP Server Test Program" << std::endl; + std::cout << "=======================" << std::endl; + + // 默认端口为8080 + int port = 8080; + if (argc > 1) { + port = std::stoi(argv[1]); + } + + TCPServerTest serverTest(port); + + // 初始化服务器 + if (!serverTest.Initialize()) { + std::cerr << "Failed to initialize server" << std::endl; + return -1; + } + + // 启动服务器 + if (!serverTest.Start()) { + std::cerr << "Failed to start server" << std::endl; + return -1; + } + + // 等待用户输入以停止服务器 + std::cout << "Server is running. Press Enter to stop..." << std::endl; + while(true){ + std::cin.get(); + std::this_thread::sleep_for(std::chrono::seconds(10)); + } + + // 停止服务器 + serverTest.Stop(); + + std::cout << "TCP Server Test completed!" << std::endl; + return 0; +} diff --git a/Test/tcpserver/tcpserver_test.pro b/Test/tcpserver/tcpserver_test.pro new file mode 100644 index 0000000..678af22 --- /dev/null +++ b/Test/tcpserver/tcpserver_test.pro @@ -0,0 +1,37 @@ +# TCP Server Test Project + +TEMPLATE = app +TARGET = tcpserver_test + +# C++14标准 +CONFIG += c++14 + +win32-msvc { + QMAKE_CXXFLAGS += /utf-8 +} + +# 源文件 +SOURCES += \ + tcp_server_test.cpp + +# 头文件路径 +INCLUDEPATH += ../../VrNets/TCPServer/Inc + +# Windows平台特定配置 +win32 { + LIBS += -lws2_32 +} + +win32:CONFIG(debug, debug|release) { + LIBS += -L../../VrNets/debug -lVrTCPServer + LIBS += -L../../VrUtils/debug -lVrUtils +}else:win32:CONFIG(release, debug|release){ + LIBS += -L../../VrNets/release -lVrTCPServer + LIBS += -L../../VrUtils/release -lVrUtils +}else:unix:!macx { + LIBS += -L../../VrNets -lVrTCPServer + LIBS += -L../../VrUtils -lVrUtils + + # 添加系统库依赖 + LIBS += -lpthread +}