Qt+webservice的多线程实现

项目使用Qt搭建了一个数据库软件,需要远程访问公司的MES系统,使用webservice技术进行通信并以XML格式传输数据,为了使网络监听过程中不影响主线程程序的正常运行,我们需要将webservice相关功能放在新开的独立线程中。

本项目使用的是QtCreator(Qt5.5.0)+VisualStudio2013+gSOAP2.8搭建。其他版本只要版本是正确对应的,都大同小异。

WebService相关概念

参见参考文献[3]。

WebService是一种跨编程语言和跨操作系统平台的远程调用技术。所谓跨编程语言和跨操作平台,就是说服务端和和客户端的搭建平台与编程语言可以都不相同。所谓远程调用,就是一台计算机a上 的一个程序可以调用到另外一台计算机b上的一个对象的方法。

其实可以从多个角度来理解WebService,从表面上看,WebService就是一个应用程序向外界暴露出一个能通过Web进行调用的API,也就是说能用编程的方法通过 Web来调用这个应用程序。我们把调用这个WebService的应用程序叫做客户端,而把提供这个WebService的应用程序叫做服务端。从深层次看,WebService是建立可互操作的分布式应用程序的新平台,是一个平台,是一套标准。它定义了应用程序如何在Web上实现互操作性,你可以用任何 你喜欢的语言,在任何你喜欢的平台上写Webservice ,只要我们可以通过Webservice标准对这些服务进行查询和访问。

WebService平台需要一套协议来实现分布式应用程序的创建。任何平台都有它的数据表示方法和类型系统。要实现互操作性,WebService平台必须提供一套标准的类型系统,用于沟通不同平台、编程语言和组件模型中的不同类型系统。Webservice平台必须提供一种标准来描述Webservice,让客户可以得到足够的信息来调用这个Webservice。最后,我们还必须有一种方法来对这个Webservice进行远程调用,这种方法实际是一种远程过程调用协议(RPC)。为了达到互操作性,这种RPC协议还必须与平台和编程语言无关。

XML+XSD,SOAP和WSDL就是构成WebService平台的三大技术。

  • XML+XSD
    WebService采用HTTP协议传输数据,采用XML格式封装数据(即XML中说明调用远程服务对象的哪个方法,传递的参数是什么,以及服务对象的 返回结果是什么),XML是WebService平台中表示数据的格式,其优点在于它既是平台无关又是厂商无关的。
    XML解决了数据表示的问题,但它没有定义一套标准的数据类型,更没有说怎么去扩展这套数据类型。例如整形数到底代表什么?16位,32位,64位?这些细节对实现互操作性很重要。XML Schema(XSD)就是专门解决这个问题的一套标准。它定义了一套标准的数据类型,并给出了一种语言来扩展这套数据类型。WebService平台就是用XSD来作为其数据类型系统的。当你用某种语言(如VB.NET或C#)来构造一个Webservice时,为了符合WebService标准,所有你使用的数据类型都必须被转换为XSD类型。你用的工具可能已经自动帮你完成了这个转换,但你很可能会根据你的需要修改一下转换过程。

  • SOAP
    SOAP是"简单对象访问协议",SOAP协议 = HTTP协议 + XML数据格式。
    WebService通过HTTP协议发送请求和接收结果时,发送的请求内容和结果内容都采用XML格式封装,并增加了一些特定的HTTP消息头,以说明HTTP消息的内容格式,这些特定的HTTP消息头和XML内容格式就是SOAP协议。SOAP提供了标准的RPC方法来调用WebService。
    SOAP协议定义了SOAP消息的格式,SOAP协议是基于HTTP协议的,SOAP也是基于XML和XSD的,XML是SOAP的数据编码方式。打个比喻:HTTP就是普通公路,XML就是中间的绿色隔离带和两边的防护栏,SOAP就是普通公路经过加隔离带和防护栏改造过的高速公路。

  • WSDL
    好比我们去商店买东西,首先要知道商店里有什么东西可买,然后再来购买,商家的做法就是张贴广告海报。 WebService也一样,WebService客户端要调用一个WebService服务,首先要有知道这个服务的地址在哪,以及这个服务里有什么方法可以调用,所以WebService务器端首先要通过一个WSDL文件来说明自己家里有啥服务可以对外调用,服务是什么(服务中有哪些方法,方法接受的参数是什么,返回值是什么),服务的网络地址用哪个url地址表示,服务通过什么方式来调用。
    WSDL(Web Services Description Language)就是这样一个基于XML的语言,用于描述WebService及其函数、参数和返回值。它是WebService客户端和服务器端都能理解的标准格式。因为是基于XML的,所以WSDL既是机器可阅读的,又是人可阅读的,这将是一个很大的好处。一些最新的开发工具既能根据你的Webservice生成WSDL文档,又能导入WSDL文档,生成调用相应WebService的代理类代码。
    WSDL文件保存在Web服务器上,通过一个url地址就可以访问到它。客户端要调用一个WebService服务之前,要知道该服务的WSDL文件的地址。 WebService服务提供商可以通过两种方式来暴露它的WSDL文件地址:1.注册到UDDI服务器,以便被人查找;2.直接告诉给客户端调用者。

gSOAP总结

gsoap概念:是一种能够把C/C++语言的接口转换成基于soap协议的webservice服务的工具。从官网的说明文档可知gSOAP可以为我们完成以下工作:

1、自动生成C和C++源代码,以使用和部署XML、JSON REST API以及SOAP/XML API;
2、使用gSOAP的快速XML流处理模型进行XML解析和验证,实现实现可移植的快速的和精简的API,每秒处理10K+消息仅需几KB代码和数据;
3、将WSDL转换为有效的C或C++源代码以使用或部署XML Web服务;
4、将XML schemas(XSD)转换为高效的C或C++源代码,以使用gSOAP全面的XML schemas功能覆盖来使用或部署XML REST API;
5、为WSDL和/或XSD定义的大型复杂XML规范生成高效的C和C ++代码,例如eBay,ONVIF,HL7,FHIR,HIPAA 5010,CDISC,XMPP XEP,TR-069,AWS,EWS,ACORD,ISO 20022和SWIFT,FixML,XBRL,OTA,IATA NDC,FedEx等(您甚至可以将多个组合在一起);
6、安全的XML处理过程:gSOAP不容易受到大部分XML攻击手段的攻击;
7、使用强大的XML数据绑定准确地序列化XML中的C和C++数据,这有助于通过静态类型快速开发类型安全的API,以避免运行时错误;
8、在WSDL和XSD文件上使用wsdl2h选项-O2或-O4进行“schema slicing”,通过自动删除未使用的schema类型(WSDL和XSD根目录中无法访问的类型)来优化XML代码库的大小;
9、使用和部署JSON REST API;
10、使用SAML令牌安全地使用HTTP/S,WS-Security和WS-Trust来使用和部署API;
11、使用测试信使CLI测试您的SOAP/XML API,它会自动生成基于WSDL的完整和随机化的SOAP/XML消息(使用带有选项-g的wsdl2h和soapcpp2)来测试Web服务API和客户端;
12、使用gSOAP Apache模块在Apache Web服务器中部署API,在IIS中使用gSOAP ISAPI扩展部署API;
13、使用gSOAP cURL插件和gSOAP WinInet插件来使用API;
14、符合XML的行业标准,包括XML REST和SOAP,WSDL,MIME,MTOM,DIME,WS-Trust,WS-Security(集成了WS-Policy和WS-SecurityPolicy),WS-ReliableMessaging,WS-Discovery,WS-Addressing等;

gSOAP简介

gSOAP一种跨平台的开源的C/C++软件开发工具包。生成C/C++的RPC代码,XML数据绑定,对SOAP Web服务和其他应用形成高效的具体架构解析器,它们都受益于一个XML接口。 这个工具包提供了一个全面和透明的XML数据绑定解决方案,Autocoding节省大量开发时间来执行SOAP/XML Web服务中的C/C++。此外,使用XML数据绑定大大简化了XML自动映射。应用开发人员不再需要调整应用程序逻辑的具体库和XML为中心的数据。

gSOAP结构

使用gSOAP首先需要用到了两个工具就是../gsoap-2.8/gsoap/bin/win32/wsdl2h.exe../gsoap-2.8/gsoap/bin/win32/soapcpp2.exe,用于自动生成包含接口的c/c++源文件。

wsdl2h.exe

该工具可以根据输入的wsdl或XSD或URL产生相应的C/C++形式的.h供soapcpp2.exe使用。示例如下:

新建一个文件夹,将wsdl2h.exe(和.wsdl文件)放入,命令行进入当前路径后输入以下命令:

wsdl2h [options] XSD and WSDL files ...

wsdl2h -o file.h file1.wsdl
wsdl2h -o file.h http://www.genivia.com/calc.wsdl

根据WSDL自动生成file.h头文件,以供soapcpp2.exe使用。

wsdl2h主要的运行选项如下:

选项描述
-a对匿名类型产生基于顺序号的结构体名称
-b提供单向响应消息的双向操作(双工)
-c生成c代码
-c++生成c++代码
-c++11生成c++11代码
-d使用DOM填充xs:any和xsd:anyType元素
-D使用指针使具有默认值的属性成员可选
-e不要限定枚举名称,此选项用于向后兼容gSOAP 2.4.1及更早版本,该选项不会生成符合WS-I Basic Profile 1.0a的代码。
-f为schema扩展生成平面C++类层次结构
-g生成全局顶级元素声明
-h显示帮助信息
-I path包含文件时指明路径,相当于#import
-i不导入(高级选项)
-j不生成SOAP_ENV__Header和SOAP_ENV__Detail定义
-k不生成SOAP_ENV__Header mustUnderstand限定符
-l在输出中包含许可证信息
-m使用xsd.h模块导入基元类型
-N name用name 来指定服务命名空间的前缀
-n name用name 作为命名空间的前缀取代缺省的ns
-O1通过省略重复的选择/序列成员来优化
-O2优化-O1并省略未使用的模式类型(从根目录无法访问)
-o file输出文件名
-P不要创建从xsd__anyType继承的多态类型
-p创建从base xsd__anyType继承的多态类型,当WSDL包含多态定义时,会自动执行此操作
-q name使用name作为所有声明的C++命名空间
-R在WSDL中为REST绑定生成REST操作
-r host[:port[:uid:pwd]]通过代理主机,端口和代理凭据连接
-r:uid:pwd连接身份验证凭据(验证身份验证需要SSL)
-s不生成STL代码(没有std :: string和没有std :: vector)
-t file使用类型映射文件而不是默认文件typemap.dat
-U将Unicode XML名称映射到UTF8编码的Unicode C/C++标识符
-u不产生工会unions
-V显示当前版本并退出
-v详细输出
-W抑制编译器警告
-w始终在响应结构中包装响应参数
-x不生成_XML any/anyAttribute可扩展性元素
-y为结构和枚举生成typedef同义词

soapcpp2.exe

该工具是一个根据.h文件生成若干支持webservice代码文件生成工具,生成的代码文件包括webservice客户端和服务端的实现框架,XML数据绑定等,具体说明如下:

新建一个文件夹,将soapcpp2.exe.h文件放入,命令行进入当前路径后输入以下命令:

soapcpp2 [options] header_file.h

soapcpp2 mySoap.h
soapcpp2 -c -r -CL calc.h

会生成如下c类型文件:
[外链图片转存失败(img-WtNYPDjL-1567667757111)(https://github.com/KunBB/MarkdownPhotos/blob/master/QtWebservice/Figure_1.jpg?raw=true)]

使用命令-i会生成如下c++类型文件:
[外链图片转存失败(img-x8rFsHgR-1567667757111)(https://github.com/KunBB/MarkdownPhotos/blob/master/QtWebservice/Figure_2.jpg?raw=true)]

文件描述
soapStub.h根据输入的.h文件生成的数据定义文件,一般我们不直接引用它
soapH.h所有客户端服务端都应包含的主头文件
soapC.cpp指定数据结构的序列化和反序列化方法
soapClient.cpp用于远程操作的客户端存根例程
soapServer.cpp服务框架例程
soapClientLib.cpp客户端存根与本地静态(反)序列化器结合使用
soapServerLib.cpp服务框架与本地静态(反)序列化器结合使用
soapXYZProxy.h使用选项-i:c++服务端对象(与soapC.cpp和soapXYZProxy.cpp链接)
soapXYZProxy.cpp使用选项-i:客户端代码
soapXYZService.h使用选项-i:server对象(与soapC.cpp和soapXYZService.cpp链接)
soapXYZService.cpp使用选项-i:服务端代码
.xsdns.XSD由XML Schema生成,ns为命名空间前缀名,我们可以看看是否满足我们的协议格式(如果有此要求)
.wsdlns.wsdl由WSDL描述生成
.xml生成了几个SOAP/XML请求和响应文件。即满足webservice定义的例子message(实际的传输消息),我们可以看看是否满足我们的协议格式(如果有此要求)
.nsmap根据输入soapcpp2.exe的头文件中定义的命名空间前缀ns生成ns.nsmap,该文件包含可在客户端和服务端使用的命名空间映射表

soapcpp的主要运行选项如下:

选项描述
-1生成SOAP 1.1绑定
-2生成SOAP 1.2绑定
-0没有SOAP绑定,使用REST
-C仅生成客户端代码
-S仅生成服务端代码
-T生成服务端自动测试代码
-Ec为深度数据复制生成额外的例程
-Ed为深度数据删除生成额外的例程
-Et使用walker函数为数据遍历生成额外的例程
-L不生成soapClientLib / soapServerLib
-a使用SOAPAction和WS-Addressing来调用服务端操作
-A要求SOAPAction调用服务端操作
-b序列化字节数组char [N]为字符串
-c生成纯C代码
-d 保存到指定目录
-e生成SOAP RPC编码样式绑定
-f N多个soapC文件,每个文件有N个序列化程序定义(N≥10)
-h打印一条简短的用法消息
-i生成从soap struct继承的服务代理类和对象
-j生成可以共享soap结构的C++服务代理类和对象
-I 包含其他文件时使用,指明 < path > (多个的话,用`:'分割),相当于#import ,该路径一般是gSOAP目录下的import目录,该目录下有一堆文件供soapcpp2生成代码时使用
-l生成可链接模块(实验)
-m为MEX编译器生成Matlab代码
-n用于生成支持多个客户端和服务器端
-p 生成的文件前缀采用< name > ,而不是缺省的 “soap”
-q 使用name作为c++所有声明的命名空间
-r生成soapReadme.md报告
-s生成的代码在反序列化时,严格检查XML的有效性
-t生成的代码在发送消息时,采用xsi:type方式
-u通过抑制XML注释来取消注释WSDL / schema输出中的注释
-V显示当前版本并退出
-v详细输出
-w不生成WSDL和schema文件
-x不生成示例XML消息文件
-y在示例XML消息中包含C / C ++类型访问信息

实例介绍

功能介绍

1、客户端能够向服务端发送字符串数据;
2、服务端能够接收到客户端发送的字符串数据;
3、服务端对字符串数据解析、查重并录入数据库;
4、软件窗口显示连接状态。

最终效果如下所示:
点击“开始连接”按钮:
[外链图片转存失败(img-lLGL2EqX-1567667757112)(https://github.com/KunBB/MarkdownPhotos/blob/master/QtWebservice/Figure_4.png?raw=true)]

从客户端接收到一组数据后:
[外链图片转存失败(img-FErlePrT-1567667757112)(https://github.com/KunBB/MarkdownPhotos/blob/master/QtWebservice/Figure_5.png?raw=true)]

实例步骤

1. 生成源代码
由于没有.wsdl文件,因此我们跳过wsdl2h.exe这一步骤,手动编写供soapcpp2.exe使用的头文件mySoap.h

//gsoap ns service name: sendMsg
//gsoap ns service style: rpc
//gsoap ns service encoding: encoded
//gsoap ns service namespace: urn:sendMsg

int ns__sendMsg(char* szMsgXml,struct nullResponse{} *out);

新建文件夹,将soapcpp2.exemySoap.h放入,打开命令后进入该目录下,运行命令soapcpp2 mySoap.h,生成如下文件:
[外链图片转存失败(img-noE3eTSN-1567667757112)(https://github.com/KunBB/MarkdownPhotos/blob/master/QtWebservice/Figure_3.jpg?raw=true)]

2. 客户端程序编写
在QtCreator中向客户端工程目录添加文件sendMsg.nsmapsoapH.hsoapStub.hstdsoap2.hsoapC.cppsoapClient.cppstdsoap2.cpp(stdsoap2.hstdsoap2.cpp位于文件夹../gsoap-2.8/gsoap)。

客户端项目构建的是一个控制台程序,因此直接在main函数里进行编写代码:

#include <QCoreApplication>
#include "stdio.h"
#include "gservice.nsmap"
#include "soapStub.h"

int main(int argc, char *argv[])
{
    QCoreApplication a(argc, argv);
    printf("The Client is runing...\n");

    struct soap *CalculateSoap = soap_new();

    char server_addr[] = "127.0.0.1:8080"; // url地址,IP号+设备端口号
    char* data = "flange,6,7,8,9,10,11,12,13"; // 所需传递的数据

    nullResponse result; // 用于sendMsg的空返回值
    int iRet = soap_call_ns__sendMsg(CalculateSoap,server_addr,NULL,data,&result); // 调用之前定义的ns__sendMsg方法(即服务端提供的方法)

    if ( iRet == SOAP_ERR){
        printf("Error while calling the soap_call_ns__sendmsg");
    }
    else
    {
        printf("Calling the soap_call_ns__add success。\n");
    }

    soap_end(CalculateSoap);
    soap_done(CalculateSoap);

    return a.exec();
}

3. 服务端程序编写
在QtCreator中向服务端工程目录添加文件sendMsg.nsmapsoapH.hsoapStub.hstdsoap2.hsoapC.cppsoapServer.cppstdsoap2.cpp

由于服务端网络通信功能需要不断对端口进行监听,因此为避免影响软件其他功能的运行,在此需要新开一条线程。项目头文件源码如下:
socketconnect.h:

#ifndef SOCKETCONNECT_H
#define SOCKETCONNECT_H

#include <QWidget>
#include <QDebug>
#include <QVector>
#include "webservice_thread.h" // 多线程头文件
#include <QMessageBox>

namespace Ui {
class SocketConnect;
}

class SocketConnect : public QWidget
{
    Q_OBJECT

public:
    void setDbconn(QSqlDatabase *dbconn); // 主线程数据库
    explicit SocketConnect(QWidget *parent = 0);
    ~SocketConnect();

private slots:
    void pB_lj_clicked(); // 连接按钮
    void pB_dk_clicked(); // 断开连接按钮
    void linkState_accept(QVector<QString>); // 用于在窗口显示连接状态
    void data_accept(QVector<QString>); // 接收数据,检查后录入数据库

private:
    QSqlDatabase *dbconn;
    WebserviceThread *WebT;
    QVector<QString> v_linkstate;

    Ui::SocketConnect *ui;
};

#endif // SOCKETCONNECT_H

socketconnect.cpp:

#include "socketconnect.h"
#include "ui_socketconnect.h"
#include "scrollwidget.h"

SocketConnect::SocketConnect(QWidget *parent) :
    QWidget(parent),
    ui(new Ui::SocketConnect)
{
    ui->setupUi(this);

    ui->tabWidget->setTabText(1, QStringLiteral("法兰盘部件"));
    ui->tabWidget->setCurrentIndex(0);

    ui->lineEdit->setText("127.0.0.1"); // 暂时无用
    ui->lineEdit_2->setText("8080");
    ui->lineEdit_3->setText("helloworld");
    ui->lineEdit_4->setText("helloworld");
    ui->lineEdit_4->setEchoMode(QLineEdit::Password);

    ui->tableWidget_fla->setEditTriggers(QAbstractItemView::NoEditTriggers); // 表格不可编辑
    ui->tableWidget_fla->setSelectionBehavior(QAbstractItemView::SelectRows);

    ui->tableWidget_fla->setAlternatingRowColors(true);
    ui->tableWidget_fla->setStyleSheet("QTableView{alternate-background-color: rgb(183, 242, 238);}"); // 设置隔行换色

    connect(ui->pushButton_lj,SIGNAL(clicked(bool)),this,SLOT(pB_lj_clicked()));
    connect(ui->pushButton_dk,SIGNAL(clicked(bool)),this,SLOT(pB_dk_clicked()));
}

SocketConnect::~SocketConnect()
{
    delete ui;
}

void SocketConnect::setDbconn(QSqlDatabase *db){
    this->dbconn=db;
}

void SocketConnect::pB_lj_clicked()
{
    if(ui->lineEdit_3->text()=="helloworld"&&ui->lineEdit_4->text()=="helloworld"){
        WebT = new WebserviceThread(this);
        int port = ui->lineEdit_2->text().toInt();
        WebT->setParameters(port);
        connect(WebT,SIGNAL(linkState_send(QVector<QString>)),this,SLOT(linkState_accept(QVector<QString>)));
        connect(ui->pushButton_dk,SIGNAL(clicked(bool)),WebT,SLOT(stopclick_accept()));
        connect(WebT,SIGNAL(data_send(QVector<QString>)),this,SLOT(data_accept(QVector<QString>)));
        WebT->start(); // 进入线程
    }
    else{
        QMessageBox::critical(NULL, QString::fromLocal8Bit("警告"), QStringLiteral("账号或密码错误,无法连接!"), QMessageBox::Yes);
    }
}

void SocketConnect::linkState_accept(QVector<QString> link_state){
    ScrollWidget *s_linkstate = new ScrollWidget; // 自己定义的QWidget的子类,用于增加、更新、清空QList<QWidget*>

    QString linkstate;
    for(int i=0;i<link_state.size();i++){
        linkstate+=link_state[i];
    }
    v_linkstate.append(linkstate); // 存储线程文件传过来的连接状态

    for(int i=0;i<v_linkstate.size();i++){
        QLabel *label_linkstate = new QLabel(v_linkstate[i]);
        s_linkstate->addWidget(label_linkstate);
    }
    s_linkstate->updateWidget();
    ui->scrollArea->setWidget(s_linkstate); // 更新scrollarea
}

void SocketConnect::data_accept(QVector<QString> content){
    dbconn->open();

    QString dataClass = content[0]; // 第一个数据存储了数据类型信息
    content.erase(content.begin());

    //检查编号数据是否重复或为空
    dbconn->open();
    if(content[0].isEmpty()){
        QMessageBox::critical(NULL, QString::fromLocal8Bit("警告"), QStringLiteral("远程传输数据的编号为空,请检查并重新录入!"), QMessageBox::Yes);
        return;
    }
    QSqlQueryModel *model_sql = new QSqlQueryModel;
    model_sql->setQuery(QString("SELECT * FROM wbq.%1").arg(dataClass));

    for(int k=0 ;k<model_sql->rowCount(); k++){
        QModelIndex index = model_sql->index(k,0);
        if(content[0] == model_sql->data(index).toString()){
            QMessageBox::critical(NULL, QString::fromLocal8Bit("警告"), QStringLiteral("远程传输数据的编号已存在于数据库中,请检查并重新录入!"), QMessageBox::Yes);
            return;
        }
    }
    //结束检查

    if(dataClass=="flange"){
        ui->tabWidget->setCurrentIndex(1);

        //数据写入MySQL数据库
        QSqlQueryModel *model = new QSqlQueryModel;
        model->setQuery(QStringLiteral("INSERT INTO wbq.coordinator SET 编号=%1, ......").arg(content[0])......; // 输入数据
        dbconn->close();
        delete model;

        //数据显示到表格中
        int row = ui->tableWidget_fla->rowCount()+1;
        ui->tableWidget_fla->setRowCount(row);
        for(int i=0;i<content.size();i++){
            ui->tableWidget_fla->setItem(row-1,i,new QTableWidgetItem);
            ui->tableWidget_fla->item(row-1,i)->setText(content[i]);
        }
    }
    else{
        QMessageBox::critical(NULL, QString::fromLocal8Bit("警告"), QStringLiteral("信息格式有误或信息错误!"), QMessageBox::Yes);
    }
}

void SocketConnect::pB_dk_clicked()
{
    ui->scrollArea->takeWidget();

    QVector<QString> tmp;
    v_linkstate.swap(tmp);

    ui->tableWidget_fla->setRowCount(0);
}

多线程头文件webservice_thread.h:

#ifndef WEBSERVICE_THREAD
#define WEBSERVICE_THREAD

#include<QtSql/QSql>
#include<QtSql/qsqlquerymodel.h>
#include<QtSql/QSqlQuery>
#include<QtSql/qsqldatabase.h>
#include<QSqlError>
#include<QStandardItem>

#include <QThread>
#include <QDebug>
#include <QVector>
#include <QMetaType>
#include <QWaitCondition>

class WebserviceThread: public QThread{
    Q_OBJECT

public:
    WebserviceThread(QObject *parent=0);
    ~WebserviceThread();
    void run();
    void setParameters(int);

signals:
    void linkState_send(QVector<QString>); // 向主线程发送连接状态
    void data_send(QVector<QString>); // 向主线程发送从客户端接收的数据

private slots:
    void stopclick_accept(); // 改变runstate

private:
    QVector<QString> readXML(QString);
    int runstate; // 用于终止循环
    int nPort;
};

#endif // WEBSERVICE_THREAD

多线程文件webservice_thread.cpp:

#include "webservice_thread.h"
#include <sstream>
//gsoap文件
#include"gservice.nsmap"
#include"soapH.h"
#include"soapStub.h"
#include"stdsoap2.h"
#include"stdsoap2.cpp"
#include"soapC.cpp"
#include"soapServer.cpp"

QString Msg; // 存储客户端发送过来的数据

WebserviceThread::WebserviceThread(QObject *parent):QThread(parent){
    qRegisterMetaType<QVector<QString>>("QVector<QString>");
    runstate=0; //置1时停止网络循环
}

WebserviceThread::~WebserviceThread(){

}

void WebserviceThread::setParameters(int port){
    nPort=port;
}

int http_get_wbq(soap *);
void WebserviceThread::run(){
    struct soap wbq_soap;
    soap_init(&wbq_soap);
    wbq_soap.fget = http_get_wbq; // 网上有人说如果要传输的数据量大的话应该用http post
    int nMaster = (int)soap_bind(&wbq_soap,NULL,nPort,100); // 端口绑定
    if(nMaster<0)
        soap_print_fault(&wbq_soap,stderr);
    else{
        QVector<QString> link_state_1;
        link_state_1.append(QString("Socket connection successful: master socket = "));
        link_state_1.append(QString::number(nMaster, 10));
        emit linkState_send(link_state_1); // 发射初始连接后的状态信息

        for(int i=0;;i++){
            int nSlave = (int)soap_accept(&wbq_soap); // 端口监听,获取客户端连接信息
            if(nSlave<0){
                soap_print_fault(&wbq_soap,stderr);
                break;
            }

            QVector<QString> link_state_2;
            link_state_2.append(QString("Times "));
            link_state_2.append(QString::number(i, 10));
            link_state_2.append(QString(". Accepted connection from "));
            link_state_2.append(QString::number(int((wbq_soap.ip>>24)&0xFF), 10));
            link_state_2.append(QString("."));
            link_state_2.append(QString::number(int((wbq_soap.ip>>16)&0xFF), 10));
            link_state_2.append(QString("."));
            link_state_2.append(QString::number(int((wbq_soap.ip>>8)&0xFF), 10));
            link_state_2.append(QString("."));
            link_state_2.append(QString::number(int((wbq_soap.ip)&0xFF), 10));
            link_state_2.append(QString(": slave socket = "));
            link_state_2.append(QString::number(nSlave, 10));
            if(!runstate)
                emit linkState_send(link_state_2); // 发射客户端连接信息

            if(runstate)//点击断开连接后的下一次循环可以ping通,但无法调用服务端的sendMsg方法,即数据无法传送,不会造成数据丢失。
                break;

            if(soap_serve(&wbq_soap)!=SOAP_OK)
                soap_print_fault(&wbq_soap,stderr);
            soap_destroy(&wbq_soap);
            soap_end(&wbq_soap);

            QVector<QString> data = readXML(Msg); // 解析数据
            emit data_send(data); // 向主线程传递解析后的数据
            Msg="";
        }
    }
    soap_done(&wbq_soap);
}

void WebserviceThread::stopclick_accept(){
    runstate = 1;
}

/*
此功能是按自己格式解析的
*/
QVector<QString> WebserviceThread::readXML(QString Msg){
    QVector<QString> data;
    QStringList msglist = Msg.split(",");
    for(int i=0;i<msglist.size();i++){
        data.append(msglist[i]);
    }
    return data;
}

int ns__sendMsg(struct soap *soap, char* smsg, nullResponse* result)
{
    Msg = QString(smsg);
    return SOAP_OK;
}

/*
用于在网页页面正常显示信息(百度得到的解决方案)
*/
int http_get_wbq(struct soap *soap){
    soap_response(soap, SOAP_HTML); // HTTP response header with text/html
    soap_send(soap, "<HTML>My Web server is operational.</HTML>");
    soap_end_send(soap);
    return SOAP_OK;
}

存在问题

1、gSOAP中有对数据序列化与反序列化的功能,不应按自己的格式来传送数据;
2、断开连接按功能在问题,不能及时断开连接。点击“断开连接”按钮后,客户端需要再试图与服务端通信一次,服务端才能真正跳出循环,目前只能做到最后一次通信客户端报错,数据无法传送,不会造成数据丢失。
3、这种方法并没有使用XML传输数据,并不正规,仅供参考。


Reference:

[1] Qt中多线程的使用:https://blog.csdn.net/mao19931004/article/details/53131872
[2] Qt使用多线程的一些心得——1.继承QThread的多线程使用方法:https://blog.csdn.net/czyt1988/article/details/64441443
[3] WebService学习总结(一)——WebService的相关概念:https://www.cnblogs.com/xdp-gacl/p/4048937.html
[4] gsoap使用总结:https://blog.csdn.net/byxdaz/article/details/51679117


本博客与https://xuyunkun.com同步更新

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值