孫婧 蔣浩 曹斐 孟景濤
摘要:目前的航空、航天測控項(xiàng)目中,測控軟件的主要功能之一是對(duì)測控系統(tǒng)各分機(jī)設(shè)備進(jìn)行監(jiān)控,這就對(duì)服務(wù)端的密集并行處理能力提出了較高要求。C/S架構(gòu)對(duì)于安全性要求較高的測控項(xiàng)目有著無可比擬的優(yōu)勢。對(duì)于測控軟件后臺(tái)服務(wù)端的各種服務(wù)來說,主要功能是接收客戶端發(fā)送來的原始數(shù)據(jù),進(jìn)行處理,然后返回結(jié)果。這些后臺(tái)服務(wù)目前運(yùn)行在國產(chǎn)化服務(wù)器上,調(diào)用其他服務(wù)進(jìn)行并行計(jì)算。針對(duì)測控項(xiàng)目的需求,提出了一種C/S架構(gòu)的服務(wù)端設(shè)計(jì),主要介紹了網(wǎng)絡(luò)處理模塊與任務(wù)處理模塊的設(shè)計(jì),以及程序結(jié)構(gòu)和工作流程,并使用QT進(jìn)行實(shí)現(xiàn),可以作為航空、航天測控軟件架構(gòu)服務(wù)器實(shí)現(xiàn)的參考。
關(guān)鍵詞:測控;客戶端;服務(wù)器;國產(chǎn)化
中圖分類號(hào):TP319文獻(xiàn)標(biāo)志碼:A文章編號(hào):1008-1739(2022)17-52-05

在航天、航空測控領(lǐng)域的地面站測控系統(tǒng)中,站內(nèi)測控軟件的主要功能之一是有序地控制各分系統(tǒng)設(shè)備的工作參數(shù),同時(shí)實(shí)時(shí)接收各分系統(tǒng)設(shè)備上報(bào)的測控?cái)?shù)據(jù)并進(jìn)行后續(xù)數(shù)據(jù)處理,這就需要測控軟件與測站分系統(tǒng)內(nèi)各類設(shè)備之間進(jìn)行數(shù)據(jù)交互。這些設(shè)備的特點(diǎn)是設(shè)備類型繁多、接口形式復(fù)雜,但是對(duì)單個(gè)設(shè)備而言,傳輸?shù)臄?shù)據(jù)量不會(huì)特別大。對(duì)于測控軟件后臺(tái)服務(wù)端的各種服務(wù)來說,主要功能是接收客戶端發(fā)送來的原始數(shù)據(jù),進(jìn)行處理,然后返回結(jié)果。這些后臺(tái)服務(wù)目前運(yùn)行在國產(chǎn)化服務(wù)器上,調(diào)用其他服務(wù)進(jìn)行并行計(jì)算。對(duì)于這些后臺(tái)服務(wù)來說,與其連接的客戶端數(shù)量是不確定的,在某些情況下會(huì)存在高并發(fā)現(xiàn)象,但是在另一些情況下客戶端的數(shù)據(jù)傳輸頻率較高,但輸入輸出的數(shù)據(jù)量不會(huì)太大。因此需要設(shè)計(jì)保證服務(wù)器不受客戶端的影響,能夠做到對(duì)各個(gè)客戶端均進(jìn)行及時(shí)響應(yīng),從而不影響測控任務(wù)的執(zhí)行。
目前,測控軟件使用客戶端—服務(wù)架構(gòu)[1],在這種架構(gòu)下,服務(wù)端的設(shè)計(jì)至關(guān)重要[2],為了滿足上文提到的客戶端的高并發(fā)連接請(qǐng)求及高速率的傳輸需求,本文設(shè)計(jì)了一種較為通用的可用于國產(chǎn)服務(wù)器(性能不那么高)的服務(wù)端設(shè)計(jì)及實(shí)現(xiàn)方案,該方案具有可動(dòng)態(tài)、靈活地進(jìn)行配置的優(yōu)點(diǎn),可以有效減少開發(fā)人員的開發(fā)時(shí)間,服務(wù)設(shè)計(jì)具備如下特點(diǎn)。
首先,采用線程池技術(shù),將客戶端的監(jiān)聽、連接及后續(xù)的數(shù)據(jù)傳輸、處理等過程使用線程池中的獨(dú)立線程來實(shí)現(xiàn)[3],從而在客戶端的高并發(fā)連接請(qǐng)求處理和高速率的傳輸需求之間達(dá)成取舍。其次,使用流水線結(jié)構(gòu)進(jìn)行數(shù)據(jù)處理,從而減少在某些特定情況下,高數(shù)據(jù)量或密集請(qǐng)求客戶端對(duì)其他客戶端的處理產(chǎn)生影響[4]。整個(gè)服務(wù)端基于C++及Qt進(jìn)行開發(fā)實(shí)現(xiàn),可以完全適配國產(chǎn)CPU及銀河麒麟操作系統(tǒng),從而滿足國產(chǎn)化的要求。
服務(wù)端的設(shè)計(jì)主要包含網(wǎng)絡(luò)管理及任務(wù)處理兩大模塊[5]。
網(wǎng)絡(luò)處理模塊:用于管理服務(wù)端的所有Socket(網(wǎng)絡(luò)套接字),包括其監(jiān)聽、連接、數(shù)據(jù)傳輸及關(guān)閉的全過程,同時(shí)對(duì)各Socket處理線程中的數(shù)據(jù)交換進(jìn)行管理。其中,數(shù)據(jù)的接收、發(fā)送由獨(dú)立的線程池進(jìn)行負(fù)責(zé),使用線程事件循環(huán)來實(shí)現(xiàn)。在Qt中,QThread的特點(diǎn)是線程對(duì)象綁定到哪個(gè)QObject類上,這個(gè)QObject類的信號(hào)—槽事件循環(huán)便屬于該線程,利用這個(gè)特性,便能夠方便地將某個(gè)Socket的對(duì)象與想要使用的線程進(jìn)行綁定[6]。
任務(wù)處理模塊:主要負(fù)責(zé)服務(wù)器所有已連接的Socket客戶端的收發(fā)數(shù)據(jù)處理。在某些需要進(jìn)行集中計(jì)算的后臺(tái)服務(wù)中,數(shù)據(jù)處理的負(fù)擔(dān)很重,因此需要將數(shù)據(jù)的處理過程與網(wǎng)絡(luò)的傳輸過程分開進(jìn)行。在這種情況下,如果使用普通的線程池設(shè)計(jì)方法,在客戶端的高并發(fā)連接請(qǐng)求發(fā)生并且每個(gè)連接都比較耗時(shí),會(huì)將其他Socket客戶端的響應(yīng)阻塞,從而產(chǎn)生數(shù)據(jù)處理隊(duì)列阻塞的問題。本設(shè)計(jì)采用流水線線程池來避免這個(gè)問題,其原理是將各個(gè)Socket客戶端的業(yè)務(wù)操作進(jìn)行細(xì)化拆分,形成比較小的顆粒度,然后在線程池中使用一個(gè)大的環(huán)形隊(duì)列,在這個(gè)隊(duì)列中每次只處理一個(gè)Socket客戶端的一個(gè)小顆粒度單位的任務(wù),在一個(gè)小顆粒度單位的任務(wù)完成后,這個(gè)Socket客戶端的其他剩余任務(wù)被重新排列到整個(gè)環(huán)形隊(duì)列的尾部,這個(gè)機(jī)制在單個(gè)客戶端不處理超大數(shù)據(jù)量的情況下,可以最大限度地保證Socket客戶端的整體處理時(shí)延是比較小的。
3.1網(wǎng)絡(luò)處理模塊設(shè)計(jì)
3.1.1程序結(jié)構(gòu)設(shè)計(jì)
網(wǎng)絡(luò)處理模塊使用線程池來管理TCP服務(wù)端對(duì)所有Socket的監(jiān)聽、連接、數(shù)據(jù)傳輸及各Socket處理線程中的數(shù)據(jù)交換。主要包括客戶端的接入管理線程及客戶端的數(shù)據(jù)傳輸線程。根據(jù)當(dāng)前線程池中的各個(gè)傳輸線程的負(fù)載,來對(duì)收到的新客戶端Socket接入申請(qǐng)進(jìn)行評(píng)估,并最終將新生成的Socket描述符傳遞給線程池中負(fù)載最小的傳輸線程中去執(zhí)行后續(xù)的連接(accept)等操作,從而實(shí)現(xiàn)了一個(gè)精簡版本的負(fù)載均衡功能。
網(wǎng)絡(luò)處理模塊合作關(guān)系如圖1所示。

該模塊幾個(gè)類的主要功能設(shè)計(jì)如下。
CTINet_Engine類:網(wǎng)絡(luò)處理引擎類,其基類是QObject,是整個(gè)網(wǎng)絡(luò)處理模塊的外部接口及管理類,作為網(wǎng)絡(luò)處理模塊的引擎,提供了處理線程池的動(dòng)態(tài)配置(包括監(jiān)聽器設(shè)置、傳輸參數(shù)等)等功能。
CTINetListenThread類:客戶端監(jiān)聽線程類,其基類是QObject,主要功能是創(chuàng)建監(jiān)聽線程,并使用其來接受各種客戶端Socket的連接請(qǐng)求。當(dāng)有新的客戶端接入申請(qǐng)時(shí),使用Qt的信號(hào)把新客戶端的套接字描述符傳出,由CTINet_Engine類進(jìn)行負(fù)載均衡,然后選擇線程池中負(fù)載最小的傳輸線程(CTINetTransThread)去接受該Socket的接入申請(qǐng)。
CTINetTransThread類:數(shù)據(jù)傳輸線程類,其基類是QObject,使用傳輸線程完成數(shù)據(jù)的傳輸。一個(gè)傳輸線程(CTINetTransThread)設(shè)計(jì)用來進(jìn)行多個(gè)客戶端Socket的數(shù)據(jù)接收、發(fā)送請(qǐng)求。
CTITcpServer類:網(wǎng)絡(luò)通信類,其基類是QTcpServer。它的主要功能是進(jìn)行Socket連接的監(jiān)聽工作,該類重載了QTcpServer類中的incomingConnection()方法,這個(gè)方法實(shí)現(xiàn)的功能是當(dāng)一個(gè)Socket客戶端發(fā)起連接時(shí),其會(huì)被立即調(diào)用。在CTITcpServer類的實(shí)現(xiàn)中,沒有定義新的網(wǎng)絡(luò)套接字,只是將基類中的sig_newClientArrived信號(hào)觸發(fā),這個(gè)信號(hào)會(huì)把套接字描述符傳遞給其他線程,其他的線程使用該套接字描述符完成套接字的創(chuàng)建[7]。
3.1.2客戶端接入設(shè)計(jì)
當(dāng)某一個(gè)Socket客戶端發(fā)起接入的請(qǐng)求時(shí),首先會(huì)觸發(fā)網(wǎng)絡(luò)通信類(CTITcpServer)中的連接信號(hào)(sig_incomingConnection),然后使用newClientArrived信號(hào)將套接字描述符作為參數(shù)傳出,在newClientArrived信號(hào)的槽函數(shù)中,網(wǎng)絡(luò)處理引擎(CTINet_Engine)類進(jìn)行負(fù)載均衡[8],然后選擇線程池中負(fù)載最小的傳輸線程(CTINetTransThread)去接受該Socket的接入申請(qǐng),然后把Socket描述符傳遞給數(shù)據(jù)傳輸線程類。使用另外一個(gè)信號(hào)sig_establishConnection將負(fù)載均衡策略確定的承接線程和Socket描述符廣播給所有的傳輸線程對(duì)象,最后在各個(gè)傳輸線程對(duì)象的連接信號(hào)(sig_incomingConnection)的槽函數(shù)中,使用該套接字描述符完成套接字的創(chuàng)建,從而完成客戶端接入過程[9]。
3.1.3數(shù)據(jù)接收設(shè)計(jì)
在成功創(chuàng)建了套接字以后,數(shù)據(jù)的收發(fā)都在傳輸線程中運(yùn)行,當(dāng)Socket接收到數(shù)據(jù)時(shí),會(huì)觸發(fā)數(shù)據(jù)接收事件(dataReceived),然后由其槽函數(shù)完成數(shù)據(jù)處理過程[10]。
3.1.4數(shù)據(jù)發(fā)送設(shè)計(jì)
數(shù)據(jù)發(fā)送的設(shè)計(jì)原則是:不直接使用Socket的緩存區(qū)來緩存需要發(fā)送的數(shù)據(jù),改為使用額外的數(shù)據(jù)發(fā)送隊(duì)列,在進(jìn)行數(shù)據(jù)的發(fā)送處理時(shí),每次會(huì)進(jìn)行固定長度的數(shù)據(jù)緩存并順序地發(fā)送[11]。采用這種發(fā)送方式,就可以增加緩沖區(qū)大小的檢查功能,從而方便對(duì)數(shù)據(jù)進(jìn)行進(jìn)一步的持久化工作等。舉例來說,在數(shù)據(jù)發(fā)送隊(duì)列中緩存的數(shù)據(jù)量非常大(≥200 MB)時(shí),再有數(shù)據(jù)需要發(fā)送,就可以把新進(jìn)入隊(duì)列的數(shù)據(jù)先緩存在硬盤中,而不是緩存在內(nèi)存里,從而降低程序?qū)?nèi)存資源的占用。
3.2任務(wù)處理模塊設(shè)計(jì)
3.2.1流水線線程池設(shè)計(jì)
在傳統(tǒng)的服務(wù)器程序設(shè)計(jì)中,為了實(shí)現(xiàn)多客戶端的并發(fā)連接和通信,通常采用一個(gè)客戶端分配一個(gè)線程的傳統(tǒng)多線程技術(shù),實(shí)現(xiàn)少量的無阻塞并發(fā)網(wǎng)絡(luò)通信處理,這就是傳統(tǒng)的“任務(wù)伴隨者”模式[12]。
當(dāng)并發(fā)的客戶端較多時(shí),采用這種設(shè)計(jì)模式,就需要為所有的客戶端各開啟一個(gè)線程,當(dāng)開啟的線程數(shù)達(dá)到幾百上千時(shí),由于國產(chǎn)化CPU(龍芯、飛騰等)的物理核心支持的線程規(guī)模有限,此時(shí)CPU會(huì)消耗大量的資源進(jìn)行各個(gè)線程的上下文切換和環(huán)境恢復(fù),從而使本就不如因特爾CPU的計(jì)算能力更處劣勢。
為了避免出現(xiàn)上文中描述的情形,采用一種新的技術(shù)進(jìn)行多線程的并行計(jì)算,即線程池的設(shè)計(jì)模式。在這種設(shè)計(jì)模式下,能夠有效利用國產(chǎn)CPU(龍芯、飛騰等)的物理核心,避免頻繁切換各個(gè)客戶端線程的上下文。服務(wù)器軟件只創(chuàng)建與CPU物理核心數(shù)差不多的線程,使這些線程形成一個(gè)資源池的概念,即線程池(ThreadPool)。通過將服務(wù)器所需要實(shí)現(xiàn)的功能進(jìn)行細(xì)化顆粒的拆分,可以將服務(wù)器功能分為很多小的任務(wù),將這些任務(wù)全部進(jìn)行隊(duì)列排序,并在線程池中按照先進(jìn)先出(FIFO)的次序進(jìn)行處理[13]。該模式的示意如圖2所示。

這種模式在并發(fā)連接較大時(shí)盡可能地提高CPU的計(jì)算能力,但是由于線程數(shù)量的限制,在單位時(shí)間內(nèi)處理的任務(wù)數(shù)量級(jí)是固定的,這樣就有可能會(huì)將某些客戶端阻塞。如果同時(shí)處理的任務(wù)都是比較耗時(shí)的任務(wù),則所有的客戶端都有可能被阻塞,無論是耗時(shí)的客戶端任務(wù)還是簡單的客戶端任務(wù)。
本文的架構(gòu)在設(shè)計(jì)之初就是為了支持高并發(fā)且允許部分大數(shù)據(jù)量連接的客戶端,因此對(duì)傳統(tǒng)的線程池技術(shù)進(jìn)行了改進(jìn),使用基于線程池的流水線技術(shù),從而實(shí)現(xiàn)在國產(chǎn)CPU下的多客戶端低阻塞情況的最優(yōu)線程處理。這項(xiàng)技術(shù)最重要的設(shè)計(jì)是:把各個(gè)客戶端所產(chǎn)生的任務(wù)再進(jìn)一步地細(xì)化處理,線程池進(jìn)行隊(duì)列處理時(shí)不以一個(gè)客戶端的任務(wù)為一個(gè)單位,而是以任務(wù)再次細(xì)化后的個(gè)指令為一個(gè)集合,不管某個(gè)客戶端緩存了多少個(gè)任務(wù),每次隊(duì)列處理時(shí)都處理相同的個(gè)指令,然后就跳轉(zhuǎn)到下一個(gè)任務(wù)的指令集合去處理[14]。流水線處理示意如圖3所示。
在上文描述的方法中,流水線處理機(jī)制可以實(shí)現(xiàn)最大限度地并行化處理所有客戶端的任務(wù)請(qǐng)求,保證國產(chǎn)化CPU有限處理資源的最大化利用,從而使系統(tǒng)的整體吞吐量有大幅的提高[15]。同時(shí),在這種處理架構(gòu)下,可以通過配置,將優(yōu)先級(jí)最高的客戶端請(qǐng)求進(jìn)行優(yōu)先處理。

3.2.2程序結(jié)構(gòu)設(shè)計(jì)
線程池類合作關(guān)系如圖4所示。
