張立立,徐博文,楊金柱*,王 彤,高東博
(1.東北大學 計算機科學與工程學院 計算機國家級實驗教學示范中心,沈陽 110819;2.中核控制系統(tǒng)工程有限公司,北京 102400)
學習利用cpp 知識、參考Qt 手冊,綜合運用計算機網(wǎng)絡,操作系統(tǒng)原理(多線程同步),數(shù)據(jù)結(jié)構,數(shù)據(jù)庫技術,MVC 設計模式,面向?qū)ο蟪绦蛟O計方法,軟件工程學科等知識開發(fā)實際系統(tǒng)。熟悉應用軟件的開發(fā)過程,要求軟件界面美觀,操作簡便,符合象棋棋牌室的實際游戲場景。
基于以上基本教學要求,本文開發(fā)了一套象棋棋牌室游戲系統(tǒng),課程目標是通過學生熟悉的網(wǎng)絡游戲入手,逐步完善游戲功能,讓學生在編程學習中建立信心和成就感,從而對編程產(chǎn)生興趣,進行更深入的研究和探索。
系統(tǒng)由服務器、客戶端兩部分組成,客戶端具有選座界面、對局界面。
用戶啟動客戶端軟件之后,客戶端顯示界面并自動嘗試與服務器建立連接,若連接成功,界面顯示“已連接”;若連接失敗,系統(tǒng)顯示“未連接”,用戶可以通過“重新連接”按鈕來重新連接服務器。
服務器連接成功后,用戶可以輸入用戶名、密碼向服務器發(fā)送登錄請求,服務器校驗后向客戶端發(fā)送成功/失敗反饋。若已有客戶端使用此用戶名登錄,則不可以重復登陸。
完成登陸后,系統(tǒng)顯示當前登錄的用戶名和積分。此時,客戶端顯示選座界面,用戶可以:(1)創(chuàng)建比賽對局(房間);(2)取消創(chuàng)建的對局(房間);(3)加入已有的對局(房間);(4)觀戰(zhàn)正在進行的對局。
當找到對手(加入房間/創(chuàng)建的房間被加入)或選擇觀戰(zhàn)模式之后,界面切換到對局界面。非觀戰(zhàn)模式下,用戶可以依據(jù)棋規(guī)移動棋子,移動不符合棋規(guī)時,系統(tǒng)不響應。
雙人對戰(zhàn)過程中如果某一方獲勝,對戰(zhàn)終止。
此時,勝負兩方分別加減積分,客戶端返回選座界面;如果有一方點擊“退出/認輸”按鈕,則判定這一方為負,其他同上。觀戰(zhàn)過程中,點擊“退出/認輸”按鈕,客戶端返回選座界面。流程如圖1 所示。

圖1 系統(tǒng)功能流程圖
整個系統(tǒng)由一個中心服務器和若干個客戶端組成。用戶功能上主要有對局系統(tǒng)和棋牌室座位管理系統(tǒng)兩部分。
服務器和客戶端均利用C++和Qt 框架編寫。為了實現(xiàn)了多人時操作、客戶端網(wǎng)絡通信不影響用戶操作,使用了多線程技術。服務器和客戶端之間采用TCP 協(xié)議進行通信,服務器采用改進過的bio 模式,即為每個客戶端連接建立兩個線程分別用來收發(fā)網(wǎng)絡消息。
選座系統(tǒng)、棋規(guī)判定系統(tǒng)和棋盤模型等均位于服務器端。客戶端部分除了網(wǎng)絡模塊之外,僅保存視圖。服務器端連接mysql 數(shù)據(jù)庫存儲用戶名、密碼和積分信息。
Qt 是一個跨平臺的C++應用程序開發(fā)框架。它提供給開發(fā)者建立圖形用戶界面所需的功能,廣泛用于開發(fā)GUI 程序,也可用于開發(fā)非GUI 程序。Qt 是完全面向?qū)ο蟮模苋菀讛U展,并且允許真正地組件編程。
服務器端由一個線程負責建立TCP 連接,連接建立成功后,開啟兩個新的線程用來進行socket 通信。
一個線程用于向客戶端發(fā)送數(shù)據(jù),相當于“生產(chǎn)者消費者”模型中的消費者。在沒有需要發(fā)送的消息時利用Qt 框架的WaitCondition 等待(相當于優(yōu)化后的自旋鎖,可以有效降低cpu 使用率)。當需要發(fā)送消息時,其他線程設置好此消息線程對象的消息內(nèi)容成員,并喚醒此線程(相當于打開自旋鎖),就可以實現(xiàn)消息發(fā)送。在使用Qt 的信號與槽連接時,使用Qt::Dirrectconnection連接方案,否則會因為目標線程阻塞而無法喚醒線程發(fā)送消息。
另一個線程用于接收客戶端數(shù)據(jù),對于每一次接收的數(shù)據(jù)。利用Qt 的信號與槽機制注冊到UserConnection類的doRecv 函數(shù)。這樣就形成了如下設計。
每個抽象的UserConnection 對應一個實際的用戶連接。
所有連接(UserConnection)共享一個RoomManager類的對象roommanager。
所有連接(UserConnection)共享一個數(shù)據(jù)庫操作類。
每個連接有一個獨立的連接抽象層(組合了收發(fā)線程的TCPConnection 類)用于管理收發(fā)線程。
對于服務器的消息發(fā)送,調(diào)用連接抽象層的send函數(shù)即可向客戶端發(fā)送消息。
對于服務器的消息接收,直接回調(diào)doRecv 函數(shù),相應的業(yè)務就可以在此函數(shù)中得到處理,而無需考慮網(wǎng)絡具體實現(xiàn)。
用這樣類似java web 中servlet 的方式可以對底層的網(wǎng)絡部分更好地封裝,降低耦合便于拓展。
客戶端的網(wǎng)絡模塊設計與服務器類似,只是不同于服務器多個連接,客戶端僅有收發(fā)兩個線程。
系統(tǒng)整體采用mvc 設計模式,便于降低代碼耦合性,有利于組件的重用。如圖2 所示。

圖2 mvc 設計模式
客戶端的ChessView 等類繼承自Qt 的Widget,用來與用戶交互。
服務器的ChessField 類是棋盤模型,棋規(guī)判定、勝負判定等業(yè)務邏輯在此類中實現(xiàn)。這個類聚合了抽象類ChessPiece 的不同實現(xiàn),對應不同種類的棋子。每個子類有各自的邊界判斷,移動判斷函數(shù)。
控制器為上文提到的UserConnection 類。
每一個UserConnection 中都有一個相同的Room-Manager 類指針指向一個共享的RoomManager 對象,程序截圖如圖3 所示。

圖3 RoomManager 類指針函數(shù)程序
可以看到:該類提供了新建房間、加入房間、觀看對局等接口,接收的參數(shù)是User,用戶連接使用時通過傳入this 指針就可以實現(xiàn)操作。
存儲的數(shù)據(jù)結(jié)構使用了鍵值對(QMap),其結(jié)果如圖4 所示。

圖4 QMap 結(jié)構圖
對于一個沒有對手的“單人房間”,在room_struct 當中存放兩個nullptr,這時鍵UserConnection 代表房間,作為房間句柄。
“加入比賽”時,為新加入的人也創(chuàng)建一個鍵值對,即一個“房間”分配兩個空間,這樣可以更加高效地定位棋盤模型和對手。
調(diào)用roomInfo 函數(shù)不直接返回該數(shù)據(jù)結(jié)構,而是將冗余的第二份房間信息去除,這樣得到的QMap 的鍵UserConnection 可以唯一標識一個房間,作為房間句柄使用。程序截圖如圖5 所示。

圖5 QMap 的鍵函數(shù)程序
這個類同時也維護用戶列表,提供重復登陸判斷等接口。
1.并發(fā)過程中數(shù)據(jù)一致性問題
問題描述:調(diào)試時,程序運行過程中出現(xiàn)非預期的不合理結(jié)果。比如修改某些數(shù)據(jù)不生效等情況。
原因:roommanager 共享對象在多線程環(huán)境下有可能同時被多個線程同時寫入,進而出現(xiàn)數(shù)據(jù)一致性問題。
解決方案:roommanager 等多線程共享對象,在需要寫入時需要用Qt 提供的QMutex 互斥鎖對給資源加鎖,在修改完成后釋放互斥鎖,在此過程中其他的線程將不能進行修改。
2.客戶端連接釋放問題
問題描述:調(diào)試過程中,客戶端程序突然終止連接后,服務器程序常常無響應或是異常終止。而作為一個服務器,因為某客戶端的某些行為導致崩潰是不能接受的。
原因:C++語言是不同于java 一類具有垃圾回收器的編程語言,需要手動管理內(nèi)存及相應的資源。在客戶端中斷連接以后,必須要結(jié)束為該用戶創(chuàng)建的連接線程,釋放內(nèi)存,清除所有此用戶的對局狀態(tài)信息,同時從已登陸用戶列表中移除該用戶。
解決方案:必須找到合理的方式釋放資源。
在線程創(chuàng)建過程中將RecvThread 類的die 信號與TCPConnection 類的析構方法連接。當檢測到客戶端連接中斷后,RecvThread 發(fā)出die 信號。這時,首先析構TCPConnection 對象,在~TCPConnection()中析構User-Connection 類的對象。
為了清除所有此用戶的對局狀態(tài)信息,同時從已登陸用戶列表中移除該用戶:UserConnection 類的析構方法調(diào)用RoomManager::logoff,此函數(shù)釋放所有包含UserConnection 的對局。并在users:QSet 中刪除這個User,從而實現(xiàn)將該用戶標記為非登陸狀態(tài)。
完成以上這些之后,關閉TCPSendThread 線程,由于此線程處于阻塞狀態(tài)(等待網(wǎng)絡消息的生產(chǎn)者生產(chǎn)消息),故不能直接結(jié)束線程。使用Qt 提供的terminate()函數(shù)強制終止線程有會帶來一系列的問題,比如影響到其他正常運行的線程。故設計了如圖6 所示的interrupt 方法。

圖6 interrupt 方法程序設計
最后,需要終止TCPRecvThread。
圖7 為系統(tǒng)登錄界面,正確輸入用戶名和密碼后,點擊登錄按鈕,可以進入到選座系統(tǒng)界面,如圖8 所示。

圖7 系統(tǒng)登錄成功界面

圖8 選座系統(tǒng)界面
選座完成后,點擊建立房間,進入對戰(zhàn)模式,如圖9所示。

圖9 對戰(zhàn)界面
本文給出了使用C++和Qt 技術進行象棋棋牌室游戲軟件的設計方案,詳細介紹了系統(tǒng)各部分的功能原理、運行流程及具體的實現(xiàn)方案,最后對系統(tǒng)各個功能進行測試。本設計案例重在鞏固學生對理論知識的理解,讓學生明白只有通過實踐才能驗證所學所得、才能熟練掌握課程知識。在實踐中多層次全方位的學習知識、在實踐中加深對所學知識的印象,鍛煉軟件開發(fā)的能力,以及對系統(tǒng)調(diào)試和解決實際問題的能力。