基于Qt的智能终端服务器及其在造纸工业中的应用
2014-08-15张天宇
张天宇
(天津环球磁卡股份有限公司,天津,300202)
1 Qt
1.1 Qt是什么
[1]Qt是一个1991年由奇趣科技开发的跨平台C++图形用户界面应用程序开发框架。它既可以开发GUI程序,也可用于开发非GUI程序,比如控制台工具和服务器。Qt是面向对象的框架,使用特殊的代码生成扩展 (称为元对象编译器 (Meta Object Compiler,moc))以及一些宏,易于扩展,允许组件编程。2008年,奇趣科技被诺基亚公司收购,QT也因此成为诺基亚旗下的编程语言工具。2012年,Qt被Digia收购。
1.2 Qt平台支持
● MS/Windows-95、98、NT4.0、ME、2000、XP、Vista、Win7、win8、win2008。
● Unix/X11-Linux、SunSolaris、HP-UX、CompaqTru64 UNIX、IBMAIX、SGI IRIX、FreeBSD、BSD/OS 和其它很多X11平台。
●Macintosh-Mac OS X。
●Embedded-有帧缓冲 (framebuffer)支持的嵌入式Linux平台,Windows CE。
1.3 Qt Creator
Qt Creator是Qt开发跨平台 IDE,Qt Creator和Qt共同构成的Qt SDK,包含了开发跨平台应用程序所需的全部功能。
Qt Creator是一个用于Qt开发的轻量级跨平台集成开发环境。Qt Creator可提供首个专为支持跨平台开发而设计的集成开发环境(IDE)。
Qt Creator包含了一套用于创建和测试基于Qt应用程序的高效工具,包括:
一个高级的C++代码编辑器,上下文感知帮助系统,可视化调试器,源代码管理,项目和构建管理工具。
Qt Creator在LGPL2.1版本授权下有效,并且接受代码贡献。
Qt Linguist被称为Qt语言家。它的主要任务只是读取翻译文件、为翻译人员提供友好的翻译界面,它是用于界面国际化的重要工具。
Linguist工具从4.5开始可以支持Gettext的PO文件格式。
Qt的良好封装机制使得 Qt的模块化程度非常高,可重用性较好,对于用户开发来说是非常方便的。 Qt提供了一种称为 signals/slots的安全类型来替代callback,这使得各个元件之间的协同工作变得十分简单。
Qt包括多达 250个以上的C++类,还提供基于模板的 collections,serialization, file,I/O device,directory management,date/time类。甚至还包括正则表达式的处理功能。
支持 2D/3D图形渲染,支持 OpenGL,支持XML。
2 多线程
2.1 概念
[2]在一个程序中,这些独立运行的程序片段叫作“线程”(Thread),利用它编程的概念就叫作“多线程处理”。
每个正在系统上运行的程序都是一个进程。每个进程包含一到多个线程。进程也可能是整个程序或者是部分程序的动态执行。线程是一组指令的集合,或者是程序的特殊段,它可以在程序里独立执行。也可以把它理解为代码运行的上下文。所以线程基本上是轻量级的进程,它负责在单个程序里执行多任务。通常由操作系统负责多个线程的调度和执行。
线程是程序中一个单一的顺序控制流程。在单个程序中同时运行多个线程完成不同的工作,称为多线程。
线程和进程的区别在于,子进程和父进程有不同的代码和数据空间,而多个线程则共享数据空间,每个线程有自己的执行堆栈和程序计数器为其执行上下文。多线程主要是为了节约CPU时间,发挥利用,根据具体情况而定。线程的运行中需要使用计算机的内存资源和CPU。
2.2 优点
●使用线程可以把占据时间长的程序中的任务放到后台去处理。
●程序的运行速度可能加快。
●在一些等待的任务实现上如用户输入、文件读写和网络收发数据等,线程就比较有用了。在这种情况下可以释放一些珍贵的资源如内存占用等等。
2.3 缺点
●如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换。
●更多的线程需要更多的内存空间。
●线程可能会给程序带来更多“bug”,因此要小心使用。
●线程的中止需要考虑其对程序运行的影响。
●通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生。
2.4 Qt多线程
传统的图形用户界面应用程序都只有一个执行线程,并且一次只执行一个操作。如果用户从用户界面中调用一个比较耗时的操作,那么当执行这个操作时,虽然实际上该操作正在进行,但用户界面通常会冻结而不再响应,多线程正是一种解决方案。
在多线程应用程序中,图形用户界面运行于它自己的线程中,而另外的事件处理过程则会发生在一个或多个其他线程中。这样做之后,即使在处理那些数据密集的事件时,应用程序也能对用户界面保持响应。当在一个处理器上运行时,多线程应用程序可能会比实现同样功能的单线程应用程序运行得更慢一些,无法体现出其优势。但在目前多处理器系统越来越普及的情况下,多线程应用程序可以在不同的处理器中同时执行多个线程,从而获得更好的总体性能。
QThread提供了与平台无关的线程。一个QThread代表单独运行于程序的线程。在QT中使用多线程,建立一个类(Thread)继承QThread类即可。QThread类也有一个虚函数,这个函数是run(),线程建立并启动(QThread::start())后,就会执行这里面的代码,因此,线程的逻辑过程就应该在run()里面定义。
例如:
1.//mythread.h
2.#ifndef MYTHREAD_H
3.#define MYTHREAD_H
4.#include 5.class MyThread:public QThread 6.{ 7.public: 8.MyThread(); 9.MyThread(int count); 10.void run(); 11.private: 12.int count; 13.}; 14.#endif//MYTHREAD_H 1.//mythread.cpp 2.#include?“mythread.h” 3.#include“QThread” 4.#include 5.MyThread::MyThread() 6.{ 7.} 8.MyThread::MyThread(int count) 9.{ 10.this->count=count; 11.} 12.void MyThread::run() 13.{ 14.while(count<20000) 15.{ 16.qDebug()< 17.} 18.} 1.//main.cpp 2.#include 3.#include?“mythread.h” 4.#include 5.int main(int argc,char*argv[]) 6.{ 7.QCoreApplication a(argc,argv); 8.qDebug()<<“Main Start”; 9.MyThread myThread1(0); 10.myThread1.setObjectName(“MyThread1”); 11.myThread1.start(); 12.MyThread myThread2(0); 13.myThread2.setObjectName(“MyThread2”); 14.myThread2.start(); 15.int c=0; 16.while(c<20000) 17.{ 18.qDebug()<<“Main Thread”< 19.} 20.return a.exec(); 21.} 在main函数中我定义了两个线程,并分别设置了线程名称。运行过程中有三个线程会可能同时运行。主函数线程,myThread1线程,myThread2线程。 我们可以通过调用 QObject::moveToThread()来改变QObject对象和线程之前的关系,它会改变对象本身以及它的孩子与线程之前的关系。由于QObject不是线程安全的,所以我们必须在它所在的线程中使用;也就是说,你仅仅可以在他们所处的线程中把它移动到另一个线程去,而不能从其他线程中把它从所在的线程中移动过来。而且,Qt要求一个QObject对象的孩子必须和他的父亲在同一个线程中,也就是说:如果一个对象有父亲,那么不能使用 QObject::moveToThread()把它移动到其他线程;不能在QThread?类中以QThread为父亲创建对象。 面对多线程一个好的办法是:把“工作”部分从“控制”部分分离出来,创建QObject子类对象,然后使用 QObject::moveToThread()来改 变 对 象 所 在 的 线程。 例如: 22.class Worker:public QObject 23.{ 24.Q_OBJECT 25. 26.public slots: 27.void doWork(){ 28./*...*/ 29.} 30.}; 31. 32./*...*/ 33.QThread*thread=new QThread; 34.Worker*worker=new Worker; 35.connect(obj,SIGNAL(workReady()),worker,SLOT(doWork())); 36.worker->moveToThread(thread); 37.thread->start(); 这是Qt4.7及以后版本推荐的工作方式。其主要特点就是利用Qt的事件驱动特性,将需要在次线程中处理的业务放在独立的模块(类)中,由主线程创建完该对象后,将其移交给指定的线程,且可以将多个类似的对象移交给同一个线程。 可以… ●在QThread子类中添加信号。这是很安全的,而且可以“正确工作”(前面提到;发送者所在线程是无关紧要的) 不应该… ●使用moveToThread(this) ●强制连接类型:这通常说明你在做一些错误的事情,例如混合了QThread控制接口和程序逻辑(它应该在该线程创建的对象中) ●在QThread子类中增加槽函数:它们会在 “错误的”线程中被调用,不是在QThread管理的线程中,而是在QThread对象创建的线程,迫使你使用direct connection或使用moveToThread(this)函数 ● 使用 QThread::terminate 函数 禁止… ●在线程还在运行时退出程序。应使用 QThread::wait等待线程终止 ●当QThread管理的线程还在运行时,删除QTread对象。如果你想要“自动析构”,你可以将finished()信号连接到deleteLater()槽函数上 [3]Transmission Control Protocol传输控制协议TCP是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,由 IETF的RFC 793说明(specified)。TCP在IP报文的协议号是6。在简化的计算机网络OSI模型中,它完成第四层传输层所指定的功能,UDP是同一层内另一个重要的传输协议。 [4]Qt提供了QTcpSocket类,它将实现TCP传输协议。TCP是一个可靠的面向连接的协议,它按照网络节点间的数据流形式进行操作。这个协议可以用于创建网络客户端和服务器应用程序。若要创建服务器应用程序,还需要QTcpServer类来处理引用的TCP连接。 对于服务器来说,多线程的这个特性太有用了,因为多线程使得服务器可能同时响应多个客户端的请求,所以现在服务器大多采用多线程。 不管是多线程,还是服务器,QT中已经封装好了特定的类,所以使用起来也很方便。下面建立一个支持多线程、TCP的服务器。 首先建立一个服务器。新建一个类(Server)继承QT中的QTcpServer类即可。服务器的职责是监听端口。当监听到有客户端试图与服务器建立连接的时候,分配socket与客户端连接,再进行数据通信。QTcpServer的listen()方法执行监听过程,可以指定监听的地址和端口。若给定了QHostAddress类型的监听地址,则监听该地址,否则,监听所有地址;若给定了quint16类型的监听端口,则监听该端口,否则,随机选定一个监听端口。 例如: if(!tcpServer.listen(QHostAddress::Any,9999)){ QMessageBox::critical(this,tr("Fortune Server"), tr("Unable to start the server:%1.") .arg(tcpServer.errorString())); close(); return; } QString ipAddress; QList for(int i=0;i if(ipAddressesList.at(i)!=QHostAddress::LocalHost&& ipAddressesList.at(i).toIPv4Address()){ ipAddress=ipAddressesList.at(i).toString(); break; } } if(ipAddress.isEmpty()) { ipAddress=QHostAddress(QHostAddress::LocalHost).toString(); } QTcpServer有一个虚函数incomingConnection(int socketDescriptor),服务器每当监听到一个客户端试图建立连接的时候,会自动调用这个函数,因此,处理这个请求的过程就可以在这个函数中,即在子类Server的定义阶段,重新定义incomingConnection(int socketDescriptor)这个函数。 对于一个多线程的服务器,每当客户端试图连接的时候,服务器应该启动一个线程,负责对这个客户端进行服务,所以,incomingConnection()这个函数所要做的就是建立一个线程,而所建立的线程的作用就是对客户端进行服务,而这其中建立socket连接是基础。服务器在监听到客户端试图建立socket连接时,会为此socket分配一个唯一的标识socket-Descriptor,这个标识将在服务器端建立socket连接时使用,所以应提供给每一个线程。QTcpServer只负责监听,不会通讯。当它发现有连接请求时,他会给出一个socketDescriptor,只要用这个描述符创建一个socket就会连接到对方。 例如: QThread*thread=new QThread(); readMes*readthread=new readMes(socketDescriptor); readthread->moveToThread(thread); thread->start(); … m_tcpClient=new QTcpSocket(); m_tcpClient->setSocketDescriptor(m_socketDescriptor); … / ***********************************************************************/ void readMes::recevieData() { qDebug()< quint16 i,iSize,iRecLen; QString tmp; QDataStream in(m_tcpClient); in.setVersion(QDataStream::Qt_4_0); if(m_tcpClient->bytesAvailable()<(int)sizeof(quint16))return; in>>iSize; if(m_tcpClient->bytesAvailable() m_baRecData.clear(); m_recStr.clear(); in>>m_iRecCmd; in>>m_iRecFactor; in>>m_iRecLen; in>>m_recStr; if(m_iRecLen!=iRecLen){ qDebug()< return; } tcpRecFinished(m_iRecCmd); } / ***********************************************************************/ void readMes::sendDatatoClient(quint16 test1,quint16 test2,quint16 test3,QByteArray &data,quint8 chk) { QByteArray block; QDataStream out(&block,QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out<<(quint16)0; out<<(quint16)test1;//test1 out<<(quint16)test2;//test2 out<<(quint16)test3;//test3 out< out<<(qint8)chk; out.device()->seek(0); out<< (quint16)(block.size ()-sizeof(quint16)); m_tcpClient->write(block); m_tcpClient->flush(); } / ***********************************************************************/ void readMes::sendStringtoClient(quint16 test1,quint16 test2,quint16 test3,QStringList¶) { QByteArray block; QDataStream out(&block,QIODevice::WriteOnly); out.setVersion(QDataStream::Qt_4_0); out<<(quint16)0; out<<(quint16)test1;//test1 out<<(quint16)test2; //test2 out<<(quint16)test3;//test3 for(int i=0;i out< out.device()->seek(0); out<< (quint16)(block.size()-sizeof(quint16)); m_tcpClient->write(block); m_tcpClient->flush(); } / **********************************************************************/ 可以通过QDataStream向socket中读取,写入数据,达到客户端和服务端通讯的目的。为了保证在客户端能接收到完整的文件,在数据流的最开始写入完整文件的大小信息,这样客户端就可以根据大小信息来判断是否接收到了完整的文件。同理,客户端向服务端发送数据时也要包含文件大小信息,便于服务器进行读取。 每一台机器与服务器建立连接,就会在服务器的界面上增加一条记录。可以通过勾选,对任意一台机器的文件选择上传/下载。原理是:通过TCP服务器,向客户端发送控制命令,客户端根据收到的控制命令,启动一个FTP服务,进而通过FTP进行文件的上传下载。 对于造纸工业而言,我们可以将各个部分的机器的状态,如:电压、电流、温度、湿度等,实时的上传到服务器上,服务器可以直观的将这些数据显示出来,我们也可以利用这些数据对当前设备生产状况进行分析,通过服务端更改设置,将数据传输到某一设备,进而控制该设备,如:升温、降温。体现了友好的人机对话。我们可以通过电脑,掌握所有设备的实时动态,进而控制设备,大大提升了工作效率,而且降低了设备的故障率。通过对数据进行更进一步的分析,结合产量,我们可以得出当设备工作在哪种状态下,它的效率是最高的。通过对工艺参数的更改,进一步提升产品质量。 判断对方(设备,进程或其它网元)是否正常动行,一般采用定时发送简单的通讯包,如果在指定时间段内未收到对方响应,则判断对方已经当掉。用于检测TCP的异常断开。 这里采用的思路是:客户端连接上服务端以后,服务端维护一个计数器,客户端每隔一段时间,向服务器发送一个心跳包,服务器接收到包以后,计数器的值都会更新为0;一旦服务端超过规定时间没有接收到客户端发来的包,计数器的值将会加1,当计数器的值累计大于等于3,则视为掉线。 例如: maxHeart=0; heartBeatNum=0; timer=new QTimer(this); connect(timer,SIGNAL(timeout()),this,SLOT(receiveHeartBeat())); timer->start(5000); timer2=new QTimer(this); connect(timer2,SIGNAL(timeout()),this,SLOT(heartBeatTest())); timer2->start(1000); … void readMes::receiveHeartBeat() { if(heartBeatNum>5) maxHeart++; if(maxHeart>=3) { timer->stop(); timer2->stop(); // emit clientMiss(m_socketDescriptor); clientMissSLOT(); } } void readMes::heartBeatTest() { heartBeatNum++; qDebug()< } 对于服务器来说,多线程的这个特性太有用了,因为多线程使得服务器可以同时响应多个客户端的请求,所以现在服务器大多采用多线程。程序运行效率的提高,也进一步优化了用户体验。应用于造纸行业,实现了生产过程及生产信息的系统集成、共享、监控,提高了公司的生产管理水平;实时监测工艺参数判定信息,加强了质量监管,实现了全程质量管理;对关键生产工艺过程参数的监控和干预,改善和提升产品质量;对关键设备运行状态的监控和分析,降低设备事故、提升设备产能。可以预料,将计算机网络技术和现代控制理论应用于造纸工业,将对造纸行业的发展起到巨大的推动作用。 [1]Stanley B.Lippman,Josee Lajoie,Barbara E.Moo.C++Primer[M].北京:人民邮电出版社,2006. [2]孙鑫,余安萍.VC++深入详解 [M].北京:电子工业出版社,2006. [3]Jasmin Blanchette,MarkSummerfield.C++GUI Programming with Qt 4[M].Prentice Hall,2006. [4](美)史蒂文斯(W.RichardStevens) 著,范建华等译.TCP/IP详解[M].机械工业出版社,2008.2.5 应该做&不应该做
3 TCP网络通讯
3.1 概念
3.2 Qt—tcp
3.3 界面
3.4 心跳机制
4 结语
猜你喜欢
杂志排行