漆 藝
(四川廣播電視大學,四川 成都 610000)
國家開放大學期末考試是全國性的統(tǒng)一考試,其重要性不言而喻。由于辦學性質(zhì)的特殊性,每年的6月和12月,由國家開放大學統(tǒng)一安排,各分部安排合適場地,組織進行期末考試。以四川分部為例,全省范圍內(nèi)共有150多個考點。為了加強考風考紀的建設(shè),打擊違規(guī)替考等不正之風,需要在進入考場之前,對考生進行“實人+實名+實證”的驗證。而傳統(tǒng)的人工核驗易受到人為因素干擾,且無法防止證件作假的情況,由此背景下,亟需設(shè)計一套考場身份認證系統(tǒng),輔助考場規(guī)范管理,嚴肅考風考紀。
由于考試入場事件集中在考前30至15分鐘,因此在這段時間內(nèi),首先需要系統(tǒng)在響應(yīng)規(guī)模、并發(fā)處理能力、數(shù)據(jù)交互量,以及自身資源消耗等方面,有較高要求。其次,為了方便擴充和演進,系統(tǒng)最好是“scalable”系統(tǒng),即通過提升硬件能力(增加內(nèi)存或CPU數(shù)量)而達到提升性能的目的。
在計算機系統(tǒng)中,設(shè)備I/O相對于其他操作是比較慢的,于是在多線程結(jié)構(gòu)中通常采用異步I/O的方式進行設(shè)備I/O操作。從本質(zhì)上講,重疊I/O模型也是一種異步I/O模型,它的核心是一個重疊數(shù)據(jù)結(jié)構(gòu):
typedef struct _OVERLAPPED {
DWORD Internal;
DWORD InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED, *LPOVERLAPPED;
從Winsock 2開始,重疊I/O便集成到了新的WinSock函數(shù)中。通過使用重疊結(jié)構(gòu),應(yīng)用程序可以一次投遞一個或多個異步I/O請求。要想在一個套接字上使用重疊I/O模型來處理網(wǎng)絡(luò)數(shù)據(jù)通信,必須使用WSA_FLAG_OVERLAPPED這個標志來創(chuàng)建套接字。如下所示:
WSASocket(AF_INET,SOCK_STEAM,0,NULL,0 WSA_FLAG_OVERLAPPED);
應(yīng)用程序有多種方法獲取重疊I/O請求操作完成的通知,如等待事件對象置信,或使用完成例程等,但它們都有一個共同的缺點,即為每一個I/O都開了一個線程,當同時有成千上萬個請求發(fā)生時,系統(tǒng)會深陷于線程上下文切換的泥潭里。為此,Windows引入了更為先進的完成端口模型IOCP,用線程池來解決這個問題。
IOCP(Input/Output Completion Port,I/O完成端口)是一種非常適合C/S模式的網(wǎng)絡(luò)服務(wù)器模型,主要針對數(shù)據(jù)吞吐量和連接并發(fā)量而設(shè)計。自從Windows NT 3.5支持IOCP模型后,它被廣泛應(yīng)用于各類高性能網(wǎng)絡(luò)服務(wù)器中。以往無論是event對象或是APCs,都十分緊密的和線程綁在一起,一旦接入的客戶端數(shù)量過多,系統(tǒng)中會有很多的線程并行的運行,Windows內(nèi)核會花費大量的時間進行上下文切換,而無力去執(zhí)行線程體,從而導致效率低下。
IOCP模型可以克服這種“一個Client一個線程”的問題,它提供了工作者(I/O Worker)線程的概念,讓應(yīng)用程序在線程池中創(chuàng)建有限數(shù)量的服務(wù)器線程,而這個線程池的調(diào)用由Windows系統(tǒng)來維護,由系統(tǒng)內(nèi)核幫忙調(diào)度去完成多客戶端的I/O請求。從原理上看,IOCP類似于通知隊列,當一個重疊I/O完成后,會被加入到隊列中,操作系統(tǒng)從線程池中喚醒一個線程來處理。在此期間,線程會一直被掛起,而不會占用CPU時間。
大體上講,使用IOCP需要遵循如下幾個關(guān)鍵步驟:
1.創(chuàng)建完成端口對象
調(diào)用CreateIoCompletionPort()函數(shù),創(chuàng)建一個完成端口對象,用它面向任意數(shù)量的套接字句柄,管理多個I/O請求。
HANDLE CreateIoCompletionPort(
HANDLE File Handle,
HANDLE ExistingCompletionPort,
ULONG_PTR CompletionKey,
DWORD NumberOfConcurrentThreads);
在創(chuàng)建完成端口時,需注意一下Number Of Concurrent Threads參數(shù),它定義了在一個完成端口上,同時允許執(zhí)行的線程數(shù)量。理想情況下,每個CPU負責一個線程的運行,這樣可以讓CPU盡可能的忙碌而不至于出現(xiàn)“過飽和”的情況。在一般情況下,將該參數(shù)設(shè)為0,表明系統(tǒng)內(nèi)安裝了多少個處理器,便允許同時運行多少個線程。即:
m_hIOCompletionPort=CreateIoCompletionPort(I NVALID_HANDLE_VALUE, NULL, 0, 0);
2.創(chuàng)建I/O工作者(I/O Worker)線程
成功創(chuàng)建一個完成端口后,再根據(jù)系統(tǒng)中的CPU個數(shù),建立對應(yīng)數(shù)量的工作者線程,這些線程是專門用來和客戶端通信的。由于在創(chuàng)建完成端口時,已經(jīng)指定了同一時刻最多允許的線程數(shù)等于CPU個數(shù),因此,工作者線程的數(shù)量不能小于CPU個數(shù),否則會出現(xiàn)CPU“空閑”的情況。最好是工作者線程數(shù)略大于CPU個數(shù),以便在某些工作者線程被掛起時,能充分發(fā)揮系統(tǒng)的潛力。對此,MSDN的建議是:
工作者線程數(shù) = CPU個數(shù) * 2
3.建立監(jiān)聽線程,投遞異步I/O請求
在監(jiān)聽套接字上調(diào)用異步AcceptEx()函數(shù)。AcceptEx是一個特殊的winsock 1.1擴展函數(shù),它最初設(shè)計的宗旨是為了在Windows NT上使用WIN32的重疊I/O機制。AcceptEx的定義如下:
其中,第一個參數(shù)sListenSocket指定的是一個監(jiān)聽套接字,第二個參數(shù)sAcceptSocket指定的是另一個套接字,負責“接受”連接請求。因此,在調(diào)用AcceptEx之前,需要事先準備好套接字,這有區(qū)別于傳統(tǒng)的accept函數(shù)。值得注意的是,由于AcceptEx函數(shù)是用于接收新的連接,在應(yīng)用程序運行的過程中會非常頻繁的調(diào)用,所以需要使用WSAIcotl函數(shù)將AcceptEx加載到內(nèi)存,以減少函數(shù)調(diào)用帶來的消耗。
第三個參數(shù)lpOutputBuffer指向一塊內(nèi)存區(qū),當AcceptEx成功時,里面會保存客戶端第一次發(fā)送的數(shù)據(jù)、sever的地址和client的地址,是一個非常重要的參數(shù)。
第四個參數(shù)dwReceiveDataLength用于存放數(shù)據(jù)的空間大小。如果為0,則AccepEx會立即返回,不會等待數(shù)據(jù)到來。因此,通常將該參數(shù)設(shè)成:
sizeof(lpOutputBuffer)(實參的實際空間大小) -2*(sizeof sockaddr_in +16)
4.掃描完成端口隊列,處理網(wǎng)絡(luò)請求
當有客戶端連入的時候,再次調(diào)用CreateIo-CompletionPort()函數(shù),把新連入的Socket與完成端口綁定??蛻舳诉B入之后,可以在Socket上提交一個網(wǎng)絡(luò)請求,此時,在工作者線程里需要調(diào)用GetQueuedCompletionStatus()函數(shù),掃描完成端口隊列里是否有網(wǎng)絡(luò)通信的請求存在。如果有,則將這個請求從完成端口隊列中取回來,然后繼續(xù)執(zhí)行本線程中后面的處理代碼,處理完畢之后,再繼續(xù)投遞下一個網(wǎng)絡(luò)通信請求,如此循環(huán)。
系統(tǒng)采用C/S架構(gòu),客戶端部署在各考場的入口處,考生入場時,采集身份證信息、人臉特征信息,在本地完成身份認證和核驗,然后將驗證數(shù)據(jù)實時上傳,并存入數(shù)據(jù)庫中。系統(tǒng)拓撲圖如圖1所示。
圖1 系統(tǒng)拓撲圖
網(wǎng)絡(luò)協(xié)議采用面向連接的傳輸控制協(xié)議TCP,保證服務(wù)器端無差錯的接收客戶端發(fā)送的字節(jié)流,確保系統(tǒng)的可靠性和傳輸數(shù)據(jù)的正確性。
系統(tǒng)選擇“公有云+私有云”混合部署方式。關(guān)鍵的數(shù)據(jù)服務(wù)器放在省校中心機房內(nèi),應(yīng)用服務(wù)、數(shù)據(jù)分析等業(yè)務(wù)部署在公有云環(huán)境中,通過VPN獲取考生數(shù)據(jù),并將最終的分析結(jié)果上傳至省校數(shù)據(jù)服務(wù)器,最大限度保證數(shù)據(jù)的安全性和可靠性。系統(tǒng)部署方式如圖2所示。
圖2 系統(tǒng)部署方式
客戶端由主機(客戶端軟件)、身份證讀卡器、攝像頭等組成,利用身份證讀卡器,讀出身份證信息,同時采集持證人人臉特征進行人臉核驗,然后和組考數(shù)據(jù)進行比對,確認考生的真實身份??蛻舳说墓ぷ髁鞒倘鐖D3所示。
客戶端軟件的工作流程為:
1.客戶端軟件控制身份證讀卡器,對考生身份信息進行采集。
2.客戶端軟件控制攝像頭,提取刷證人的人臉特征,同證件照進行比對。
3.同組考信息進行比對,判斷身份、考試場次是否一致。
4.判定結(jié)果進入消息隊列,同時保存本地數(shù)據(jù)庫備查。
5.連接線程輪詢消息隊列,一旦發(fā)現(xiàn)有數(shù)據(jù),即啟動發(fā)送線程,將數(shù)據(jù)發(fā)送至Server。
服務(wù)器端的核心功能是,將接收到的客戶端數(shù)據(jù)按照定義的格式進行解析,重組成獨立的身份驗證信息存入數(shù)據(jù)庫中。使用IOCP模型后,服務(wù)器端代碼架構(gòu)得以簡化,服務(wù)器端工作流程如圖4所示。
圖4 服務(wù)器端工作流程圖
需要注意的是:
1.使用WSASocket函數(shù)
在為AcceptEx提前準備套接字時,需使用WSASocket函數(shù)。它是Windows專門用于支持異步操作的API,和通用的網(wǎng)絡(luò)編程接口socket不同之處在于:WSASocket的發(fā)送操作和接收操作都可以被重疊使用,形成緩沖區(qū),而socket只能在發(fā)送后等待消息返回才可以做下一步操作。
2.不要在工作者線程中處理數(shù)據(jù)
在調(diào)用GetQueuedCompletionStatus()所在的工作者線程里,最好不要做過多的數(shù)據(jù)處理操作。IOCP工作者線程池設(shè)計的目的是為了充分發(fā)揮CPU的性能,如果在工作者線程里進行過多的數(shù)據(jù)處理,會影響服務(wù)器的接收效率。最好的方法是設(shè)計一個I/O數(shù)據(jù)隊列,工作者線程接收到數(shù)據(jù)后直接入隊,單獨使用一個I/O處理線程進行數(shù)據(jù)處理。
3.I/O數(shù)據(jù)隊列的設(shè)計
服務(wù)器端的I/O數(shù)據(jù)隊列不建議采用STL的數(shù)據(jù)結(jié)構(gòu)。當客戶端數(shù)量較多時(大于500),網(wǎng)絡(luò)數(shù)據(jù)傳輸峰值較大,使用STL的數(shù)據(jù)結(jié)構(gòu)會使占用內(nèi)存成倍數(shù)增長,從而導致系統(tǒng)崩潰。最好根據(jù)數(shù)據(jù)包的有效數(shù)據(jù)格式,設(shè)計為多級隊列Q1、Q2…Qn,每個隊列的大小固定,當前一個隊列Qi數(shù)據(jù)滿了之后,新到的數(shù)據(jù)由Qi+1入隊,同時啟動I/O線程將Qi的數(shù)據(jù)一次性寫入數(shù)據(jù)庫,依次循環(huán)。I/O數(shù)據(jù)隊列的工作流程如圖5所示。
圖5 I/O數(shù)據(jù)隊列工作流程
數(shù)據(jù)包格式由消息頭+源地址+幀號+數(shù)據(jù)長度+數(shù)據(jù)體+校驗位+消息尾組成。除數(shù)據(jù)體外,其他字段均為定長,可降低數(shù)據(jù)包結(jié)構(gòu)的復雜度。數(shù)據(jù)包格式定義如圖6所示。
圖6 數(shù)據(jù)包格式定義
在本系統(tǒng)的應(yīng)用場景下,消息體長度基本固定,數(shù)據(jù)包的長度遠遠小于MSS,但從通用性的角度考慮,數(shù)據(jù)包的設(shè)計需盡可能的完整,以便后續(xù)系統(tǒng)功能的擴充。
使用兩臺Inter(R) Core(TM) i7-8700 CPU3.2G、8G內(nèi)存的PC機,操作系統(tǒng)為Windows 10 64位。在其中一臺安裝客戶端,另一臺安裝服務(wù)器端。
為了能模擬大量客戶,在客戶端程序中增加一個DEBUG模式。該模式下,客戶端會同時啟動N個線程來連接服務(wù)器,并向服務(wù)器發(fā)送標準測試數(shù)據(jù)。每個線程發(fā)送M條數(shù)據(jù),每條數(shù)據(jù)發(fā)送間隔為1秒,總數(shù)據(jù)量為N * M。服務(wù)器端接收數(shù)據(jù),并實時寫入數(shù)據(jù)庫系統(tǒng)。
客戶端在DEBUG模式下,分別啟動100、200、500、1000個線程,每個線程發(fā)送的標準測試數(shù)據(jù)分別為1000、500,200,100條,總數(shù)量均為10萬條。測試結(jié)果如表1所示。
表1 服務(wù)器測試結(jié)果對比
從測試結(jié)果可以看出,CPU的占用率一直穩(wěn)定在8%左右,并未出現(xiàn)較大的增長。另外,內(nèi)存的消耗量與客戶端連接數(shù)量的增加成正比,且呈現(xiàn)線性緩慢增長,表現(xiàn)出了優(yōu)良的穩(wěn)定性。
基于IOCP模型設(shè)計了一套通用的考場身份認證系統(tǒng),詳細的闡述了IOCP模型的關(guān)鍵技術(shù)點,同時給出了具體的設(shè)計思路和實現(xiàn)方法,最后針對服務(wù)器端關(guān)心的兩個重要指標:CPU占用率和內(nèi)存消耗,對系統(tǒng)進行了對比測試,結(jié)果證明了使用該模型的性能優(yōu)勢。另外,基于本系統(tǒng)封裝的網(wǎng)絡(luò)通信模塊,經(jīng)過簡單修改即可移植到在線考試系統(tǒng),具有一定的通用性。