郭驍 陸海燕 陶星州 王浩
摘要:針對典型聊天室開發(fā)中所涉及的服務(wù)端與客戶端程序設(shè)計、數(shù)據(jù)同步、客戶端UI設(shè)計、群組管理、掃碼進(jìn)群方面的基本技術(shù)做了較為詳盡的介紹,詳細(xì)地分析了各個功能模塊的實現(xiàn)方法,提出了一系列在Unity3D開發(fā)客戶端程序的設(shè)計思路,為在Unity3D平臺實現(xiàn)網(wǎng)絡(luò)聊天功能提供了較為完整的解決方案,并以實例證明了該方案的可行性。
關(guān)鍵詞:Unity3D;聊天室;服務(wù)端;客戶端;掃碼進(jìn)群;群組管理
中圖分類號:TP311 ? ?文獻(xiàn)標(biāo)識碼:A
文章編號:1009-3044(2019)15-0073-03
Abstract: This paper introduces in detail the basic technologies of client and server programming, data synchronization, client UI design, group management, scanning and grouping involved in the development of typical chat rooms, analyses in detail the implementation methods of each functional module, and puts forward a series of design ideas for developing client program in Unity3D, in order to realize network chat on Unity3D platform. The sky function provides a more complete solution, and the feasibility of the solution is proved by an example.
Key words: U3D; chat room; server; client; sweep code into group; group management
1 背景
隨著網(wǎng)絡(luò)通信技術(shù)的迅猛發(fā)展,特別是微信、QQ和釘釘?shù)壬缃卉浖膽?yīng)用普及,采用聊天室方式的信息溝通成為生活與工作中的重要需求。在各種實際工作場景中,聊天室這一用于實現(xiàn)消息通知、文件及音視頻傳輸?shù)膽?yīng)用,已經(jīng)成為一種基本功能。通常一個具體的網(wǎng)絡(luò)應(yīng)用程序都擁有屬于自己的用戶群體,而聊天室正是用戶群體間信息交流中不可或缺的一部分。
Unity3D作為一款游戲引擎,正在越來越多地被應(yīng)用于游戲之外的各種領(lǐng)域如建筑、汽車、醫(yī)學(xué)、教育等傳統(tǒng)行業(yè);同時Unity3D也是VR技術(shù)發(fā)展中一股重要的力量,可以預(yù)見在不遠(yuǎn)的將來,會出現(xiàn)各種具有聊天室功能并以Unity3D為平臺的網(wǎng)絡(luò)應(yīng)用程序。本文采用面向客戶/服務(wù)器模型(Client/Server,C/S)設(shè)計的Socket技術(shù)[1],基于Unity3D平臺,探索典型聊天室開發(fā)的解決方案。本文開發(fā)環(huán)境采用Windows10操作平臺,MySQL5.6Server數(shù)據(jù)庫和Unity2018.3.10f1 (64-bit)個人版。
2 總體方案設(shè)計
2.1 需求分析
在本項目中,將基于Unity平臺實現(xiàn)網(wǎng)絡(luò)聊天室的基本功能,包括登錄、群聊、群組管理、掃碼進(jìn)群等。
2.2 基本設(shè)計概念和處理流程
服務(wù)器首先將Socket初始化,綁定指定的端口,進(jìn)行端口監(jiān)聽,調(diào)用BeginAccept方法實現(xiàn)異步接收,等待客戶端連接,同時服務(wù)器連接到MySQL數(shù)據(jù)庫,讀取數(shù)據(jù)庫中信息,載入歷史配置。當(dāng)有客戶端發(fā)出連接請求時,服務(wù)器將實例化一個Conn對象至連接池,并初始化Conn對象的Socket,如果連接成功,便可以在客戶端與服務(wù)端之間傳輸數(shù)據(jù)。服務(wù)端接收客戶端發(fā)送的數(shù)據(jù)請求并處理,處理結(jié)束后將處理結(jié)果發(fā)送給指定客戶端,該客戶端接收到服務(wù)端發(fā)來的數(shù)據(jù),并利用該數(shù)據(jù)執(zhí)行各種業(yè)務(wù)邏輯。當(dāng)結(jié)束數(shù)據(jù)傳輸之后則關(guān)閉連接,客戶端與服務(wù)端不再進(jìn)行交互。
該聊天室采用面向連接的傳輸控制協(xié)議,即TCP(Transmission Control Protocol),在傳輸數(shù)據(jù)之前必須先建立連接[2],完成數(shù)據(jù)輸出之后應(yīng)釋放連接。服務(wù)器采用異步接收,可以并發(fā)處理多個用戶的連接請求,需要注意的是,每一次開辟新的線程來處理連接時,都需要向系統(tǒng)申請內(nèi)存,隨著連接數(shù)的增加,系統(tǒng)內(nèi)存損耗也會迅速上升,因此當(dāng)客戶端不再傳輸數(shù)據(jù)時要及時銷毀線程,釋放內(nèi)存。
3 服務(wù)端設(shè)計
在服務(wù)端程序中首先需要綁定一個固定端口并監(jiān)聽來自客戶端的連接,同時連接到數(shù)據(jù)庫并同步歷史數(shù)據(jù)(如群組信息、資源文件路徑等),初始化連接池,等待接收來自客戶端的連接。
在客戶端發(fā)出連接請求時,服務(wù)端與該客戶端建立Socket連接,若連接成功,將在連接池中實例化Conn對象,同時開辟一個新的線程來處理服務(wù)端與該客戶端的連接。使用多線程的策略是因為服務(wù)端需要同時處理多個客戶端的連接[3-4],在實際應(yīng)用場景中,在同一時刻往往會有大量客戶端訪問同一服務(wù)端,使用單線程逐個響應(yīng)顯然在效率上是無法接受的。
在成功建立連接后,服務(wù)端將處理來自客戶端的各種請求,并在處理完成將結(jié)果回傳給客戶端,如果這些請求中包含了數(shù)據(jù)庫相關(guān)的操作,服務(wù)端將訪問啟動時連接的數(shù)據(jù)庫,對數(shù)據(jù)庫進(jìn)行增、刪、改、查等操作。
3.1 綁定端口
實例化ServNet對象,并調(diào)用Start函數(shù),在該函數(shù)中,將調(diào)用Bind方法綁定端口與具體的IP地址,在本地測試時將使用本機地址127.0.0.1,若需要在公網(wǎng)狀態(tài)下測試,需要重新配置Socket。在服務(wù)端程序中提供了手動配置的接口,測試人員可以通過命令行對正在運行的服務(wù)端程序進(jìn)行重啟程序、重置端口等操作,便于在不同環(huán)境下的調(diào)試。
3.2 建立連接
在成功啟動服務(wù)器后,程序?qū)⒆詣诱{(diào)用BeginAccept方法,與發(fā)出連接請求的客戶端建立連接[5],如果出現(xiàn)連接失敗的情況,程序?qū)伋霎惓?,并在連接池中銷毀錯誤的Conn對象。在BeginAccept方法中需要傳入一個回調(diào)函數(shù),在該回調(diào)函數(shù)中將繼續(xù)調(diào)用BeginAccept方法,實現(xiàn)服務(wù)端不斷接收客戶端的連接請求,同時調(diào)用BeginReceive方法,不斷接收客戶端發(fā)送的信息。
3.3 響應(yīng)處理
針對客戶端的各種數(shù)據(jù)請求,服務(wù)端將采取不同的處理策略,對于聊天內(nèi)容,服務(wù)端首先將發(fā)送者ID、內(nèi)容、所屬群組、發(fā)送時間等信息整合后存入數(shù)據(jù)庫作為聊天記錄,然后遍歷該消息所屬群組的成員,將消息廣播至所有群組成員所屬的客戶端。對于命令類的數(shù)據(jù)請求,如登錄賬號、創(chuàng)建群組、管理群組等,服務(wù)端首先判斷該命令是否有效,確認(rèn)為合法命令后解析命令字段,獲取命令的具體類型,然后在ProcessData方法中使用分支結(jié)構(gòu),對不同命令進(jìn)行相應(yīng)的處理,然后將處理結(jié)果發(fā)回該客戶端,供客戶端執(zhí)行后續(xù)的操作。
4 聊天室典型功能的實現(xiàn)
聊天室典型功能有登錄賬號、建立群組、群聊及群員管理等,下面介紹具體實現(xiàn)。
1)登錄功能的實現(xiàn)
系統(tǒng)將所有用戶的信息組織為一張表userdata,該表存放在與服務(wù)端相連的數(shù)據(jù)庫msgboard中。用戶輸入賬號與密碼后單擊Login按鈕,客戶端向服務(wù)器發(fā)出登錄請求,在收到登錄請求后,服務(wù)端將根據(jù)客戶端提供的賬號信息與數(shù)據(jù)庫中的信息進(jìn)行驗證,并回傳驗證結(jié)果。驗證成功后,服務(wù)器會為發(fā)出請求的Conn對象初始化User信息(包括用戶名、權(quán)限等級等),將該用戶設(shè)置為在線狀態(tài),從而實現(xiàn)用戶的登錄。
2)群聊功能的實現(xiàn)
群聊功能可以拆分為創(chuàng)建群組、發(fā)送信息、接收信息、消息提示幾個部分,下面將分別論述各部分功能的具體實現(xiàn)方法。
創(chuàng)建群組的實現(xiàn)。用戶在主界面中可以找到創(chuàng)建群組選項,輸入群組信息后單擊確認(rèn)按鈕,客戶端將向服務(wù)端發(fā)出創(chuàng)建群組請求,服務(wù)端根據(jù)服務(wù)端發(fā)送的初始數(shù)據(jù)(群名、人數(shù)等)在數(shù)據(jù)庫msgboard中的表grouptable中添加記錄,同時生成一張與群組名對應(yīng)的表,用于存放群聊記錄,從而完成新群組在數(shù)據(jù)庫的注冊。
發(fā)送信息的實現(xiàn)。客戶端通過交互界面獲取用戶將要發(fā)送的字符串消息,將字符串使用Base64碼制編碼后,利用Socket提供的異步發(fā)送方法BeginSend將字節(jié)流發(fā)送至服務(wù)器,這一過程將在客戶端開辟新的線程,從而避免系統(tǒng)阻塞。由于存在編碼后報文長度較長的情況,因此本項目中統(tǒng)一使用了分包的策略,當(dāng)發(fā)送信息時,系統(tǒng)首先根據(jù)信息的長度將數(shù)據(jù)分為若干個數(shù)據(jù)包,并在包頭寫入包體長度等驗證信息,然后再發(fā)送給服務(wù)器。當(dāng)服務(wù)器接收到所有的數(shù)據(jù)包后,將會根據(jù)包頭中附帶的長度信息檢驗包體的完整性,然后再發(fā)送給群組中的其他用戶。
接收信息的實現(xiàn)。由于客戶端與服務(wù)器均使用了分包發(fā)送策略,因此在客戶端實際接收到的數(shù)據(jù)也是多個數(shù)據(jù)包,這里同樣根據(jù)包頭附帶信息校驗完整性,并進(jìn)行粘包操作,整合報文,然后對整合后的Base64編碼進(jìn)行解碼操作得到常用的UTF-8編碼,最后在交互界面進(jìn)行相應(yīng)的顯示。
消息提示的實現(xiàn)。無論用戶是否正處在群聊界面,每當(dāng)客戶端收到消息都會把消息實例化為MessageCache對象(MessageCache為本項目中用于描述消息屬性的類)并存儲在MessageCacheList中。如果用戶不在群聊界面,將提示用戶收到新消息(僅顯示數(shù)目),而當(dāng)用戶正在群聊時,則會遍歷MessageCacheList,根據(jù)列表中每一個MessageCache對象中的數(shù)據(jù)逐條顯示消息內(nèi)容。
3)掃碼進(jìn)群聊功能的實現(xiàn):
掃碼進(jìn)群功能分為兩個基本步驟,即生成群組二維碼與解析群組二維碼。
群組二維碼的生成。本項目中使用了草料二維碼的開源庫ZXing.DLL,通過交互界面讀取用于生成二維碼的群組信息,然后調(diào)用ZXing自帶的Encode接口,即可得到相應(yīng)的Texture2D對象,再將該Texture2D對象賦值給Unity原生的RawImage組件即可實現(xiàn)在屏幕上顯示二維碼。用戶也可以選擇單擊二維碼生成界面的Save按鈕將二維碼圖片保存至本地。
掃描二維碼。首先利用ZXing在BarcodeReader類中提供的讀取攝像頭內(nèi)容的方法,將拍攝到的畫面實例化為RawImage對象,并在Update方法中根據(jù)攝像頭獲取的畫面持續(xù)更新RawImage對象,從而實現(xiàn)對畫面的實時捕捉。同時在Update方法中監(jiān)聽BarcodeReader類提供的Decode方法,當(dāng)該方法將攝像頭獲取到的畫面識別為有效二維碼時,二維碼解析成功,從屏幕捕捉到的二維碼將被轉(zhuǎn)換為普通的UTF-8字符串,客戶端解析字符串中所包含的群組信息后,向服務(wù)器發(fā)送加入群組請求,服務(wù)器收到請求后,將用戶信息插入群組對應(yīng)的表中,完成用戶信息在數(shù)據(jù)庫的同步,從而實現(xiàn)掃碼進(jìn)群。
5 客戶端設(shè)計
客戶端程序首先通過套接字連接到服務(wù)器IP地址(在本地測試中為本機IP地址,即127.0.0.1),默認(rèn)端口3000??蛻舳伺c服務(wù)端建立連接后,客戶端通過圖形交互界面獲取用戶輸入流,并向服務(wù)端發(fā)送信息,同時監(jiān)聽服務(wù)器端口[6],以接收來自服務(wù)器的信息。客戶端的具體工作流程如下:在使用客戶端提供的各項功能之前,需要用戶輸入要進(jìn)行連接的服務(wù)端IP與端口,當(dāng)用戶單擊確認(rèn)按鈕后,程序調(diào)用Connect方法與服務(wù)端建立連接,服務(wù)端才可以接收并處理客戶端發(fā)出的各項請求。
本項目中使用了Unity作為客戶端可視化圖形界面開發(fā)工具,與傳統(tǒng)的GUI開發(fā)工具(如MFC、QT等)不同,Unity作為一個游戲引擎,展示給用戶的是場景(Scene)而不是對話框(Dialog),因此在實際工程中,不同功能所對應(yīng)的UI將被分別放在不同的場景中實現(xiàn)。
1)登錄界面。在Title4Android場景中,為用戶提供了四個Inputfile組件用于輸入服務(wù)器IP、Port以及用戶的賬號、密碼,并在登錄按鈕中綁定了Connect與Login方法,用于實現(xiàn)連接服務(wù)器、登錄賬號功能。
2)主界面。在MainUI4Android場景中,展示了用戶的個人信息以及當(dāng)前加入的群組,其中群組信息通過一組GroupInfo預(yù)制體與其父物體上的GridGroup組件共同組成,每當(dāng)用戶進(jìn)入MainUIOnPC場景或單擊InitGroup按鈕來手動刷新群組信息時,客戶端都會向服務(wù)器發(fā)出查詢請求,通過服務(wù)器返回的查詢結(jié)果將各個群組的信息分別實例化為GroupInfo對象,并掛載在父物體Groups上,從而實現(xiàn)群組信息的列表化展示。
3)群聊界面。在ChatOnPC場景中,展示了用戶當(dāng)前訪問的群組信息以及群組中各個成員發(fā)送的群聊消息。消息的列表化展示與群組信息類似,同樣使用了Prefab+GridGroup(即預(yù)制體+網(wǎng)格組)的策略[7],即先設(shè)計承載一條消息內(nèi)容(包括發(fā)送者ID、消息內(nèi)容)的預(yù)制體,然后將多個預(yù)制體實例化后依次掛載在GridGroup組件下。每當(dāng)收到群員消息時,客戶端程序都會將收到報文解析為發(fā)送者信息與內(nèi)容字段,并以此為參數(shù)實例化預(yù)制體對象,然后掛載到父物體,從而實現(xiàn)消息的動態(tài)顯示。
6 結(jié)束語
本項目結(jié)合Socket網(wǎng)絡(luò)通訊、MySQL數(shù)據(jù)庫技術(shù),實現(xiàn)了數(shù)據(jù)信息在網(wǎng)絡(luò)中的傳輸,并基于Unity平臺實現(xiàn)了網(wǎng)絡(luò)聊天室的基本功能。程序中的各功能模塊之間較好地實現(xiàn)了松散耦合,因此本項目很容易單獨作為一個功能嵌入到其他Unity工程中。
(下轉(zhuǎn)第82頁)
(上接第75頁)
但由于服務(wù)器使用了多線程技術(shù),系統(tǒng)會為每一個連接開辟新的線程[8],內(nèi)存開銷較大,因此服務(wù)器的負(fù)載能力有限,同時由于程序本身機制所限,在同時處理大量連接以及傳輸數(shù)據(jù)包體過大時,系統(tǒng)的運行速度會明顯降低,還需要對系統(tǒng)性能做進(jìn)一步的優(yōu)化。
參考文獻(xiàn):
[1] 陳潔, 孟曉景. 基于Socket接口的Linux與Windows網(wǎng)絡(luò)聊天室設(shè)計與實現(xiàn)[J]. 軟件導(dǎo)刊, 2015(6).
[2] 葛福鴻, 張麗萍. 基于Socket技術(shù)的聊天軟件設(shè)計與實現(xiàn)[J]. 電腦編程技巧與維護, 2012(5).
[3] 楊心強, 陳國有. 數(shù)據(jù)通信與計算機網(wǎng)絡(luò)[M]. 北京: 電子工業(yè)出版社, 2018(6).
[4] 常穎. 基于ASP網(wǎng)絡(luò)聊天室設(shè)計與實現(xiàn)[J]. 電子技術(shù)與軟件工程, 2017(3).
[5] 羅培羽. Unity3D網(wǎng)絡(luò)游戲?qū)崙?zhàn)[M]. 北京: 機械工業(yè)出版社, 2015.
[6] 宋傳磊, 劉俊婷, 張光亮, 等. Unity3D項目腳本優(yōu)化分析與研究[J]. 中國新通信, 2017(1).
[7] 宣雨松. Unity 3D游戲開發(fā)[M]. 北京: 人民郵電出版, 2012.
[8] 鄧亞君, 楊剛. 基于Python的網(wǎng)絡(luò)聊天室設(shè)計[J]. 電子技術(shù)與軟件工程, 2019(3).
【通聯(lián)編輯:謝媛媛】