├── .gitignore ├── SimpleTcpClient ├── main.cpp ├── widget.h ├── SimpleTcpClient.pro ├── widget.cpp └── widget.ui ├── SimpleTcpServer ├── main.cpp ├── widget.h ├── SimpleTcpServer.pro ├── widget.ui └── widget.cpp ├── SimpleUdpClient ├── main.cpp ├── widget.h ├── SimpleUdpClient.pro ├── widget.cpp └── widget.ui ├── MultithreadTcpServer ├── main.cpp ├── MyTcpServer.h ├── widget.h ├── MultithreadTcpServer.pro ├── ServerOperate.h ├── MyTcpServer.cpp ├── ServerOperate.cpp ├── widget.ui └── widget.cpp ├── TcpNetworkControl ├── main.cpp ├── Readme.txt ├── MyTcpServer.h ├── ControlClient.h ├── TcpNetworkControl.pro ├── MainWindow.h ├── ControlServer.h ├── MyTcpSocket.h ├── MyTcpServer.cpp ├── ControlClient.cpp ├── ProtocolParser.h ├── ProtocolParser.cpp ├── MyTcpSocket.cpp ├── ControlServer.cpp ├── MainWindow.ui └── MainWindow.cpp ├── HelloQtNetwork.pro ├── README.md ├── TcpFileTransfer ├── main.cpp ├── Readme.txt ├── ClientWidget.h ├── ServerWidget.h ├── TcpFileTransfer.pro ├── ServerWidget.cpp ├── ServerOperate.h ├── ClientWidget.cpp ├── ClientOperate.h ├── ServerWidget.ui ├── ClientWidget.ui ├── ClientOperate.cpp └── ServerOperate.cpp └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /bin 2 | /build 3 | 4 | *.pro.user 5 | -------------------------------------------------------------------------------- /SimpleTcpClient/main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /SimpleTcpServer/main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /SimpleUdpClient/main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /MultithreadTcpServer/main.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | 3 | #include 4 | 5 | int main(int argc, char *argv[]) 6 | { 7 | QApplication a(argc, argv); 8 | Widget w; 9 | w.show(); 10 | return a.exec(); 11 | } 12 | -------------------------------------------------------------------------------- /TcpNetworkControl/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "MainWindow.h" 3 | 4 | int main(int argc, char *argv[]) 5 | { 6 | QApplication app(argc, argv); 7 | MainWindow w; 8 | w.show(); 9 | return app.exec(); 10 | } 11 | -------------------------------------------------------------------------------- /HelloQtNetwork.pro: -------------------------------------------------------------------------------- 1 | TEMPLATE = subdirs 2 | 3 | SUBDIRS += \ 4 | MultithreadTcpServer \ 5 | SimpleTcpClient \ 6 | SimpleTcpServer \ 7 | SimpleUdpClient \ 8 | TcpFileTransfer \ 9 | TcpNetworkControl 10 | 11 | DISTFILES += \ 12 | LICENSE \ 13 | README.md 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HelloQtNetwork 2 | 3 | Show the use of Qt network module. 展示Qt网络模块的使用 4 | 5 | # Environment (开发环境) 6 | 7 | (2023-11-13)Qt5.15.2 + MSVC2019/2022 32bit/64bit 8 | 9 | # Note (备注) 10 | 11 | (2023-03-03)Qt5.8 之后默认使用系统代理,有时可能影响 Qt 网络访问,可以设置全局或者 Socket 的代理模式,如全局禁用代理 QNetworkProxy::setApplicationProxy(QNetworkProxy::NoProxy)。 12 | -------------------------------------------------------------------------------- /TcpFileTransfer/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "ServerWidget.h" 4 | #include "ClientWidget.h" 5 | 6 | int main(int argc, char *argv[]) 7 | { 8 | QApplication a(argc, argv); 9 | 10 | //客户端作为发送端 11 | ClientWidget c_w; 12 | c_w.move(100,100); 13 | c_w.show(); 14 | //服务端作为接收端 15 | ServerWidget s_w; 16 | s_w.move(600,100); 17 | s_w.show(); 18 | 19 | return a.exec(); 20 | } 21 | -------------------------------------------------------------------------------- /TcpNetworkControl/Readme.txt: -------------------------------------------------------------------------------- 1 | 网络控制协议 2 | 控制信息通过UTF8编码的JSON传递,图像数据通过二进制传递 3 | 帧结构:帧头4+帧长4+JSON数据长度4+JSON内容N+二进制数据长度4+二进制数据内容N+校验4+帧尾4 4 | 4Byte head:0xFF 0xFF 0x00 0x00 5 | 4Byte frame length:帧长,uint32小端,含头尾等,即整个帧的字节长度 6 | 4Byte control length:control 数据的字节长度,uint32小端 7 | NByte control:控制信息采用UTF8编码的Json字符串 8 | 4Byte data length:data 数据的字节长度,uint32小端,命令消息固定0 9 | NByte data:放图像等二进制数据 10 | 4Byte check:校验位,暂未使用全0 11 | 4Byte tail:0x00 0x00 0xFF 0xFF 12 | -------------------------------------------------------------------------------- /TcpFileTransfer/Readme.txt: -------------------------------------------------------------------------------- 1 | 服务端为接收端 2 | 客户端为发送端 3 | 4 | 客户端连接服务端后,选择文件并点击发送,服务端根据设置的路径进行存储 5 | 可点击取消终止文件传输操作 6 | 7 | 传输协议 8 | 帧结构:帧头4+帧长2+帧类型1+帧数据N+帧尾2(没有校验段,懒得写) 9 | 帧头:4字节定值 0x0F 0xF0 0x00 0xFF 10 | 帧长:2字节数据段长度值 arr[4]*0x100+arr[5] 前面为高位后面为低位 11 | 帧类型:1字节 12 | - 0x01 [S/C]准备发送文件,后跟四字节文件长度和N字节utf8文件名,长度计算同帧长一样前面为高位后面为低位 13 | - 0x02 [C]文件数据 14 | - 0x03 [S/C]发送结束,客户端无数据段,服务端接收返回1字节数据段:成功=1,失败=0,可扩展失败原因枚举 15 | - 0x04 [S/C]取消发送 16 | (服务端收到0x01 0x03开始和结束发送两个命令要进行应答,回同样的命令码无数据段) 17 | 帧尾:2字节定值 0x0D 0x0A 18 | -------------------------------------------------------------------------------- /MultithreadTcpServer/MyTcpServer.h: -------------------------------------------------------------------------------- 1 | #ifndef MYTCPSERVER_H 2 | #define MYTCPSERVER_H 3 | 4 | #include 5 | 6 | //重载QTcpServer的incomingConnection,将socket handle转发出去 7 | class MyTcpServer : public QTcpServer 8 | { 9 | Q_OBJECT 10 | public: 11 | explicit MyTcpServer(QObject *parent = nullptr); 12 | 13 | protected: 14 | void incomingConnection(qintptr handle) override; 15 | 16 | signals: 17 | void newConnectionHandle(qintptr handle); 18 | }; 19 | 20 | #endif // MYTCPSERVER_H 21 | -------------------------------------------------------------------------------- /SimpleUdpClient/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class Widget; } 9 | QT_END_NAMESPACE 10 | 11 | //udp demo 12 | class Widget : public QWidget 13 | { 14 | Q_OBJECT 15 | public: 16 | Widget(QWidget *parent = nullptr); 17 | ~Widget(); 18 | 19 | private: 20 | //初始化client操作 21 | void initClient(); 22 | //更新当前状态 23 | void updateState(); 24 | 25 | private: 26 | Ui::Widget *ui; 27 | //socket对象 28 | QUdpSocket *udpSocket; 29 | }; 30 | #endif // WIDGET_H 31 | -------------------------------------------------------------------------------- /SimpleTcpClient/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class Widget; } 9 | QT_END_NAMESPACE 10 | 11 | //simple Tcp 客户端 12 | class Widget : public QWidget 13 | { 14 | Q_OBJECT 15 | 16 | public: 17 | Widget(QWidget *parent = nullptr); 18 | ~Widget(); 19 | 20 | private: 21 | //初始化client操作 22 | void initClient(); 23 | //更新当前状态 24 | void updateState(); 25 | 26 | private: 27 | Ui::Widget *ui; 28 | //socket对象 29 | QTcpSocket *client; 30 | }; 31 | #endif // WIDGET_H 32 | -------------------------------------------------------------------------------- /MultithreadTcpServer/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | #include "ServerOperate.h" 7 | 8 | QT_BEGIN_NAMESPACE 9 | namespace Ui { class Widget; } 10 | QT_END_NAMESPACE 11 | 12 | //为了便于演示,我把server和client的相关操作都卸载了同一个界面 13 | //为了简化流程,地址我都是随机或者固定值,主要目的是展示tcpserver多线程的运用 14 | class Widget : public QWidget 15 | { 16 | Q_OBJECT 17 | public: 18 | Widget(QWidget *parent = nullptr); 19 | ~Widget(); 20 | 21 | private: 22 | void initClient(); 23 | void initServer(); 24 | 25 | private: 26 | Ui::Widget *ui; 27 | 28 | //模拟的客户端列表,在ui线程直接操作 29 | QList clientList; 30 | //服务端操作,简易的封装 31 | ServerOperate *server; 32 | }; 33 | #endif // WIDGET_H 34 | -------------------------------------------------------------------------------- /TcpFileTransfer/ClientWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENTWIDGET_H 2 | #define CLIENTWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | #include "ClientOperate.h" 8 | 9 | namespace Ui { 10 | class ClientWidget; 11 | } 12 | 13 | //客户端界面--客户端作为发送 14 | class ClientWidget : public QWidget 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | explicit ClientWidget(QWidget *parent = nullptr); 20 | ~ClientWidget(); 21 | 22 | signals: 23 | //使用信号槽操作线程中的ClientOperate 24 | void connectTcp(const QString &address,quint16 port); 25 | void disconnectTcp(); 26 | 27 | private: 28 | Ui::ClientWidget *ui; 29 | 30 | //线程 31 | QThread *thread; 32 | //client处理放到线程中 33 | ClientOperate *operate; 34 | }; 35 | 36 | #endif // CLIENTWIDGET_H 37 | -------------------------------------------------------------------------------- /SimpleTcpServer/widget.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGET_H 2 | #define WIDGET_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | QT_BEGIN_NAMESPACE 9 | namespace Ui { class Widget; } 10 | QT_END_NAMESPACE 11 | 12 | //simple Tcp 服务端 13 | class Widget : public QWidget 14 | { 15 | Q_OBJECT 16 | 17 | public: 18 | Widget(QWidget *parent = nullptr); 19 | ~Widget(); 20 | 21 | private: 22 | //初始化server操作 23 | void initServer(); 24 | //close server 25 | void closeServer(); 26 | //更新当前状态 27 | void updateState(); 28 | 29 | private: 30 | Ui::Widget *ui; 31 | //server用于监听端口,获取新的tcp连接的描述符 32 | QTcpServer *server; 33 | //存储已连接的socket对象 34 | QList clientList; 35 | }; 36 | #endif // WIDGET_H 37 | -------------------------------------------------------------------------------- /TcpFileTransfer/ServerWidget.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVERWIDGET_H 2 | #define SERVERWIDGET_H 3 | 4 | #include 5 | #include 6 | 7 | #include "ServerOperate.h" 8 | 9 | QT_BEGIN_NAMESPACE 10 | namespace Ui { class ServerWidget; } 11 | QT_END_NAMESPACE 12 | 13 | //服务端界面--服务端作为接收 14 | class ServerWidget : public QWidget 15 | { 16 | Q_OBJECT 17 | 18 | public: 19 | ServerWidget(QWidget *parent = nullptr); 20 | ~ServerWidget(); 21 | 22 | signals: 23 | //使用信号槽操作线程中的ServerOperate 24 | void listen(const QString &address,quint16 port); 25 | void dislisten(); 26 | 27 | private: 28 | Ui::ServerWidget *ui; 29 | 30 | //线程 31 | QThread *thread; 32 | //server处理放到线程中 33 | ServerOperate *operate; 34 | }; 35 | #endif // SERVERWIDGET_H 36 | -------------------------------------------------------------------------------- /MultithreadTcpServer/MultithreadTcpServer.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets network 2 | 3 | CONFIG += c++11 utf8_source 4 | 5 | # You can make your code fail to compile if it uses deprecated APIs. 6 | # In order to do so, uncomment the following line. 7 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 8 | 9 | SOURCES += \ 10 | MyTcpServer.cpp \ 11 | ServerOperate.cpp \ 12 | main.cpp \ 13 | widget.cpp 14 | 15 | HEADERS += \ 16 | MyTcpServer.h \ 17 | ServerOperate.h \ 18 | widget.h 19 | 20 | FORMS += \ 21 | widget.ui 22 | 23 | # Default rules for deployment. 24 | qnx: target.path = /tmp/$${TARGET}/bin 25 | else: unix:!android: target.path = /opt/$${TARGET}/bin 26 | !isEmpty(target.path): INSTALLS += target 27 | -------------------------------------------------------------------------------- /TcpNetworkControl/MyTcpServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "MyTcpSocket.h" 6 | 7 | /** 8 | * @brief TcpServer 封装 9 | * @author 龚建波 10 | * @date 2023-11-01 11 | */ 12 | class MyTcpServer : public QTcpServer 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit MyTcpServer(QObject *parent = nullptr); 17 | ~MyTcpServer(); 18 | 19 | // 客户端发来的命令,会在线程中回调该接口 20 | virtual void onRecvFrame(MyTcpSocket *socket, const ProtocolFrame &frame); 21 | 22 | protected: 23 | // 有新的连接 24 | void incomingConnection(qintptr descriptor) override; 25 | 26 | signals: 27 | // 发送给所有客户端 28 | void syncAll(const ProtocolFrame &frame); 29 | 30 | protected: 31 | // 保存连进来的socket列表 32 | QHash socketTable; 33 | }; 34 | -------------------------------------------------------------------------------- /TcpNetworkControl/ControlClient.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "MyTcpSocket.h" 3 | 4 | /** 5 | * @brief 设备控制客户端 6 | * @author 龚建波 7 | * @date 2023-11-01 8 | */ 9 | class ControlClient : public MyTcpSocket 10 | { 11 | Q_OBJECT 12 | public: 13 | explicit ControlClient(); 14 | ~ControlClient(); 15 | 16 | // 获取代理端信息 17 | void getPolicyInfo(); 18 | // 获取设备信息 19 | void getDeviceInfo(const QString &deviceId); 20 | // 设置拍图模式,0-AED模式,1=软触发 21 | void setTriggerMode(const QString &deviceId, unsigned char triggerMode); 22 | // 执行软触发 23 | void softTrigger(const QString &deviceId); 24 | // 设置积分时间,单位毫秒 25 | void setIntegTime(const QString &deviceId, unsigned short integTime); 26 | 27 | // 请求图像 28 | void requestImage(const QString &deviceId, const QString &imageId); 29 | }; 30 | -------------------------------------------------------------------------------- /TcpNetworkControl/TcpNetworkControl.pro: -------------------------------------------------------------------------------- 1 | QT += core 2 | QT += gui 3 | QT += widgets 4 | QT += network 5 | QT += concurrent 6 | 7 | CONFIG += c++17 8 | CONFIG += utf8_source 9 | 10 | DEFINES += QT_DEPRECATED_WARNINGS 11 | DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 12 | 13 | SOURCES += \ 14 | ControlClient.cpp \ 15 | ControlServer.cpp \ 16 | MyTcpServer.cpp \ 17 | MyTcpSocket.cpp \ 18 | ProtocolParser.cpp \ 19 | main.cpp \ 20 | MainWindow.cpp 21 | 22 | HEADERS += \ 23 | ControlClient.h \ 24 | ControlServer.h \ 25 | MainWindow.h \ 26 | MyTcpServer.h \ 27 | MyTcpSocket.h \ 28 | ProtocolParser.h 29 | 30 | FORMS += \ 31 | MainWindow.ui 32 | 33 | # Default rules for deployment. 34 | qnx: target.path = /tmp/$${TARGET}/bin 35 | else: unix:!android: target.path = /opt/$${TARGET}/bin 36 | !isEmpty(target.path): INSTALLS += target 37 | 38 | DISTFILES += \ 39 | Readme.txt 40 | -------------------------------------------------------------------------------- /MultithreadTcpServer/ServerOperate.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVEROPERATE_H 2 | #define SERVEROPERATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include "MyTcpServer.h" 8 | 9 | //server管理,一个连接一个线程 10 | //(只是演示基本操作,拿到socket handle怎么处理都行) 11 | class ServerOperate : public QObject 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit ServerOperate(QObject *parent = nullptr); 16 | ~ServerOperate(); 17 | 18 | private: 19 | void initServer(); 20 | 21 | signals: 22 | //暂时用端口号来遍历查找对应的连接 23 | void clientConnected(quint16 port,const QString &id); 24 | void clientDisconnected(quint16 port); 25 | void clientMessage(quint16 port,const QString &msg); 26 | 27 | public slots: 28 | void closeConnect(quint16 port); 29 | 30 | private: 31 | //自定义的server类 32 | MyTcpServer *server; 33 | //线程列表 34 | QMap threadList; 35 | }; 36 | 37 | #endif // SERVEROPERATE_H 38 | -------------------------------------------------------------------------------- /MultithreadTcpServer/MyTcpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "MyTcpServer.h" 2 | 3 | MyTcpServer::MyTcpServer(QObject *parent) 4 | : QTcpServer(parent) 5 | { 6 | 7 | } 8 | 9 | void MyTcpServer::incomingConnection(qintptr handle) 10 | { 11 | //参照默认实现,可以把这个handle转发出去 12 | emit newConnectionHandle(handle); 13 | 14 | //目前我想到的是, 15 | //要么把handle或者tcpsocket转发出去, 16 | //要么在server类里直接管理这些thread和socket 17 | //多线程可以是一个socket一个线程,也可以放线程池轮询, 18 | //这个demo我打算就直接来一个连接就创建一个线程 19 | } 20 | 21 | //QTcpServer默认实现 22 | //void QTcpServer::incomingConnection(qintptr socketDescriptor) 23 | //{ 24 | //#if defined (QTCPSERVER_DEBUG) 25 | // qDebug("QTcpServer::incomingConnection(%i)", socketDescriptor); 26 | //#endif 27 | // QTcpSocket *socket = new QTcpSocket(this); 28 | // socket->setSocketDescriptor(socketDescriptor); 29 | // addPendingConnection(socket); 30 | //} 31 | // 32 | //void QTcpServer::addPendingConnection(QTcpSocket* socket) 33 | //{ 34 | // d_func()->pendingConnections.append(socket); 35 | //} 36 | -------------------------------------------------------------------------------- /SimpleTcpClient/SimpleTcpClient.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets network 2 | 3 | CONFIG += c++11 utf8_source 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Please consult the documentation of the 8 | # deprecated API in order to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | main.cpp \ 18 | widget.cpp 19 | 20 | HEADERS += \ 21 | widget.h 22 | 23 | FORMS += \ 24 | widget.ui 25 | 26 | # Default rules for deployment. 27 | qnx: target.path = /tmp/$${TARGET}/bin 28 | else: unix:!android: target.path = /opt/$${TARGET}/bin 29 | !isEmpty(target.path): INSTALLS += target 30 | -------------------------------------------------------------------------------- /SimpleTcpServer/SimpleTcpServer.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets network 2 | 3 | CONFIG += c++11 utf8_source 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Please consult the documentation of the 8 | # deprecated API in order to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | main.cpp \ 18 | widget.cpp 19 | 20 | HEADERS += \ 21 | widget.h 22 | 23 | FORMS += \ 24 | widget.ui 25 | 26 | # Default rules for deployment. 27 | qnx: target.path = /tmp/$${TARGET}/bin 28 | else: unix:!android: target.path = /opt/$${TARGET}/bin 29 | !isEmpty(target.path): INSTALLS += target 30 | -------------------------------------------------------------------------------- /SimpleUdpClient/SimpleUdpClient.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets network 2 | 3 | CONFIG += c++11 utf8_source 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Please consult the documentation of the 8 | # deprecated API in order to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | main.cpp \ 18 | widget.cpp 19 | 20 | HEADERS += \ 21 | widget.h 22 | 23 | FORMS += \ 24 | widget.ui 25 | 26 | # Default rules for deployment. 27 | qnx: target.path = /tmp/$${TARGET}/bin 28 | else: unix:!android: target.path = /opt/$${TARGET}/bin 29 | !isEmpty(target.path): INSTALLS += target 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 - 2023 龚建波 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /TcpNetworkControl/MainWindow.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "ControlClient.h" 5 | #include "ControlServer.h" 6 | 7 | QT_BEGIN_NAMESPACE 8 | namespace Ui { class MainWindow; } 9 | QT_END_NAMESPACE 10 | 11 | // 主界面 12 | class MainWindow : public QMainWindow 13 | { 14 | Q_OBJECT 15 | public: 16 | MainWindow(QWidget *parent = nullptr); 17 | ~MainWindow(); 18 | 19 | public slots: 20 | // 连接 21 | void doConnect(); 22 | // 断开 23 | void doDisconnect(); 24 | // 心跳 25 | void doKeepAlive(); 26 | // 代理端信息 27 | void doPolicyInfo(); 28 | // 设备信息 29 | void doDeviceInfo(); 30 | // 触发模式设置 31 | void doTriggerMode(); 32 | // 软触发 33 | void doSoftTrigger(); 34 | // 积分时间设置 35 | void doIntegTime(); 36 | 37 | // 模拟服务端通知有图像 38 | void newImage(); 39 | // 模拟客户端下载图像 40 | void loadImage(const QString &deviceId, const QString &imageId); 41 | 42 | private: 43 | Ui::MainWindow *ui; 44 | 45 | // 服务端 46 | ControlServer *ctrlServer{nullptr}; 47 | ControlServer *imageServer{nullptr}; 48 | // 设备控制 49 | ControlClient *ctrlClient{nullptr}; 50 | QThread *clientThread{nullptr}; 51 | }; 52 | -------------------------------------------------------------------------------- /TcpNetworkControl/ControlServer.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "MyTcpServer.h" 3 | 4 | /** 5 | * @brief 设备控制服务端 6 | * @author 龚建波 7 | * @date 2023-11-01 8 | * @details 9 | * ControlServer 通过信号槽或其他中介者和设备 IO 操作的对象交互 10 | */ 11 | class ControlServer : public MyTcpServer 12 | { 13 | Q_OBJECT 14 | public: 15 | explicit ControlServer(QObject *parent = nullptr); 16 | ~ControlServer(); 17 | 18 | // 客户端发来的命令,会在线程中回调该接口 19 | void onRecvFrame(MyTcpSocket *socket, const ProtocolFrame &frame) override; 20 | 21 | // 获取代理端信息 22 | ProtocolFrame getPolicyInfo(); 23 | // 获取设备信息 24 | ProtocolFrame getDeviceInfo(const QString &deviceId); 25 | // 同步设备状态 26 | void syncStatus(const QString &deviceId, int status, int error); 27 | // 同步图像加载完成状态 28 | void syncImage(const QString &deviceId, const QString &imageId); 29 | 30 | // 设置拍图模式,0-AED模式,1=软触发 31 | void setTriggerMode(const QString &deviceId, unsigned char triggerMode); 32 | // 执行软触发 33 | ProtocolFrame softTrigger(const QString &deviceId); 34 | // 设置积分时间,单位毫秒 35 | void setIntegTime(const QString &deviceId, unsigned short integTime); 36 | 37 | // 获取图片 38 | ProtocolFrame getImage(const QString &deviceId, const QString &imageId); 39 | }; 40 | -------------------------------------------------------------------------------- /TcpFileTransfer/TcpFileTransfer.pro: -------------------------------------------------------------------------------- 1 | QT += core gui widgets network 2 | 3 | CONFIG += c++11 utf8_source 4 | 5 | # The following define makes your compiler emit warnings if you use 6 | # any Qt feature that has been marked deprecated (the exact warnings 7 | # depend on your compiler). Please consult the documentation of the 8 | # deprecated API in order to know how to port your code away from it. 9 | DEFINES += QT_DEPRECATED_WARNINGS 10 | 11 | # You can also make your code fail to compile if it uses deprecated APIs. 12 | # In order to do so, uncomment the following line. 13 | # You can also select to disable deprecated APIs only up to a certain version of Qt. 14 | #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 15 | 16 | SOURCES += \ 17 | ClientOperate.cpp \ 18 | ClientWidget.cpp \ 19 | ServerOperate.cpp \ 20 | main.cpp \ 21 | ServerWidget.cpp 22 | 23 | HEADERS += \ 24 | ClientOperate.h \ 25 | ClientWidget.h \ 26 | ServerOperate.h \ 27 | ServerWidget.h 28 | 29 | FORMS += \ 30 | ClientWidget.ui \ 31 | ServerWidget.ui 32 | 33 | # Default rules for deployment. 34 | qnx: target.path = /tmp/$${TARGET}/bin 35 | else: unix:!android: target.path = /opt/$${TARGET}/bin 36 | !isEmpty(target.path): INSTALLS += target 37 | 38 | DISTFILES += \ 39 | Readme.txt 40 | -------------------------------------------------------------------------------- /TcpNetworkControl/MyTcpSocket.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include "ProtocolParser.h" 6 | 7 | /** 8 | * @brief TcpSocket 封装 9 | * @author 龚建波 10 | * @date 2023-11-01 11 | */ 12 | class MyTcpSocket : public QObject 13 | { 14 | Q_OBJECT 15 | public: 16 | explicit MyTcpSocket(qintptr descriptor = 0, QObject *parent = nullptr); 17 | ~MyTcpSocket(); 18 | 19 | // 初始化 20 | void init(); 21 | 22 | // 连接,客户端连接服务端使用 23 | void connectServer(const QString &ip, quint16 port, bool heart = true); 24 | // 断开 25 | void disconnectSocket(); 26 | // 判断连接状态 27 | bool isConnected(); 28 | 29 | // 发送心跳包 30 | void keepAlive(); 31 | // 等待发送完成 32 | bool waitWritten(); 33 | 34 | signals: 35 | // 连接状态 36 | void connectStateChanged(bool connected); 37 | // 收到命令 38 | void recvFrame(const ProtocolFrame &frame); 39 | 40 | public slots: 41 | // 发送命令 42 | void sendFrame(const ProtocolFrame &frame); 43 | // 接收网络数据 44 | void recvData(); 45 | 46 | protected: 47 | qintptr socketDescriptor{0}; 48 | // 网络 49 | QTcpSocket *socket{nullptr}; 50 | // 缓存数据流 51 | QByteArray cache; 52 | 53 | // 心跳定时器 54 | QTimer *keepTimer{nullptr}; 55 | // 心跳计数 56 | int keepCounter{0}; 57 | // 是否发心跳 58 | bool doKeep{true}; 59 | }; 60 | 61 | -------------------------------------------------------------------------------- /TcpNetworkControl/MyTcpServer.cpp: -------------------------------------------------------------------------------- 1 | #include "MyTcpServer.h" 2 | #include 3 | 4 | MyTcpServer::MyTcpServer(QObject *parent) 5 | : QTcpServer(parent) 6 | { 7 | qDebug() << "init server"; 8 | } 9 | 10 | MyTcpServer::~MyTcpServer() 11 | { 12 | QList thread_list = socketTable.keys(); 13 | socketTable.clear(); 14 | for (QThread *thread : qAsConst(thread_list)) { 15 | thread->quit(); 16 | thread->wait(); 17 | } 18 | qDeleteAll(thread_list); 19 | qDebug() << "free server"; 20 | } 21 | 22 | void MyTcpServer::onRecvFrame(MyTcpSocket *, const ProtocolFrame &) 23 | { 24 | } 25 | 26 | void MyTcpServer::incomingConnection(qintptr descriptor) 27 | { 28 | // 构造一个socket,然后move到线程中 29 | MyTcpSocket *socket = new MyTcpSocket(descriptor); 30 | 31 | QThread *thread = new QThread; 32 | socketTable.insert(thread, socket); 33 | socket->moveToThread(thread); 34 | 35 | // 线程退出时释放 36 | connect(thread, &QThread::finished, socket, &QTcpSocket::deleteLater); 37 | connect(this, &MyTcpServer::syncAll, socket, &MyTcpSocket::sendFrame); 38 | connect(socket, &MyTcpSocket::recvFrame, [=](const ProtocolFrame &frame){ 39 | onRecvFrame(socket, frame); 40 | }); 41 | connect(socket, &MyTcpSocket::connectStateChanged, this, [=](bool connected){ 42 | if (thread && !connected) { 43 | thread->quit(); 44 | thread->wait(); 45 | socketTable.remove(thread); 46 | thread->deleteLater(); 47 | } 48 | }); 49 | connect(thread, &QThread::started, socket, &MyTcpSocket::init); 50 | thread->start(); 51 | } 52 | -------------------------------------------------------------------------------- /TcpNetworkControl/ControlClient.cpp: -------------------------------------------------------------------------------- 1 | #include "ControlClient.h" 2 | #include 3 | #include 4 | #include 5 | 6 | ControlClient::ControlClient() 7 | { 8 | 9 | } 10 | 11 | ControlClient::~ControlClient() 12 | { 13 | 14 | } 15 | 16 | void ControlClient::getPolicyInfo() 17 | { 18 | ProtocolFrame frame; 19 | frame.command = CC_PolicyInfo; 20 | frame.error = CE_NoErr; 21 | sendFrame(frame); 22 | } 23 | 24 | void ControlClient::getDeviceInfo(const QString &deviceId) 25 | { 26 | ProtocolFrame frame; 27 | frame.command = CC_DeviceInfo; 28 | frame.error = CE_NoErr; 29 | frame.content = QJsonObject{{"deviceId", deviceId}}; 30 | sendFrame(frame); 31 | } 32 | 33 | void ControlClient::setTriggerMode(const QString &deviceId, unsigned char triggerMode) 34 | { 35 | ProtocolFrame frame; 36 | frame.command = CC_TriggerMode; 37 | frame.error = CE_NoErr; 38 | frame.content = QJsonObject{ 39 | {"deviceId", deviceId}, 40 | {"triggerMode", (int)triggerMode} 41 | }; 42 | sendFrame(frame); 43 | } 44 | 45 | void ControlClient::softTrigger(const QString &deviceId) 46 | { 47 | ProtocolFrame frame; 48 | frame.command = CC_SoftTrigger; 49 | frame.error = CE_NoErr; 50 | frame.content = QJsonObject{{"deviceId", deviceId}}; 51 | sendFrame(frame); 52 | } 53 | 54 | void ControlClient::setIntegTime(const QString &deviceId, unsigned short integTime) 55 | { 56 | ProtocolFrame frame; 57 | frame.command = CC_IntegTime; 58 | frame.error = CE_NoErr; 59 | frame.content = QJsonObject{ 60 | {"deviceId", deviceId}, 61 | {"integrationTime", (int)integTime} 62 | }; 63 | sendFrame(frame); 64 | } 65 | 66 | void ControlClient::requestImage(const QString &deviceId, const QString &imageId) 67 | { 68 | ProtocolFrame frame; 69 | frame.command = CC_LoadImage; 70 | frame.error = CE_NoErr; 71 | frame.content = QJsonObject{ 72 | {"deviceId", deviceId}, 73 | {"imageId", imageId} 74 | }; 75 | sendFrame(frame); 76 | } 77 | -------------------------------------------------------------------------------- /TcpFileTransfer/ServerWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "ServerWidget.h" 2 | #include "ui_ServerWidget.h" 3 | 4 | #include 5 | 6 | ServerWidget::ServerWidget(QWidget *parent) 7 | : QWidget(parent) 8 | , ui(new Ui::ServerWidget) 9 | { 10 | ui->setupUi(this); 11 | 12 | thread = new QThread(this); 13 | operate = new ServerOperate; 14 | operate->moveToThread(thread); 15 | //退出时释放 16 | connect(thread,&QThread::finished,operate,&ServerOperate::deleteLater); 17 | //点击了listen,服务端监听开关 18 | connect(ui->btnListen,&QPushButton::clicked,[this]{ 19 | if(operate->isListening()){ 20 | emit dislisten(); 21 | }else{ 22 | emit listen(ui->editAddress->text(),ui->editPort->text().toUShort()); 23 | } 24 | }); 25 | connect(this,&ServerWidget::listen,operate,&ServerOperate::listen); 26 | connect(this,&ServerWidget::dislisten,operate,&ServerOperate::dislisten); 27 | //服务器监听状态改变 28 | connect(operate,&ServerOperate::listenStateChanged,this,[this](bool isListen){ 29 | ui->btnListen->setText(isListen?"Dislisten":"Listen"); 30 | ui->editAddress->setEnabled(!isListen); 31 | ui->editPort->setEnabled(!isListen); 32 | ui->editPath->setEnabled(!isListen); 33 | ui->btnSelect->setEnabled(!isListen); 34 | }); 35 | //选择文件路径 36 | connect(ui->btnSelect,&QPushButton::clicked,[=]{ 37 | const QString dir_path=QFileDialog::getExistingDirectory(this); 38 | ui->editPath->setText(dir_path); 39 | }); 40 | connect(ui->editPath,&QLineEdit::textChanged,operate,&ServerOperate::setFilePath); 41 | //取消文件传输 42 | connect(ui->btnCancel,&QPushButton::clicked,operate,&ServerOperate::cancelFileTransfer); 43 | //日志 44 | connect(operate,&ServerOperate::logMessage,this,[=](const QString &msg){ 45 | ui->textEdit->append(msg); 46 | }); 47 | //进度条 48 | connect(operate,&ServerOperate::progressChanged,ui->progressBar,&QProgressBar::setValue); 49 | 50 | //启动线程 51 | thread->start(); 52 | } 53 | 54 | ServerWidget::~ServerWidget() 55 | { 56 | thread->quit(); 57 | thread->wait(); 58 | delete ui; 59 | } 60 | 61 | -------------------------------------------------------------------------------- /TcpFileTransfer/ServerOperate.h: -------------------------------------------------------------------------------- 1 | #ifndef SERVEROPERATE_H 2 | #define SERVEROPERATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //服务端socket处理--服务端作为接收 13 | class ServerOperate : public QObject 14 | { 15 | Q_OBJECT 16 | public: 17 | explicit ServerOperate(QObject *parent = nullptr); 18 | ~ServerOperate(); 19 | 20 | //get/set 文件路径、监听状态等变量,使用了互斥锁 21 | QString getFilePath() const; 22 | void setFilePath(const QString &path); 23 | 24 | bool isListening() const; 25 | 26 | signals: 27 | //操作记录发送到ui显示 28 | void logMessage(const QString &msg); 29 | //服务端监听状态 30 | void listenStateChanged(bool isListen); 31 | //接收进度0-100 32 | void progressChanged(int value); 33 | 34 | public slots: 35 | //server监听 36 | void listen(const QString &address,quint16 port); 37 | //server取消监听 38 | void dislisten(); 39 | //取消文件传输 40 | void cancelFileTransfer(); 41 | 42 | private: 43 | //初始化 44 | void initOperate(); 45 | //把槽对应的实际操作分离出来是为了复用,这样便于组合 46 | void doDislisten(); 47 | void doCloseFile(); 48 | void doCancel(); 49 | bool readyReceiveFile(qint64 size,const QString &filename); 50 | void onReceiveFile(const char *data,qint64 size); 51 | void sendData(char type,const QByteArray &data); 52 | void operateReceiveData(const QByteArray &data); 53 | 54 | private: 55 | //用来锁文件路径、监听状态等变量 56 | mutable QMutex dataMutex; 57 | //文件存储路径 58 | QString filePath; 59 | //地址和端口 60 | QString address; 61 | quint16 port; 62 | //套接字,本demo只允许一个客户端连接 63 | QTcpServer *server = nullptr; 64 | QTcpSocket *socket = nullptr; 65 | //文件操作 66 | QFile *file = nullptr; 67 | //文件长度 68 | qint64 fileSize = 0; 69 | //已接收文件长度 70 | qint64 receiveSize = 0; 71 | //接收缓存,实际操作时还是用char*好点 72 | QByteArray dataTemp; 73 | 74 | //帧头+长度+类型 75 | char frameHead[7] = { 0x0F, (char)0xF0, 0x00, (char)0xFF, 0x00, 0x00, 0x00 }; 76 | //帧尾 77 | char frameTail[2] = { 0x0D, 0x0A }; 78 | }; 79 | 80 | #endif // SERVEROPERATE_H 81 | -------------------------------------------------------------------------------- /TcpFileTransfer/ClientWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "ClientWidget.h" 2 | #include "ui_ClientWidget.h" 3 | 4 | #include 5 | 6 | ClientWidget::ClientWidget(QWidget *parent) : 7 | QWidget(parent), 8 | ui(new Ui::ClientWidget) 9 | { 10 | ui->setupUi(this); 11 | 12 | thread = new QThread(this); 13 | operate = new ClientOperate; 14 | operate->moveToThread(thread); 15 | //退出时释放 16 | connect(thread,&QThread::finished,operate,&ClientOperate::deleteLater); 17 | //点击了connect 18 | connect(ui->btnConnect,&QPushButton::clicked,[this]{ 19 | if(operate->isConnected()){ 20 | emit disconnectTcp(); 21 | }else{ 22 | emit connectTcp(ui->editAddress->text(),ui->editPort->text().toUShort()); 23 | } 24 | }); 25 | connect(this,&ClientWidget::connectTcp,operate,&ClientOperate::connectTcp); 26 | connect(this,&ClientWidget::disconnectTcp,operate,&ClientOperate::disconnectTcp); 27 | //服务器监听状态改变 28 | connect(operate,&ClientOperate::connectStateChanged,this,[this](bool isConnected){ 29 | ui->btnConnect->setText(isConnected?"Disconnect":"Connect"); 30 | ui->editAddress->setEnabled(!isConnected); 31 | ui->editPort->setEnabled(!isConnected); 32 | }); 33 | //选择文件路径 34 | connect(ui->btnSelect,&QPushButton::clicked,[=]{ 35 | const QString dir_path=QFileDialog::getOpenFileName(this); 36 | ui->editPath->setText(dir_path); 37 | }); 38 | connect(ui->editPath,&QLineEdit::textChanged,operate,&ClientOperate::setFilePath); 39 | //文件传输 40 | connect(ui->btnSend,&QPushButton::clicked,operate,&ClientOperate::startFileTransfer); 41 | connect(ui->btnCancel,&QPushButton::clicked,operate,&ClientOperate::cancelFileTransfer); 42 | //日志 43 | connect(operate,&ClientOperate::logMessage,this,[=](const QString &msg){ 44 | ui->textEdit->append(msg); 45 | }); 46 | //进度条 47 | connect(operate,&ClientOperate::progressChanged,ui->progressBar,&QProgressBar::setValue); 48 | 49 | //启动线程 50 | thread->start(); 51 | } 52 | 53 | ClientWidget::~ClientWidget() 54 | { 55 | thread->quit(); 56 | thread->wait(); 57 | delete ui; 58 | } 59 | -------------------------------------------------------------------------------- /TcpFileTransfer/ClientOperate.h: -------------------------------------------------------------------------------- 1 | #ifndef CLIENTOPERATE_H 2 | #define CLIENTOPERATE_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | //客户端socket处理--客户端作为发送 13 | //帧没有数据校验字段 14 | //没有流量控制 15 | //没有错误数据段重发机制 16 | //没有确认返回超时机制 17 | //没有流程状态记录,如果当前正在发送却收到开始发送的错误命令,就乱套了 18 | class ClientOperate : public QObject 19 | { 20 | Q_OBJECT 21 | public: 22 | explicit ClientOperate(QObject *parent = nullptr); 23 | ~ClientOperate(); 24 | 25 | //get/set 文件路径、连接状态等变量,使用了互斥锁 26 | QString getFilePath() const; 27 | void setFilePath(const QString &path); 28 | 29 | bool isConnected() const; 30 | void setConnected(bool connected); 31 | 32 | signals: 33 | //操作记录发送到ui显示 34 | void logMessage(const QString &msg); 35 | //连接状态改变 36 | void connectStateChanged(bool isConnect); 37 | //接收进度0-100 38 | void progressChanged(int value); 39 | 40 | public slots: 41 | //连接 42 | void connectTcp(const QString &address,quint16 port); 43 | //断开连接 44 | void disconnectTcp(); 45 | //传输文件 46 | void startFileTransfer(); 47 | //取消文件传输 48 | void cancelFileTransfer(); 49 | 50 | private: 51 | //初始化 52 | void initOperate(); 53 | //把槽对应的实际操作分离出来是为了复用,这样便于组合 54 | void doDisconnect(); 55 | void doCloseFile(); 56 | void doCancel(); 57 | void sendData(char type,const QByteArray &data); 58 | void sendFile(const char *data,int size); 59 | void operateReceiveData(const QByteArray &data); 60 | 61 | private: 62 | //用来锁文件路径、连接状态等 63 | mutable QMutex dataMutex; 64 | //文件存储路径 65 | QString filePath; 66 | //地址和端口 67 | QString address; 68 | quint16 port; 69 | //连接状态 70 | bool connectState = false; 71 | //套接字 72 | QTcpSocket *socket = nullptr; 73 | //文件操作 74 | QFile *file = nullptr; 75 | //发送数据的定时器 76 | QTimer *timer = nullptr; 77 | //发送的字节数,因为Qt接口是int64,本来想用无符号类型 78 | qint64 sendSize=0; 79 | //文件大小 80 | qint64 fileSize=0; 81 | //接收数据的缓存,实际操作时还是用char*好点 82 | QByteArray dataTemp; 83 | 84 | //读取文件到缓冲区 85 | char fileBuffer[4096]={0}; 86 | //帧头+长度+类型 87 | char frameHead[7] = { 0x0F, (char)0xF0, 0x00, (char)0xFF, 0x00, 0x00, 0x00 }; 88 | //帧尾 89 | char frameTail[2] = { 0x0D, 0x0A }; 90 | }; 91 | 92 | #endif // CLIENTOPERATE_H 93 | -------------------------------------------------------------------------------- /TcpNetworkControl/ProtocolParser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | // 命令类型 10 | enum ControlCommandType { 11 | // 心跳保活 12 | CC_KeepAlive = 0, 13 | // 代理端信息 14 | CC_PolicyInfo = 1, 15 | // 设备信息 16 | CC_DeviceInfo = 2, 17 | // 设备状态同步 18 | CC_StatusSync = 3, 19 | 20 | // 触发模式 21 | CC_TriggerMode = 100, 22 | // 软触发 23 | CC_SoftTrigger = 101, 24 | // 积分时间 25 | CC_IntegTime = 102, 26 | 27 | // 加载图片 28 | CC_LoadImage = 200 29 | }; 30 | 31 | // 设备状态 32 | enum ControlStatusCode 33 | { 34 | // 有设备连接 35 | CS_Connect = 0, 36 | // 设备断开 37 | CS_Disonnect = 1, 38 | // 等待拍图 39 | CS_WaitTrigger = 2, 40 | // 正在从设备Load图像 41 | CS_TransImage = 3, 42 | // 图片Load完成等待发送给客户端 43 | CS_TransFinish = 4, 44 | // 设备异常 45 | CS_DeviceError = 5 46 | }; 47 | 48 | // 错误码 49 | enum ControlErrorCode 50 | { 51 | // 无错误 52 | CE_NoErr = 0, 53 | // 无效的命令码 54 | CE_CommandErr = 0, 55 | // 无效的设备Id 56 | CE_DeviceIdErr = 0, 57 | // 无效的图像Id 58 | CE_ImageIdErr = 0, 59 | // 其他错误 60 | CE_OtherErr 61 | }; 62 | 63 | // 帧信息 64 | struct ProtocolFrame 65 | { 66 | // 命令类型 CP_CommandType 67 | int command; 68 | // 错误码 CP_ErrorCode 69 | int error; 70 | // 控制消息 71 | QJsonObject content; 72 | // 数据内容 73 | QByteArray data; 74 | }; 75 | 76 | /** 77 | * @brief 协议解析 78 | * @author 龚建波 79 | * @date 2023-10-31 80 | */ 81 | class ProtocolParser 82 | { 83 | public: 84 | ProtocolParser(); 85 | ~ProtocolParser(); 86 | 87 | /** 88 | * @brief 帧内容根据协议装箱为字节数据 89 | * @param frame 帧内容 90 | * @param ok 可传入 bool 接收返回 91 | * @return 返回按协议生成的数据 92 | */ 93 | static QByteArray pack(const ProtocolFrame &frame, bool *ok); 94 | 95 | /** 96 | * @brief 字节数据根据协议拆箱为数据结构 97 | * @param data 帧字节数据 98 | * @param ok 可传入 bool 接收返回 99 | * @return 返回按协议生成的数据 100 | */ 101 | static ProtocolFrame unpack(const QByteArray &data, bool *ok); 102 | 103 | // uin32 转小端字节数组 104 | static QByteArray uint32ToBytes(unsigned int num); 105 | // 小端字节数组转 uint32 106 | static unsigned int uint32FromBytes(QByteArray bytes); 107 | 108 | public: 109 | static constexpr char FRAME_HEAD[4] = { (char)0xFF, (char)0xFF, 0x00, 0x00 }; 110 | static constexpr char FRAME_TAIL[4] = { 0x00, 0x00, (char)0xFF, (char)0xFF }; 111 | static constexpr char FRAME_EMPTY[4] = { 0x00, 0x00, 0x00, 0x00 }; 112 | }; 113 | 114 | -------------------------------------------------------------------------------- /MultithreadTcpServer/ServerOperate.cpp: -------------------------------------------------------------------------------- 1 | #include "ServerOperate.h" 2 | 3 | #include 4 | #include 5 | 6 | ServerOperate::ServerOperate(QObject *parent) 7 | : QObject(parent) 8 | { 9 | initServer(); 10 | } 11 | 12 | ServerOperate::~ServerOperate() 13 | { 14 | const QList thread_list=threadList.keys(); 15 | qDebug()<<"thread count"<quit(); 19 | thread->wait(); 20 | } 21 | qDeleteAll(thread_list); 22 | } 23 | 24 | void ServerOperate::initServer() 25 | { 26 | //只是做演示,没有对流程进行封装 27 | server=new MyTcpServer(this); 28 | //我重载了server的incomingConnection接口,将socket handle传了出来 29 | connect(server,&MyTcpServer::newConnectionHandle, 30 | this,[=](qintptr handle){ 31 | //构造一个qtcpsocket,然后move到线程中收发数据 32 | //(可以封装一个类,我这里简化操作) 33 | QTcpSocket *socket=new QTcpSocket; 34 | socket->setSocketDescriptor(handle); 35 | const quint16 peer_port=socket->peerPort(); 36 | //qDebug()<<"new connect"<moveToThread(thread); 40 | 41 | //线程退出时释放 42 | connect(thread,&QThread::finished,socket,&QTcpSocket::deleteLater); 43 | //2021-6-28 修改 44 | //之前这里接收者填了this,导致readAll在this线程执行,没有达到多线程处理的效果 45 | //现去掉接收者this,默认为发送者socket所在线程处理 46 | connect(socket,&QTcpSocket::readyRead,[=]{ 47 | while(socket->bytesAvailable()>0){ 48 | //没有考虑编码,也没考虑数据帧完整性 49 | const QString msg=socket->readAll()+QString::asprintf("%p",QThread::currentThreadId()); 50 | emit this->clientMessage(peer_port,msg); 51 | //qDebug()<<"socket read"<quit(); 60 | thread->wait(); 61 | threadList.remove(thread); 62 | thread->deleteLater(); 63 | } 64 | }); 65 | connect(thread,&QThread::started,[=]{ 66 | //started信号在QThread::run之前触发的,处理同一个函数中,所以线程也都是在子线程 67 | //没有填接收者对象,槽函数也是在发送者同一线程执行,获取到的线程id也就是发送者线程的id 68 | 69 | //通知ui有新的连接 70 | emit clientConnected(peer_port,QString::asprintf("%p",QThread::currentThreadId())); 71 | }); 72 | thread->start(); 73 | }); 74 | server->listen(QHostAddress("127.0.0.1"),23456); 75 | } 76 | 77 | void ServerOperate::closeConnect(quint16 port) 78 | { 79 | QThread *thread=threadList.key(port,nullptr); 80 | if(thread){ 81 | thread->quit(); 82 | thread->wait(); 83 | threadList.remove(thread); 84 | thread->deleteLater(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /TcpNetworkControl/ProtocolParser.cpp: -------------------------------------------------------------------------------- 1 | #include "ProtocolParser.h" 2 | #include 3 | 4 | ProtocolParser::ProtocolParser() 5 | { 6 | 7 | } 8 | 9 | ProtocolParser::~ProtocolParser() 10 | { 11 | 12 | } 13 | 14 | QByteArray ProtocolParser::pack(const ProtocolFrame &frame, bool *ok) 15 | { 16 | if (ok) { 17 | *ok = false; 18 | } 19 | QByteArray data; 20 | // Control 信息 21 | QJsonObject control_json; 22 | control_json.insert("command", frame.command); 23 | control_json.insert("content", frame.content); 24 | control_json.insert("error", frame.error); 25 | QByteArray control_data = QJsonDocument(control_json).toJson(QJsonDocument::Compact); 26 | QByteArray control_len = uint32ToBytes(control_data.size()); 27 | data.append(control_len); 28 | data.append(control_data); 29 | // Data 信息 30 | QByteArray data_len = uint32ToBytes(frame.data.size()); 31 | data.append(data_len); 32 | if (frame.data.size() > 0) { 33 | data.append(frame.data); 34 | } 35 | if (ok) { 36 | *ok = true; 37 | } 38 | return data; 39 | } 40 | 41 | ProtocolFrame ProtocolParser::unpack(const QByteArray &data, bool *ok) 42 | { 43 | if (ok) { 44 | *ok = false; 45 | } 46 | ProtocolFrame frame; 47 | unsigned int offset = 0; 48 | // Control 长度 + Data 长度 = 8 字节 49 | if (data.size() - offset < 8) { 50 | return frame; 51 | } 52 | unsigned int control_len = uint32FromBytes(data.mid(offset, 4)); 53 | offset += 4; 54 | // Control 数据 + Data 长度 55 | if (data.size() - offset < control_len + 4) { 56 | return frame; 57 | } 58 | QByteArray control_data = data.mid(offset, control_len); 59 | offset += control_len; 60 | QJsonDocument control_doc = QJsonDocument::fromJson(control_data); 61 | if (!control_doc.isObject()) { 62 | return frame; 63 | } 64 | QJsonObject control_json = control_doc.object(); 65 | if (!control_json.contains("command")) { 66 | return frame; 67 | } 68 | frame.command = control_json.value("command").toInt(); 69 | frame.content = control_json.value("content").toObject(); 70 | frame.error = control_json.value("eror").toInt(); 71 | unsigned int data_len = uint32FromBytes(data.mid(offset, 4)); 72 | offset += 4; 73 | // Data 数据 74 | if (data.size() - offset < data_len) { 75 | return frame; 76 | } 77 | frame.data = data.mid(offset, data_len); 78 | offset += data_len; 79 | if (offset != data.size()) { 80 | return frame; 81 | } 82 | if (ok) { 83 | *ok = true; 84 | } 85 | return frame; 86 | } 87 | 88 | QByteArray ProtocolParser::uint32ToBytes(unsigned int num) 89 | { 90 | QByteArray bytes(4, 0x00); 91 | qToLittleEndian(num, bytes.data()); 92 | return bytes; 93 | } 94 | 95 | unsigned int ProtocolParser::uint32FromBytes(QByteArray bytes) 96 | { 97 | if (bytes.size() != sizeof(unsigned int)) 98 | return 0; 99 | unsigned int num; 100 | qFromLittleEndian(bytes.data(), 1, &num); 101 | return num; 102 | } 103 | -------------------------------------------------------------------------------- /TcpFileTransfer/ServerWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ServerWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 400 11 | 12 | 13 | 14 | Server && Receiver 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 地址 23 | 24 | 25 | 26 | 27 | 28 | 29 | 127.0.0.1 30 | 31 | 32 | 33 | 34 | 35 | 36 | 端口 37 | 38 | 39 | 40 | 41 | 42 | 43 | 1992 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | 54 | 40 55 | 20 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Listen 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 保存路径 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Select 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 传输进度 96 | 97 | 98 | 99 | 100 | 101 | 102 | 0 103 | 104 | 105 | Qt::AlignCenter 106 | 107 | 108 | 109 | 110 | 111 | 112 | Cancel 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /MultithreadTcpServer/widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 994 10 | 459 11 | 12 | 13 | 14 | Multithread Tcp Server 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 客户端列表 23 | 24 | 25 | 26 | 27 | 28 | 29 | QAbstractItemView::NoEditTriggers 30 | 31 | 32 | QAbstractItemView::NoSelection 33 | 34 | 35 | true 36 | 37 | 38 | 80 39 | 40 | 41 | true 42 | 43 | 44 | 45 | 端口 46 | 47 | 48 | 49 | 50 | 操作 51 | 52 | 53 | 54 | 55 | 发送 56 | 57 | 58 | 59 | 60 | 文本框 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 服务端面板 73 | 74 | 75 | 76 | 77 | 78 | 79 | QAbstractItemView::NoEditTriggers 80 | 81 | 82 | QAbstractItemView::NoSelection 83 | 84 | 85 | true 86 | 87 | 88 | 80 89 | 90 | 91 | true 92 | 93 | 94 | 95 | 端口 96 | 97 | 98 | 99 | 100 | 线程 101 | 102 | 103 | 104 | 105 | 操作 106 | 107 | 108 | 109 | 110 | 接收文本 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /TcpFileTransfer/ClientWidget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ClientWidget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 450 10 | 400 11 | 12 | 13 | 14 | Client && Sender 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 地址 23 | 24 | 25 | 26 | 27 | 28 | 29 | 127.0.0.1 30 | 31 | 32 | 33 | 34 | 35 | 36 | 端口 37 | 38 | 39 | 40 | 41 | 42 | 43 | 1992 44 | 45 | 46 | 47 | 48 | 49 | 50 | Qt::Horizontal 51 | 52 | 53 | 54 | 40 55 | 20 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | Connect 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 选择文件 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | Select 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 传输进度 96 | 97 | 98 | 99 | 100 | 101 | 102 | 0 103 | 104 | 105 | Qt::AlignCenter 106 | 107 | 108 | 109 | 110 | 111 | 112 | Cancel 113 | 114 | 115 | 116 | 117 | 118 | 119 | Send 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /SimpleTcpClient/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | 4 | #include 5 | 6 | Widget::Widget(QWidget *parent) 7 | : QWidget(parent) 8 | , ui(new Ui::Widget) 9 | { 10 | ui->setupUi(this); 11 | setWindowTitle("Client"); 12 | 13 | initClient(); 14 | } 15 | 16 | Widget::~Widget() 17 | { 18 | //析构关闭连接 19 | //client->disconnectFromHost(); 20 | //if(client->state()!=QAbstractSocket::UnconnectedState){ 21 | // client->waitForDisconnected(); 22 | //} 23 | //关闭套接字的I/O设备,并调用disconnectFromHost()关闭套接字的连接。 24 | //client->close(); 25 | //中止当前连接并重置套接字。与disconnectFromHost()不同, 26 | //此函数立即关闭套接字,并丢弃写缓冲区中的所有待处理数据。 27 | client->abort(); 28 | delete ui; 29 | } 30 | 31 | void Widget::initClient() 32 | { 33 | //创建client对象 34 | client = new QTcpSocket(this); 35 | 36 | //点击连接,根据ui设置的服务器地址进行连接 37 | connect(ui->btnConnect,&QPushButton::clicked,[this]{ 38 | //判断当前是否已连接,连接了就断开 39 | if(client->state()==QAbstractSocket::ConnectedState){ 40 | //如果使用disconnectFromHost()不会重置套接字,isValid还是会为true 41 | client->abort(); 42 | }else if(client->state()==QAbstractSocket::UnconnectedState){ 43 | //从界面上读取ip和端口 44 | const QHostAddress address=QHostAddress(ui->editAddress->text()); 45 | const unsigned short port=ui->editPort->text().toUShort(); 46 | //连接服务器 47 | client->connectToHost(address,port); 48 | }else{ 49 | ui->textRecv->append("It is not ConnectedState or UnconnectedState"); 50 | } 51 | }); 52 | 53 | //连接状态 54 | connect(client,&QTcpSocket::connected,[this]{ 55 | //已连接就设置为不可编辑 56 | ui->btnConnect->setText("Disconnect"); 57 | ui->editAddress->setEnabled(false); 58 | ui->editPort->setEnabled(false); 59 | updateState(); 60 | }); 61 | connect(client,&QTcpSocket::disconnected,[this]{ 62 | //断开连接还原状态 63 | ui->btnConnect->setText("Connect"); 64 | ui->editAddress->setEnabled(true); 65 | ui->editPort->setEnabled(true); 66 | updateState(); 67 | }); 68 | 69 | //发送数据 70 | connect(ui->btnSend,&QPushButton::clicked,[this]{ 71 | //判断是可操作,isValid表示准备好读写 72 | if(!client->isValid()) 73 | return; 74 | //将发送区文本发送给客户端 75 | const QByteArray send_data=ui->textSend->toPlainText().toUtf8(); 76 | //数据为空就返回 77 | if(send_data.isEmpty()) 78 | return; 79 | client->write(send_data); 80 | //client->waitForBytesWritten(); 81 | }); 82 | 83 | //收到数据,触发readyRead 84 | connect(client,&QTcpSocket::readyRead,[this]{ 85 | //没有可读的数据就返回 86 | if(client->bytesAvailable()<=0) 87 | return; 88 | //注意收发两端文本要使用对应的编解码 89 | const QString recv_text=QString::fromUtf8(client->readAll()); 90 | ui->textRecv->append(QString("[%1:%2]") 91 | .arg(client->peerAddress().toString()) 92 | .arg(client->peerPort())); 93 | ui->textRecv->append(recv_text); 94 | }); 95 | 96 | //error信号在5.15换了名字 97 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 98 | //错误信息 99 | connect(client, static_cast(&QAbstractSocket::error), 100 | [this](QAbstractSocket::SocketError){ 101 | ui->textRecv->append("Socket Error:"+client->errorString()); 102 | }); 103 | #else 104 | //错误信息 105 | connect(client,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){ 106 | ui->textRecv->append("Socket Error:"+client->errorString()); 107 | }); 108 | #endif 109 | } 110 | 111 | void Widget::updateState() 112 | { 113 | //将当前client地址和端口写在标题栏 114 | if(client->state()==QAbstractSocket::ConnectedState){ 115 | setWindowTitle(QString("Client[%1:%2]") 116 | .arg(client->localAddress().toString()) 117 | .arg(client->localPort())); 118 | }else{ 119 | setWindowTitle("Client"); 120 | } 121 | } 122 | 123 | -------------------------------------------------------------------------------- /SimpleTcpServer/widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 399 10 | 378 11 | 12 | 13 | 14 | Widget 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Address: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 127.0.0.1 30 | 31 | 32 | 33 | 34 | 35 | 36 | Port: 37 | 38 | 39 | 40 | 41 | 42 | 43 | 1992 44 | 45 | 46 | 47 | 48 | 49 | 50 | Listen 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Recv: 60 | 61 | 62 | 63 | 64 | 65 | 66 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 67 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 68 | p, li { white-space: pre-wrap; } 69 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 70 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Tcp Server</p></body></html> 71 | 72 | 73 | 74 | 75 | 76 | 77 | Send: 78 | 79 | 80 | 81 | 82 | 83 | 84 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 85 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 86 | p, li { white-space: pre-wrap; } 87 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 88 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">GongJianBo 1992</p></body></html> 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Qt::Horizontal 98 | 99 | 100 | 101 | 40 102 | 20 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | Send 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /MultithreadTcpServer/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | 4 | #include 5 | #include 6 | 7 | Widget::Widget(QWidget *parent) 8 | : QWidget(parent) 9 | , ui(new Ui::Widget) 10 | { 11 | ui->setupUi(this); 12 | 13 | initClient(); 14 | initServer(); 15 | } 16 | 17 | Widget::~Widget() 18 | { 19 | delete ui; 20 | for(QTcpSocket *socket:qAsConst(clientList)) 21 | socket->abort(); 22 | qDeleteAll(clientList); 23 | } 24 | 25 | void Widget::initClient() 26 | { 27 | //表格里随便弄几个客户端socket列表 28 | //每个客户端可以给server发文本数据 29 | const int row_count=10; 30 | ui->clientTable->setRowCount(row_count); 31 | for(int row=0;rowclientTable->setCellWidget(row,0,lab); 39 | //col操作 40 | QPushButton *btn_connect=new QPushButton("connect"); 41 | connect(btn_connect,&QPushButton::clicked,this,[=]{ 42 | if(socket->state()==QAbstractSocket::ConnectedState){ 43 | socket->abort(); 44 | }else{ 45 | socket->connectToHost(QHostAddress("127.0.0.1"),23456); 46 | } 47 | }); 48 | connect(socket,&QTcpSocket::connected,this,[=]{ 49 | lab->setText(QString::number(socket->localPort())); 50 | btn_connect->setText("disconnect"); 51 | }); 52 | connect(socket,&QTcpSocket::disconnected,this,[=]{ 53 | lab->clear(); 54 | btn_connect->setText("connect"); 55 | }); 56 | ui->clientTable->setCellWidget(row,1,btn_connect); 57 | //col发送 58 | QPushButton *btn_send=new QPushButton("send"); 59 | ui->clientTable->setCellWidget(row,2,btn_send); 60 | //col文本框 61 | QLineEdit *edit=new QLineEdit; 62 | edit->setText(QString("hello %1").arg(row)); 63 | ui->clientTable->setCellWidget(row,3,edit); 64 | connect(btn_send,&QPushButton::clicked,this,[=]{ 65 | if(socket->isValid()) 66 | socket->write(edit->text().toLatin1()); 67 | }); 68 | } 69 | } 70 | 71 | void Widget::initServer() 72 | { 73 | server=new ServerOperate(this); 74 | //新的连接 75 | connect(server,&ServerOperate::clientConnected, 76 | this,[=](quint16 port,const QString &id){ 77 | const int row_count=ui->serverTable->rowCount(); 78 | QTableWidget *table=ui->serverTable; 79 | table->insertRow(row_count); 80 | table->setCellWidget(row_count,0,new QLabel(QString::number(port))); 81 | table->setCellWidget(row_count,1,new QLabel(id)); 82 | QPushButton *btn=new QPushButton("close"); 83 | connect(btn,&QPushButton::clicked,this,[=]{ 84 | server->closeConnect(port); 85 | }); 86 | table->setCellWidget(row_count,2,btn); 87 | table->setCellWidget(row_count,3,new QLabel); 88 | }); 89 | //连接断开 90 | //暂时用端口号来遍历查找对应的连接 91 | connect(server,&ServerOperate::clientDisconnected, 92 | this,[=](quint16 port){ 93 | const int row_count=ui->serverTable->rowCount(); 94 | const QString port_str=QString::number(port); 95 | for(int row=0;row(ui->serverTable->cellWidget(row,0)); 98 | if(labport&&labport->text()==port_str) 99 | { 100 | ui->serverTable->removeRow(row); 101 | return; 102 | } 103 | } 104 | }); 105 | //客户端发送过来的消息 106 | //暂时用端口号来遍历查找对应的连接 107 | connect(server,&ServerOperate::clientMessage, 108 | this,[=](quint16 port,const QString &msg){ 109 | const int row_count=ui->serverTable->rowCount(); 110 | const QString port_str=QString::number(port); 111 | for(int row=0;row(ui->serverTable->cellWidget(row,0)); 114 | if(labport&&labport->text()==port_str) 115 | { 116 | QLabel *labmsg=qobject_cast(ui->serverTable->cellWidget(row,3)); 117 | if(labmsg){ 118 | labmsg->setText(msg); 119 | } 120 | return; 121 | } 122 | } 123 | }); 124 | } 125 | -------------------------------------------------------------------------------- /SimpleTcpClient/widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 399 10 | 378 11 | 12 | 13 | 14 | Widget 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Address: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 127.0.0.1 30 | 31 | 32 | 33 | 34 | 35 | 36 | Port: 37 | 38 | 39 | 40 | 41 | 42 | 43 | 1992 44 | 45 | 46 | 47 | 48 | 49 | 50 | Connet 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | Recv: 60 | 61 | 62 | 63 | 64 | 65 | 66 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 67 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 68 | p, li { white-space: pre-wrap; } 69 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 70 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Tcp Client</p></body></html> 71 | 72 | 73 | 74 | 75 | 76 | 77 | Send: 78 | 79 | 80 | 81 | 82 | 83 | 84 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 85 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 86 | p, li { white-space: pre-wrap; } 87 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 88 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">龚建波 1992</p> 89 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">Tcp数据流需要对接收的数据做缓存处理,并根据自定义帧结构进行解析,这样才能处理所谓的粘包。</p></body></html> 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | Qt::Horizontal 99 | 100 | 101 | 102 | 40 103 | 20 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | Send 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /TcpNetworkControl/MyTcpSocket.cpp: -------------------------------------------------------------------------------- 1 | #include "MyTcpSocket.h" 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | static int number; 9 | 10 | MyTcpSocket::MyTcpSocket(qintptr descriptor, QObject *parent) 11 | : QObject(parent), socketDescriptor(descriptor) 12 | { 13 | qDebug() << "init socket" << number++; 14 | } 15 | 16 | MyTcpSocket::~MyTcpSocket() 17 | { 18 | socket->abort(); 19 | keepTimer->stop(); 20 | qDebug() << "free socket" << --number; 21 | } 22 | 23 | void MyTcpSocket::init() 24 | { 25 | keepTimer = new QTimer(this); 26 | keepTimer->setInterval(1000 + (rand() % 500)); 27 | connect(keepTimer, &QTimer::timeout, this, [this](){ 28 | if (!doKeep) { 29 | keepTimer->stop(); 30 | return; 31 | } 32 | // 长时间没收到心跳,断开 33 | if (--keepCounter < 0) { 34 | disconnectSocket(); 35 | return; 36 | } 37 | // 双端都发心跳包,不响应 38 | keepAlive(); 39 | }); 40 | 41 | socket = new QTcpSocket(this); 42 | connect(socket, &QTcpSocket::connected, this, [this](){ 43 | keepCounter = 5; 44 | cache.clear(); 45 | if (doKeep) { 46 | keepTimer->start(); 47 | } 48 | emit connectStateChanged(true); 49 | }); 50 | connect(socket, &QTcpSocket::disconnected, this, [this](){ 51 | keepTimer->stop(); 52 | cache.clear(); 53 | emit connectStateChanged(false); 54 | }); 55 | connect(socket, &QTcpSocket::readyRead, this, &MyTcpSocket::recvData); 56 | 57 | if (socketDescriptor) { 58 | socket->setSocketDescriptor(socketDescriptor); 59 | // 相当于已连接 60 | keepCounter = 5; 61 | cache.clear(); 62 | if (doKeep) { 63 | keepTimer->start(); 64 | } 65 | } 66 | } 67 | 68 | void MyTcpSocket::connectServer(const QString &ip, quint16 port, bool heart) 69 | { 70 | if (socket->state() != QTcpSocket::UnconnectedState) 71 | socket->abort(); 72 | doKeep = heart; 73 | socket->connectToHost(ip, port); 74 | socket->waitForConnected(3000); 75 | } 76 | 77 | void MyTcpSocket::disconnectSocket() 78 | { 79 | socket->abort(); 80 | } 81 | 82 | bool MyTcpSocket::isConnected() 83 | { 84 | return socket->state() == QTcpSocket::ConnectedState; 85 | } 86 | 87 | void MyTcpSocket::keepAlive() 88 | { 89 | ProtocolFrame frame; 90 | frame.command = CC_KeepAlive; 91 | frame.error = CE_NoErr; 92 | sendFrame(frame); 93 | } 94 | 95 | bool MyTcpSocket::waitWritten() 96 | { 97 | while (isConnected()) { 98 | if (socket->bytesToWrite() == 0) { 99 | return true; 100 | } 101 | socket->waitForBytesWritten(1000); 102 | } 103 | return false; 104 | } 105 | 106 | void MyTcpSocket::sendFrame(const ProtocolFrame &frame) 107 | { 108 | if (!isConnected()) { 109 | return; 110 | } 111 | bool ok = false; 112 | QByteArray frame_data = ProtocolParser::pack(frame, &ok); 113 | if (!ok) { 114 | return; 115 | } 116 | socket->write(ProtocolParser::FRAME_HEAD, 4); 117 | unsigned int frame_len = 16 + frame_data.size(); 118 | socket->write(ProtocolParser::uint32ToBytes(frame_len)); 119 | socket->write(frame_data); 120 | socket->write(ProtocolParser::FRAME_EMPTY, 4); 121 | socket->write(ProtocolParser::FRAME_TAIL, 4); 122 | socket->waitForBytesWritten(1000); 123 | } 124 | 125 | void MyTcpSocket::recvData() 126 | { 127 | if (socket->bytesAvailable() <= 0) 128 | return; 129 | keepCounter = 5; 130 | cache += socket->readAll(); 131 | 132 | // 处理数据 133 | while (true) 134 | { 135 | // 判断帧头 136 | while (cache.size() > 4 && memcmp(ProtocolParser::FRAME_HEAD, cache.data(), 4) != 0) { 137 | cache.remove(0, 1); 138 | } 139 | if (cache.size() < 16) 140 | return; 141 | // 帧长 142 | const unsigned int data_size = ProtocolParser::uint32FromBytes(cache.mid(4, 4)); 143 | if ((unsigned int)cache.size() < data_size) 144 | return; 145 | // 判断帧尾 146 | if (memcmp(ProtocolParser::FRAME_TAIL, cache.data() + data_size - 4, 4) != 0) { 147 | cache.clear(); 148 | return; 149 | } 150 | // 校验,略 151 | bool ok = false; 152 | ProtocolFrame frame = ProtocolParser::unpack(cache.mid(8, data_size - 16), &ok); 153 | // command=0是心跳包 154 | if (ok && frame.command != CC_KeepAlive) { 155 | emit recvFrame(frame); 156 | } 157 | // 移除已处理数据 158 | cache.remove(0, data_size); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /TcpNetworkControl/ControlServer.cpp: -------------------------------------------------------------------------------- 1 | #include "ControlServer.h" 2 | #include 3 | #include 4 | 5 | ControlServer::ControlServer(QObject *parent) 6 | : MyTcpServer(parent) 7 | { 8 | 9 | } 10 | 11 | ControlServer::~ControlServer() 12 | { 13 | 14 | } 15 | 16 | void ControlServer::onRecvFrame(MyTcpSocket *socket, const ProtocolFrame &frame) 17 | { 18 | qDebug() << "server recv frame" << frame.command << frame.content << frame.error; 19 | // 这里需要和设备管理类交互,进行控制 20 | if (frame.command == CC_PolicyInfo) { 21 | // 获取代理端信息 22 | socket->sendFrame(getPolicyInfo()); 23 | } else if (frame.command == CC_DeviceInfo) { 24 | // 获取设备信息 25 | socket->sendFrame(getDeviceInfo(frame.content.value("deviceId").toString())); 26 | } else if (frame.command == CC_TriggerMode) { 27 | // 设置触发模式 28 | setTriggerMode(frame.content.value("deviceId").toString(), 29 | frame.content.value("triggerMode").toInt()); 30 | } else if (frame.command == CC_SoftTrigger) { 31 | // 软触发 32 | socket->sendFrame(softTrigger(frame.content.value("deviceId").toString())); 33 | } else if (frame.command == CC_IntegTime) { 34 | // 设置积分时间 35 | setIntegTime(frame.content.value("deviceId").toString(), 36 | frame.content.value("integrationTime").toInt()); 37 | } else if (frame.command == CC_LoadImage) { 38 | // 获取图片 39 | socket->sendFrame(getImage(frame.content.value("deviceId").toString(), 40 | frame.content.value("imageId").toString())); 41 | // 等待发送结束 42 | socket->waitWritten(); 43 | // 断开连接 44 | socket->disconnectSocket(); 45 | } 46 | } 47 | 48 | ProtocolFrame ControlServer::getPolicyInfo() 49 | { 50 | // DeviceManager::getDeviceList(); 51 | ProtocolFrame frame; 52 | frame.command = CC_PolicyInfo; 53 | frame.error = CE_NoErr; 54 | frame.content = QJsonObject{ 55 | {"ip", "xx.xx.xx.xx"}, 56 | {"version", "v0.0.0(2023-11-11)"}, 57 | {"startTime", "2023-11-12 xxx"}, 58 | {"deviceList", QJsonValue(QJsonArray{"11110001", "11110002"})} 59 | }; 60 | return frame; 61 | } 62 | 63 | ProtocolFrame ControlServer::getDeviceInfo(const QString &deviceId) 64 | { 65 | // DeviceManager::getDeviceInfo(); 66 | ProtocolFrame frame; 67 | frame.command = CC_DeviceInfo; 68 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 69 | frame.content = QJsonObject{ 70 | {"deviceId", deviceId}, 71 | {"deviceType", 0}, 72 | {"status", 3}, 73 | {"width", 200}, 74 | {"height", 200}, 75 | {"integrationTime", 100}, 76 | {"triggerMode", 0} 77 | }; 78 | return frame; 79 | } 80 | 81 | void ControlServer::syncStatus(const QString &deviceId, int status, int error) 82 | { 83 | // DeviceManager::emit deviceStatusChanged(deviceId, status, error); 84 | ProtocolFrame frame; 85 | frame.command = CC_StatusSync; 86 | // 根据操作结果设置错误码 87 | frame.error = (status == CS_DeviceError) ? error : (deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr); 88 | frame.content = QJsonObject{ 89 | {"deviceId", deviceId}, 90 | {"status", status} 91 | }; 92 | emit syncAll(frame); 93 | } 94 | 95 | void ControlServer::syncImage(const QString &deviceId, const QString &imageId) 96 | { 97 | // DeviceManager::emit imageLoadFinished(deviceId, imageId); 98 | ProtocolFrame frame; 99 | frame.command = CC_StatusSync; 100 | // 根据操作结果设置错误码 101 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 102 | frame.content = QJsonObject{ 103 | {"deviceId", deviceId}, 104 | {"status", (int)CS_TransFinish}, 105 | {"imageId", imageId} 106 | }; 107 | emit syncAll(frame); 108 | } 109 | 110 | void ControlServer::setTriggerMode(const QString &deviceId, unsigned char triggerMode) 111 | { 112 | // 设置类的接口 113 | // 1.可以server发信号给device设置后,device发信号反馈到server槽函数,server再同步给所有的客户端 114 | // 2.device接口加锁在server中直接操作 115 | ProtocolFrame frame; 116 | frame.command = CC_TriggerMode; 117 | // 根据操作结果设置错误码 118 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 119 | frame.content = QJsonObject{ 120 | {"deviceId", deviceId}, 121 | {"triggerMode", (int)triggerMode} 122 | }; 123 | emit syncAll(frame); 124 | } 125 | 126 | ProtocolFrame ControlServer::softTrigger(const QString &deviceId) 127 | { 128 | ProtocolFrame frame; 129 | frame.command = CC_SoftTrigger; 130 | // 根据操作结果设置错误码 131 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 132 | frame.content = QJsonObject{{"deviceId", deviceId}}; 133 | return frame; 134 | } 135 | 136 | void ControlServer::setIntegTime(const QString &deviceId, unsigned short integTime) 137 | { 138 | ProtocolFrame frame; 139 | frame.command = CC_IntegTime; 140 | // 根据操作结果设置错误码 141 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 142 | frame.content = QJsonObject{ 143 | {"deviceId", deviceId}, 144 | {"integrationTime", (int)integTime} 145 | }; 146 | 147 | emit syncAll(frame); 148 | } 149 | 150 | ProtocolFrame ControlServer::getImage(const QString &deviceId, const QString &imageId) 151 | { 152 | ProtocolFrame frame; 153 | frame.command = CC_LoadImage; 154 | // 根据操作结果设置错误码 155 | frame.error = deviceId.isEmpty() ? CE_ImageIdErr : CE_NoErr; 156 | frame.content = QJsonObject{ 157 | {"deviceId", deviceId}, 158 | {"imageId", imageId}, 159 | {"width", 200}, 160 | {"height", 200} 161 | }; 162 | // 模拟数据,注意 32 位不要一次性加载大文件 163 | QFile file("D:/Download/qt-unified-windows-x64-4.6.0-online.exe"); 164 | if (file.open(QIODevice::ReadOnly)) { 165 | frame.data = file.readAll(); 166 | file.close(); 167 | } else { 168 | QByteArray data(200 * 200 * 2, 0x00); 169 | data[0] = (char)0xFF; 170 | data[data.size() - 1] = (char)0xFF; 171 | frame.data = data; 172 | } 173 | return frame; 174 | } 175 | -------------------------------------------------------------------------------- /SimpleTcpServer/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | 4 | #include 5 | 6 | Widget::Widget(QWidget *parent) 7 | : QWidget(parent) 8 | , ui(new Ui::Widget) 9 | { 10 | ui->setupUi(this); 11 | setWindowTitle("Server"); 12 | 13 | initServer(); 14 | } 15 | 16 | Widget::~Widget() 17 | { 18 | //关闭server 19 | closeServer(); 20 | delete ui; 21 | } 22 | 23 | void Widget::initServer() 24 | { 25 | //创建Server对象 26 | server = new QTcpServer(this); 27 | 28 | //点击监听按钮,开始监听 29 | connect(ui->btnListen,&QPushButton::clicked,[this]{ 30 | //判断当前是否已开启,是则close,否则listen 31 | if(server->isListening()){ 32 | //server->close(); 33 | closeServer(); 34 | //关闭server后恢复界面状态 35 | ui->btnListen->setText("Listen"); 36 | ui->editAddress->setEnabled(true); 37 | ui->editPort->setEnabled(true); 38 | }else{ 39 | //从界面上读取ip和端口 40 | //可以使用 QHostAddress::Any 监听所有地址的对应端口 41 | const QString address_text=ui->editAddress->text(); 42 | const QHostAddress address=(address_text=="Any") 43 | ?QHostAddress::Any 44 | :QHostAddress(address_text); 45 | const unsigned short port=ui->editPort->text().toUShort(); 46 | //开始监听,并判断是否成功 47 | if(server->listen(address,port)){ 48 | //连接成功就修改界面按钮提示,以及地址栏不可编辑 49 | ui->btnListen->setText("Close"); 50 | ui->editAddress->setEnabled(false); 51 | ui->editPort->setEnabled(false); 52 | } 53 | } 54 | updateState(); 55 | }); 56 | 57 | //监听到新的客户端连接请求 58 | connect(server,&QTcpServer::newConnection,this,[this]{ 59 | //如果有新的连接就取出 60 | while(server->hasPendingConnections()) 61 | { 62 | //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象 63 | //套接字是作为服务器的子级创建的,这意味着销毁QTcpServer对象时会自动删除该套接字。 64 | //最好在完成处理后显式删除该对象,以避免浪费内存。 65 | //返回的QTcpSocket对象不能从另一个线程使用,如有需要可重写incomingConnection(). 66 | QTcpSocket *socket=server->nextPendingConnection(); 67 | clientList.append(socket); 68 | ui->textRecv->append(QString("[%1:%2] Soket Connected") 69 | .arg(socket->peerAddress().toString()) 70 | .arg(socket->peerPort())); 71 | 72 | //关联相关操作的信号槽 73 | //收到数据,触发readyRead 74 | connect(socket,&QTcpSocket::readyRead,[this,socket]{ 75 | //没有可读的数据就返回 76 | if(socket->bytesAvailable()<=0) 77 | return; 78 | //注意收发两端文本要使用对应的编解码 79 | const QString recv_text=QString::fromUtf8(socket->readAll()); 80 | ui->textRecv->append(QString("[%1:%2]") 81 | .arg(socket->peerAddress().toString()) 82 | .arg(socket->peerPort())); 83 | ui->textRecv->append(recv_text); 84 | }); 85 | 86 | //error信号在5.15换了名字 87 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 88 | //错误信息 89 | connect(socket, static_cast(&QAbstractSocket::error), 90 | [this,socket](QAbstractSocket::SocketError){ 91 | ui->textRecv->append(QString("[%1:%2] Soket Error:%3") 92 | .arg(socket->peerAddress().toString()) 93 | .arg(socket->peerPort()) 94 | .arg(socket->errorString())); 95 | }); 96 | #else 97 | //错误信息 98 | connect(socket,&QAbstractSocket::errorOccurred,[this,socket](QAbstractSocket::SocketError){ 99 | ui->textRecv->append(QString("[%1:%2] Soket Error:%3") 100 | .arg(socket->peerAddress().toString()) 101 | .arg(socket->peerPort()) 102 | .arg(socket->errorString())); 103 | }); 104 | #endif 105 | 106 | //连接断开,销毁socket对象,这是为了开关server时socket正确释放 107 | connect(socket,&QTcpSocket::disconnected,[this,socket]{ 108 | socket->deleteLater(); 109 | clientList.removeOne(socket); 110 | ui->textRecv->append(QString("[%1:%2] Soket Disonnected") 111 | .arg(socket->peerAddress().toString()) 112 | .arg(socket->peerPort())); 113 | updateState(); 114 | }); 115 | } 116 | updateState(); 117 | }); 118 | 119 | //server向client发送内容 120 | connect(ui->btnSend,&QPushButton::clicked,[this]{ 121 | //判断是否开启了server 122 | if(!server->isListening()) 123 | return; 124 | //将发送区文本发送给客户端 125 | const QByteArray send_data=ui->textSend->toPlainText().toUtf8(); 126 | //数据为空就返回 127 | if(send_data.isEmpty()) 128 | return; 129 | for(QTcpSocket *socket:qAsConst(clientList)) 130 | { 131 | socket->write(send_data); 132 | //socket->waitForBytesWritten(); 133 | } 134 | }); 135 | 136 | //server的错误信息 137 | //如果发生错误,则serverError()返回错误的类型, 138 | //并且可以调用errorString()以获取对所发生事件的易于理解的描述 139 | connect(server,&QTcpServer::acceptError,[this](QAbstractSocket::SocketError){ 140 | ui->textRecv->append("Server Error:"+server->errorString()); 141 | }); 142 | } 143 | 144 | void Widget::closeServer() 145 | { 146 | //停止服务 147 | server->close(); 148 | for(QTcpSocket * socket:qAsConst(clientList)) 149 | { 150 | //断开与客户端的连接 151 | socket->disconnectFromHost(); 152 | if(socket->state()!=QAbstractSocket::UnconnectedState){ 153 | socket->abort(); 154 | } 155 | } 156 | } 157 | 158 | void Widget::updateState() 159 | { 160 | //将当前server地址和端口、客户端连接数写在标题栏 161 | if(server->isListening()){ 162 | setWindowTitle(QString("Server[%1:%2] connections:%3") 163 | .arg(server->serverAddress().toString()) 164 | .arg(server->serverPort()) 165 | .arg(clientList.count())); 166 | }else{ 167 | setWindowTitle("Server"); 168 | } 169 | } 170 | 171 | -------------------------------------------------------------------------------- /TcpNetworkControl/MainWindow.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | MainWindow 4 | 5 | 6 | 7 | 0 8 | 0 9 | 441 10 | 269 11 | 12 | 13 | 14 | Tcp Network Control 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 连接 24 | 25 | 26 | 27 | 28 | 29 | 30 | 断开 31 | 32 | 33 | 34 | 35 | 36 | 37 | 心跳 38 | 39 | 40 | 41 | 42 | 43 | 44 | Qt::Horizontal 45 | 46 | 47 | 48 | 40 49 | 20 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 代理端信息 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 设备信息 72 | 73 | 74 | 75 | 76 | 77 | 78 | Qt::Horizontal 79 | 80 | 81 | 82 | 40 83 | 20 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | X光 97 | 98 | 99 | 100 | 101 | 软触发 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 触发模式 110 | 111 | 112 | 113 | 114 | 115 | 116 | 软触发 117 | 118 | 119 | 120 | 121 | 122 | 123 | Qt::Horizontal 124 | 125 | 126 | 127 | 40 128 | 20 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 65535 141 | 142 | 143 | 100 144 | 145 | 146 | 147 | 148 | 149 | 150 | 积分时间 151 | 152 | 153 | 154 | 155 | 156 | 157 | Qt::Horizontal 158 | 159 | 160 | 161 | 40 162 | 20 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | Qt::Vertical 173 | 174 | 175 | 176 | 20 177 | 114 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 有图了 188 | 189 | 190 | 191 | 192 | 193 | 194 | Qt::Horizontal 195 | 196 | 197 | 198 | 40 199 | 20 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 0 212 | 0 213 | 441 214 | 21 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | -------------------------------------------------------------------------------- /SimpleUdpClient/widget.cpp: -------------------------------------------------------------------------------- 1 | #include "widget.h" 2 | #include "ui_widget.h" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | Widget::Widget(QWidget *parent) 10 | : QWidget(parent) 11 | , ui(new Ui::Widget) 12 | { 13 | ui->setupUi(this); 14 | setWindowTitle("Client"); 15 | 16 | initClient(); 17 | } 18 | 19 | Widget::~Widget() 20 | { 21 | //关闭套接字,并丢弃写缓冲区中的所有待处理数据。 22 | udpSocket->abort(); 23 | delete ui; 24 | } 25 | 26 | void Widget::initClient() 27 | { 28 | //创建udp socket对象 29 | udpSocket = new QUdpSocket(this); 30 | 31 | //获取本机ip 32 | QList ipAddressesList = QNetworkInterface::allAddresses(); 33 | qDebug()<<"ip list:"<comboBox, QOverload::of(&QComboBox::currentIndexChanged), 36 | [=](int index){ 37 | switch (index) { 38 | case 0: 39 | ui->editLocalAddress->setText(ipAddressesList.first().toString()); 40 | ui->editPeerAddress->setText(ipAddressesList.first().toString()); 41 | break; 42 | case 1: 43 | ui->editLocalAddress->setText("Any"); 44 | ui->editPeerAddress->setText("Broadcast"); 45 | break; 46 | case 2: 47 | ui->editLocalAddress->setText("224.0.0.2"); 48 | ui->editPeerAddress->setText("224.0.0.2"); 49 | break; 50 | default: 51 | break; 52 | } 53 | }); 54 | ui->editLocalAddress->setText(ipAddressesList.first().toString()); 55 | ui->editPeerAddress->setText(ipAddressesList.first().toString()); 56 | 57 | //点击绑定端口,根据ui设置进行绑定 58 | connect(ui->btnBind,&QPushButton::clicked,[this]{ 59 | //判断当前是否已绑定,bind了就取消 60 | if(udpSocket->state()==QAbstractSocket::BoundState){ 61 | //关闭套接字,并丢弃写缓冲区中的所有待处理数据。 62 | udpSocket->abort(); 63 | }else if(udpSocket->state()==QAbstractSocket::UnconnectedState){ 64 | //从界面上读取ip和端口 65 | const QHostAddress address=QHostAddress(ui->editLocalAddress->text()); 66 | const unsigned short port=ui->editLocalPort->text().toUShort(); 67 | //绑定本机地址 68 | //combobox:单播-广播-组播 69 | switch (ui->comboBox->currentIndex()) 70 | { 71 | case 0: 72 | //可以指定本地绑定的ip 73 | udpSocket->bind(address,port); 74 | //udpSocket->bind(port); 75 | break; 76 | case 1: 77 | //udpSocket->bind(address,port); 78 | //udpSocket->bind(port); 79 | udpSocket->bind(QHostAddress::AnyIPv4,port); 80 | break; 81 | case 2: 82 | //组播ip必须是D类ip 83 | //D类IP段 224.0.0.0 到 239.255.255.255 84 | //且组播地址不能是224.0.0.1 85 | udpSocket->bind(QHostAddress::AnyIPv4,port); //根据Qt示例,组播的话IPv4和v6分开的 86 | udpSocket->joinMulticastGroup(address); //QHostAddress("224.0.0.2") 87 | break; 88 | default: 89 | break; 90 | } 91 | }else{ 92 | ui->textRecv->append("It is not BoundState or UnconnectedState"); 93 | } 94 | }); 95 | 96 | //绑定状态改变 97 | connect(udpSocket,&QUdpSocket::stateChanged,[this](QAbstractSocket::SocketState socketState){ 98 | //已绑定就设置为不可编辑 99 | const bool is_bind=(socketState==QAbstractSocket::BoundState); 100 | ui->btnBind->setText(is_bind?"Disbind":"Bind"); 101 | ui->editLocalAddress->setEnabled(!is_bind); 102 | ui->editLocalPort->setEnabled(!is_bind); 103 | ui->editPeerAddress->setEnabled(!is_bind); 104 | ui->editPeerPort->setEnabled(!is_bind); 105 | ui->comboBox->setEnabled(!is_bind); 106 | 107 | updateState(); 108 | }); 109 | 110 | //发送数据 111 | connect(ui->btnSend,&QPushButton::clicked,[this]{ 112 | //判断是可操作,isValid表示准备好读写 113 | if(!udpSocket->isValid()) 114 | return; 115 | //将发送区文本发送给客户端 116 | const QByteArray send_data=ui->textSend->toPlainText().toUtf8(); 117 | //数据为空就返回 118 | if(send_data.isEmpty()) 119 | return; 120 | //从界面上读取ip和端口 121 | const QString address_text=ui->editPeerAddress->text(); 122 | const QHostAddress address=QHostAddress(address_text); 123 | const unsigned short port=ui->editPeerPort->text().toUShort(); 124 | //combobox:单播-广播-组播 125 | switch (ui->comboBox->currentIndex()) 126 | { 127 | case 0: 128 | udpSocket->writeDatagram(QNetworkDatagram(send_data,address,port)); 129 | break; 130 | case 1: 131 | udpSocket->writeDatagram(QNetworkDatagram(send_data,QHostAddress::Broadcast,port)); 132 | break; 133 | case 2: 134 | udpSocket->writeDatagram(QNetworkDatagram(send_data,address,port)); //QHostAddress("224.0.0.2") 135 | break; 136 | default: 137 | break; 138 | } 139 | }); 140 | 141 | //收到数据,触发readyRead 142 | connect(udpSocket,&QUdpSocket::readyRead,[this]{ 143 | //没有可读的数据就返回 144 | if(!udpSocket->hasPendingDatagrams()|| 145 | udpSocket->pendingDatagramSize()<=0) 146 | return; 147 | //注意收发两端文本要使用对应的编解码 148 | QNetworkDatagram recv_datagram=udpSocket->receiveDatagram(); 149 | const QString recv_text=QString::fromUtf8(recv_datagram.data()); 150 | ui->textRecv->append(QString("[%1:%2]") 151 | .arg(recv_datagram.senderAddress().toString()) 152 | .arg(recv_datagram.senderPort())); 153 | ui->textRecv->append(recv_text); 154 | }); 155 | 156 | //error信号在5.15换了名字 157 | #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) 158 | //错误信息 159 | connect(udpSocket, static_cast(&QAbstractSocket::error), 160 | [this](QAbstractSocket::SocketError){ 161 | ui->textRecv->append("Socket Error:"+udpSocket->errorString()); 162 | }); 163 | #else 164 | //错误信息 165 | connect(udpSocket,&QAbstractSocket::errorOccurred,[this](QAbstractSocket::SocketError){ 166 | ui->textRecv->append("Socket Error:"+udpSocket->errorString()); 167 | }); 168 | #endif 169 | } 170 | 171 | void Widget::updateState() 172 | { 173 | //将当前socket绑定的地址和端口写在标题栏 174 | if(udpSocket->state()==QAbstractSocket::BoundState){ 175 | setWindowTitle(QString("Client[%1:%2]") 176 | .arg(udpSocket->localAddress().toString()) 177 | .arg(udpSocket->localPort())); 178 | }else{ 179 | setWindowTitle("Client"); 180 | } 181 | } 182 | -------------------------------------------------------------------------------- /SimpleUdpClient/widget.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | Widget 4 | 5 | 6 | 7 | 0 8 | 0 9 | 459 10 | 482 11 | 12 | 13 | 14 | Widget 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 40 24 | 0 25 | 26 | 27 | 28 | 29 | 40 30 | 16777215 31 | 32 | 33 | 34 | Local: 35 | 36 | 37 | 38 | 39 | 40 | 41 | Address: 42 | 43 | 44 | 45 | 46 | 47 | 48 | 127.0.0.1 49 | 50 | 51 | 52 | 53 | 54 | 55 | Port: 56 | 57 | 58 | 59 | 60 | 61 | 62 | 1992 63 | 64 | 65 | 66 | 67 | 68 | 69 | Bind 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 40 82 | 0 83 | 84 | 85 | 86 | 87 | 40 88 | 16777215 89 | 90 | 91 | 92 | Peer: 93 | 94 | 95 | 96 | 97 | 98 | 99 | Address: 100 | 101 | 102 | 103 | 104 | 105 | 106 | 127.0.0.1 107 | 108 | 109 | 110 | 111 | 112 | 113 | Port: 114 | 115 | 116 | 117 | 118 | 119 | 120 | 1992 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 75 129 | 0 130 | 131 | 132 | 133 | 134 | 单播 135 | 136 | 137 | 138 | 139 | 广播 140 | 141 | 142 | 143 | 144 | 组播 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | Recv: 155 | 156 | 157 | 158 | 159 | 160 | 161 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 162 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 163 | p, li { white-space: pre-wrap; } 164 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 165 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">User Datagram Protocol</p></body></html> 166 | 167 | 168 | 169 | 170 | 171 | 172 | Send: 173 | 174 | 175 | 176 | 177 | 178 | 179 | <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> 180 | <html><head><meta name="qrichtext" content="1" /><style type="text/css"> 181 | p, li { white-space: pre-wrap; } 182 | </style></head><body style=" font-family:'SimSun'; font-size:9pt; font-weight:400; font-style:normal;"> 183 | <p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;">龚建波 1992</p></body></html> 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | Qt::Horizontal 193 | 194 | 195 | 196 | 40 197 | 20 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | Send 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | -------------------------------------------------------------------------------- /TcpNetworkControl/MainWindow.cpp: -------------------------------------------------------------------------------- 1 | #include "MainWindow.h" 2 | #include "ui_MainWindow.h" 3 | #include 4 | #include 5 | #include 6 | 7 | MainWindow::MainWindow(QWidget *parent) 8 | : QMainWindow(parent) 9 | , ui(new Ui::MainWindow) 10 | { 11 | ui->setupUi(this); 12 | 13 | ctrlServer = new ControlServer(this); 14 | ctrlServer->listen(QHostAddress("127.0.0.1"), 12315); 15 | 16 | imageServer = new ControlServer(this); 17 | imageServer->listen(QHostAddress("127.0.0.1"), 12316); 18 | 19 | ctrlClient = new ControlClient(); 20 | clientThread = new QThread(this); 21 | ctrlClient->moveToThread(clientThread); 22 | connect(clientThread, &QThread::started, ctrlClient, &ControlClient::init); 23 | connect(clientThread, &QThread::finished, ctrlClient, &ControlClient::deleteLater); 24 | ui->btnDisconnect->setEnabled(false); 25 | connect(ctrlClient, &ControlClient::connectStateChanged, this, [this](bool connected){ 26 | ui->btnConnect->setEnabled(!connected); 27 | ui->btnDisconnect->setEnabled(connected); 28 | // 设备连接后,先获取代理端信息&&设备列表 29 | QMetaObject::invokeMethod(ctrlClient, [this](){ 30 | ctrlClient->getPolicyInfo(); 31 | }); 32 | }); 33 | qRegisterMetaType("ProtocolFrame"); 34 | connect(ctrlClient, &ControlClient::recvFrame, this, [this](const ProtocolFrame &frame){ 35 | qDebug() << "client recv frame" << frame.command << frame.content << frame.error; 36 | if (frame.error != CE_NoErr) 37 | return; 38 | if (frame.command == CC_PolicyInfo) { 39 | // 更新代理端信息 40 | QJsonArray arr = frame.content.value("deviceList").toArray(); 41 | QStringList ids; 42 | for (int i = 0; i < arr.size(); i++) 43 | { 44 | ids.append(arr[i].toString()); 45 | } 46 | ui->deviceListBox->clear(); 47 | ui->deviceListBox->addItems(ids); 48 | } else if (frame.command == CC_DeviceInfo) { 49 | // 设备信息 50 | } else if (frame.command == CC_StatusSync) { 51 | // 设备状态 52 | QString device_id = frame.content.value("deviceId").toString(); 53 | int status = frame.content.value("status").toInt(); 54 | if (status == CS_TransFinish && device_id == ui->deviceListBox->currentText()) { 55 | QString image_id = frame.content.value("imageId").toString(); 56 | // 来图了,开启线程去下载图片 57 | loadImage(device_id, image_id); 58 | } 59 | } 60 | }); 61 | clientThread->start(); 62 | 63 | connect(ui->btnConnect, &QPushButton::clicked, this, &MainWindow::doConnect); 64 | connect(ui->btnDisconnect, &QPushButton::clicked, this, &MainWindow::doDisconnect); 65 | connect(ui->btnKeepAlive, &QPushButton::clicked, this, &MainWindow::doKeepAlive); 66 | connect(ui->btnPolicyInfo, &QPushButton::clicked, this, &MainWindow::doPolicyInfo); 67 | connect(ui->btnDeviceInfo, &QPushButton::clicked, this, &MainWindow::doDeviceInfo); 68 | connect(ui->btnTriggerMode, &QPushButton::clicked, this, &MainWindow::doTriggerMode); 69 | connect(ui->btnSoftTrigger, &QPushButton::clicked, this, &MainWindow::doSoftTrigger); 70 | connect(ui->btnIntegTime, &QPushButton::clicked, this, &MainWindow::doIntegTime); 71 | 72 | connect(ui->btnImage, &QPushButton::clicked, this, &MainWindow::newImage); 73 | } 74 | 75 | MainWindow::~MainWindow() 76 | { 77 | delete ui; 78 | clientThread->quit(); 79 | clientThread->wait(); 80 | } 81 | 82 | void MainWindow::doConnect() 83 | { 84 | QMetaObject::invokeMethod(ctrlClient, [=](){ 85 | ctrlClient->connectServer("127.0.0.1", 12315); 86 | }); 87 | } 88 | 89 | void MainWindow::doDisconnect() 90 | { 91 | QMetaObject::invokeMethod(ctrlClient, [=](){ 92 | ctrlClient->disconnectSocket(); 93 | }); 94 | } 95 | 96 | void MainWindow::doKeepAlive() 97 | { 98 | QMetaObject::invokeMethod(ctrlClient, [=](){ 99 | ctrlClient->keepAlive(); 100 | }); 101 | } 102 | 103 | void MainWindow::doPolicyInfo() 104 | { 105 | QMetaObject::invokeMethod(ctrlClient, [=](){ 106 | ctrlClient->getPolicyInfo(); 107 | }); 108 | } 109 | 110 | void MainWindow::doDeviceInfo() 111 | { 112 | if (ui->deviceListBox->count() == 0) 113 | return; 114 | const QString device_id = ui->deviceListBox->currentText(); 115 | QMetaObject::invokeMethod(ctrlClient, [=](){ 116 | ctrlClient->getDeviceInfo(device_id); 117 | }); 118 | } 119 | 120 | void MainWindow::doTriggerMode() 121 | { 122 | if (ui->deviceListBox->count() == 0) 123 | return; 124 | const QString device_id = ui->deviceListBox->currentText(); 125 | const int trigger_mode = ui->triggerModeBox->currentIndex(); 126 | QMetaObject::invokeMethod(ctrlClient, [=](){ 127 | ctrlClient->setTriggerMode(device_id, trigger_mode); 128 | }); 129 | } 130 | 131 | void MainWindow::doSoftTrigger() 132 | { 133 | if (ui->deviceListBox->count() == 0) 134 | return; 135 | const QString device_id = ui->deviceListBox->currentText(); 136 | QMetaObject::invokeMethod(ctrlClient, [=](){ 137 | ctrlClient->softTrigger(device_id); 138 | }); 139 | } 140 | 141 | void MainWindow::doIntegTime() 142 | { 143 | if (ui->deviceListBox->count() == 0) 144 | return; 145 | const QString device_id = ui->deviceListBox->currentText(); 146 | const unsigned short integ_time = ui->integTimeBox->value(); 147 | QMetaObject::invokeMethod(ctrlClient, [=](){ 148 | ctrlClient->setIntegTime(device_id, integ_time); 149 | }); 150 | } 151 | 152 | void MainWindow::newImage() 153 | { 154 | if (ui->deviceListBox->count() == 0) 155 | return; 156 | ctrlServer->syncImage(ui->deviceListBox->currentText(), "idxxx"); 157 | } 158 | 159 | void MainWindow::loadImage(const QString &deviceId, const QString &imageId) 160 | { 161 | std::thread th([=]{ 162 | QEventLoop loop; 163 | ControlClient socket; 164 | socket.init(); 165 | // 传输时间较长,不要心跳检测 166 | socket.connectServer("127.0.0.1", 12316, false); 167 | if (!socket.isConnected()) 168 | return; 169 | socket.requestImage(deviceId, imageId); 170 | connect(&socket, &ControlClient::connectStateChanged, [&](bool connected){ 171 | if (!connected) { 172 | // 异常断开 173 | loop.quit(); 174 | } 175 | }); 176 | connect(&socket, &ControlClient::recvFrame, [&](const ProtocolFrame &frame){ 177 | qDebug() << "client recv file" << frame.command << frame.content << frame.error; 178 | if (frame.command == CC_LoadImage) { 179 | // 图像数据 180 | if (frame.error == CE_NoErr) { 181 | // 保存图片 182 | QFile file(qApp->applicationDirPath() + "/image.raw"); 183 | if (file.open(QIODevice::WriteOnly)) { 184 | qDebug() << "save file" << frame.data.size() << file.fileName(); 185 | file.write(frame.data); 186 | file.close(); 187 | } 188 | } 189 | loop.quit(); 190 | } 191 | }); 192 | // 等待图片传完或者网络断开 193 | loop.exec(); 194 | socket.disconnectSocket(); 195 | qDebug() << "load image finished"; 196 | }); 197 | th.detach(); 198 | } 199 | -------------------------------------------------------------------------------- /TcpFileTransfer/ClientOperate.cpp: -------------------------------------------------------------------------------- 1 | #include "ClientOperate.h" 2 | 3 | #include 4 | 5 | ClientOperate::ClientOperate(QObject *parent) 6 | : QObject(parent) 7 | { 8 | initOperate(); 9 | } 10 | 11 | ClientOperate::~ClientOperate() 12 | { 13 | doDisconnect(); 14 | } 15 | 16 | QString ClientOperate::getFilePath() const 17 | { 18 | QMutexLocker locker(&dataMutex); 19 | return filePath; 20 | } 21 | 22 | void ClientOperate::setFilePath(const QString &path) 23 | { 24 | QMutexLocker locker(&dataMutex); 25 | filePath=path; 26 | } 27 | 28 | bool ClientOperate::isConnected() const 29 | { 30 | QMutexLocker locker(&dataMutex); 31 | return connectState; 32 | } 33 | 34 | void ClientOperate::setConnected(bool connected) 35 | { 36 | QMutexLocker locker(&dataMutex); 37 | connectState=connected; 38 | } 39 | 40 | void ClientOperate::connectTcp(const QString &address, quint16 port) 41 | { 42 | if(socket->state()==QAbstractSocket::UnconnectedState){ 43 | //连接服务器 44 | socket->connectToHost(QHostAddress(address),port); 45 | }else{ 46 | emit logMessage("socket->state() != QAbstractSocket::UnconnectedState"); 47 | } 48 | } 49 | 50 | void ClientOperate::disconnectTcp() 51 | { 52 | doDisconnect(); 53 | } 54 | 55 | void ClientOperate::startFileTransfer() 56 | { 57 | //之前如果打开了先释放 58 | doCloseFile(); 59 | if(!socket->isValid()) 60 | return; 61 | const QString file_path=getFilePath(); 62 | //无效路径 63 | if(file_path.isEmpty() || !QFile::exists(file_path)){ 64 | emit logMessage("无效的文件路径"+file_path); 65 | return; 66 | } 67 | file=new QFile(this); 68 | file->setFileName(file_path); 69 | //打开失败 70 | if(!file->open(QIODevice::ReadOnly)){ 71 | doCloseFile(); 72 | emit logMessage("打开文件失败"+file_path); 73 | return; 74 | } 75 | 76 | sendSize=0; 77 | fileSize=file->size(); 78 | if(fileSize<0) 79 | fileSize=0; 80 | //大小高位字节顺序在前 81 | char file_size[4]={0}; 82 | const quint64 data_size=fileSize; //有符号转无符号,会被截断 83 | file_size[3]=data_size>>0%0x100; 84 | file_size[2]=data_size>>8%0x100; 85 | file_size[1]=data_size>>16%0x100; 86 | file_size[0]=data_size>>24; 87 | //把文件大小和文件名发送给服务端,然后等待确认命令的返回 88 | QFileInfo info(file_path); 89 | sendData(0x01,QByteArray(file_size,4)+info.fileName().toUtf8()); 90 | } 91 | 92 | void ClientOperate::cancelFileTransfer() 93 | { 94 | //关闭文件 95 | doCancel(); 96 | //发送停止传输指令 97 | sendData(0x04,QByteArray()); 98 | } 99 | 100 | void ClientOperate::initOperate() 101 | { 102 | socket=new QTcpSocket(this); 103 | 104 | //收到数据,触发readyRead 105 | connect(socket,&QTcpSocket::readyRead,[this]{ 106 | //没有可读的数据就返回 107 | if(socket->bytesAvailable()<=0) 108 | return; 109 | //读取数据 110 | operateReceiveData(socket->readAll()); 111 | }); 112 | 113 | //连接状态改变 114 | connect(socket,&QTcpSocket::connected,[this]{ 115 | setConnected(true); 116 | emit connectStateChanged(true); 117 | emit logMessage(QString("已连接服务器 [%1:%2]") 118 | .arg(socket->peerAddress().toString()) 119 | .arg(socket->peerPort())); 120 | }); 121 | connect(socket,&QTcpSocket::disconnected,[this]{ 122 | setConnected(false); 123 | emit connectStateChanged(false); 124 | emit logMessage(QString("与服务器连接已断开 [%1:%2]") 125 | .arg(socket->peerAddress().toString()) 126 | .arg(socket->peerPort())); 127 | }); 128 | 129 | timer=new QTimer(this); 130 | 131 | //通过定时器来控制数据发送 132 | connect(timer,&QTimer::timeout,[this]{ 133 | if(!socket->isValid()){ 134 | doCancel(); 135 | emit logMessage("Socket不可操作,发送终止"); 136 | return; 137 | } 138 | if(!file||!file->isOpen()){ 139 | doCancel(); 140 | emit logMessage("文件操作失败,发送终止"); 141 | return; 142 | } 143 | 144 | const qint64 read_size=file->read(fileBuffer,4096); 145 | //socket->write(fileBuffer,read_size); 146 | sendFile(fileBuffer,read_size); 147 | sendSize+=read_size; 148 | file->seek(sendSize); 149 | if(!socket->waitForBytesWritten()){ 150 | doCancel(); 151 | emit logMessage("文件发送超时,发送终止"); 152 | return; 153 | } 154 | //避免除零 155 | if(fileSize>0){ 156 | emit progressChanged(sendSize*100/fileSize); 157 | } 158 | if(sendSize>=fileSize){ 159 | doCancel(); 160 | emit logMessage("文件发送完成"); 161 | emit progressChanged(100); 162 | sendData(0x03,QByteArray()); 163 | return; 164 | } 165 | }); 166 | } 167 | 168 | void ClientOperate::doDisconnect() 169 | { 170 | //断开socket连接,释放资源 171 | socket->abort(); 172 | doCloseFile(); 173 | } 174 | 175 | void ClientOperate::doCloseFile() 176 | { 177 | if(file){ 178 | file->close(); 179 | delete file; 180 | file=nullptr; 181 | } 182 | } 183 | 184 | void ClientOperate::doCancel() 185 | { 186 | timer->stop(); 187 | if(file){ 188 | //关闭文件 189 | doCloseFile(); 190 | } 191 | } 192 | 193 | void ClientOperate::sendData(char type,const QByteArray &data) 194 | { 195 | //传输协议 196 | //帧结构:帧头4+帧长2+帧类型1+帧数据N+帧尾2(没有校验段,懒得写) 197 | //帧头:4字节定值 0x0F 0xF0 0x00 0xFF 198 | //帧长:2字节数据段长度值 arr[4]*0x100+arr[5] 前面为高位后面为低位 199 | //帧类型:1字节 200 | //- 0x01 准备发送文件,后跟四字节文件长度和N字节utf8文件名,长度计算同帧长一样前面为高位后面为低位 201 | //- 0x02 文件数据 202 | //- 0x03 发送结束 203 | //- 0x04 取消发送 204 | //(服务端收到0x01 0x03开始和结束发送两个命令要进行应答,回同样的命令码无数据段) 205 | //帧尾:2字节定值 0x0D 0x0A 206 | if(!socket->isValid()) 207 | return; 208 | frameHead[6]=type; 209 | const quint64 data_size=data.count(); 210 | frameHead[5]=data_size%0x100; 211 | frameHead[4]=data_size/0x100; 212 | 213 | //发送头+数据+尾 214 | socket->write(frameHead,7); 215 | socket->write(data); 216 | socket->write(frameTail,2); 217 | } 218 | 219 | void ClientOperate::sendFile(const char *data, int size) 220 | { 221 | if(!socket->isValid()) 222 | return; 223 | frameHead[6]=(char)0x02; 224 | const quint64 data_size=size; 225 | frameHead[5]=data_size%0x100; 226 | frameHead[4]=data_size/0x100; 227 | 228 | //发送头+数据+尾 229 | socket->write(frameHead,7); 230 | socket->write(data,size); 231 | socket->write(frameTail,2); 232 | } 233 | 234 | void ClientOperate::operateReceiveData(const QByteArray &data) 235 | { 236 | static QByteArray frame_head=QByteArray(frameHead,4); 237 | //这里只是简单的处理,所以用了QByteArray容器做缓存 238 | dataTemp+=data; 239 | 240 | //处理数据 241 | while(true){ 242 | //保证以帧头为起始 243 | while(!dataTemp.startsWith(frame_head)&&dataTemp.size()>4){ 244 | dataTemp.remove(0,1); //左边移除一字节 245 | } 246 | //小于最小帧长 247 | if(dataTemp.size()<7+2) 248 | return; 249 | //取数据段长度,这里没有判断长度有效性 250 | const int data_size=uchar(dataTemp[4])*0x100+uchar(dataTemp[5]); 251 | if(dataTemp.size()<7+2+data_size) 252 | return; 253 | //帧尾不一致,无效数据--这里懒得写校验位了 254 | if(memcmp(dataTemp.constData()+7+data_size,frameTail,2)!=0){ 255 | dataTemp.clear(); 256 | return; 257 | } 258 | //取数据类型 259 | const char type=dataTemp[6]; 260 | switch(type) 261 | { 262 | case 0x01: //开始发送数据应答 263 | timer->start(0); 264 | emit logMessage("服务器已准备好接收数据,开始发送"+getFilePath()); 265 | break; 266 | case 0x03: //发送数据完成应答 267 | { 268 | //1成功,0失败 269 | const bool result=(dataTemp[7]==(char)0x01); 270 | emit logMessage(QString("服务器文件接收完毕,发送")+(result?"成功":"失败")); 271 | } 272 | break; 273 | case 0x04: //服务端取消发送 274 | doCancel(); 275 | emit logMessage("服务器取消发送,发送终止"); 276 | break; 277 | default: break; 278 | } 279 | //移除处理完的字节 280 | dataTemp.remove(0,7+2+data_size); 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /TcpFileTransfer/ServerOperate.cpp: -------------------------------------------------------------------------------- 1 | #include "ServerOperate.h" 2 | 3 | #include 4 | 5 | ServerOperate::ServerOperate(QObject *parent) 6 | : QObject(parent) 7 | { 8 | initOperate(); 9 | } 10 | 11 | ServerOperate::~ServerOperate() 12 | { 13 | dislisten(); 14 | } 15 | 16 | QString ServerOperate::getFilePath() const 17 | { 18 | QMutexLocker locker(&dataMutex); 19 | return filePath; 20 | } 21 | 22 | void ServerOperate::setFilePath(const QString &path) 23 | { 24 | QMutexLocker locker(&dataMutex); 25 | filePath=path; 26 | } 27 | 28 | bool ServerOperate::isListening() const 29 | { 30 | QMutexLocker locker(&dataMutex); 31 | //这个锁没啥用,毕竟设置不是我控制的,待修改 32 | return server->isListening(); 33 | } 34 | 35 | void ServerOperate::listen(const QString &address, quint16 port) 36 | { 37 | if(server->isListening()) 38 | doDislisten(); 39 | //启动监听 40 | const bool result=server->listen(QHostAddress(address),port); 41 | emit listenStateChanged(result); 42 | emit logMessage(result?"服务启动成功":"服务启动失败"); 43 | } 44 | 45 | void ServerOperate::dislisten() 46 | { 47 | doDislisten(); 48 | emit listenStateChanged(false); 49 | emit logMessage("服务关闭"); 50 | } 51 | 52 | void ServerOperate::cancelFileTransfer() 53 | { 54 | //关闭文件 55 | doCancel(); 56 | //发送停止传输指令 57 | sendData(0x04,QByteArray()); 58 | } 59 | 60 | void ServerOperate::initOperate() 61 | { 62 | server=new QTcpServer(this); 63 | //监听到新的客户端连接请求 64 | connect(server,&QTcpServer::newConnection,this,[this]{ 65 | //如果有新的连接就取出 66 | while(server->hasPendingConnections()) 67 | { 68 | //nextPendingConnection返回下一个挂起的连接作为已连接的QTcpSocket对象 69 | QTcpSocket *new_socket=server->nextPendingConnection(); 70 | emit logMessage(QString("新的客户端连接 [%1:%2]") 71 | .arg(new_socket->peerAddress().toString()) 72 | .arg(new_socket->peerPort())); 73 | //demo只支持一个连接,多余的释放掉 74 | if(socket){ 75 | new_socket->abort(); 76 | new_socket->deleteLater(); 77 | emit logMessage("目前已有客户端连接,新连接已释放"); 78 | continue; 79 | }else{ 80 | socket=new_socket; 81 | } 82 | 83 | //收到数据,触发readyRead 84 | connect(socket,&QTcpSocket::readyRead,[this]{ 85 | //没有可读的数据就返回 86 | if(socket->bytesAvailable()<=0) 87 | return; 88 | //读取数据 89 | operateReceiveData(socket->readAll()); 90 | }); 91 | 92 | //连接断开,销毁socket对象 93 | connect(socket,&QTcpSocket::disconnected,[this]{ 94 | emit logMessage(QString("客户端连接已断开 [%1:%2]") 95 | .arg(socket->peerAddress().toString()) 96 | .arg(socket->peerPort())); 97 | socket->deleteLater(); 98 | socket=nullptr; 99 | }); 100 | } 101 | }); 102 | } 103 | 104 | void ServerOperate::doDislisten() 105 | { 106 | //关闭服务,断开socket连接,释放资源 107 | server->close(); 108 | if(socket){ 109 | socket->abort(); 110 | } 111 | if(file){ 112 | file->close(); 113 | } 114 | } 115 | 116 | void ServerOperate::doCloseFile() 117 | { 118 | if(file){ 119 | file->close(); 120 | delete file; 121 | file=nullptr; 122 | } 123 | } 124 | 125 | void ServerOperate::doCancel() 126 | { 127 | if(file){ 128 | //关闭文件 129 | doCloseFile(); 130 | } 131 | } 132 | 133 | bool ServerOperate::readyReceiveFile(qint64 size, const QString &filename) 134 | { 135 | //重置状态 136 | fileSize=size; 137 | receiveSize=0; 138 | if(file){ 139 | doCloseFile(); 140 | } 141 | //创建qfile用于写文件 142 | file=new QFile(this); 143 | QString file_path=getFilePath(); 144 | if(file_path.isEmpty()) 145 | file_path=QApplication::applicationDirPath(); 146 | file->setFileName(file_path+"/"+filename); 147 | //Truncate清掉原本内容 148 | if(!file->open(QIODevice::WriteOnly)){ 149 | emit logMessage("创建文件失败,无法进行接收"+file->fileName()); 150 | return false; 151 | } 152 | emit logMessage("创建文件成功,准备接收"+file->fileName()); 153 | return true; 154 | } 155 | 156 | void ServerOperate::onReceiveFile(const char *data, qint64 size) 157 | { 158 | if(!file||!file->isOpen()){ 159 | doCancel(); 160 | //发送停止传输指令 161 | sendData(0x04,QByteArray()); 162 | emit logMessage("文件操作失败,取消接收"); 163 | return; 164 | } 165 | if(size>0){ 166 | const qint64 write_size = file->write(data,size); 167 | //感觉这个waitForBytesWritten没啥用啊 168 | if(write_size!=size && !file->waitForBytesWritten(3000)){ 169 | doCancel(); 170 | //发送停止传输指令 171 | sendData(0x04,QByteArray()); 172 | emit logMessage("文件写入超时,取消接收"); 173 | return; 174 | } 175 | } 176 | receiveSize+=size; 177 | //避免除零 178 | if(fileSize>0){ 179 | emit progressChanged(receiveSize*100/fileSize); 180 | } 181 | if(receiveSize>=fileSize){ 182 | doCancel(); 183 | emit logMessage("文件接收完成"); 184 | emit progressChanged(100); 185 | return; 186 | } 187 | } 188 | 189 | void ServerOperate::sendData(char type, const QByteArray &data) 190 | { 191 | //传输协议 192 | //帧结构:帧头4+帧长2+帧类型1+帧数据N+帧尾2(没有校验段,懒得写) 193 | //帧头:4字节定值 0x0F 0xF0 0x00 0xFF 194 | //帧长:2字节数据段长度值 arr[4]*0x100+arr[5] 前面为高位后面为低位 195 | //帧类型:1字节 196 | //- 0x01 准备发送文件,后跟四字节文件长度和N字节utf8文件名,长度计算同帧长一样前面为高位后面为低位 197 | //- 0x02 文件数据 198 | //- 0x03 发送结束 199 | //- 0x04 取消发送 200 | //(服务端收到0x01 0x03开始和结束发送两个命令要进行应答,回同样的命令码无数据段) 201 | //帧尾:2字节定值 0x0D 0x0A 202 | if(!socket->isValid()) 203 | return; 204 | frameHead[6]=type; 205 | const quint64 data_size=data.count(); 206 | frameHead[5]=data_size%0x100; 207 | frameHead[4]=data_size/0x100; 208 | 209 | //发送头+数据+尾 210 | socket->write(frameHead,7); 211 | socket->write(data); 212 | socket->write(frameTail,2); 213 | } 214 | 215 | void ServerOperate::operateReceiveData(const QByteArray &data) 216 | { 217 | static QByteArray frame_head=QByteArray(frameHead,4); 218 | //这里只是简单的处理,所以用了QByteArray容器做缓存 219 | dataTemp+=data; 220 | 221 | //处理数据 222 | while(true){ 223 | //保证以帧头为起始 224 | while(!dataTemp.startsWith(frame_head)&&dataTemp.size()>4){ 225 | dataTemp.remove(0,1); //左边移除一字节 226 | } 227 | //小于最小帧长 228 | if(dataTemp.size()<7+2) 229 | return; 230 | //取数据段长度,这里没有判断长度有效性 231 | const int data_size=uchar(dataTemp[4])*0x100+uchar(dataTemp[5]); 232 | if(dataTemp.size()<7+2+data_size) 233 | return; 234 | //帧尾不一致,无效数据--这里懒得写校验位了 235 | if(memcmp(dataTemp.constData()+7+data_size,frameTail,2)!=0){ 236 | dataTemp.clear(); 237 | return; 238 | } 239 | //取数据类型 240 | const char type=dataTemp[6]; 241 | switch(type) 242 | { 243 | case 0x01: //开始发送数据 244 | { 245 | //这里也可以做个弹框,询问是否接收数据 246 | doCloseFile(); 247 | if(data_size<5){ 248 | //无效帧,没有长度和文件名 249 | break; 250 | } 251 | //解析长度和文件名 252 | qint64 file_size=0; 253 | //file_size+=uchar(dataTemp[7]); 254 | //file_size<<=8; 255 | //file_size+=uchar(dataTemp[8]); 256 | //file_size<<=8; 257 | //file_size+=uchar(dataTemp[9]); 258 | //file_size<<=8; 259 | //file_size+=uchar(dataTemp[10]); 260 | file_size=uchar(dataTemp[7])*0x1000000+ 261 | uchar(dataTemp[8])*0x10000+ 262 | uchar(dataTemp[9])*0x100+ 263 | uchar(dataTemp[10]); 264 | QString file_name=QString::fromUtf8(dataTemp.constData()+7+4,data_size-4); 265 | if(readyReceiveFile(file_size,file_name)){ 266 | emit logMessage("准备好接收客户端文件"); 267 | //应答 268 | sendData(0x01,QByteArray()); 269 | }else{ 270 | emit logMessage("准备接收客户端文件失败"); 271 | } 272 | } 273 | break; 274 | case 0x02: //数据传输 275 | onReceiveFile(dataTemp.constData()+7,data_size); 276 | break; 277 | case 0x03: //发送数据完成 278 | { 279 | doCloseFile(); 280 | emit logMessage("客户端文件发送完毕"); 281 | //应答 282 | sendData(0x03,QByteArray(1,(char)((receiveSize==fileSize)?0x01:0x00))); 283 | } 284 | break; 285 | case 0x04: //客户端取消发送 286 | doCancel(); 287 | emit logMessage("客户端取消发送,发送终止"); 288 | break; 289 | default: break; 290 | } 291 | //移除处理完的字节 292 | dataTemp.remove(0,7+2+data_size); 293 | } 294 | } 295 | --------------------------------------------------------------------------------