皮带撕裂接受网络图片进行显示
This commit is contained in:
parent
66332da0eb
commit
c86572de95
@ -5,6 +5,7 @@ greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
TEMPLATE = app
|
||||
|
||||
CONFIG += c++17
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
|
||||
SUBDIRS += ../BeltTearingConfig/BeltTearingConfig.pro \
|
||||
../VrNet/VrTcpClient.pro
|
||||
|
||||
@ -44,9 +44,10 @@ inline std::string BeltTearingWorkStatusToString(BeltTearingWorkStatus status) {
|
||||
|
||||
// 皮带撕裂检测结果结构体
|
||||
struct BeltTearingResult {
|
||||
QImage image; // 检测图像
|
||||
QImage image; // 检测图像
|
||||
bool bImageValid = false;
|
||||
QString result; // 检测结果描述
|
||||
double confidence = 0.0; // 置信度
|
||||
double confidence = 0.0; // 置信度
|
||||
QString serverName; // 服务器名称
|
||||
QDateTime timestamp; // 时间戳
|
||||
};
|
||||
@ -59,6 +60,9 @@ public:
|
||||
|
||||
// 状态文字回调
|
||||
virtual void OnStatusUpdate(const QString& statusMessage) = 0;
|
||||
|
||||
// 回调需要显示几个图像
|
||||
virtual void OnNeedShowImageCount(int count) = 0;
|
||||
|
||||
// 撕裂检测结果回调
|
||||
virtual void OnTearingResult(const BeltTearingResult& result) = 0;
|
||||
|
||||
@ -7,6 +7,11 @@
|
||||
#include <QString>
|
||||
#include "widgets/DeviceStatusWidget.h"
|
||||
|
||||
enum class ByteDataType {
|
||||
Text = 0x01,
|
||||
Image = 0x02
|
||||
};
|
||||
|
||||
class BeltTearingPresenter : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
@ -24,6 +29,9 @@ public:
|
||||
// 设置设备状态控件
|
||||
void setDeviceStatusWidget(DeviceStatusWidget* widget) { m_deviceStatusWidget = widget; }
|
||||
|
||||
// 设置状态更新接口
|
||||
void setStatusUpdate(IStatusUpdate* statusUpdate) { m_statusUpdate = statusUpdate; }
|
||||
|
||||
private:
|
||||
// IBeltTearingPresenter interface implementation
|
||||
bool initializeConfig(const QString &configPath);
|
||||
@ -40,7 +48,7 @@ private slots:
|
||||
void onTcpError(const QString &serverName, const QString &error);
|
||||
|
||||
private:
|
||||
|
||||
IStatusUpdate* m_statusUpdate; // 状态更新接口
|
||||
IVrBeltTearingConfig * m_config;
|
||||
DeviceStatusWidget* m_deviceStatusWidget; // 设备状态控件
|
||||
QMap<QString, VrTcpClient*> m_tcpClients; // 服务器名称 -> TCP客户端映射
|
||||
|
||||
@ -61,6 +61,10 @@ bool BeltTearingPresenter::initializeConfig(const QString &configPath)
|
||||
LOG_WARNING("No servers configured");
|
||||
return false;
|
||||
}
|
||||
|
||||
if(m_statusUpdate){
|
||||
m_statusUpdate->OnNeedShowImageCount(servers.size());
|
||||
}
|
||||
|
||||
// 清空现有配置
|
||||
m_serverInfos.clear();
|
||||
@ -238,10 +242,40 @@ void BeltTearingPresenter::onDisconnected(const QString &serverName)
|
||||
|
||||
void BeltTearingPresenter::onDataReceived(const QString &serverName, const QByteArray &data)
|
||||
{
|
||||
// 尝试解析图像数据
|
||||
QImage image;
|
||||
if (image.loadFromData(data)) {
|
||||
// 解析数据包
|
||||
quint8 dataType;
|
||||
quint32 dataSize;
|
||||
|
||||
QDataStream stream(data);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
stream >> dataType >> dataSize;
|
||||
QByteArray byteArray = data.mid(5, dataSize);
|
||||
|
||||
BeltTearingResult tearResult;
|
||||
tearResult.bImageValid = false;
|
||||
|
||||
if (dataType == static_cast<int>(ByteDataType::Text))
|
||||
{
|
||||
// 处理文本数据
|
||||
QString textData = QString::fromUtf8(byteArray);
|
||||
// 这里可以添加处理文本数据的逻辑
|
||||
LOG_DEBUG("Received text data from server %s: %s \n", serverName.toStdString().c_str(), textData.toStdString().c_str());
|
||||
}
|
||||
else if (dataType == static_cast<int>(ByteDataType::Image))
|
||||
{
|
||||
// 处理图像数据
|
||||
QImage image;
|
||||
if (tearResult.image.loadFromData(byteArray)) {
|
||||
// 这里可以添加处理图像数据的逻辑
|
||||
tearResult.bImageValid = true;
|
||||
LOG_DEBUG("Received image data from server %s, size: %dx%d \n", serverName.toStdString().c_str(), image.width(), image.height());
|
||||
}
|
||||
}
|
||||
|
||||
if(m_statusUpdate){
|
||||
tearResult.serverName = serverName;
|
||||
tearResult.timestamp = QDateTime::currentDateTime();
|
||||
m_statusUpdate->OnTearingResult(tearResult);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -45,6 +45,7 @@ MainWindow::MainWindow(QWidget *parent)
|
||||
|
||||
// 将设备状态控件与Presenter关联
|
||||
m_presenter->setDeviceStatusWidget(m_deviceStatusWidget);
|
||||
m_presenter->setStatusUpdate(this);
|
||||
|
||||
m_presenter->Init();
|
||||
|
||||
@ -106,10 +107,7 @@ void MainWindow::resizeEvent(QResizeEvent* event)
|
||||
}
|
||||
|
||||
void MainWindow::on_btn_test_clicked()
|
||||
{
|
||||
// 初始化4个图片格子
|
||||
m_gridView->initImages(4);
|
||||
|
||||
{
|
||||
// 定义图片资源路径
|
||||
QStringList imageResources = {
|
||||
"C:/project/QT/GrabBag/GrabBagApp/resource/config_camera.png",
|
||||
@ -137,22 +135,17 @@ void MainWindow::OnStatusUpdate(const QString &statusMessage)
|
||||
LOG_DEBUG("Status update: %s", statusMessage.toStdString().c_str());
|
||||
}
|
||||
|
||||
void MainWindow::OnTearingResult(const BeltTearingResult &result)
|
||||
// 回调需要显示几个图像
|
||||
void MainWindow::OnNeedShowImageCount(int count)
|
||||
{
|
||||
// 显示撕裂检测结果
|
||||
QString resultText = QString("服务器: %1, 结果: %2, 置信度: %3")
|
||||
.arg(result.serverName)
|
||||
.arg(result.result)
|
||||
.arg(result.confidence);
|
||||
|
||||
statusBar()->showMessage(resultText);
|
||||
LOG_DEBUG("Tearing result: %s", resultText.toStdString().c_str());
|
||||
|
||||
m_gridView->initImages(count);
|
||||
}
|
||||
|
||||
void MainWindow::OnTearingResult(const BeltTearingResult &result)
|
||||
{
|
||||
// 如果图像有效,显示在网格控件中
|
||||
if (!result.image.isNull()) {
|
||||
static int imageIndex = 0;
|
||||
m_gridView->setImages(imageIndex % 4, result.image);
|
||||
imageIndex++;
|
||||
if (result.bImageValid && !result.image.isNull()) {
|
||||
m_gridView->setImages(0, result.image);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -29,6 +29,7 @@ public:
|
||||
|
||||
// IStatusUpdate接口方法声明
|
||||
void OnStatusUpdate(const QString& statusMessage) override;
|
||||
void OnNeedShowImageCount(int count) override;
|
||||
void OnTearingResult(const BeltTearingResult& result) override;
|
||||
void OnServerConnected(const QString& serverName) override;
|
||||
void OnServerDisconnected(const QString& serverName) override;
|
||||
|
||||
@ -7,7 +7,7 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>1920</width>
|
||||
<height>1080</height>
|
||||
<height>1032</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
@ -162,7 +162,7 @@ background-color: rgba(255, 255, 255, 0);</string>
|
||||
<string>工作状态</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignmentFlag::AlignCenter</set>
|
||||
<set>Qt::AlignCenter</set>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QPushButton" name="btn_close">
|
||||
@ -250,36 +250,36 @@ background-color: rgba(255, 255, 255, 0);</string>
|
||||
<x>20</x>
|
||||
<y>140</y>
|
||||
<width>1311</width>
|
||||
<height>891</height>
|
||||
<height>850</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color:rgb(37, 38, 42)</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QFrame" name="device_status">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>1344</x>
|
||||
<y>852</y>
|
||||
<y>850</y>
|
||||
<width>556</width>
|
||||
<height>178</height>
|
||||
<height>140</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="styleSheet">
|
||||
<string notr="true">background-color: rgb(37, 38, 42);</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<widget class="QFrame" name="detect_data">
|
||||
@ -295,10 +295,10 @@ background-color: rgba(255, 255, 255, 0);</string>
|
||||
<string notr="true">background-color: rgb(37, 38, 42);</string>
|
||||
</property>
|
||||
<property name="frameShape">
|
||||
<enum>QFrame::Shape::StyledPanel</enum>
|
||||
<enum>QFrame::StyledPanel</enum>
|
||||
</property>
|
||||
<property name="frameShadow">
|
||||
<enum>QFrame::Shadow::Raised</enum>
|
||||
<enum>QFrame::Raised</enum>
|
||||
</property>
|
||||
</widget>
|
||||
<zorder>groupBox</zorder>
|
||||
|
||||
@ -8,9 +8,9 @@
|
||||
ImageGridWidget::ImageGridWidget(QWidget* parent)
|
||||
: QWidget(parent) {
|
||||
m_layout = new QGridLayout(this);
|
||||
m_layout->setContentsMargins(8, 8, 8, 8);
|
||||
m_layout->setHorizontalSpacing(8);
|
||||
m_layout->setVerticalSpacing(8);
|
||||
m_layout->setContentsMargins(0, 0, 0, 0);
|
||||
m_layout->setHorizontalSpacing(0);
|
||||
m_layout->setVerticalSpacing(0);
|
||||
setLayout(m_layout);
|
||||
|
||||
// 设置控件大小策略为可扩展
|
||||
@ -127,14 +127,14 @@ void ImageGridWidget::rebuildGrid() {
|
||||
}
|
||||
|
||||
void ImageGridWidget::resizeEvent(QResizeEvent* event) {
|
||||
// QWidget::resizeEvent(event);
|
||||
QWidget::resizeEvent(event);
|
||||
|
||||
// // 防止递归调用,只在尺寸真正变化时更新
|
||||
// static QSize lastSize;
|
||||
// if (lastSize != event->size()) {
|
||||
// lastSize = event->size();
|
||||
// updateTileSizes();
|
||||
// }
|
||||
// 防止递归调用,只在尺寸真正变化时更新
|
||||
static QSize lastSize;
|
||||
if (lastSize != event->size()) {
|
||||
lastSize = event->size();
|
||||
updateTileSizes();
|
||||
}
|
||||
}
|
||||
|
||||
void ImageGridWidget::updateTileSizes() {
|
||||
@ -146,13 +146,15 @@ void ImageGridWidget::updateTileSizes() {
|
||||
int availableHeight = height() - m_layout->contentsMargins().top() - m_layout->contentsMargins().bottom();
|
||||
|
||||
// 计算每个格子的基础尺寸(根据窗口大小动态调整)
|
||||
int baseTileWidth = qMax(100, availableWidth / m_columns - m_layout->horizontalSpacing() * (m_columns - 1));
|
||||
int baseTileHeight = qMax(100, availableHeight / m_rows - m_layout->verticalSpacing() * (m_rows - 1));
|
||||
// 确保减去的间距不会导致负数
|
||||
int horizontalSpacingTotal = m_layout->horizontalSpacing() * (m_columns - 1);
|
||||
int verticalSpacingTotal = m_layout->verticalSpacing() * (m_rows - 1);
|
||||
|
||||
int baseTileWidth = qMax(100, (availableWidth - horizontalSpacingTotal) / m_columns);
|
||||
int baseTileHeight = qMax(100, (availableHeight - verticalSpacingTotal) / m_rows);
|
||||
|
||||
// 根据窗口大小动态调整尺寸
|
||||
m_sizeNormal = QSize(baseTileWidth, baseTileHeight);
|
||||
m_sizeSelected = QSize(baseTileWidth * 1.5, baseTileHeight * 1.5);
|
||||
m_sizeOther = QSize(baseTileWidth * 0.75, baseTileHeight * 0.75);
|
||||
m_sizeExpanded = QSize(availableWidth, availableHeight);
|
||||
|
||||
for (int i = 0; i < m_tiles.size(); ++i) {
|
||||
|
||||
@ -43,7 +43,5 @@ protected:
|
||||
int m_columns {0};
|
||||
int m_rows {0};
|
||||
QSize m_sizeNormal {160,160};
|
||||
QSize m_sizeSelected {240,240};
|
||||
QSize m_sizeOther {120,120};
|
||||
QSize m_sizeExpanded {400,300};
|
||||
};
|
||||
|
||||
@ -79,7 +79,7 @@ void ImageTileWidget::paintEvent(QPaintEvent*) {
|
||||
p.setRenderHint(QPainter::Antialiasing, true);
|
||||
|
||||
// 绘制背景
|
||||
p.fillRect(rect(), QColor(245, 245, 245));
|
||||
p.fillRect(rect(), QColor(37, 38, 42));
|
||||
|
||||
if (m_expanded) {
|
||||
// 展开模式:全屏显示图片,在左下角显示图片名称
|
||||
@ -97,13 +97,13 @@ void ImageTileWidget::paintEvent(QPaintEvent*) {
|
||||
// 绘制图片(完整显示,不超出区域)
|
||||
p.drawPixmap(x, y, scaled);
|
||||
} else {
|
||||
p.setPen(Qt::gray);
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(imageRect, Qt::AlignCenter, "No Image");
|
||||
}
|
||||
|
||||
} else {
|
||||
// 正常模式:图片完全显示,不拉伸
|
||||
QRect inner = rect().adjusted(8, 8, -8, -8);
|
||||
QRect inner = rect().adjusted(0, 0, 0, 0);
|
||||
if (!m_pix.isNull()) {
|
||||
// 使用KeepAspectRatio保持比例,确保整个图片可见
|
||||
QPixmap scaled = m_pix.scaled(inner.size(), Qt::KeepAspectRatio, Qt::SmoothTransformation);
|
||||
@ -114,7 +114,7 @@ void ImageTileWidget::paintEvent(QPaintEvent*) {
|
||||
|
||||
p.drawPixmap(x, y, scaled);
|
||||
} else {
|
||||
p.setPen(Qt::gray);
|
||||
p.setPen(Qt::white);
|
||||
p.drawText(inner, Qt::AlignCenter, "No Image");
|
||||
}
|
||||
|
||||
@ -138,10 +138,10 @@ void ImageTileWidget::paintEvent(QPaintEvent*) {
|
||||
}
|
||||
|
||||
// 绘制边框
|
||||
QPen border(m_selected ? QColor(66, 133, 244) : QColor(200, 200, 200));
|
||||
border.setWidth(m_selected ? 3 : 1);
|
||||
QPen border(QColor(0, 0, 0));
|
||||
border.setWidth(1);
|
||||
p.setPen(border);
|
||||
p.drawRoundedRect(rect().adjusted(1,1,-1,-1), 6, 6);
|
||||
p.drawRoundedRect(rect().adjusted(1,1,-1,-1), 1, 1);
|
||||
}
|
||||
|
||||
void ImageTileWidget::mousePressEvent(QMouseEvent* ev) {
|
||||
|
||||
121
BeltTearingServer/BeltTearingAlgo.cpp
Normal file
121
BeltTearingServer/BeltTearingAlgo.cpp
Normal file
@ -0,0 +1,121 @@
|
||||
#include "BeltTearingAlgo.h"
|
||||
#include <climits>
|
||||
#include <iostream>
|
||||
|
||||
BeltTearingAlgo::BeltTearingAlgo() {
|
||||
algoParam.differnceBinTh = 1.0;
|
||||
algoParam.extractPara.gapChkWin = 7;
|
||||
algoParam.extractPara.sameGapTh = 20.0;
|
||||
algoParam.scanXScale = 4.0;
|
||||
algoParam.scanYScale = 0.6;
|
||||
algoParam.tearingMinGap = 50.0; //两个同位置的纵撕的最小间隔。大于此门限视为两个撕裂
|
||||
algoParam.tearingMinLen = 30.0;
|
||||
lineIndex = 0;
|
||||
|
||||
// 初始化
|
||||
ResetParameter();
|
||||
}
|
||||
|
||||
void BeltTearingAlgo::ResetParameter() {
|
||||
total_tearings.clear();
|
||||
beltTearings_new.clear();
|
||||
beltTearings_growing.clear();
|
||||
beltTearings_ended.clear();
|
||||
beltTearings_unknown.clear();
|
||||
lineIndex = 0;
|
||||
int errCode = 0;
|
||||
sg_detectBeltTearing(
|
||||
NULL, //空扫描线,用于复位内部静态变量
|
||||
0,
|
||||
0,
|
||||
&errCode,
|
||||
hLineWorkers,
|
||||
beltTearings_new,
|
||||
beltTearings_growing,
|
||||
beltTearings_ended,
|
||||
beltTearings_unknown, //未判明,应用无需处理。
|
||||
algoParam);
|
||||
}
|
||||
|
||||
void BeltTearingAlgo::Training(std::vector<SVzNL3DLaserLine> lines) {
|
||||
// 本算法无需训练
|
||||
}
|
||||
|
||||
void BeltTearingAlgo::Training(SVzNL3DLaserLine* lines) {
|
||||
// 本算法无需训练
|
||||
}
|
||||
|
||||
std::vector<SSG_beltTearingInfo> BeltTearingAlgo::Predit(SVzNL3DLaserLine* a_line, int lineId) {
|
||||
total_tearings.clear();
|
||||
int errCode = 0;
|
||||
sg_detectBeltTearing(
|
||||
a_line, //一条扫描线
|
||||
lineIndex,
|
||||
a_line->nPositionCnt,
|
||||
&errCode,
|
||||
hLineWorkers,
|
||||
beltTearings_new,
|
||||
beltTearings_growing,
|
||||
beltTearings_ended,
|
||||
beltTearings_unknown, //未判明,应用无需处理。
|
||||
algoParam);
|
||||
if (beltTearings_ended.size() > 0) //收集撕裂点
|
||||
{
|
||||
total_tearings.insert(total_tearings.end(), beltTearings_ended.begin(), beltTearings_ended.end());
|
||||
}
|
||||
|
||||
return total_tearings;
|
||||
}
|
||||
|
||||
std::vector<SSG_beltTearingInfo> BeltTearingAlgo::Predit(std::vector<SVzNL3DLaserLine> lines, int lineIdx) {
|
||||
total_tearings.clear();
|
||||
if (lines.empty()) {
|
||||
return total_tearings;
|
||||
}
|
||||
|
||||
if (lineIndex >= INT_MAX - lines.size() ) {
|
||||
lineIndex = 0;
|
||||
}
|
||||
|
||||
hLineWorkers.resize(lines[0].nPositionCnt);
|
||||
int errCode = 0;
|
||||
for(int i = 0; i < lines.size(); i ++) {
|
||||
SVzNL3DLaserLine* a_line = &lines[i];
|
||||
sg_detectBeltTearing(
|
||||
a_line, //一条扫描线
|
||||
lineIndex,
|
||||
a_line->nPositionCnt,
|
||||
&errCode,
|
||||
hLineWorkers,
|
||||
beltTearings_new,
|
||||
beltTearings_growing,
|
||||
beltTearings_ended,
|
||||
beltTearings_unknown, //未判明,应用无需处理。
|
||||
algoParam);
|
||||
lineIndex += 1;
|
||||
// if (beltTearings_unknown.size() > 0) {
|
||||
// std::cout << "未知的撕裂数量: " << beltTearings_unknown.size() << std::endl;
|
||||
// }
|
||||
|
||||
// if (beltTearings_growing.size() > 0) {
|
||||
// std::cout << "进行中的撕裂数量: " << beltTearings_growing.size() << std::endl;
|
||||
// }
|
||||
|
||||
if (beltTearings_new.size() > 0) //收集撕裂点
|
||||
{
|
||||
total_tearings.insert(total_tearings.end(), beltTearings_new.begin(), beltTearings_new.end());
|
||||
}
|
||||
|
||||
if (beltTearings_ended.size() > 0) //收集撕裂点
|
||||
{
|
||||
total_tearings.insert(total_tearings.end(), beltTearings_ended.begin(), beltTearings_ended.end());
|
||||
}
|
||||
else if (i == lines.size() - 1) //测试数据最后一条扫描线。(实际中没有)
|
||||
{
|
||||
total_tearings.insert(total_tearings.end(), beltTearings_growing.begin(), beltTearings_growing.end());
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: 返回值有可能为空,也有可能为当前检测的撕裂
|
||||
return total_tearings;
|
||||
}
|
||||
28
BeltTearingServer/BeltTearingAlgo.h
Normal file
28
BeltTearingServer/BeltTearingAlgo.h
Normal file
@ -0,0 +1,28 @@
|
||||
#ifndef BELTTEARINGALGO_H
|
||||
#define BELTTEARINGALGO_H
|
||||
#include "beltTearingDetection_Export.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class BeltTearingAlgo
|
||||
{
|
||||
public:
|
||||
BeltTearingAlgo();
|
||||
void ResetParameter();
|
||||
void Training(SVzNL3DLaserLine* lines);
|
||||
void Training(std::vector<SVzNL3DLaserLine> lines);
|
||||
std::vector<SSG_beltTearingInfo> Predit(SVzNL3DLaserLine* lines, int lineId);
|
||||
std::vector<SSG_beltTearingInfo> Predit(std::vector<SVzNL3DLaserLine> lines, int lineIdx);
|
||||
|
||||
private:
|
||||
SSG_beltTearingParam algoParam;
|
||||
std::vector<SSG_hLineProInfo> hLineWorkers;
|
||||
std::vector<SSG_beltTearingInfo> beltTearings_new;
|
||||
std::vector<SSG_beltTearingInfo> beltTearings_growing;
|
||||
std::vector<SSG_beltTearingInfo> beltTearings_ended;
|
||||
std::vector<SSG_beltTearingInfo> beltTearings_unknown; //未判明,应用无需处理。
|
||||
std::vector<SSG_beltTearingInfo> total_tearings;
|
||||
int lineIndex = 0;
|
||||
};
|
||||
|
||||
#endif // BELTTEARINGALGO_H
|
||||
209
BeltTearingServer/BeltTearingPresenter.cpp
Normal file
209
BeltTearingServer/BeltTearingPresenter.cpp
Normal file
@ -0,0 +1,209 @@
|
||||
#include "BeltTearingPresenter.h"
|
||||
#include <QUuid>
|
||||
#include <QDataStream>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QBuffer>
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
#include <iostream>
|
||||
|
||||
BeltTearingPresenter::BeltTearingPresenter(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_tcpServer(new QTcpServer(this))
|
||||
, m_dataTimer(new QTimer(this))
|
||||
, m_port(0)
|
||||
{
|
||||
// 连接信号槽
|
||||
connect(m_tcpServer, &QTcpServer::newConnection, this, &BeltTearingPresenter::onNewConnection);
|
||||
connect(m_dataTimer, &QTimer::timeout, this, &BeltTearingPresenter::onSendSimulatedData);
|
||||
}
|
||||
|
||||
BeltTearingPresenter::~BeltTearingPresenter()
|
||||
{
|
||||
stopServer();
|
||||
}
|
||||
|
||||
bool BeltTearingPresenter::startServer(quint16 port)
|
||||
{
|
||||
if (m_tcpServer->isListening()) {
|
||||
stopServer();
|
||||
}
|
||||
|
||||
m_port = port;
|
||||
bool result = m_tcpServer->listen(QHostAddress::Any, port);
|
||||
|
||||
if (result) {
|
||||
std::cout << "TCP server started on port " << port << std::endl;
|
||||
} else {
|
||||
std::cout << "Failed to start TCP server: " << m_tcpServer->errorString().toStdString() << std::endl;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::stopServer()
|
||||
{
|
||||
// 断开所有客户端连接
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
it.value()->disconnectFromHost();
|
||||
}
|
||||
m_clients.clear();
|
||||
|
||||
// 停止服务器监听
|
||||
if (m_tcpServer->isListening()) {
|
||||
m_tcpServer->close();
|
||||
}
|
||||
|
||||
// 停止数据发送定时器
|
||||
stopSendingSimulatedData();
|
||||
|
||||
m_port = 0;
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::startSendingSimulatedData()
|
||||
{
|
||||
// 启动定时器,每2秒发送一次模拟数据
|
||||
m_dataTimer->start(1000);
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::stopSendingSimulatedData()
|
||||
{
|
||||
if (m_dataTimer->isActive()) {
|
||||
m_dataTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::onNewConnection()
|
||||
{
|
||||
while (m_tcpServer->hasPendingConnections()) {
|
||||
QTcpSocket *socket = m_tcpServer->nextPendingConnection();
|
||||
|
||||
// 生成客户端ID
|
||||
QString clientId = generateClientId(socket);
|
||||
|
||||
// 存储客户端连接
|
||||
m_clients[clientId] = socket;
|
||||
|
||||
// 连接客户端信号
|
||||
connect(socket, &QTcpSocket::disconnected, this, &BeltTearingPresenter::onClientDisconnected);
|
||||
|
||||
std::cout << "Client connected: " << clientId.toStdString() << std::endl;
|
||||
|
||||
// 发送欢迎消息
|
||||
QByteArray welcomeMsg = "Welcome to BeltTearing Server!";
|
||||
socket->write(welcomeMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::onClientDisconnected()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找断开连接的客户端ID
|
||||
QString clientId;
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it.value() == socket) {
|
||||
clientId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId.isEmpty()) {
|
||||
m_clients.remove(clientId);
|
||||
std::cout << "Client disconnected: " << clientId.toStdString() << std::endl;
|
||||
}
|
||||
|
||||
socket->deleteLater();
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::onSendSimulatedData()
|
||||
{
|
||||
// 向所有连接的客户端发送模拟数据
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
QTcpSocket *socket = it.value();
|
||||
if (socket->state() == QAbstractSocket::ConnectedState) {
|
||||
// 随机选择发送图像或日志记录
|
||||
sendSimulatedImage(socket);
|
||||
sendSimulatedLogRecords(socket);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::sendSimulatedImage(QTcpSocket *socket)
|
||||
{
|
||||
// 创建一个模拟的图像
|
||||
cv::Mat image(480, 640, CV_8UC3, cv::Scalar(128, 128, 128));
|
||||
|
||||
// 在图像上绘制一些随机内容
|
||||
cv::circle(image, cv::Point(320, 240), 50, cv::Scalar(0, 0, 255), -1);
|
||||
cv::rectangle(image, cv::Rect(100, 100, 200, 150), cv::Scalar(0, 255, 0), 2);
|
||||
|
||||
static unsigned int s_imageCount = 0;
|
||||
// 在图像上叠加数字
|
||||
QString numberText = QString::number(++s_imageCount); // 生成一个随机数
|
||||
cv::putText(image, numberText.toStdString(), cv::Point(50, 50), cv::FONT_HERSHEY_SIMPLEX, 1, cv::Scalar(255, 255, 255), 2);
|
||||
|
||||
// 编码图像
|
||||
std::vector<uchar> imgBuffer;
|
||||
std::vector<int> params = {cv::IMWRITE_PNG_COMPRESSION, 3};
|
||||
cv::imencode(".png", image, imgBuffer, params);
|
||||
|
||||
QByteArray byteArray(reinterpret_cast<const char*>(imgBuffer.data()), imgBuffer.size());
|
||||
|
||||
// 创建数据包
|
||||
QByteArray package;
|
||||
QDataStream stream(&package, QIODevice::WriteOnly);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
stream << quint8(static_cast<quint8>(ByteDataType::Image)) << quint32(byteArray.size());
|
||||
package.append(byteArray);
|
||||
package.append("___END___\r\n");
|
||||
|
||||
// 发送数据
|
||||
socket->write(package);
|
||||
|
||||
std::cout << "Sent simulated image data to client" << std::endl;
|
||||
}
|
||||
|
||||
void BeltTearingPresenter::sendSimulatedLogRecords(QTcpSocket *socket)
|
||||
{
|
||||
// 创建模拟日志记录
|
||||
QJsonArray logRecords;
|
||||
|
||||
for (int i = 0; i < 3; i++) {
|
||||
QJsonObject record;
|
||||
record["timestamp"] = QDateTime::currentDateTime().toString(Qt::ISODate);
|
||||
record["level"] = QString("INFO");
|
||||
record["message"] = QString("Simulated log message %1").arg(i + 1);
|
||||
record["source"] = QString("BeltTearingDetector");
|
||||
|
||||
logRecords.append(record);
|
||||
}
|
||||
|
||||
// 将日志记录转换为JSON字符串
|
||||
QJsonDocument doc(logRecords);
|
||||
QByteArray message = doc.toJson(QJsonDocument::Compact);
|
||||
|
||||
// 创建数据包
|
||||
QByteArray package;
|
||||
QDataStream stream(&package, QIODevice::WriteOnly);
|
||||
stream.setByteOrder(QDataStream::BigEndian);
|
||||
stream << quint8(static_cast<quint8>(ByteDataType::Text)) << quint32(message.size());
|
||||
package.append(message);
|
||||
package.append("___END___\r\n");
|
||||
|
||||
// 发送数据
|
||||
socket->write(package);
|
||||
|
||||
std::cout << "Sent simulated log records to client" << std::endl;
|
||||
}
|
||||
|
||||
QString BeltTearingPresenter::generateClientId(QTcpSocket *socket)
|
||||
{
|
||||
// 使用UUID生成唯一的客户端ID
|
||||
return QUuid::createUuid().toString();
|
||||
}
|
||||
50
BeltTearingServer/BeltTearingPresenter.h
Normal file
50
BeltTearingServer/BeltTearingPresenter.h
Normal file
@ -0,0 +1,50 @@
|
||||
#ifndef BELTTEARINGPRESENTER_H
|
||||
#define BELTTEARINGPRESENTER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
#include <QMap>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
// 假设ByteDataType枚举定义
|
||||
enum class ByteDataType : quint8 {
|
||||
Text = 1,
|
||||
Image = 2
|
||||
};
|
||||
|
||||
class BeltTearingPresenter : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BeltTearingPresenter(QObject *parent = nullptr);
|
||||
~BeltTearingPresenter();
|
||||
|
||||
// 初始化TCP服务器
|
||||
bool startServer(quint16 port);
|
||||
void stopServer();
|
||||
|
||||
// 模拟数据发送
|
||||
void startSendingSimulatedData();
|
||||
void stopSendingSimulatedData();
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onClientDisconnected();
|
||||
void onSendSimulatedData();
|
||||
|
||||
private:
|
||||
QTcpServer *m_tcpServer;
|
||||
QMap<QString, QTcpSocket*> m_clients;
|
||||
QTimer *m_dataTimer;
|
||||
quint16 m_port;
|
||||
|
||||
// 模拟数据发送方法
|
||||
void sendSimulatedImage(QTcpSocket *socket);
|
||||
void sendSimulatedLogRecords(QTcpSocket *socket);
|
||||
QString generateClientId(QTcpSocket *socket);
|
||||
};
|
||||
|
||||
#endif // BELTTEARINGPRESENTER_H
|
||||
104
BeltTearingServer/BeltTearingServer.pro
Normal file
104
BeltTearingServer/BeltTearingServer.pro
Normal file
@ -0,0 +1,104 @@
|
||||
QT += core gui
|
||||
QT += serialport
|
||||
QT += network
|
||||
|
||||
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
|
||||
|
||||
TEMPLATE = app
|
||||
|
||||
CONFIG += c++11 console
|
||||
|
||||
# You can make your code fail to compile if it uses deprecated APIs.
|
||||
# In order to do so, uncomment the following line.
|
||||
#DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0
|
||||
|
||||
INCLUDEPATH += $$PWD/../VrCommon/Inc
|
||||
INCLUDEPATH += $$PWD/../VrUtils/Inc
|
||||
INCLUDEPATH += $$PWD/Presenter/Inc
|
||||
INCLUDEPATH += $$PWD/Utils/Inc
|
||||
|
||||
INCLUDEPATH += $$PWD/../VrConfig/Inc
|
||||
INCLUDEPATH += $$PWD/../VrEyeDevice/Inc
|
||||
|
||||
SOURCES += \
|
||||
BeltTearingAlgo.cpp \
|
||||
beltTearingDetection.cpp \
|
||||
main.cpp \
|
||||
BeltTearingPresenter.cpp
|
||||
|
||||
HEADERS += \
|
||||
SG_baseAlgo_Export.h \
|
||||
SG_baseDataType.h \
|
||||
SG_errCode.h \
|
||||
beltTearingDetection_Export.h \
|
||||
BeltTearingAlgo.h \
|
||||
BeltTearingPresenter.h
|
||||
|
||||
FORMS +=
|
||||
|
||||
win32:CONFIG(debug, debug|release) {
|
||||
LIBS += -L../VrUtils/debug -lVrUtils
|
||||
LIBS += -L../VrEyeDevice/debug -lVrEyeDevice
|
||||
}else:win32:CONFIG(release, debug|release){
|
||||
LIBS += -L../VrUtils/release -lVrUtils
|
||||
LIBS += -L../VrEyeDevice/release -lVrEyeDevice
|
||||
}else:unix:!macx {
|
||||
# Unix/Linux平台库链接(包括交叉编译)
|
||||
# 注意链接顺序:依赖关系从高到低
|
||||
LIBS += -L../VrUtils -lVrUtils
|
||||
LIBS += -L../VrEyeDevice -lVrEyeDevice
|
||||
|
||||
# 添加系统库依赖
|
||||
LIBS += -lpthread
|
||||
}
|
||||
|
||||
#linux下的为unix ,windows下用的win32
|
||||
|
||||
INCLUDEPATH += ../SDK/VzNLSDK/_Inc
|
||||
INCLUDEPATH += ../SDK/VzNLSDK/Inc
|
||||
|
||||
win32:CONFIG(release, debug|release): {
|
||||
LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Release
|
||||
LIBS += -lVzKernel -lVzNLDetect -lVzNLGraphics
|
||||
}
|
||||
else:win32:CONFIG(debug, debug|release): {
|
||||
LIBS += -L$$PWD/../SDK/VzNLSDK/Windows/x64/Debug
|
||||
LIBS += -lVzKerneld -lVzNLDetectd -lVzNLGraphicsd
|
||||
}
|
||||
else:unix:!macx: {
|
||||
LIBS += -L$$PWD/../SDK/VzNLSDK/Arm/aarch64
|
||||
LIBS += -lVzEyeSecurityLoader-shared -lVzKernel -lVzNLDetect -lVzNLGraphics
|
||||
}
|
||||
|
||||
|
||||
# 算法
|
||||
INCLUDEPATH += ../SDK/OpenCV320/include
|
||||
win32:CONFIG(release, debug|release): {
|
||||
LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320
|
||||
}
|
||||
else:win32:CONFIG(debug, debug|release): {
|
||||
LIBS += -L$$PWD/../SDK/OpenCV320/Windows/vc14/Release -lopencv_world320
|
||||
}
|
||||
else:unix:!macx: {
|
||||
LIBS += -L$$PWD/../SDK/OpenCV320/Arm/aarch64 -lopencv_core -lopencv_imgproc -lopencv_highgui
|
||||
}
|
||||
|
||||
# 添加libmodbus依赖
|
||||
win32 {
|
||||
LIBS += -lws2_32
|
||||
LIBS += Advapi32.lib
|
||||
}
|
||||
|
||||
# Default rules for deployment.
|
||||
unix {
|
||||
target.path = /usr/lib
|
||||
# Link real-time library for POSIX shared memory functions
|
||||
LIBS += -lrt
|
||||
}
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
|
||||
|
||||
# Default rules for deployment.
|
||||
qnx: target.path = /tmp/$${TARGET}/bin
|
||||
else: unix:!android: target.path = /opt/$${TARGET}/bin
|
||||
!isEmpty(target.path): INSTALLS += target
|
||||
232
BeltTearingServer/SG_baseAlgo_Export.h
Normal file
232
BeltTearingServer/SG_baseAlgo_Export.h
Normal file
@ -0,0 +1,232 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(SG_API_LIBRARY)
|
||||
# define SG_APISHARED_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define SG_APISHARED_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#include "SG_baseDataType.h"
|
||||
#include <vector>
|
||||
#include <opencv2/opencv.hpp>
|
||||
|
||||
//滤除离群点:z跳变门限方法(大于门限视为不连续,根据连续段点数量判断噪声)
|
||||
void sg_lineDataRemoveOutlier(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
SSG_outlierFilterParam filterParam,
|
||||
std::vector<SVzNL3DPosition>& filerData,
|
||||
std::vector<int>& noisePts);
|
||||
//滤除离群点:z跳变门限方法,改变原始数据
|
||||
void sg_lineDataRemoveOutlier_changeOriginData(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
SSG_outlierFilterParam filterParam);
|
||||
|
||||
//滤除离群点:点距离门限方法(大于门限视为不连续,根据连续段点数量判断噪声)
|
||||
void sg_lineDataRemoveOutlier_ptDistMethod(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
SSG_outlierFilterParam filterParam,
|
||||
std::vector<SVzNL3DPosition>& filerData,
|
||||
std::vector<int>& noisePts);
|
||||
//平滑
|
||||
void sg_lineDataSmoothing(
|
||||
std::vector<SVzNL3DPosition>& input,
|
||||
int smoothWin,
|
||||
std::vector<SVzNL3DPosition>& output);
|
||||
|
||||
//VZ_APISHARED_EXPORT void sg_getLineMeanVar();
|
||||
void sg_getLineLVFeature(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
int lineIdx,
|
||||
const SSG_slopeParam slopeParam,
|
||||
const SSG_VFeatureParam valleyPara,
|
||||
SSG_lineFeature* line_features);
|
||||
|
||||
//获取扫描线拐点特征
|
||||
void sg_getLineCornerFeature(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
int lineIdx,
|
||||
const SSG_cornerParam cornerPara, //scale通常取bagH的1/4
|
||||
SSG_lineFeature* line_features);
|
||||
|
||||
/// <summary>
|
||||
/// 提取激光线上的极值点(极大值点和极小值点)
|
||||
///
|
||||
/// </summary>
|
||||
void sg_getLineLocalPeaks(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
int lineIdx,
|
||||
const double scaleWin,
|
||||
std::vector< SSG_basicFeature1D>& localMax,
|
||||
std::vector< SSG_basicFeature1D>& localMin);
|
||||
|
||||
void sg_getLineDownJumps(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
int lineIdx,
|
||||
const double jumpTh,
|
||||
std::vector< SSG_basicFeature1D>& downJumps);
|
||||
|
||||
//对feature进行生长
|
||||
void sg_getFeatureGrowingTrees(
|
||||
std::vector<SSG_lineFeature>& lineFeatures,
|
||||
std::vector<SSG_featureTree>& trees,
|
||||
SSG_treeGrowParam growParam);
|
||||
|
||||
//对ending进行生长
|
||||
void sg_getEndingGrowingTrees(
|
||||
std::vector<SSG_2DValueI>& lineEndings,
|
||||
SVzNL3DLaserLine* laser3DPoints,
|
||||
bool isVScan,
|
||||
int featureType,
|
||||
std::vector<SSG_featureTree>& trees,
|
||||
SSG_treeGrowParam growParam);
|
||||
|
||||
//对扫描线上的
|
||||
void sg_LVFeatureGrowing(
|
||||
std::vector<SSG_lineFeature>& lineFeatures,
|
||||
std::vector<SSG_featureTree>& trees,
|
||||
SSG_bagParam bagParam,
|
||||
SSG_treeGrowParam growParam,
|
||||
std::vector<SSG_2DValueI>& edgePts_0,
|
||||
std::vector<SSG_2DValueI>& edgePts_1);
|
||||
|
||||
void sg_peakFeatureGrowing(
|
||||
std::vector<std::vector< SSG_basicFeature1D>>& lineFeatures,
|
||||
std::vector<SSG_featureTree>& trees,
|
||||
SSG_treeGrowParam growParam);
|
||||
|
||||
SSG_meanVar _computeMeanVar(double* data, int size);
|
||||
|
||||
///搜索局部最高点(z最小点)。
|
||||
///搜索方法:每次步进搜索窗口长度的一半。对局部最高点进行标记,防止被重复记录。
|
||||
void sg_getLocalPeaks(
|
||||
SVzNL3DLaserLine* scanLines,
|
||||
int lineNum,
|
||||
std::vector<SSG_2DValueI>& peaks,
|
||||
SSG_localPkParam searchWin);
|
||||
|
||||
/// <summary>
|
||||
/// 区域生长法:以局部最高点作为生长种子进行生长
|
||||
/// 生长方法与一般的区域生长不同:以种子点为圆心作圆周扫描,记录扫描到的边界
|
||||
/// </summary>
|
||||
//void sg_peakPolarScan(cv::Mat& edgeMask, SVzNL2DPoint a_peak, SSG_polarScanParam polarScanParam, std::vector< SSG_2DValueI>& rgnContour);
|
||||
//从Peak点进行水平垂直扫描得到区域边界
|
||||
void sg_peakXYScan(
|
||||
SVzNL3DLaserLine* laser3DPoints,
|
||||
int lineNum,
|
||||
cv::Mat& featureEdgeMask,
|
||||
SSG_2DValueI a_peak,
|
||||
SSG_treeGrowParam growParam,
|
||||
SSG_bagParam bagParam,
|
||||
bool rgnPtAsEdge,
|
||||
std::vector< SSG_lineConotours>& topContour,
|
||||
std::vector< SSG_lineConotours>& bottomContour,
|
||||
std::vector< SSG_lineConotours>& leftContour,
|
||||
std::vector< SSG_lineConotours>& rightContour,
|
||||
int* maxEdgeId_top,
|
||||
int* maxEdgeId_btm,
|
||||
int* maxEdgeId_left,
|
||||
int* maxEdgeId_right);
|
||||
|
||||
//取出与给定edgeId相同的边界点
|
||||
void sg_getContourPts(
|
||||
std::vector< SSG_lineConotours>& contour_all,
|
||||
int vldEdgeId,
|
||||
std::vector< SSG_2DValueI>& contourFilter,
|
||||
int* lowLevelFlag);
|
||||
|
||||
//从边界点对中取出与给定edgeId相同的边界点
|
||||
void sg_getPairingContourPts(
|
||||
std::vector<SSG_conotourPair>& contourPairs,
|
||||
std::vector<SSG_intPair>& idPairs,
|
||||
std::vector< SSG_conotourPair>& contourFilter,
|
||||
SVzNLRangeD range,
|
||||
bool isTBDir,
|
||||
int* lowLevelFlag_0,
|
||||
int* lowLevelFlag_1);
|
||||
|
||||
//取出最短距离的边界点
|
||||
void sg_contourPostProc(
|
||||
std::vector< SSG_contourPtInfo>& contour,
|
||||
int maxEdgeIdx,
|
||||
double sameConturDistTh,
|
||||
std::vector< SSG_2DValueI>& contourFilter,
|
||||
int sideID,
|
||||
int* blockFlag);
|
||||
|
||||
//距离变换
|
||||
//input, output均为float型
|
||||
void sg_distanceTrans(const cv::Mat input, cv::Mat& output, int distType);
|
||||
|
||||
/// <summary>
|
||||
/// 以5x5方式寻找localPeaks
|
||||
/// </summary>
|
||||
/// <param name="input"></param>
|
||||
/// <param name="peaks"></param>
|
||||
void sg_getLocalPeaks_distTransform(
|
||||
cv::Mat& input,
|
||||
std::vector<SSG_2DValueI>& peaks,
|
||||
SSG_localPkParam searchWin);
|
||||
|
||||
/// <summary>
|
||||
/// 使用模板法提取直角特征
|
||||
/// 水平向下直角特征:拐点左侧deltaZ在一个很小的范围内;拐点右侧deltaY在一个很小的范围内
|
||||
/// </summary>
|
||||
void sg_getLineRigthAngleFeature(
|
||||
SVzNL3DPosition* lineData,
|
||||
int dataSize,
|
||||
int lineIdx,
|
||||
const SSG_lineRightAngleParam templatePara_HF,
|
||||
const SSG_lineRightAngleParam templatePara_FH,
|
||||
const SSG_lineRightAngleParam templatePara_HR,
|
||||
const SSG_lineRightAngleParam templatePara_RH,
|
||||
SSG_lineFeature* line_features);
|
||||
|
||||
//计算扫描ROI
|
||||
SVzNL3DRangeD sg_getScanDataROI(
|
||||
SVzNL3DLaserLine* laser3DPoints,
|
||||
int lineNum);
|
||||
|
||||
//XY平面直线拟合
|
||||
void lineFitting(
|
||||
std::vector< SVzNL3DPoint>& inliers,
|
||||
double* _k,
|
||||
double* _b);
|
||||
|
||||
//Bresenham算法
|
||||
void drawLine(
|
||||
int x0,
|
||||
int y0,
|
||||
int x1,
|
||||
int y1,
|
||||
std::vector<SVzNL2DPoint>& pts);
|
||||
|
||||
/// <summary>
|
||||
/// 两步法标注
|
||||
/// </summary>
|
||||
/// <param name="bwImg"> 目标点为“1”, 空白点为“0”</param>
|
||||
/// <param name="labImg"> 标注结果。每个点为rgnID, ID从2开始 </param>
|
||||
/// <param name="labelRgns"></param>
|
||||
void SG_TwoPassLabel(
|
||||
const cv::Mat& bwImg,
|
||||
cv::Mat& labImg,
|
||||
std::vector<SSG_Region>& labelRgns,
|
||||
int connectivity = 4);
|
||||
|
||||
//计算一个平面调平参数。
|
||||
//数据输入中可以有一个地平面和参考调平平面,以最高的平面进行调平
|
||||
//旋转矩阵为调平参数,即将平面法向调整为垂直向量的参数
|
||||
SSG_planeCalibPara sg_getPlaneCalibPara(
|
||||
SVzNL3DLaserLine* laser3DPoints,
|
||||
int lineNum);
|
||||
|
||||
// 从旋转矩阵计算欧拉角(Z-Y-X顺序)
|
||||
SSG_EulerAngles rotationMatrixToEulerZYX(const double R[3][3]);
|
||||
// 从欧拉角计算旋转矩阵(Z-Y-X顺序)
|
||||
void eulerToRotationMatrixZYX(const SSG_EulerAngles& angles, double R[3][3]);
|
||||
337
BeltTearingServer/SG_baseDataType.h
Normal file
337
BeltTearingServer/SG_baseDataType.h
Normal file
@ -0,0 +1,337 @@
|
||||
#pragma once
|
||||
|
||||
#include <VZNL_Types.h>
|
||||
#include <vector>
|
||||
|
||||
#define PI 3.141592654
|
||||
|
||||
// 定义欧拉角结构体(单位:度)
|
||||
typedef struct{
|
||||
double roll; // 绕X轴旋转(横滚)
|
||||
double pitch; // 绕Y轴旋转(俯仰)
|
||||
double yaw; // 绕Z轴旋转(偏航)
|
||||
}SSG_EulerAngles;
|
||||
|
||||
//以工作轴为X轴(扫描方向),平行地面与Z轴垂直的轴为Y轴,垂直地面的轴为Z轴
|
||||
typedef struct
|
||||
{
|
||||
bool validFlag; //指示结果是否有效
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
double pitchAngle; //俯仰角:绕Y轴的偏转, 弧度
|
||||
double rollAngle; //滚转角:绕X轴的偏转, 弧度
|
||||
double yawAngle; //偏转角:绕Z轴的偏转, 弧度
|
||||
}SSG_6AxisAttitude;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
bool validFlag; //指示结果是否有效
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
}SSG_3AxisAttitude;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
keSG_PoseSorting_Uknown = 0,
|
||||
keSG_PoseSorting_ZMin2Max, ///按Z排序
|
||||
keSG_PoseSorting_L2R_T2B, ///从左到右,从上到下排序
|
||||
keSG_PoseSorting_T2B_L2R, ///从上到下,从左到右排序
|
||||
} ESG_poseSortingMode;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int data_0;
|
||||
int data_1;
|
||||
int idx;
|
||||
}SSG_intPair;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double left;
|
||||
double right;
|
||||
double top;
|
||||
double bottom;
|
||||
}SSG_ROIRectD;
|
||||
|
||||
#define LINE_FEATURE_NUM 16
|
||||
#define LINE_FEATURE_UNDEF 0
|
||||
#define LINE_FEATURE_L_JUMP_H2L 1
|
||||
#define LINE_FEATURE_L_JUMP_L2H 2
|
||||
#define LINE_FEATURE_L_SLOPE_H2L 3
|
||||
#define LINE_FEATURE_L_SLOPE_L2H 4
|
||||
#define LINE_FEATURE_V_SLOPE 5
|
||||
#define LINE_FEATURE_LINE_ENDING_0 6 //ending起点
|
||||
#define LINE_FEATURE_LINE_ENDING_1 7 //ending终点
|
||||
#define LINE_FEATURE_RGN_EDGE 8 //迭代处理时已经得到的目标的边缘点标记
|
||||
#define LINE_FEATURE_RIGHT_ANGLE_HR 9 //直角特征:水平-上升
|
||||
#define LINE_FEATURE_RIGHT_ANGLE_HF 10 //直角特征:水平-下降
|
||||
#define LINE_FEATURE_RIGHT_ANGLE_RH 11 //直角特征:上升-水平
|
||||
#define LINE_FEATURE_RIGHT_ANGLE_FH 12 //直角特征:下降-水平
|
||||
#define LINE_FEATURE_PEAK_TOP 13
|
||||
#define LINE_FEATURE_PEAK_BOTTOM 14
|
||||
#define LINE_FEATURE_CORNER_V 15
|
||||
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int featureType;
|
||||
SVzNL2DPoint jumpPos2D;
|
||||
SVzNL3DPoint jumpPos;
|
||||
}SSG_basicFeature1D;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int lineIdx;
|
||||
double gap_start;
|
||||
double gap_width;
|
||||
double gap_depth;
|
||||
SVzNL3DPosition gapPt_0; //gap的端点
|
||||
SVzNL3DPosition gapPt_1; //gap的端点
|
||||
SSG_ROIRectD roi;
|
||||
}SSG_basicFeatureGap;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int lineIdx;
|
||||
std::vector<SSG_basicFeature1D> features;
|
||||
std::vector<SSG_basicFeature1D> endings;
|
||||
}SSG_lineFeature;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
/* 点是否连续门限,小于此门限,视为点连续 */
|
||||
double continuityTh; //点连续门限。当使用z跳变判断连续性时,为z跳变门限;当使用点距离判断时,为点距离门限
|
||||
/* 噪声判断门限,当连续段的点数据小于此门限,视为噪声 */
|
||||
double outlierTh;
|
||||
}SSG_outlierFilterParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double LSlopeZWin; //对L型Slope进行坡度计算的窗口长度
|
||||
double validSlopeH;
|
||||
double minLJumpH;
|
||||
double minEndingGap;//连续段门限。大于此门限,为不连续
|
||||
}SSG_slopeParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double minEndingGap; //连续段门限。大于此门限,为不连续
|
||||
double scale; //计算方向角的窗口比例尺
|
||||
double cornerTh; //拐角门限,大于此门限,为有效拐点
|
||||
double jumpCornerTh_1; //判断拐角是否为跳变的两个门限。当一个门限大于jumpCornerTh_1且另一个小于jumpCornerTh_2时跳变
|
||||
double jumpCornerTh_2;
|
||||
}SSG_cornerParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double H_len; //直角特征水平段的长度
|
||||
double V_len; //直角特征垂直段的长度
|
||||
double maxDelta;
|
||||
}SSG_lineRightAngleParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double valleyMinH;
|
||||
double valleyMaxW;
|
||||
}SSG_VFeatureParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double sameGapTh;
|
||||
int gapChkWin;
|
||||
}SSG_tearFeatureExtactPara;
|
||||
typedef struct
|
||||
{
|
||||
double yDeviation_max;//生长时相邻特征最大的Y偏差
|
||||
double zDeviation_max; //生长时相邻特征最大的Z偏差
|
||||
int maxLineSkipNum; //生长时相邻特征的最大线间隔, -1时使用maxDkipDistance
|
||||
double maxSkipDistance; //若maxLineSkipNum为-1, 使用此参数.设为-1时,此参数无效
|
||||
double minLTypeTreeLen; //生长树最少的节点数目。小于此数目的生长树被移除
|
||||
double minVTypeTreeLen; //生长树最少的节点数目。小于此数目的生长树被移除
|
||||
}SSG_treeGrowParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double bagL; //长
|
||||
double bagW; //宽
|
||||
double bagH; //高
|
||||
}SSG_bagParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
ESG_poseSortingMode sortMode;
|
||||
}SSG_objSortParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double angleStep;
|
||||
double radiusStep;
|
||||
}SSG_polarScanParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int treeState;
|
||||
int treeType;
|
||||
int sLineIdx;
|
||||
int eLineIdx;
|
||||
SSG_ROIRectD roi;
|
||||
std::vector< SSG_basicFeature1D> treeNodes;
|
||||
}SSG_featureTree;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int vTreeFlag;
|
||||
int treeIdx;
|
||||
int treeType;
|
||||
int sLineIdx;
|
||||
int eLineIdx;
|
||||
SSG_ROIRectD roi;
|
||||
}SSG_treeInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int seachW_lines;
|
||||
int searchW_pts;
|
||||
}SSG_localPkParam;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int value;
|
||||
double valueD;
|
||||
int sideID; //1-T, 2-B, 3-L, 4-R
|
||||
}SSG_2DValueI;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int start;
|
||||
int len;
|
||||
int value;
|
||||
}SSG_RUN;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double mean;
|
||||
double var;
|
||||
}SSG_meanVar;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int x;
|
||||
int y;
|
||||
int type;
|
||||
int edgeId;
|
||||
int blockFlag;//遮挡标志
|
||||
double scanDist;
|
||||
SVzNL3DPoint edgePt;
|
||||
}SSG_contourPtInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int lineIdx;
|
||||
std::vector<SSG_contourPtInfo> contourPts;
|
||||
}SSG_lineConotours;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int lineIdx;
|
||||
int edgeId_0;
|
||||
int edgeId_1;
|
||||
double ptPairDist;
|
||||
SSG_contourPtInfo contourPt_0;
|
||||
SSG_contourPtInfo contourPt_1;
|
||||
}SSG_conotourPair;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double x;
|
||||
double y;
|
||||
double z;
|
||||
double x_roll;
|
||||
double y_pitch;
|
||||
double z_yaw;
|
||||
}SSG_6DOF;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double L;
|
||||
double W;
|
||||
double H;
|
||||
double angle;
|
||||
SVzNL3DPoint endings[4];
|
||||
} SSG_boxCarDimension;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SVzNL3DPoint opCenter; //定子中心位置
|
||||
int neighbourNum; //相邻目标数量
|
||||
double opAngle; //定子外部相隔120度操作点的操作角度。以Y形为基准角度(0度)
|
||||
double obstacleDist; //距离周边障碍的距离
|
||||
}SSG_motorStatorPosition;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int pkRgnIdx;
|
||||
SVzNLSizeD objSize;
|
||||
SVzNL2DPoint pos2D;
|
||||
SSG_6DOF centerPos;
|
||||
}SSG_peakRgnInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SVzNL3DPoint sPt;
|
||||
SVzNL3DPoint ePt;
|
||||
double scanDist;
|
||||
int ptNum;
|
||||
}SSG_contourEdgeInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double meanDist;
|
||||
double varDist;
|
||||
double minDist;
|
||||
double maxDist;
|
||||
int matchNum;
|
||||
int level2_num; //level2为高可信度(两端为Jump或Ending;
|
||||
int level1_num; //level1-为中可信度(一端为Jump或Ending)
|
||||
SSG_ROIRectD roi;
|
||||
}SSG_edgeMatchInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int id1;
|
||||
int id2;
|
||||
int matchPts;
|
||||
int matchType; //可信度, 2为高可信度(两端为Jump或Ending; 1-为中可信度(一端为Jump或Ending);0-为低可信度
|
||||
double matchValue;
|
||||
double varValue;
|
||||
double minMatchValue;
|
||||
double maxMatchValue;
|
||||
SSG_ROIRectD roi;
|
||||
}SSG_matchPair;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int objIdx;
|
||||
bool isValid; //是否为合格目标。顶层目标,但编织袋横放(扫描到长边)为不合格目标
|
||||
SSG_ROIRectD objROI;
|
||||
SVzNL3DPoint objPos;
|
||||
SSG_6DOF graspPos;
|
||||
}SSG_sideBagInfo;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
SVzNLRect roi;
|
||||
int ptCounter;
|
||||
int labelID;
|
||||
}SSG_Region;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double planeCalib[9]; //旋转矩阵,将点云地面调平
|
||||
double planeHeight;//地面调平后的高度,用于去除地面数据
|
||||
double invRMatrix[9]; //旋转矩阵,回到原坐标系
|
||||
}SSG_planeCalibPara;
|
||||
7
BeltTearingServer/SG_errCode.h
Normal file
7
BeltTearingServer/SG_errCode.h
Normal file
@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#define SG_ERR_3D_DATA_INVLD -1000
|
||||
#define SG_ERR_FOUND_NO_TOP_PLANE -1001
|
||||
#define SG_ERR_NOT_GRID_FORMAT -1002
|
||||
#define SG_ERR_LABEL_INFO_ERROR -1003
|
||||
#define SG_ERR_INVLD_SORTING_MODE -1004
|
||||
1283
BeltTearingServer/beltTearingDetection.cpp
Normal file
1283
BeltTearingServer/beltTearingDetection.cpp
Normal file
File diff suppressed because it is too large
Load Diff
94
BeltTearingServer/beltTearingDetection_Export.h
Normal file
94
BeltTearingServer/beltTearingDetection_Export.h
Normal file
@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#if defined(SG_API_LIBRARY)
|
||||
# define SG_APISHARED_EXPORT __declspec(dllexport)
|
||||
#else
|
||||
# define SG_APISHARED_EXPORT __declspec(dllimport)
|
||||
#endif
|
||||
|
||||
#include "SG_baseDataType.h"
|
||||
#include <vector>
|
||||
|
||||
#define ENABLE_CROSS_WISE_TEAR 0
|
||||
#define OUTPUT_TEARING_POINTS 1
|
||||
#define KEEP_GAP_AS_FEATURE 1
|
||||
#define SCAN_BUFF_SIZE 21 //缓存区大小,缓存21条扫描线,处理中间扫描线
|
||||
|
||||
typedef struct
|
||||
{
|
||||
double scanXScale; //3D扫描的X精度,取决于两条扫描线的间距。
|
||||
double scanYScale; //3D扫描的Y精度,取决于点间距。scanXScale和scanYScale可以在现场安装后扫描数据得到
|
||||
double differnceBinTh; //一阶差分二值化门限。其物理意义为撕裂斜面的斜率值。默认为1.0
|
||||
SSG_tearFeatureExtactPara extractPara;
|
||||
double tearingMinLen; //最小撕裂长度,达到此长度时输出撕裂
|
||||
double tearingMinGap; //两个同位置的撕裂的间隔。小于此间隔时,视为同一个撕裂。
|
||||
}SSG_beltTearingParam;
|
||||
|
||||
typedef enum
|
||||
{
|
||||
keSG_tearType_Uknown = 0,
|
||||
keSG_tearType_MachineDir, //纵撕
|
||||
keSG_tearType_CrossWise,//横撕
|
||||
}ESG_tearType;
|
||||
typedef enum
|
||||
{
|
||||
keSG_tearStatus_Uknown = 0,
|
||||
keSG_tearStatus_New, //新发现的撕裂
|
||||
keSG_tearStatus_Growing, //持续的撕裂
|
||||
keSG_tearStatus_Ended, //撕裂结束
|
||||
keSG_tearStatus_Invalid, //无效撕裂(可能被合并)
|
||||
}ESG_tearStatus;
|
||||
typedef struct
|
||||
{
|
||||
int tearID; //每个撕裂有个唯一的序号
|
||||
ESG_tearStatus tearStatus;
|
||||
ESG_tearType tearType;
|
||||
int statLineIdx; //撕裂开始时的扫描线序号
|
||||
int endLineIdx; //撕裂结束时的扫描线序号,当撕裂持续时,为最新的扫描线序号
|
||||
double tearDepth;//撕裂深度。-1表示是贯穿型撕裂
|
||||
double tearWidth;//撕裂宽度
|
||||
SSG_ROIRectD roi; //撕裂的ROI范围,为{左,右,上,下}
|
||||
#if OUTPUT_TEARING_POINTS
|
||||
std::vector<SVzNL3DPosition> pts; //撕裂的边界点,debug或显示目的
|
||||
#endif
|
||||
}SSG_beltTearingInfo;
|
||||
|
||||
//处理状态机
|
||||
typedef enum
|
||||
{
|
||||
keSG_HLineProc_Init = 0, //初态
|
||||
keSG_HLineProc_First_Half,
|
||||
keSG_HLineProc_Wait_Second,
|
||||
keSG_HLineProc_Second_Half,
|
||||
}ESG_HLineProcSM;
|
||||
|
||||
typedef struct
|
||||
{
|
||||
int hLineIdx;
|
||||
//SVzNL3DPosition scanBuff[SCAN_BUFF_SIZE]; //循环buff
|
||||
//int buffHead;//循环buff的头位置
|
||||
//int buffSize;//循环buff
|
||||
SVzNL3DPosition preVldData;
|
||||
SVzNL3DPosition gapPos;
|
||||
ESG_HLineProcSM hLineSM; //水平扫描线处理状态机
|
||||
SSG_RUN firstHalf;
|
||||
SVzNL3DPosition firstHalf_startPt;
|
||||
SVzNL3DPosition firstHalf_endPt;
|
||||
SSG_RUN secondHalf;
|
||||
SVzNL3DPosition secondHalf_startPt;
|
||||
SVzNL3DPosition secondHalf_endPt;
|
||||
double gapMaxZ;
|
||||
}SSG_hLineProInfo;
|
||||
|
||||
|
||||
void sg_detectBeltTearing(
|
||||
SVzNL3DLaserLine* laser3DPoints, //一条扫描线
|
||||
int lineIdx,
|
||||
int nPointCount, //每条扫描线的点数量。当采用Grid数据格式时,每条扫描线的点的数量是相同的。必须使用Grid格式
|
||||
int* errCode,
|
||||
std::vector<SSG_hLineProInfo>& hLineWorkers,
|
||||
std::vector<SSG_beltTearingInfo>& beltTearings_new,
|
||||
std::vector<SSG_beltTearingInfo>& beltTearings_growing,
|
||||
std::vector<SSG_beltTearingInfo>& beltTearings_ended,
|
||||
std::vector<SSG_beltTearingInfo>& beltTearings_unknown, //未判明,应用无需处理。
|
||||
const SSG_beltTearingParam measureParam);
|
||||
25
BeltTearingServer/main.cpp
Normal file
25
BeltTearingServer/main.cpp
Normal file
@ -0,0 +1,25 @@
|
||||
|
||||
#include <iostream>
|
||||
#include <QCoreApplication>
|
||||
#include "BeltTearingPresenter.h"
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
// 创建并初始化Presenter
|
||||
BeltTearingPresenter presenter;
|
||||
|
||||
// 启动TCP服务器,监听端口12345
|
||||
if (!presenter.startServer(12345)) {
|
||||
std::cout << "Failed to start server" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
|
||||
// 开始发送模拟数据
|
||||
presenter.startSendingSimulatedData();
|
||||
|
||||
std::cout << "BeltTearing Server started. Press Ctrl+C to exit." << std::endl;
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
@ -10,13 +10,15 @@ SUBDIRS += ../Module/ModbusTCPServer/ModbusTCPServer.pro
|
||||
|
||||
SUBDIRS += ../VrEyeDevice/VrEyeDevice.pro
|
||||
|
||||
# 撕裂项目
|
||||
SUBDIRS += ../GrabBagConfig/GrabBagConfig.pro
|
||||
SUBDIRS += ../GrabBagApp/GrabBagApp.pro
|
||||
# 拆包项目
|
||||
# SUBDIRS += ../GrabBagConfig/GrabBagConfig.pro
|
||||
# SUBDIRS += ../GrabBagApp/GrabBagApp.pro
|
||||
# SUBDIRS += ../GrabBagConfigCmd
|
||||
|
||||
|
||||
# 撕裂项目
|
||||
SUBDIRS += ../VrNets/VrTcpClient.pro
|
||||
SUBDIRS += ../BeltTearingConfig/BeltTearingConfig.pro
|
||||
SUBDIRS += ../BeltTearingApp/BeltTearingApp.pro
|
||||
# SUBDIRS += ../BeltTearingServer/BeltTearingServer.pro
|
||||
|
||||
SUBDIRS += ../VrNets/VrTcpServer.pro
|
||||
SUBDIRS += ../BeltTearingServer/BeltTearingServer.pro
|
||||
|
||||
@ -1,65 +1,65 @@
|
||||
#ifndef IY_MODBUS_TCP_SERVER_H
|
||||
#define IY_MODBUS_TCP_SERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
class IYModbusTCPServer {
|
||||
public:
|
||||
// 错误代码枚举
|
||||
enum class ErrorCode {
|
||||
SUCCESS = 0,
|
||||
ILLEGAL_FUNCTION = 1,
|
||||
ILLEGAL_DATA_ADDRESS = 2,
|
||||
ILLEGAL_DATA_VALUE = 3,
|
||||
SERVER_FAILURE = 4,
|
||||
ACKNOWLEDGE = 5,
|
||||
SERVER_BUSY = 6,
|
||||
NEGATIVE_ACKNOWLEDGE = 7,
|
||||
MEMORY_PARITY = 8,
|
||||
GATEWAY_PATH_UNAVAILABLE = 10,
|
||||
GATEWAY_TARGET_FAILED = 11
|
||||
};
|
||||
|
||||
// 回调函数类型定义
|
||||
// 写线圈回调函数:unitId, startAddress, quantity, values -> ErrorCode
|
||||
using WriteCoilsCallback = std::function<ErrorCode(uint8_t, uint16_t, uint16_t, const uint8_t*)>;
|
||||
|
||||
// 写保持寄存器回调函数:unitId, startAddress, quantity, values -> ErrorCode
|
||||
using WriteRegistersCallback = std::function<ErrorCode(uint8_t, uint16_t, uint16_t, const uint16_t*)>;
|
||||
|
||||
// 连接状态回调函数:isConnected -> void
|
||||
using ConnectionStatusCallback = std::function<void(bool)>;
|
||||
|
||||
public:
|
||||
virtual ~IYModbusTCPServer() = default;
|
||||
|
||||
static bool CreateInstance(IYModbusTCPServer** ppModbusTCPServer);
|
||||
|
||||
// 启动/停止服务器
|
||||
virtual int start(int port = 502, int maxConnections = 10) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
// 设置回调函数
|
||||
virtual void setWriteCoilsCallback(WriteCoilsCallback callback) = 0;
|
||||
virtual void setWriteRegistersCallback(WriteRegistersCallback callback) = 0;
|
||||
virtual void setConnectionStatusCallback(ConnectionStatusCallback callback) = 0;
|
||||
|
||||
// 更新数据接口 - 线程安全
|
||||
virtual void updateCoil(uint16_t address, bool value) = 0;
|
||||
virtual void updateCoils(uint16_t startAddress, const std::vector<bool>& values) = 0;
|
||||
virtual void updateDiscreteInput(uint16_t address, bool value) = 0;
|
||||
virtual void updateDiscreteInputs(uint16_t startAddress, const std::vector<bool>& values) = 0;
|
||||
virtual void updateHoldingRegister(uint16_t address, uint16_t value) = 0;
|
||||
virtual void updateHoldingRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) = 0;
|
||||
virtual void updateInputRegister(uint16_t address, uint16_t value) = 0;
|
||||
virtual void updateInputRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // MODBUS_TCP_SERVER_H
|
||||
#ifndef IY_MODBUS_TCP_SERVER_H
|
||||
#define IY_MODBUS_TCP_SERVER_H
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
class IYModbusTCPServer {
|
||||
public:
|
||||
// 错误代码枚举
|
||||
enum class ErrorCode {
|
||||
SUCCESS = 0,
|
||||
ILLEGAL_FUNCTION = 1,
|
||||
ILLEGAL_DATA_ADDRESS = 2,
|
||||
ILLEGAL_DATA_VALUE = 3,
|
||||
SERVER_FAILURE = 4,
|
||||
ACKNOWLEDGE = 5,
|
||||
SERVER_BUSY = 6,
|
||||
NEGATIVE_ACKNOWLEDGE = 7,
|
||||
MEMORY_PARITY = 8,
|
||||
GATEWAY_PATH_UNAVAILABLE = 10,
|
||||
GATEWAY_TARGET_FAILED = 11
|
||||
};
|
||||
|
||||
// 回调函数类型定义
|
||||
// 写线圈回调函数:unitId, startAddress, quantity, values -> ErrorCode
|
||||
using WriteCoilsCallback = std::function<ErrorCode(uint8_t, uint16_t, uint16_t, const uint8_t*)>;
|
||||
|
||||
// 写保持寄存器回调函数:unitId, startAddress, quantity, values -> ErrorCode
|
||||
using WriteRegistersCallback = std::function<ErrorCode(uint8_t, uint16_t, uint16_t, const uint16_t*)>;
|
||||
|
||||
// 连接状态回调函数:isConnected -> void
|
||||
using ConnectionStatusCallback = std::function<void(bool)>;
|
||||
|
||||
public:
|
||||
virtual ~IYModbusTCPServer() = default;
|
||||
|
||||
static bool CreateInstance(IYModbusTCPServer** ppModbusTCPServer);
|
||||
|
||||
// 启动/停止服务器
|
||||
virtual int start(int port = 502, int maxConnections = 10) = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
// 设置回调函数
|
||||
virtual void setWriteCoilsCallback(WriteCoilsCallback callback) = 0;
|
||||
virtual void setWriteRegistersCallback(WriteRegistersCallback callback) = 0;
|
||||
virtual void setConnectionStatusCallback(ConnectionStatusCallback callback) = 0;
|
||||
|
||||
// 更新数据接口 - 线程安全
|
||||
virtual void updateCoil(uint16_t address, bool value) = 0;
|
||||
virtual void updateCoils(uint16_t startAddress, const std::vector<bool>& values) = 0;
|
||||
virtual void updateDiscreteInput(uint16_t address, bool value) = 0;
|
||||
virtual void updateDiscreteInputs(uint16_t startAddress, const std::vector<bool>& values) = 0;
|
||||
virtual void updateHoldingRegister(uint16_t address, uint16_t value) = 0;
|
||||
virtual void updateHoldingRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) = 0;
|
||||
virtual void updateInputRegister(uint16_t address, uint16_t value) = 0;
|
||||
virtual void updateInputRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) = 0;
|
||||
|
||||
};
|
||||
|
||||
#endif // MODBUS_TCP_SERVER_H
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
QT -= gui
|
||||
|
||||
CONFIG += c++17 console
|
||||
QMAKE_CXXFLAGS += /utf-8
|
||||
|
||||
# The following define makes your compiler emit warnings if you use
|
||||
# any Qt feature that has been marked deprecated (the exact warnings
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,139 +1,139 @@
|
||||
#ifndef MODBUS_TCP_SERVER_H
|
||||
#define MODBUS_TCP_SERVER_H
|
||||
|
||||
#include "IYModbusTCPServer.h"
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
// Platform-specific includes for socket operations
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/select.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
// libmodbus headers
|
||||
extern "C" {
|
||||
#include "modbus.h"
|
||||
#include "modbus-tcp.h"
|
||||
}
|
||||
|
||||
class ModbusTCPServer : public IYModbusTCPServer {
|
||||
private:
|
||||
// 客户端连接结构体
|
||||
struct ClientConnection {
|
||||
int socket;
|
||||
modbus_t* modbusCtx;
|
||||
|
||||
ClientConnection(int sock, modbus_t* ctx) : socket(sock), modbusCtx(ctx) {}
|
||||
|
||||
~ClientConnection() {
|
||||
if (modbusCtx) {
|
||||
modbus_free(modbusCtx);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (socket != -1) {
|
||||
closesocket(socket);
|
||||
}
|
||||
#else
|
||||
if (socket != -1) {
|
||||
close(socket);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
ModbusTCPServer();
|
||||
virtual ~ModbusTCPServer() override;
|
||||
|
||||
// 实现IYModbusTCPServer接口
|
||||
// 启动/停止服务器
|
||||
virtual int start(int port = 502, int maxConnections = 10) override;
|
||||
virtual void stop() override;
|
||||
bool isRunning() const { return m_isRunning.load(); }
|
||||
|
||||
// 设置回调函数
|
||||
virtual void setWriteCoilsCallback(WriteCoilsCallback callback) override { m_writeCoilsCallback = callback; }
|
||||
virtual void setWriteRegistersCallback(WriteRegistersCallback callback) override { m_writeRegistersCallback = callback; }
|
||||
virtual void setConnectionStatusCallback(ConnectionStatusCallback callback) override { m_connectionStatusCallback = callback; }
|
||||
|
||||
// 数据更新接口 - 线程安全
|
||||
virtual void updateCoil(uint16_t address, bool value) override;
|
||||
virtual void updateCoils(uint16_t startAddress, const std::vector<bool>& values) override;
|
||||
virtual void updateDiscreteInput(uint16_t address, bool value) override;
|
||||
virtual void updateDiscreteInputs(uint16_t startAddress, const std::vector<bool>& values) override;
|
||||
virtual void updateHoldingRegister(uint16_t address, uint16_t value) override;
|
||||
virtual void updateHoldingRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) override;
|
||||
virtual void updateInputRegister(uint16_t address, uint16_t value) override;
|
||||
virtual void updateInputRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) override;
|
||||
|
||||
// 获取错误信息
|
||||
std::string getLastError() const;
|
||||
|
||||
private:
|
||||
// 服务器主循环
|
||||
void serverLoop();
|
||||
|
||||
// 客户端连接管理
|
||||
void handleNewConnection();
|
||||
void handleClientData(std::shared_ptr<ClientConnection> client);
|
||||
void removeClient(int socket);
|
||||
void processModbusRequest(std::shared_ptr<ClientConnection> client, const uint8_t* query, int queryLength);
|
||||
|
||||
// 数据读取处理器(libmodbus内部调用)
|
||||
static int readCoilsHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readDiscreteInputsHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readHoldingRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readInputRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
|
||||
// 数据写入函数(libmodbus内部调用)
|
||||
static int writeCoilsHandler(modbus_t *ctx, int function_code, int addr, int nb, const uint8_t *req, int req_length, void *user_data);
|
||||
static int writeRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, const uint8_t *req, int req_length, void *user_data);
|
||||
|
||||
private:
|
||||
// libmodbus context
|
||||
modbus_t* m_modbusCtx;
|
||||
modbus_mapping_t* m_mapping;
|
||||
|
||||
// 服务器状态
|
||||
std::atomic<bool> m_isRunning;
|
||||
std::atomic<bool> m_shouldStop;
|
||||
std::unique_ptr<std::thread> m_serverThread;
|
||||
int m_serverSocket;
|
||||
int m_port;
|
||||
int m_maxConnections;
|
||||
|
||||
// 客户端连接管理
|
||||
std::unordered_map<int, std::shared_ptr<ClientConnection>> m_clients;
|
||||
std::mutex m_clientsMutex;
|
||||
|
||||
// 数据存储 - 使用libmodbus的mapping结构
|
||||
std::mutex m_dataMutex;
|
||||
|
||||
// 回调函数
|
||||
WriteCoilsCallback m_writeCoilsCallback;
|
||||
WriteRegistersCallback m_writeRegistersCallback;
|
||||
ConnectionStatusCallback m_connectionStatusCallback;
|
||||
|
||||
// 错误信息
|
||||
mutable std::mutex m_errorMutex;
|
||||
std::string m_lastError;
|
||||
|
||||
// 设置错误信息
|
||||
void setLastError(const std::string& error);
|
||||
};
|
||||
|
||||
#endif // MODBUS_TCP_SERVER_H
|
||||
#ifndef MODBUS_TCP_SERVER_H
|
||||
#define MODBUS_TCP_SERVER_H
|
||||
|
||||
#include "IYModbusTCPServer.h"
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <thread>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
// Platform-specific includes for socket operations
|
||||
#ifdef _WIN32
|
||||
#include <winsock2.h>
|
||||
#include <ws2tcpip.h>
|
||||
#pragma comment(lib, "ws2_32.lib")
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include <sys/select.h>
|
||||
#include <errno.h>
|
||||
#endif
|
||||
|
||||
// libmodbus headers
|
||||
extern "C" {
|
||||
#include "modbus.h"
|
||||
#include "modbus-tcp.h"
|
||||
}
|
||||
|
||||
class ModbusTCPServer : public IYModbusTCPServer {
|
||||
private:
|
||||
// 客户端连接结构体
|
||||
struct ClientConnection {
|
||||
int socket;
|
||||
modbus_t* modbusCtx;
|
||||
|
||||
ClientConnection(int sock, modbus_t* ctx) : socket(sock), modbusCtx(ctx) {}
|
||||
|
||||
~ClientConnection() {
|
||||
if (modbusCtx) {
|
||||
modbus_free(modbusCtx);
|
||||
}
|
||||
#ifdef _WIN32
|
||||
if (socket != -1) {
|
||||
closesocket(socket);
|
||||
}
|
||||
#else
|
||||
if (socket != -1) {
|
||||
close(socket);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
public:
|
||||
ModbusTCPServer();
|
||||
virtual ~ModbusTCPServer() override;
|
||||
|
||||
// 实现IYModbusTCPServer接口
|
||||
// 启动/停止服务器
|
||||
virtual int start(int port = 502, int maxConnections = 10) override;
|
||||
virtual void stop() override;
|
||||
bool isRunning() const { return m_isRunning.load(); }
|
||||
|
||||
// 设置回调函数
|
||||
virtual void setWriteCoilsCallback(WriteCoilsCallback callback) override { m_writeCoilsCallback = callback; }
|
||||
virtual void setWriteRegistersCallback(WriteRegistersCallback callback) override { m_writeRegistersCallback = callback; }
|
||||
virtual void setConnectionStatusCallback(ConnectionStatusCallback callback) override { m_connectionStatusCallback = callback; }
|
||||
|
||||
// 数据更新接口 - 线程安全
|
||||
virtual void updateCoil(uint16_t address, bool value) override;
|
||||
virtual void updateCoils(uint16_t startAddress, const std::vector<bool>& values) override;
|
||||
virtual void updateDiscreteInput(uint16_t address, bool value) override;
|
||||
virtual void updateDiscreteInputs(uint16_t startAddress, const std::vector<bool>& values) override;
|
||||
virtual void updateHoldingRegister(uint16_t address, uint16_t value) override;
|
||||
virtual void updateHoldingRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) override;
|
||||
virtual void updateInputRegister(uint16_t address, uint16_t value) override;
|
||||
virtual void updateInputRegisters(uint16_t startAddress, const std::vector<uint16_t>& values) override;
|
||||
|
||||
// 获取错误信息
|
||||
std::string getLastError() const;
|
||||
|
||||
private:
|
||||
// 服务器主循环
|
||||
void serverLoop();
|
||||
|
||||
// 客户端连接管理
|
||||
void handleNewConnection();
|
||||
void handleClientData(std::shared_ptr<ClientConnection> client);
|
||||
void removeClient(int socket);
|
||||
void processModbusRequest(std::shared_ptr<ClientConnection> client, const uint8_t* query, int queryLength);
|
||||
|
||||
// 数据读取处理器(libmodbus内部调用)
|
||||
static int readCoilsHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readDiscreteInputsHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readHoldingRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
static int readInputRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, uint8_t *rsp, int *rsp_length, void *user_data);
|
||||
|
||||
// 数据写入函数(libmodbus内部调用)
|
||||
static int writeCoilsHandler(modbus_t *ctx, int function_code, int addr, int nb, const uint8_t *req, int req_length, void *user_data);
|
||||
static int writeRegistersHandler(modbus_t *ctx, int function_code, int addr, int nb, const uint8_t *req, int req_length, void *user_data);
|
||||
|
||||
private:
|
||||
// libmodbus context
|
||||
modbus_t* m_modbusCtx;
|
||||
modbus_mapping_t* m_mapping;
|
||||
|
||||
// 服务器状态
|
||||
std::atomic<bool> m_isRunning;
|
||||
std::atomic<bool> m_shouldStop;
|
||||
std::unique_ptr<std::thread> m_serverThread;
|
||||
int m_serverSocket;
|
||||
int m_port;
|
||||
int m_maxConnections;
|
||||
|
||||
// 客户端连接管理
|
||||
std::unordered_map<int, std::shared_ptr<ClientConnection>> m_clients;
|
||||
std::mutex m_clientsMutex;
|
||||
|
||||
// 数据存储 - 使用libmodbus的mapping结构
|
||||
std::mutex m_dataMutex;
|
||||
|
||||
// 回调函数
|
||||
WriteCoilsCallback m_writeCoilsCallback;
|
||||
WriteRegistersCallback m_writeRegistersCallback;
|
||||
ConnectionStatusCallback m_connectionStatusCallback;
|
||||
|
||||
// 错误信息
|
||||
mutable std::mutex m_errorMutex;
|
||||
std::string m_lastError;
|
||||
|
||||
// 设置错误信息
|
||||
void setLastError(const std::string& error);
|
||||
};
|
||||
|
||||
#endif // MODBUS_TCP_SERVER_H
|
||||
|
||||
27
VrNets/VrTcpServer.pro
Normal file
27
VrNets/VrTcpServer.pro
Normal file
@ -0,0 +1,27 @@
|
||||
QT += core network
|
||||
|
||||
TARGET = VrTcpServer
|
||||
TEMPLATE = lib
|
||||
CONFIG += c++11 staticlib
|
||||
|
||||
# 源文件
|
||||
SOURCES += \
|
||||
tcpServer/Src/VrTcpServer.cpp
|
||||
|
||||
# 头文件
|
||||
HEADERS += \
|
||||
tcpServer/Inc/IVrTcpServer.h \
|
||||
tcpServer/Inc/VrTcpServer.h
|
||||
|
||||
# 包含路径
|
||||
INCLUDEPATH += \
|
||||
tcpServer/Inc
|
||||
|
||||
# 安装路径
|
||||
target.path = $$[QT_INSTALL_LIBS]
|
||||
INSTALLS += target
|
||||
|
||||
# 头文件安装路径
|
||||
headers.files = $$HEADERS
|
||||
headers.path = $$[QT_INSTALL_HEADERS]/VrTcpServer
|
||||
INSTALLS += headers
|
||||
@ -1,55 +1,55 @@
|
||||
#ifndef IVRTCPCLIENT_H
|
||||
#define IVRTCPCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
|
||||
class IVrTcpClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IVrTcpClient(QObject *parent = nullptr) : QObject(parent) {}
|
||||
virtual ~IVrTcpClient() {}
|
||||
|
||||
// 连接服务器
|
||||
virtual bool connectToServer(const QString &host, quint16 port) = 0;
|
||||
virtual bool connectToServer(const QHostAddress &address, quint16 port) = 0;
|
||||
|
||||
// 断开连接
|
||||
virtual void disconnectFromServer() = 0;
|
||||
|
||||
// 发送数据
|
||||
virtual qint64 sendData(const QByteArray &data) = 0;
|
||||
virtual qint64 sendData(const char *data, qint64 size) = 0;
|
||||
|
||||
// 获取连接状态
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
// 获取服务器地址和端口
|
||||
virtual QHostAddress serverAddress() const = 0;
|
||||
virtual quint16 serverPort() const = 0;
|
||||
|
||||
signals:
|
||||
// 连接状态变化信号
|
||||
void connected();
|
||||
void disconnected();
|
||||
void connectionError(const QString &errorString);
|
||||
|
||||
// 数据接收信号
|
||||
void dataReceived(const QByteArray &data);
|
||||
|
||||
// 数据发送信号
|
||||
void dataSent(qint64 bytesSent);
|
||||
|
||||
protected:
|
||||
// 设置连接状态(供子类使用)
|
||||
void setConnected(bool connected) { m_connected = connected; }
|
||||
|
||||
bool m_connected = false;
|
||||
QHostAddress m_serverAddress;
|
||||
quint16 m_serverPort = 0;
|
||||
};
|
||||
|
||||
#endif // IVRTCPCLIENT_H
|
||||
#ifndef IVRTCPCLIENT_H
|
||||
#define IVRTCPCLIENT_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QByteArray>
|
||||
#include <QHostAddress>
|
||||
|
||||
class IVrTcpClient : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IVrTcpClient(QObject *parent = nullptr) : QObject(parent) {}
|
||||
virtual ~IVrTcpClient() {}
|
||||
|
||||
// 连接服务器
|
||||
virtual bool connectToServer(const QString &host, quint16 port) = 0;
|
||||
virtual bool connectToServer(const QHostAddress &address, quint16 port) = 0;
|
||||
|
||||
// 断开连接
|
||||
virtual void disconnectFromServer() = 0;
|
||||
|
||||
// 发送数据
|
||||
virtual qint64 sendData(const QByteArray &data) = 0;
|
||||
virtual qint64 sendData(const char *data, qint64 size) = 0;
|
||||
|
||||
// 获取连接状态
|
||||
virtual bool isConnected() const = 0;
|
||||
|
||||
// 获取服务器地址和端口
|
||||
virtual QHostAddress serverAddress() const = 0;
|
||||
virtual quint16 serverPort() const = 0;
|
||||
|
||||
signals:
|
||||
// 连接状态变化信号
|
||||
void connected();
|
||||
void disconnected();
|
||||
void connectionError(const QString &errorString);
|
||||
|
||||
// 数据接收信号
|
||||
void dataReceived(const QByteArray &data);
|
||||
|
||||
// 数据发送信号
|
||||
void dataSent(qint64 bytesSent);
|
||||
|
||||
protected:
|
||||
// 设置连接状态(供子类使用)
|
||||
void setConnected(bool connected) { m_connected = connected; }
|
||||
|
||||
bool m_connected = false;
|
||||
QHostAddress m_serverAddress;
|
||||
quint16 m_serverPort = 0;
|
||||
};
|
||||
|
||||
#endif // IVRTCPCLIENT_H
|
||||
|
||||
@ -1,39 +1,39 @@
|
||||
#ifndef VRTCPCLIENT_H
|
||||
#define VRTCPCLIENT_H
|
||||
|
||||
#include "IVrTcpClient.h"
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
|
||||
class VrTcpClient : public IVrTcpClient
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VrTcpClient(QObject *parent = nullptr);
|
||||
~VrTcpClient() override;
|
||||
|
||||
// 接口实现
|
||||
bool connectToServer(const QString &host, quint16 port) override;
|
||||
bool connectToServer(const QHostAddress &address, quint16 port) override;
|
||||
void disconnectFromServer() override;
|
||||
qint64 sendData(const QByteArray &data) override;
|
||||
qint64 sendData(const char *data, qint64 size) override;
|
||||
bool isConnected() const override;
|
||||
QHostAddress serverAddress() const override;
|
||||
quint16 serverPort() const override;
|
||||
|
||||
// 额外功能:自动重连
|
||||
void setAutoReconnect(bool autoReconnect);
|
||||
bool autoReconnect() const;
|
||||
|
||||
void setReconnectInterval(int msec);
|
||||
int reconnectInterval() const;
|
||||
|
||||
private:
|
||||
QTcpSocket *m_socket;
|
||||
QTimer *m_reconnectTimer;
|
||||
bool m_autoReconnect = false;
|
||||
};
|
||||
|
||||
#endif // VRTCPCLIENT_H
|
||||
#ifndef VRTCPCLIENT_H
|
||||
#define VRTCPCLIENT_H
|
||||
|
||||
#include "IVrTcpClient.h"
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
|
||||
class VrTcpClient : public IVrTcpClient
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VrTcpClient(QObject *parent = nullptr);
|
||||
~VrTcpClient() override;
|
||||
|
||||
// 接口实现
|
||||
bool connectToServer(const QString &host, quint16 port) override;
|
||||
bool connectToServer(const QHostAddress &address, quint16 port) override;
|
||||
void disconnectFromServer() override;
|
||||
qint64 sendData(const QByteArray &data) override;
|
||||
qint64 sendData(const char *data, qint64 size) override;
|
||||
bool isConnected() const override;
|
||||
QHostAddress serverAddress() const override;
|
||||
quint16 serverPort() const override;
|
||||
|
||||
// 额外功能:自动重连
|
||||
void setAutoReconnect(bool autoReconnect);
|
||||
bool autoReconnect() const;
|
||||
|
||||
void setReconnectInterval(int msec);
|
||||
int reconnectInterval() const;
|
||||
|
||||
private:
|
||||
QTcpSocket *m_socket;
|
||||
QTimer *m_reconnectTimer;
|
||||
bool m_autoReconnect = false;
|
||||
};
|
||||
|
||||
#endif // VRTCPCLIENT_H
|
||||
|
||||
@ -1,150 +1,150 @@
|
||||
#include "VrTcpClient.h"
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
VrTcpClient::VrTcpClient(QObject *parent)
|
||||
: IVrTcpClient(parent)
|
||||
, m_socket(new QTcpSocket(this))
|
||||
, m_reconnectTimer(new QTimer(this))
|
||||
{
|
||||
// 设置重连定时器
|
||||
m_reconnectTimer->setInterval(5000); // 5秒重连间隔
|
||||
m_reconnectTimer->setSingleShot(true);
|
||||
|
||||
// 连接信号槽
|
||||
connect(m_socket, &QTcpSocket::connected, this, [this]() {
|
||||
setConnected(true);
|
||||
m_reconnectTimer->stop();
|
||||
emit connected();
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::disconnected, this, [this]() {
|
||||
setConnected(false);
|
||||
emit disconnected();
|
||||
|
||||
// 如果设置了自动重连,则启动重连定时器
|
||||
if (m_autoReconnect) {
|
||||
m_reconnectTimer->start();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
|
||||
this, [this](QAbstractSocket::SocketError error) {
|
||||
setConnected(false);
|
||||
emit connectionError(m_socket->errorString());
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::readyRead, this, [this]() {
|
||||
QByteArray data = m_socket->readAll();
|
||||
if (!data.isEmpty()) {
|
||||
emit dataReceived(data);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::bytesWritten, this, &IVrTcpClient::dataSent);
|
||||
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, [this]() {
|
||||
if (m_autoReconnect && !m_serverAddress.isNull() && m_serverPort > 0) {
|
||||
connectToServer(m_serverAddress, m_serverPort);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VrTcpClient::~VrTcpClient()
|
||||
{
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
bool VrTcpClient::connectToServer(const QString &host, quint16 port)
|
||||
{
|
||||
m_serverAddress = QHostAddress(host);
|
||||
m_serverPort = port;
|
||||
|
||||
if (m_serverAddress.isNull()) {
|
||||
// 如果是主机名,直接连接
|
||||
m_socket->connectToHost(host, port);
|
||||
return true;
|
||||
} else {
|
||||
return connectToServer(m_serverAddress, port);
|
||||
}
|
||||
}
|
||||
|
||||
bool VrTcpClient::connectToServer(const QHostAddress &address, quint16 port)
|
||||
{
|
||||
m_serverAddress = address;
|
||||
m_serverPort = port;
|
||||
|
||||
if (isConnected()) {
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
m_socket->connectToHost(address, port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VrTcpClient::disconnectFromServer()
|
||||
{
|
||||
m_reconnectTimer->stop();
|
||||
if (m_socket->state() != QAbstractSocket::UnconnectedState) {
|
||||
m_socket->disconnectFromHost();
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->waitForDisconnected(3000);
|
||||
}
|
||||
}
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
qint64 VrTcpClient::sendData(const QByteArray &data)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return -1;
|
||||
}
|
||||
return m_socket->write(data);
|
||||
}
|
||||
|
||||
qint64 VrTcpClient::sendData(const char *data, qint64 size)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return -1;
|
||||
}
|
||||
return m_socket->write(data, size);
|
||||
}
|
||||
|
||||
bool VrTcpClient::isConnected() const
|
||||
{
|
||||
return m_connected && m_socket->state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
QHostAddress VrTcpClient::serverAddress() const
|
||||
{
|
||||
return m_serverAddress;
|
||||
}
|
||||
|
||||
quint16 VrTcpClient::serverPort() const
|
||||
{
|
||||
return m_serverPort;
|
||||
}
|
||||
|
||||
void VrTcpClient::setAutoReconnect(bool autoReconnect)
|
||||
{
|
||||
m_autoReconnect = autoReconnect;
|
||||
if (!autoReconnect) {
|
||||
m_reconnectTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool VrTcpClient::autoReconnect() const
|
||||
{
|
||||
return m_autoReconnect;
|
||||
}
|
||||
|
||||
void VrTcpClient::setReconnectInterval(int msec)
|
||||
{
|
||||
m_reconnectTimer->setInterval(msec);
|
||||
}
|
||||
|
||||
int VrTcpClient::reconnectInterval() const
|
||||
{
|
||||
return m_reconnectTimer->interval();
|
||||
}
|
||||
#include "VrTcpClient.h"
|
||||
#include <QTcpSocket>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
VrTcpClient::VrTcpClient(QObject *parent)
|
||||
: IVrTcpClient(parent)
|
||||
, m_socket(new QTcpSocket(this))
|
||||
, m_reconnectTimer(new QTimer(this))
|
||||
{
|
||||
// 设置重连定时器
|
||||
m_reconnectTimer->setInterval(5000); // 5秒重连间隔
|
||||
m_reconnectTimer->setSingleShot(true);
|
||||
|
||||
// 连接信号槽
|
||||
connect(m_socket, &QTcpSocket::connected, this, [this]() {
|
||||
setConnected(true);
|
||||
m_reconnectTimer->stop();
|
||||
emit connected();
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::disconnected, this, [this]() {
|
||||
setConnected(false);
|
||||
emit disconnected();
|
||||
|
||||
// 如果设置了自动重连,则启动重连定时器
|
||||
if (m_autoReconnect) {
|
||||
m_reconnectTimer->start();
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_socket, QOverload<QAbstractSocket::SocketError>::of(&QTcpSocket::errorOccurred),
|
||||
this, [this](QAbstractSocket::SocketError error) {
|
||||
setConnected(false);
|
||||
emit connectionError(m_socket->errorString());
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::readyRead, this, [this]() {
|
||||
QByteArray data = m_socket->readAll();
|
||||
if (!data.isEmpty()) {
|
||||
emit dataReceived(data);
|
||||
}
|
||||
});
|
||||
|
||||
connect(m_socket, &QTcpSocket::bytesWritten, this, &IVrTcpClient::dataSent);
|
||||
|
||||
connect(m_reconnectTimer, &QTimer::timeout, this, [this]() {
|
||||
if (m_autoReconnect && !m_serverAddress.isNull() && m_serverPort > 0) {
|
||||
connectToServer(m_serverAddress, m_serverPort);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VrTcpClient::~VrTcpClient()
|
||||
{
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
bool VrTcpClient::connectToServer(const QString &host, quint16 port)
|
||||
{
|
||||
m_serverAddress = QHostAddress(host);
|
||||
m_serverPort = port;
|
||||
|
||||
if (m_serverAddress.isNull()) {
|
||||
// 如果是主机名,直接连接
|
||||
m_socket->connectToHost(host, port);
|
||||
return true;
|
||||
} else {
|
||||
return connectToServer(m_serverAddress, port);
|
||||
}
|
||||
}
|
||||
|
||||
bool VrTcpClient::connectToServer(const QHostAddress &address, quint16 port)
|
||||
{
|
||||
m_serverAddress = address;
|
||||
m_serverPort = port;
|
||||
|
||||
if (isConnected()) {
|
||||
disconnectFromServer();
|
||||
}
|
||||
|
||||
m_socket->connectToHost(address, port);
|
||||
return true;
|
||||
}
|
||||
|
||||
void VrTcpClient::disconnectFromServer()
|
||||
{
|
||||
m_reconnectTimer->stop();
|
||||
if (m_socket->state() != QAbstractSocket::UnconnectedState) {
|
||||
m_socket->disconnectFromHost();
|
||||
if (m_socket->state() == QAbstractSocket::ConnectedState) {
|
||||
m_socket->waitForDisconnected(3000);
|
||||
}
|
||||
}
|
||||
setConnected(false);
|
||||
}
|
||||
|
||||
qint64 VrTcpClient::sendData(const QByteArray &data)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return -1;
|
||||
}
|
||||
return m_socket->write(data);
|
||||
}
|
||||
|
||||
qint64 VrTcpClient::sendData(const char *data, qint64 size)
|
||||
{
|
||||
if (!isConnected()) {
|
||||
return -1;
|
||||
}
|
||||
return m_socket->write(data, size);
|
||||
}
|
||||
|
||||
bool VrTcpClient::isConnected() const
|
||||
{
|
||||
return m_connected && m_socket->state() == QAbstractSocket::ConnectedState;
|
||||
}
|
||||
|
||||
QHostAddress VrTcpClient::serverAddress() const
|
||||
{
|
||||
return m_serverAddress;
|
||||
}
|
||||
|
||||
quint16 VrTcpClient::serverPort() const
|
||||
{
|
||||
return m_serverPort;
|
||||
}
|
||||
|
||||
void VrTcpClient::setAutoReconnect(bool autoReconnect)
|
||||
{
|
||||
m_autoReconnect = autoReconnect;
|
||||
if (!autoReconnect) {
|
||||
m_reconnectTimer->stop();
|
||||
}
|
||||
}
|
||||
|
||||
bool VrTcpClient::autoReconnect() const
|
||||
{
|
||||
return m_autoReconnect;
|
||||
}
|
||||
|
||||
void VrTcpClient::setReconnectInterval(int msec)
|
||||
{
|
||||
m_reconnectTimer->setInterval(msec);
|
||||
}
|
||||
|
||||
int VrTcpClient::reconnectInterval() const
|
||||
{
|
||||
return m_reconnectTimer->interval();
|
||||
}
|
||||
|
||||
38
VrNets/tcpServer/Inc/IVrTcpServer.h
Normal file
38
VrNets/tcpServer/Inc/IVrTcpServer.h
Normal file
@ -0,0 +1,38 @@
|
||||
#ifndef IVRTCPSERVER_H
|
||||
#define IVRTCPSERVER_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QHostAddress>
|
||||
|
||||
class IVrTcpServer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit IVrTcpServer(QObject *parent = nullptr);
|
||||
~IVrTcpServer() override;
|
||||
|
||||
// 服务器接口
|
||||
virtual bool startServer(quint16 port) = 0;
|
||||
virtual void stopServer() = 0;
|
||||
virtual bool isListening() const = 0;
|
||||
virtual quint16 serverPort() const = 0;
|
||||
|
||||
// 数据发送接口
|
||||
virtual qint64 broadcastData(const QByteArray &data) = 0;
|
||||
virtual qint64 broadcastData(const char *data, qint64 size) = 0;
|
||||
|
||||
signals:
|
||||
// 服务器状态信号
|
||||
void serverStarted();
|
||||
void serverStopped();
|
||||
void serverError(const QString &error);
|
||||
|
||||
// 客户端连接信号
|
||||
void clientConnected(const QString &clientId);
|
||||
void clientDisconnected(const QString &clientId);
|
||||
void dataReceived(const QString &clientId, const QByteArray &data);
|
||||
void dataSent(const QString &clientId, qint64 bytes);
|
||||
};
|
||||
|
||||
#endif // IVRTCPSERVER_H
|
||||
48
VrNets/tcpServer/Inc/VrTcpServer.h
Normal file
48
VrNets/tcpServer/Inc/VrTcpServer.h
Normal file
@ -0,0 +1,48 @@
|
||||
#ifndef VRTCPSERVER_H
|
||||
#define VRTCPSERVER_H
|
||||
|
||||
#include "IVrTcpServer.h"
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QMap>
|
||||
#include <QTimer>
|
||||
|
||||
class VrTcpServer : public IVrTcpServer
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit VrTcpServer(QObject *parent = nullptr);
|
||||
~VrTcpServer() override;
|
||||
|
||||
// 接口实现
|
||||
bool startServer(quint16 port) override;
|
||||
void stopServer() override;
|
||||
bool isListening() const override;
|
||||
quint16 serverPort() const override;
|
||||
|
||||
// 客户端管理
|
||||
QStringList getClientIds() const;
|
||||
bool disconnectClient(const QString &clientId);
|
||||
qint64 sendDataToClient(const QString &clientId, const QByteArray &data);
|
||||
qint64 sendDataToClient(const QString &clientId, const char *data, qint64 size);
|
||||
qint64 broadcastData(const QByteArray &data);
|
||||
qint64 broadcastData(const char *data, qint64 size);
|
||||
QHostAddress getClientAddress(const QString &clientId) const;
|
||||
quint16 getClientPort(const QString &clientId) const;
|
||||
|
||||
private slots:
|
||||
void onNewConnection();
|
||||
void onClientDisconnected();
|
||||
void onClientReadyRead();
|
||||
void onClientBytesWritten(qint64 bytes);
|
||||
void onServerError(QAbstractSocket::SocketError error);
|
||||
|
||||
private:
|
||||
QTcpServer *m_server;
|
||||
QMap<QString, QTcpSocket*> m_clients;
|
||||
quint16 m_port;
|
||||
QString generateClientId(QTcpSocket *socket);
|
||||
};
|
||||
|
||||
#endif // VRTCPSERVER_H
|
||||
281
VrNets/tcpServer/Src/VrTcpServer.cpp
Normal file
281
VrNets/tcpServer/Src/VrTcpServer.cpp
Normal file
@ -0,0 +1,281 @@
|
||||
#include "VrTcpServer.h"
|
||||
#include <QTcpServer>
|
||||
#include <QTcpSocket>
|
||||
#include <QUuid>
|
||||
#include <QDebug>
|
||||
|
||||
VrTcpServer::VrTcpServer(QObject *parent)
|
||||
: IVrTcpServer(parent)
|
||||
, m_server(new QTcpServer(this))
|
||||
, m_port(0)
|
||||
{
|
||||
// 连接服务器信号
|
||||
connect(m_server, &QTcpServer::newConnection, this, &VrTcpServer::onNewConnection);
|
||||
connect(m_server, &QTcpServer::acceptError, this, [this](QAbstractSocket::SocketError error) {
|
||||
emit serverError(m_server->errorString());
|
||||
});
|
||||
}
|
||||
|
||||
VrTcpServer::~VrTcpServer()
|
||||
{
|
||||
stopServer();
|
||||
}
|
||||
|
||||
bool VrTcpServer::startServer(quint16 port)
|
||||
{
|
||||
if (m_server->isListening()) {
|
||||
stopServer();
|
||||
}
|
||||
|
||||
m_port = port;
|
||||
bool result = m_server->listen(QHostAddress::Any, port);
|
||||
|
||||
if (result) {
|
||||
emit serverStarted();
|
||||
} else {
|
||||
emit serverError(m_server->errorString());
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void VrTcpServer::stopServer()
|
||||
{
|
||||
// 断开所有客户端连接
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
it.value()->disconnectFromHost();
|
||||
}
|
||||
m_clients.clear();
|
||||
|
||||
// 停止服务器监听
|
||||
if (m_server->isListening()) {
|
||||
m_server->close();
|
||||
emit serverStopped();
|
||||
}
|
||||
|
||||
m_port = 0;
|
||||
}
|
||||
|
||||
bool VrTcpServer::isListening() const
|
||||
{
|
||||
return m_server->isListening();
|
||||
}
|
||||
|
||||
quint16 VrTcpServer::serverPort() const
|
||||
{
|
||||
return m_port;
|
||||
}
|
||||
|
||||
QStringList VrTcpServer::getClientIds() const
|
||||
{
|
||||
return m_clients.keys();
|
||||
}
|
||||
|
||||
bool VrTcpServer::disconnectClient(const QString &clientId)
|
||||
{
|
||||
if (!m_clients.contains(clientId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
QTcpSocket *socket = m_clients[clientId];
|
||||
socket->disconnectFromHost();
|
||||
return true;
|
||||
}
|
||||
|
||||
qint64 VrTcpServer::sendDataToClient(const QString &clientId, const QByteArray &data)
|
||||
{
|
||||
if (!m_clients.contains(clientId)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QTcpSocket *socket = m_clients[clientId];
|
||||
if (socket->state() != QAbstractSocket::ConnectedState) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return socket->write(data);
|
||||
}
|
||||
|
||||
qint64 VrTcpServer::sendDataToClient(const QString &clientId, const char *data, qint64 size)
|
||||
{
|
||||
if (!m_clients.contains(clientId)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
QTcpSocket *socket = m_clients[clientId];
|
||||
if (socket->state() != QAbstractSocket::ConnectedState) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return socket->write(data, size);
|
||||
}
|
||||
|
||||
QHostAddress VrTcpServer::getClientAddress(const QString &clientId) const
|
||||
{
|
||||
if (!m_clients.contains(clientId)) {
|
||||
return QHostAddress();
|
||||
}
|
||||
|
||||
return m_clients[clientId]->peerAddress();
|
||||
}
|
||||
|
||||
quint16 VrTcpServer::getClientPort(const QString &clientId) const
|
||||
{
|
||||
if (!m_clients.contains(clientId)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_clients[clientId]->peerPort();
|
||||
}
|
||||
|
||||
void VrTcpServer::onNewConnection()
|
||||
{
|
||||
while (m_server->hasPendingConnections()) {
|
||||
QTcpSocket *socket = m_server->nextPendingConnection();
|
||||
|
||||
// 生成客户端ID
|
||||
QString clientId = generateClientId(socket);
|
||||
|
||||
// 存储客户端连接
|
||||
m_clients[clientId] = socket;
|
||||
|
||||
// 连接客户端信号
|
||||
connect(socket, &QTcpSocket::disconnected, this, &VrTcpServer::onClientDisconnected);
|
||||
connect(socket, &QTcpSocket::readyRead, this, &VrTcpServer::onClientReadyRead);
|
||||
connect(socket, &QTcpSocket::bytesWritten, this, &VrTcpServer::onClientBytesWritten);
|
||||
|
||||
// 发送客户端连接信号
|
||||
emit clientConnected(clientId);
|
||||
}
|
||||
}
|
||||
|
||||
void VrTcpServer::onClientDisconnected()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找断开连接的客户端ID
|
||||
QString clientId;
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it.value() == socket) {
|
||||
clientId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId.isEmpty()) {
|
||||
m_clients.remove(clientId);
|
||||
emit clientDisconnected(clientId);
|
||||
}
|
||||
|
||||
socket->deleteLater();
|
||||
}
|
||||
|
||||
void VrTcpServer::onClientReadyRead()
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找客户端ID
|
||||
QString clientId;
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it.value() == socket) {
|
||||
clientId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId.isEmpty()) {
|
||||
QByteArray data = socket->readAll();
|
||||
if (!data.isEmpty()) {
|
||||
emit dataReceived(clientId, data);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void VrTcpServer::onClientBytesWritten(qint64 bytes)
|
||||
{
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找客户端ID
|
||||
QString clientId;
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it.value() == socket) {
|
||||
clientId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId.isEmpty()) {
|
||||
emit dataSent(clientId, bytes);
|
||||
}
|
||||
}
|
||||
|
||||
void VrTcpServer::onServerError(QAbstractSocket::SocketError error)
|
||||
{
|
||||
Q_UNUSED(error)
|
||||
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
|
||||
if (!socket) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 查找客户端ID
|
||||
QString clientId;
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
if (it.value() == socket) {
|
||||
clientId = it.key();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!clientId.isEmpty()) {
|
||||
emit serverError(QString("Client %1 error: %2").arg(clientId, socket->errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
QString VrTcpServer::generateClientId(QTcpSocket *socket)
|
||||
{
|
||||
// 使用UUID生成唯一的客户端ID
|
||||
return QUuid::createUuid().toString();
|
||||
}
|
||||
|
||||
qint64 VrTcpServer::broadcastData(const QByteArray &data)
|
||||
{
|
||||
qint64 totalBytesWritten = 0;
|
||||
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
QTcpSocket *socket = it.value();
|
||||
if (socket->state() == QAbstractSocket::ConnectedState) {
|
||||
qint64 bytesWritten = socket->write(data);
|
||||
if (bytesWritten > 0) {
|
||||
totalBytesWritten += bytesWritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalBytesWritten;
|
||||
}
|
||||
|
||||
qint64 VrTcpServer::broadcastData(const char *data, qint64 size)
|
||||
{
|
||||
qint64 totalBytesWritten = 0;
|
||||
|
||||
for (auto it = m_clients.begin(); it != m_clients.end(); ++it) {
|
||||
QTcpSocket *socket = it.value();
|
||||
if (socket->state() == QAbstractSocket::ConnectedState) {
|
||||
qint64 bytesWritten = socket->write(data, size);
|
||||
if (bytesWritten > 0) {
|
||||
totalBytesWritten += bytesWritten;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return totalBytesWritten;
|
||||
}
|
||||
60
VrNets/tcpServer/Test/test_tcp_server.cpp
Normal file
60
VrNets/tcpServer/Test/test_tcp_server.cpp
Normal file
@ -0,0 +1,60 @@
|
||||
#include "../Inc/VrTcpServer.h"
|
||||
#include <QCoreApplication>
|
||||
#include <QTimer>
|
||||
#include <QDebug>
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QCoreApplication app(argc, argv);
|
||||
|
||||
VrTcpServer server;
|
||||
|
||||
// 连接信号槽
|
||||
QObject::connect(&server, &IVrTcpServer::serverStarted, []() {
|
||||
qDebug() << "Server started!";
|
||||
});
|
||||
|
||||
QObject::connect(&server, &IVrTcpServer::serverStopped, []() {
|
||||
qDebug() << "Server stopped!";
|
||||
});
|
||||
|
||||
QObject::connect(&server, &IVrTcpServer::serverError, [](const QString &error) {
|
||||
qDebug() << "Server error:" << error;
|
||||
});
|
||||
|
||||
QObject::connect(&server, &IVrTcpServer::clientConnected, [&server](const QString &clientId) {
|
||||
qDebug() << "Client connected:" << clientId;
|
||||
|
||||
// 发送欢迎消息给新连接的客户端
|
||||
QByteArray welcomeMessage = "Welcome to the server!";
|
||||
server.sendDataToClient(clientId, welcomeMessage);
|
||||
});
|
||||
|
||||
QObject::connect(&server, &IVrTcpServer::clientDisconnected, [](const QString &clientId) {
|
||||
qDebug() << "Client disconnected:" << clientId;
|
||||
});
|
||||
|
||||
QObject::connect(&server, &IVrTcpServer::dataReceived, [&server](const QString &clientId, const QByteArray &data) {
|
||||
qDebug() << "Received data from" << clientId << ":" << data.toHex();
|
||||
|
||||
// 将收到的数据广播给所有客户端
|
||||
QByteArray message = "Broadcast from " + clientId.toUtf8() + ": " + data;
|
||||
server.broadcastData(message);
|
||||
});
|
||||
|
||||
// 启动服务器
|
||||
if (!server.startServer(12345)) {
|
||||
qDebug() << "Failed to start server";
|
||||
return -1;
|
||||
}
|
||||
|
||||
qDebug() << "Server listening on port 12345";
|
||||
|
||||
// 10秒后停止服务器并退出
|
||||
QTimer::singleShot(10000, [&server, &app]() {
|
||||
server.stopServer();
|
||||
app.quit();
|
||||
});
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user