命令模式是编程设计模式中的一种,这里介绍命令模式在Qt网络编程中的使用,讲述如何实现“一个类就是一个命令”的设计思想。
基本思想
-
把向服务器发起的请求抽象为一个C++类,相似的请求可以封装为同一个类,通过操作类型来区分。(命令的操作类型在服务器端需用,在客户端解析服务器反馈的数据时也需用。);
-
每增加一个网络命令,就增加一个C++类,这些命令类有共同的基类;
-
在命令基类里声明了接口函数(C++中为纯虚函数)供每个命令子类独立实现;
-
当使用命令时,只需要构造出命令子类(等号左边为基类指针变量,等号右边通过new关键字创建命令子类),设置好命令执行需要的相关参数,最后调用命令执行函数exec()即可向服务器发送数据。
类图.png
命令基类
commandabstract .h
#ifndef COMMANDABSTRACT_H
#define COMMANDABSTRACT_H
#include <QObject>
#include <QVariantHash>
class QTcpSocket;
class CommandAbstract : public QObject
{
Q_OBJECT
public:
explicit CommandAbstract(QObject *parent = 0);
//接口:准备要发送给服务器的数据
//参数:cmdArgs用于传递要发送给服务器的数据(或叫做命令参数)
//说明:在命令子类中实现该接口,并将命令参数保存到m_sendingData成员变量中
//(m_sendingData为最终发送给服务器的数据)
virtual void prepareSendingData(const QVariantHash& cmdArgs) = 0;
//接口:解析来自服务器的响应数据,保存到m_parsedResponseData成员变量中
//参数:data为从服务器读取到的响应数据
//说明:在命令子类中实现该接口,每个网络命令都有不同的解析方式,因此这里抽象为接口
virtual void parseResponseData(const QByteArray& data) = 0;
//获取要发送给服务器的数据m_sendingData
virtual QByteArray getSendingData() const;
//设置要发送给服务器的数据m_sendingData
virtual void setSendingData(const QByteArray& data);
//执行命令,即:向服务器发送数据m_sendingData
void exec();
//设置QTcpSocket指针到命令类中,以实现网络通信功能
void setTcpSocket(QTcpSocket* tcpSocket);
//设置命令序列,记录命令编号,根据需要设置
void setCmdIndex(int index);
//设置命令操作类型(当相同的命令子类需要实现不同的功能时使用,如:当要使用相同的
//命令子类发送不同的数据到服务器时,通过操作类型来区分服务器响应的数据)
void setOperType(int operType);
protected:
int getCmdIndex() const;
//获取命令操作类型
int getCmdOperType() const;
signals:
//通过信号对外通知命令执行数据m_parsedResponseData
void infoResultData(QVariantHash& parsedResponseData,QString& errorString);
public slots:
//当接收到网络数据响应时执行该槽函数
void onReadyRead();
private:
QVariantHash m_cmdData;//命令参数
QByteArray m_sendingData;//最终发送给服务器的命令数据
QVariantHash m_parsedResponseData;//解析之后的服务器响应数据
int m_cmdIndex;
int m_cmdOperType;//命令操作类型
QTcpSocket *m_tcpSocket;
};
#endif // COMMANDABSTRACT_H
commandabstract .cpp
#include "commandabstract.h"
#include <QTcpSocket>
CommandAbstract::CommandAbstract(QObject *parent) :
QObject(parent),
m_cmdIndex(0),
m_cmdOperType(0),
m_tcpSocket(NULL)
{
}
QByteArray CommandAbstract::getSendingData() const
{
return m_sendingData;
}
void CommandAbstract::setSendingData(const QByteArray &data)
{
m_sendingData = data;
}
void CommandAbstract::exec()
{
if(m_tcpSocket == NULL)
return;
m_tcpSocket->write(m_sendingData);
}
void CommandAbstract::setTcpSocket(QTcpSocket *tcpSocket)
{
m_tcpSocket = tcpSocket;
connect(m_tcpSocket,SIGNAL(readyRead()),this,SLOT(onReadyRead()));
}
void CommandAbstract::setCmdIndex(int index)
{
m_cmdIndex = index;
}
void CommandAbstract::setOperType(int operType)
{
m_cmdOperType = operType;
}
int CommandAbstract::getCmdIndex() const
{
return m_cmdIndex;
}
int CommandAbstract::getCmdOperType() const
{
return m_cmdOperType;
}
void CommandAbstract::onReadyRead()
{
QByteArray readData = m_tcpSocket->readAll();
parseResponseData(readData);
}
命令子类
这里DemoCommand为命令子类,实际中可以根据需要派生出许多这样的子类向服务器发送不同的命令请求。DemoCommand类实现了基类中的两个接口函数。本例中,DemoCommand命令把数据key1和key2发送给服务程序,服务程序又将数据原样返回到客户端。
democommand.h
#ifndef DEMOCOMMAND_H
#define DEMOCOMMAND_H
#include <QObject>
#include "commandabstract.h"
class DemoCommand : public CommandAbstract
{
Q_OBJECT
public:
explicit DemoCommand(QObject *parent = 0);
virtual void prepareSendingData(const QVariantHash& cmdArgs);
virtual void parseResponseData(const QByteArray& data);
};
#endif // DEMOCOMMAND_H
democommand.cpp
#include "democommand.h"
#include <QDebug>
#include <QJsonObject>
#include <QJsonDocument>
#include <QVariantHash>
DemoCommand::DemoCommand(QObject *parent):CommandAbstract(parent)
{
}
//把发送给服务器的数据key1和key2以json格式设置到命令基类中,共命令执行函数exec()使用
void DemoCommand::prepareSendingData(const QVariantHash &cmdArgs)
{
QString data1 = cmdArgs.value("key1").toString();
QString data2 = cmdArgs.value("key2").toString();
QJsonObject jsonObj;
jsonObj.insert("key1",data1);
jsonObj.insert("key2",data2);
QJsonDocument jsonDoc(jsonObj);
this->setSendingData(jsonDoc.toJson());
qDebug()<<"m_sendingData = "<<this->getSendingData();
}
//解析来自服务器的反馈数据,并通过信号发送到界面
void DemoCommand::parseResponseData(const QByteArray &data)
{
//根据不同的操作类型,对服务器返回数据data进行解析
qDebug()<<__LINE__<<__FUNCTION__<<"this->getCmdOperType() = "<<this->getCmdOperType();
qDebug()<<__LINE__<<__FUNCTION__<<"Read data = "<<data;
QVariantHash response;
response.insert("response",data);
emit this->infoResultData(response,QString());
switch (this->getCmdOperType()) {
case 1:{
}break;
case 2:{
}break;
case 3:{
}break;
default:
break;
}
this->deleteLater();
}
使用方法
void MainWindow::on_pushButtonSendCmd_clicked()
{
if(!m_tcpSocked->isOpen()){
m_tcpSocked->connectToHost("127.0.0.1",9090);
m_tcpSocked->waitForConnected();
}
QVariantHash cmdArgs;
cmdArgs.insert("key1","data1");
cmdArgs.insert("key2","data2");//data1和data2是模拟要发送给服务器的命令数据
CommandAbstract* cmd = new DemoCommand();
connect(cmd,SIGNAL(infoResultData(QVariantHash&,QString&)),this,SLOT(onInfoResultData(QVariantHash&,QString&)));
cmd->setCmdIndex(1);
cmd->setOperType(1);
cmd->prepareSendingData(cmdArgs);
cmd->setTcpSocket(m_tcpSocked);
cmd->exec();
}
//接收来自服务器反馈的数据
void MainWindow::onInfoResultData(QVariantHash &parsedResponseData, QString &errorString)
{
QString dataFromServer = parsedResponseData.value("response").toString();
ui->textEdit->append(dataFromServer);
}
运行效果
客户程序.png服务程序.png
源代码下载
百度网盘分享地址:
链接:https://pan.baidu.com/s/1O36ltEmhY_ptAlTu-MpJ4w
提取码:et2w
为了方便示例程序的运行,专门附带了服务器端的命令行可执行程序commandmodeserver.exe,本例中的客户端程序编译运行之后,会与它连接,它会反馈客户端发送的数据。
网友评论