刘 飞,张卫强,罗 彤
(宁波大学信息科学与工程学院,浙江宁波315211)
传统的网络服务器模型中,服务器收到一个客户端请求后,创建一个新线程,由该线程执行任务,任务完成后,线程退出,即“即时创建,即时删除”,对服务器来说创建线程已经比创建进程节约了不少时间,但是如果大量的客户端对服务器过于频繁的进行连接,该服务器就将在创建和删除线程的过程中耗费大量时间[1]。所以减少创建和销毁对象,尤其是减少很耗资源的对象成为提高服务程序效率的重要手段之一。线程池是一组预先创建的线程,快速、容易地处理收到的业务。比起传统的“即时创建,即时删除”的模型,该类型服务器节省了创建和回收线程的开销,响应更快,效率更高[2]。
为高图形用户界面的响应速度,Qt提供丰富的多线程编程支持[],而为减少Qt创建和删除线程的开销,Qt又提供了线程池技术的支持。为降低基于Qt的网络服务器频繁的创建线程的开销,本文就Qt线程池技术进行分析,研究其运行机制,并运用该技术创建一个服务器模型。
Qt提供了与平台无关的线程类,在Qt系统中与线程池相关的最重要的类是QThreadPool和QRunnable。QThreadPool用于管理一批线程,它负责管理和回收单个线程[4]。每个 Qt应用可通过QThreadPool::globalInstance()获得一个全局的QThreadPool对象。QRunable类表示一个任务或者一段被执行代码的一个接口。使用QThreadPool类来运行一个QRunnable对象。把一个QRunnable放入了QThreadPool的运行队列中,只要线程是可见的,QRunnable将会被拾起并且在那个线程里运行,QRunnable是一种轻量级的、以“run and forget”方式来在另一个线程开启任务的抽象类,为了实现这一功能,要做的全部事情是派生QRunnable类,并实现纯虚函数方法 run()[5]。
QTcpServer类不是QAbstractSocket抽象套接字类,而是继承于QObject基类,为编写TCP客户端和服务器应用程序提供一个 TCP基础服务类[6]。QTcpSocket类提供了基于TCP协议的通用接口,为监听每一个客户端的连接,可通过调用listen()函数来实现,每当服务器收到一个客户端的连接请求时就会发射newConnection()信号,如果要接受待处理的连接,则可通过调用nextPendingConnection()函数,并返回一个连接的 QTcpSocket()套接字[7],该套接字作为服务端的一个子对象,可以通过使用这个返回的套接字和客户端进行连接,这就意味着当QTcpServer对象要销毁时,该套接字也会随之被自动删除,即不能在其他线程中使用该套接字,如果想在其他线程中继续使用该套接字,那么需要重载void incomingConnection(int socketDescriptor)函数,这个函数将新创建一个QTcpSocket套接字,该函数中socketDescriptor参数是新连接的套接字描述符,然后在一个整形的待连接链表中将套接字存储,最后发射信号newConnection()。
线程池就是在进程中先创建好一批线程,当有任务到来时,就从创建好的线程中取出一个线程来处理该任务,任务结束之后,将线程置为空闲,放回线程池中继续等待下次任务的到来[8],工作过程如图1所示。
在Qt中通过globalInstance()方法,每个 Qt的应用程序都可获得一个全局的QThreadPool对象。
theInstance()函数功能通过Q_GLOBAL_STATIC(QThreadPool,theInstance)宏实现,以此返回一个全局的QThreadPool对象。此外由于QThreadPool类继承QObject,在QThreadPool类中可以使用Qt提供的信号与槽机制。图1为线程池工作原理。
图1 线程池工作原理
通过上面介绍的函数就可以得到一个全局的QthreadPool,但是为了能够调用该线程池中的一个线程,还需要提供继承于QRunnable的一个类,从而实现其中的run方法。然后创建一个该类的对象,传递给void QThreadPool::Start(QRunnable*runnable,int priority),该函数具体实现如下所示。
该方法通过Q_D宏获取QThreadPool命名为d的数据结构,真正开始QThreadPool一个线程是通过enqueueTask()方法实现的。
enqueueTask()方法将runnable放入队列中来管理,并唤醒QThreadPool管理的线程池中的一个线程实现一个继承QRunnable类的run方法。
在默认情况下,QthreadPool将能够自动删除创建的 QRunnable对象。使用 void QRunnable::setAutoDelete(bool autoDelete)方法可以改变这一默认行为,但是该标志必须在调用QThreadPool::Start()之前被设置,否则将会出现错误。
QThreadPool支持在QRunnable::run方法中通过调用tryStart(this)来多次执行相同的QRunnable。当最后一个线程退出run函数后,如果autoDelete启用的话,将删除QRunnable对象。在autoDelete启用的情况下,调用start()方法多次执行同一QRunnable会产生竞态,因此要避免这样做。
通过void setExpiryTimeout(int expiryTimeout)函数来设置线程的过期时间,默认过期时间为30 s。如果设置expriyTimeout为一个负数,则代表禁止使用超时机制。如果要规定最大的线程数可通set-MaxThreadCount(int maxThreadCount)来设置,其参数maxThreadCount为要设置的数量,通过 void maxThreadCount()可以查询可使用的最大线程数。为了确保该线程被释放后可循环使用,可以通过函数void releaseThread()释放该线程的,以便它可以被再次使用。
在下面的步骤中,将利用线程池技术创建一个服务器模型,以此介绍线程池的创建步骤,并通过命令客户端对创建的服务器进行测试。
首先创建一个继承QTcpServer的一个类,在该类的实现方法中监听客户端的连接每当有客户端连接时都会调用virtual void incomingConnection(int socketDescriptor)函数[9,10],因此处理这个请求的过程就可以在这个函数中实现,对一个线程池的服务器,每当客户端试图连接的时候,服务器从线程池中启动一个线程,负责对这个客户端进行服务,所以,incomingConnection()这个函数所要做的就是建立一个线程,进而对客户端进行服务。代码如下,先添加类的前置声明:
在myserver.cpp文件中,首先在构造函数中通过globalInstance()函数获取一个全局QThreadPool对象,并设置最大线程数为20,之后实现监听客户端连接。
该服务器监听到客户端试图建立一个套接字连接,该套接字将自动分配一个 SocketDescriptor标识,该标识会在服务器连接中使用,应当提供给每一个线程。
服务器在监听到客户端试图建立socket连接时,会为此socket分配一个标识socketDescriptor,该标识在建立服务器连接时使用,所以应提供给每一个线程。接下来派生QRunnable类,并实现纯虚函数run()。
在Linux环境下编译运行服务器程序结果如图2所示。
图2 编译运行
此时,如图2所示服务器已经启动,下面用Linux命令终端的telnet程序模拟一个客户端,在telnet程序中输入命令:open 127.0.0.1 1234(此处的127.0.01为程序中的设置的IP地址,即IPV4的本地主机地址,端口号是1234),请求服务器进行连接,并对其进行测试,测试结果如图3、图4所示。
图3 客户端
图4 服务器端
通过以上步骤,利用线程池技术完成了一个服务器的创建,即使用 QThreadPool类来运行一个QRunnable对象,它维护了一个线程池。当客户端请求连接时,服务器端调用已经创建好的线程池中的一个线程对该客户端请求进行处理,如图3所示,服务器将一个简单的字符串“Hello world!!”传递给客户端,并在命令客户端显示,此时服务器端打印出为该客户端服务的线程ID,如图4所示。通过以上测试,运用线程池技术实现了服务器与客户端之间的通信。
针对目前多线程服务器在接受客户端频繁连接会增加开销这一弱点,提出利用Qt线程池技术减少程序中频繁创建线程的开销的优势,在服务器模型中加入线程池技术的支持,即利用QThreadPool线程池来管理一组线程,每当有客户端连接时,就有单个线程对象来处理客户端请求并交由该线程池管理和回收,从而减少了服务器频繁创建线程的开销,提高服务器工作效率。但是由于QRunnable并非QObject类,它没有一个内置的与其他组件显式通讯的方法,必须使用底层的线程原语(比如收集结构的枷锁保护队列等)来亲自编写代码。
[1] 曾云.基于ARM+QT平台的嵌入式宾馆客服系统软件设计[D].上海:东华大学,2011:25-31.
[2] 刘新强,曾兵义.用线程池解决服务器并发请求的方案设计[J].现代电子技术,2011,34(15):141 -143.
[3] 黄宇东,胡跃明,陈安.基于Qt的多线程技术应用于研究[J].软件导刊,2009,8(10):40-42.
[4] 赵祖龙.基于Qt/Embedded的嵌入式跨平台聊天系统设计[J].信息技术,2010,34(12):144 -147.
[5] 蔡志明,卢传富,李立夏.精通Qt4编程[M].北京:电子工业出版社,2008.
[6] 崔弘珂.一种空间环境下的TCP传输技术研究[J].无线电通信技术,2011,37(4):21-24.
[7] 丁林松,黄丽琴.Qt4图形设计与嵌入式开发[M].北京:人民邮电出版社,2009.
[8] 汪成林.linux环境下基于SSL的安全文件传输系统研究[D].杭州:浙江工业大学,2012:38-45.
[9] 冯艳红,何加铭,杨任尔,等.基于Android蓝牙技术的健康服务系统设计[J].无线电通信技术,2014,40(1):61-64.85-88.
[10]马睿.基于Qt的TCP网络编程研究与应用[J].福建电脑,2010,26(11):138 -139.