前言
首先看网络编程的定义:两个不同主机设备之间的进程通信。C/S(Client-Server)是早期非常典型的软件架构,C/S架构虽然简单,但却非常适用于桌面图形化的QT项目。
本篇的QT项目是从真实的项目中简化出来,满足很多相似的场景:创建一个TCP服务,接收到消息后,通过多线程执行后台CMD命令行,并且自动把程序放到系统自启动目录中。
覆盖到QT的知识点:任务栏托盘、右键菜单、TCP服务、多线程。
功能讲解
1、创建系统托盘
// 创建托盘图标
QSystemTrayIcon *trayIcon = new QSystemTrayIcon();
trayIcon->setIcon(QIcon(":/index/img/default.png")); // 设置托盘图标
// 创建上下文菜单
QMenu *menu = new QMenu();
// 添加菜单项
menu->addAction(exitAction);
trayIcon->setContextMenu(menu);
// 显示托盘图标
trayIcon->show();
2、托盘右键菜单
// 创建上下文菜单
QMenu *menu = new QMenu();
QAction *showAction = new QAction("执行后台命令", &app);//测试用
// 连接信号与槽
QObject::connect(showAction, &QAction::triggered, []() {
//测试:打开谷歌浏览器
QString cmd = "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe";
cmdrun act;
act.Run(cmd);
});
QAction *exitAction = new QAction("退出", &app);
QObject::connect(exitAction, &QAction::triggered, &app, &QApplication::quit);
// 添加菜单项
menu->addAction(showAction);
menu->addAction(exitAction);
trayIcon->setContextMenu(menu);
showAction事件只是用来测试点击右键之后,执行本地的CMD命令是否正常,适合用来调用第三方软件达到指定的目的(比如通过window的powershell脚本获取指定EXE执行程序的图标),调通之后就可以注释掉,用TCP创建的服务执行本地的CMD命令。
3、TCP服务
首先,需要在.pro文件中加上network,编译时才不会报错
QT += core gui network
tcp服务头文件:
#ifndef MYTCPSERVER_H
#define MYTCPSERVER_H
#include <QTcpServer>
#include <QTcpSocket>
#include <QDebug>
#include <QObject>
//请求包结构
typedef struct netPackQ
{
char opCode[2];//暗号校验头--校验不对的直接丢弃
char keyword[254];//交互的核心内容
netPackQ(){
memset(opCode,0,2);
memset(keyword,0,254);
}
} netPackQ;
class Mytcpserver: public QObject {
Q_OBJECT
public:
Mytcpserver(QObject *parent = nullptr);
private slots:
void onNewConnection();
private:
QTcpServer *server;
};
#endif // MYTCPSERVER_H
TCP主文件:
#include "mytcpserver.h"
#include "cmdrun.h"
Mytcpserver::Mytcpserver(QObject *parent) : QObject(parent) {
// 创建 TCP 服务器
server = new QTcpServer(this);
// 连接新连接信号到槽函数
connect(server, &QTcpServer::newConnection, this, &Mytcpserver::onNewConnection);
// 绑定到 10086 端口
if (!server->listen(QHostAddress::Any, 10086)) {
qDebug() << "Server could not start!";
} else {
qDebug() << "Server started on port 10086.";
}
}
void Mytcpserver::onNewConnection() {
QTcpSocket *socket = server->nextPendingConnection();
// 连接读取数据信号
connect(socket, &QTcpSocket::readyRead, [socket]() {
netPackQ packet;
qint64 bytesReceived = socket->read(reinterpret_cast<char*>(&packet), sizeof(netPackQ));
if (bytesReceived == sizeof(netPackQ)) {
// 处理接收到的数据
qDebug() << "Received date";
if(packet.opCode[0] == 0x01 && packet.opCode[1]==9){//执行后台命令,打开指定应用。比如:C:\Windows\notepad.exe
qDebug() << "Received keyword:" << packet.keyword;
cmdrun act;
act.Run(packet.keyword);//====处理核心内容=====
}
socket->write("Received sucess\n"); // 回复客户端
} else {
qDebug() << "Received incomplete data";
}
});
// 连接断开信号
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
}
4、多线程执行后台CMD命令
直接用进程执行CMD命令会导致托盘卡顿,所以得用多线程来执行,cmdrun类很简单,如下:
//头文件:cmdrun.h
#ifndef CMDRUN_H
#define CMDRUN_H
#include<QString>
#include "threadCmd.h"
class cmdrun
{
public:
cmdrun();
~cmdrun();
int Run(QString cmd);
};
#endif // CMDRUN_H
//cpp文件:cmdrun.cpp
#include "cmdrun.h"
#include <QDebug>
cmdrun::cmdrun()
{
}
cmdrun::~cmdrun()
{
}
int cmdrun::Run(QString cmd){
//qDebug() << __LINE__ << cmd;
ThreadCmd *thread = new ThreadCmd(cmd);
thread->start();
return 1;
}
5、多进程执行CMD命令
//头文件:threadCmd.h
#ifndef THREADCMD_H
#define THREADCMD_H
#include <QThread>
#include <QString>
class ThreadCmd : public QThread
{
Q_OBJECT
public:
explicit ThreadCmd(const QString ¶m) ;
protected:
void run() override;
private:
QString m_param;
};
#endif // THREADCMD_H
//cpp文件:threadCmd.cpp
#ifndef THREADCMD_CPP
#define THREADCMD_CPP
#include "threadCmd.h"
#include <QDebug>
#include <QProcess>
#include <QTextCodec>
ThreadCmd::ThreadCmd(const QString ¶m) : m_param(param) {
}
void ThreadCmd::run(){
QProcess process;
// 执行命令
QString cmd = "D:\\cmdexec.cmd \"" + m_param +"\"";
//QString cmd = m_param;
//qDebug() << __LINE__ << cmd;
// 启动快捷方式
if (QProcess::startDetached(cmd)) {
qDebug() << "Application launched successfully.";
} else {
qDebug() << "Failed to launch application.";
}
// 进程使用完毕后,可以手动删除
process.deleteLater();
}
#endif // THREADCMD_CPP
6、开机自启动
系统托盘通常都有开机自启动的需求,可以手动拷贝到启动目录,或者在程序中编写把应用程序的.lnk文件放到自启动目录中:
void addToStartup(const QString appPath) {
QString startupPath = QStandardPaths::writableLocation(QStandardPaths::ApplicationsLocation) + "/Startup/";
//qDebug()<< __LINE__ << startupPath;
// 创建启动文件夹(如果不存在)
QDir dir(startupPath);
if (!dir.exists()) {
dir.mkpath(".");
}
// 创建快捷方式(Windows 特有)
QString shortcutPath = startupPath + "cstsvr.lnk";
if (!QFile::exists(shortcutPath)) {
// 使用 PowerShell 创建快捷方式 (请根据实际情况调整)
QString command = QString("powershell -command \"$ws = New-Object -ComObject WScript.Shell; $s = $ws.CreateShortcut('%1'); $s.TargetPath='%2'; $s.Save()\"").arg(shortcutPath).arg(appPath);
QProcess::execute(command);
}
}
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// 获取当前应用程序的完整路径
QString appPath = QCoreApplication::applicationFilePath();
//qDebug()<< __LINE__ << appPath;
addToStartup(appPath);// 添加自启动到启动文件夹
.......
}
篇尾
不同主机之间的进程间通信,通常需要设定一端为服务端进行侦听,当然也可以两端都设置为服务端,服务端≠服务器,服务端只代表某项服务的侦听端,比如本篇涉及的项目需求是,国产Linux系统需要获取window系统的所有应用程序的图标,并且取回到国产Linux系统的QT界面中展示,此时就需要在window系统上挂着【根据exe应用路径获取图片】的服务端,并把文件回传给Linux。