在之前的章節中,我們對服務端系統的設計實現原理進行了剖析,在這一章中,我們將對服務端框架進行實際運用,實現一款運行于內網環境的聊天系統。該聊天系統由客戶端與服務器兩部分組成,同時服務端通過數據庫維護用戶的賬號信息。本章將重點介紹如何運用該服務端框架進行服務器業務邏輯開發。
聊天系統功能分析
本聊天系統只作為服務端框架的運用展示,因此僅限于最基本的局域網聊天工具,數據傳輸均采用為明文形式,并不在安全性上進行深入探討。具體功能需求分析如下:
- l? 用戶注冊:提供新用戶賬號注冊功能,注冊內容包括新用戶的賬號名和密碼。服務器需要檢測注冊信息的有效性,并將注冊用戶的信息保存在數據庫中。
- l? 用戶登錄:用戶根據賬號密碼進行登錄操作,若未進行登錄操作將無權限請求聊天相關消息。服務器查詢數據庫驗證用戶賬號信息,同時判斷該用戶是否已經登陸。用戶賬號不允許重復登錄。
- l? 在線好友更新:已登錄用戶可以主動向服務器查詢當前在線的好友信息。服務器同樣應該及時向所有登錄用戶推送好友上線及下線信息。
- l? 好友聊天:已登錄用戶可以向在線狀態的任意好友發送聊天消息,同時也能接收到來自其他在線狀態的好友發送過來的消息。
- l? 群體聊天:已登錄用戶可以同時向所有在線狀態的好友發送群體聊天消息。同時也能接收到來自其他在線狀態的好友發送的群體聊天消息。
聊天系統服務端實現
數據庫實現
聊天系統需要維護用戶的賬號信息,記錄注冊的新賬號,并對每次登錄的用戶信息進行校驗。我們選用輕量級的Mysql作為數據庫管理系統,并選擇Mysql++作為Mysql的API操作庫。
整個聊天系統只建立了一個名為UserInfo表結構,如表5-1所示。同時根據Mysql++封裝了兩個操作接口,分別添加新用戶賬號信息,以及根據賬號名和密碼查詢該用戶是否存在。
表5-1 用戶賬號信息表UserInfo
字段名 | 數據類型 | 關鍵字 | 約束 | 含義 |
id | int(20) | Y | unique | 唯一標識符 |
username | varchar(20) | N | unique,not null | 賬號名 |
Password | varchar(20) | N | not null | 密碼 |
由于數據庫操作涉及網絡傳輸及磁盤處理,因此屬于耗時操作。而在服務端框架Reactor反應池中的所有處理必須在非阻塞環境下進行,如果進行耗時操作將會阻塞當前線程,導致其它消息事件得不到及時處理。因此在Reactor中進行數據庫操作應該以異步的方式進行,可以通過創建工作線程池的方式處理數據庫等耗時操作。但是在本聊天系統中,只有在注冊新用戶和用戶登錄兩個場景中才涉及數據庫操作。為了簡化開發模型,我們在此直接調用數據庫相關的操作。
數據庫部分并非本文論述重點,因此不再做進一步介紹。
聊天設備類型及消息事件實現
通過分析功能需求,可以得知客戶端連接主要存在兩種狀態下的消息請求,分別為臨時狀態和登錄狀態。在臨時狀態下需要能夠請求注冊新用戶和登錄操作,在登錄狀態下需要能夠請求當前在線用戶信息,向某個用戶發送消息類型和廣播消息類型。并且由于服務器存在登錄超時的機制,客戶端連接不管在臨時狀態還是登錄狀態,均需定時向服務器發送心跳消息。
客戶端的兩種狀態正好對應于服務端框架制定的兩種設備類型,即臨時設備類型和登錄設備類型。如圖5-1所示。我們為臨時設備添加新的注冊賬號消息事件,用于向所有與服務端建立了連接的客戶進行注冊操作。同時我們創建一個新的設備類型,名為ChatType設備類型,并且繼承于登錄設備類型,用于專門處理登錄后和聊天相關的消息事件。
?
圖5-1 聊天設備類型及消息事件
臨時設備類型新添加的消息事件介紹如下:
- l? registerUsr:注冊新的用戶賬號。客戶端在請求該消息事件時需同時傳入期待注冊的賬號名和密碼。服務器接收到該消息事件后,將校驗賬號名和密碼是否合法,然后調用數據庫新賬戶注冊接口,進行賬戶注冊。最后根據數據庫實際插入結果向客戶端發送注冊結果信息。由于該消息事件無需請求權限,任何連接該服務器的客戶均可進行注冊操作。為了防止存在某些客戶進行惡意注冊現象,在實際實現中添加了邀請碼信息。服務端將會驗證客戶端傳來的邀請碼是否正確,如果邀請碼正確才會實際進行注冊操作。
- l? heartMsg:心跳消息。客戶端將會定期發送心跳消息。服務器接收到該消息事件后,將會在超時隊列更新該連接的超時信息。在事件回調實現中,并不會對該消息進行進一步業務處理,而是直接返回。
?
ChatType為新創建的繼承于默認登錄設備的設備類型,具體實現的消息事件介紹如下:
- l? askFriends:請求當前在線的用戶信息。客戶端會在登錄成功后,向服務器請求該消息事件。服務器收到該消息事件后,將會查詢除請求者外,當前其他所有在線的ChatType類型的用戶信息,并整理發送給客戶端。該數據用于客戶端對在線用戶列表的初始化工作。
- l? chatSend:向一個指定在線用戶發送聊天消息。客戶端將會把聊天消息的內容,自己的信息,及期望接收該聊天消息的用戶信息發送給服務器。服務端接收到該消息事件后,將會嘗試找尋該接收用戶,并將聊天消息和發送者消息轉發給該接收用戶,并向發送用戶返回發送成功消息事件。如果該接收用戶并不在線,將只會向發送用戶返回發送失敗的消息事件。
- l? groupSend:向全體在線用戶發送聊天消息。客戶端將會把聊天消息及自身信息發送給服務器。服務器接收到該消息后,將會遍歷除請求者外,當前其他所有在線的ChatType類型的用戶,并將該聊天消息和發送者信息轉發給所有遍歷的用戶,并向發送用戶返回發送成功的消息。
- l? heartMsg:心跳消息。同臨時設備新添加的heartMsg的心跳消息。
聊天設備的生命周期實現
雖然我們已經為聊天客戶端的連接制定了具體的設備類型和消息事件,但是一些和連接狀態相關的業務邏輯仍然需要我們設計。比如如何制定與聊天客戶端相關的具體登錄過程?當某個聊天客戶端已經成功登錄后,怎樣第一時間通知其他客戶端該賬號已上線?當某個聊天客戶端退出時,又如何通知其他客戶端該賬號已下線?這些與客戶端狀態相關的操作都依賴上一章節制定的連接生命周期機制來進行管理。
?
圖5-2 聊天設備的生命周期
具體的聊天設備的連接生命周期實現如圖所示。我們只需對具體業務相關的生命周期接口的實現進行重寫即可,因此無需進行更改的生命周期接口并未被列出。需要被重寫的接口實現介紹如下:
- l? onLoginCheckMsg():當客戶端執行登錄操作,且選擇登錄類型為ChatType設備類型時,將會執行該接口。此時系統將會檢查客戶端傳來的賬號名和密碼是否合法,如果合法將會調用數據庫的賬號密碼查詢接口,判斷該賬戶是否存在。如果該賬戶存在,則返回True,并將控制權交還給系統進行進一步的登錄操作;如果該賬戶不存在,則向該客戶端發送不存在該賬號信息,此次登錄失敗的消息,并直接返回False退出整個登錄操作。
- l? onLoginSuccessMsg():當登錄類型為ChatType的客戶端進行登錄操作,執行onLoginCheckMsg()返回True,且系統整個登錄過程成功時,將會執行該接口。此時系統將會進行兩件工作:首先向該客戶端發送登錄成功的消息;其次將會遍歷除該用戶本身外的其他已登錄的ChatType類型的用戶,并向這些用戶發送通知消息表明該新用戶已上線。這樣每個登錄用戶只需在第一次登錄的時候向服務器請求當前所有在線用戶信息,而新的用戶上線或老用戶下線等信息將由服務器主動推送,而無需客戶端定期請求維護。
- l? onLoginFailureMsg():當登錄類型為ChatType的客戶端進行登錄操作,執行onLoginCheckMsg()返回True,但整個登錄過程失敗時,將會執行該接口。一般該接口被執行將表明此用戶賬號已經被登錄,服務器無法對相同賬號進行重復登錄操作。此時服務器將會向客戶端發送重復登錄,此次登錄失敗的消息,并退出整個登錄操作。
- l? onLogoutMsg():當登錄類型為ChatType的客戶端進行注銷操作時,將會執行該接口。服務器將向該客戶端返回注銷成功的消息,并對該連接進行進一步的注銷操作。
- l? releaseConnNode():當登錄類型為ChatType的客戶端進行注銷成功,或者連接由于超時或者其它異常原因被迫退出時,將會執行該接口。此時服務器將會遍歷除該用戶本身外的其他已登錄的ChatType類型的用戶,并向這些用戶發送通知消息表明該用戶已經下線。同時系統將會把該用戶連接所申請的相關資源進行釋放工作。
- l? onOverTime():當服務器一段時間內未收到該客戶端有效消息或心跳消息,將會被判超時,并執行該接口。一般執行該接口時表明該客戶端已經由于異常而停止工作,但是并未主動斷開連接。此時服務器將嘗試向該客戶端發送一條該連接已經超時的消息。由于客戶端已經處于異常狀態,并不能保證該條消息能夠被客戶端接收。然后服務器將會執行releaseConnNode()接口,并強制斷開該客戶端連接。
聊天系統展示
該聊天系統部署于局域網內,并可通過C#實現的客戶端進行相關操作。運行場景截圖如下:
圖5-3 登錄及注冊窗口
如圖5-3為客戶端的登錄窗口與注冊窗口。注冊窗口通過登錄窗口的注冊按鍵打開,并可進行新賬號的注冊操作。同時登錄窗口可以進行賬號的登錄操作,如果登錄失敗將會顯示具體失敗原因;如果登錄成功,將會進入客戶端主界面。
?
圖5-4 主界面及好友聊天窗口
如圖5-4為客戶端的主界面及聊天窗口。在主界面中顯示了當前登錄的用戶名及當前在線的用戶名列及數量。可以單擊具體用戶名進入與該用戶的聊天窗口。在聊天窗口中,可以在輸入欄輸入消息,并點擊發送鍵或按下回車鍵發送該消息。如果收到其他用戶的聊天消息,客戶端也會主動彈出該用戶的聊天窗口,并顯示接收的的聊天信息。在主界面的下方還存在群聊按鍵,點擊可以進入群聊窗口。
?
圖5-5 群聊窗口
如圖5-5為群聊窗口。在群聊窗口中可以接收到打開了該窗口的其他用戶發送的聊天信息。同時自己也能輸入相關聊天消息并發送給所有該窗口下的其他用戶。