在 Avalonia 如火如荼的現在,之前使用CPF實現的簡單IM,非常有必要基於 Avalonia 來實現了。Avalonia 在跨平臺上的表現非常出色,對信創國產作業系統(像銀河麒麟、統信UOS、Deepin等)也很不錯。
現在,我們就來使用 Avalonia 實現一個跨平臺的簡單IM,除了文字聊天外,還可以語音視訊通話。廢話不多說,我們開始吧!
下圖是這個簡單IM的Avalonia客戶端在國產統信UOS上的執行的截圖:
一. IM 即時通訊系統主要功能
這個簡單的IM系統實現了以下功能:
1.基礎功能、文字聊天
(1)客戶端使用者上下線時,通知其他線上使用者。
(2)當客戶端與服務端網路斷開時,進行自動重連,當網路恢復後,重連成功。
(3)所有線上使用者之間可以進行文字聊天(支援表情,支援撤回訊息、刪除訊息)。
(4)檔案傳送。
2.語音影片聊天、遠端桌面
(1)一方發起影片對話請求,對方同意後,即可開始影片對話。
(2)在對話的過程中,任何一方都可以結束通話,以終止對話。
(3)在對話的過程中,任何一方掉線,都會自動終止對話。
(4)雙擊影片視窗,會全屏顯示影片,按esc退出全屏。
(5)遠端桌面或遠端協助功能,也是跟影片聊天同樣的流程,不再贅述。
二.開發環境
1.開發工具:
Visual Studio 2022
2. 開發框架:
.NET Core 3.1
3.開發語言:
C#
4.其它框架:
Avalonia UI 框架(版本:0.10.22)、ESFramework 通訊框架 (版本:7.2)
注:建議 Avalonia 使用0.10.*的版本,精簡而且很穩定,而最新的11.0的版本太龐大了。
三.具體實現
下面我們講一下Demo中核心的程式碼實現,大家從文末下載原始碼並對照著原始碼看,會更清楚些。
1.自定義訊息型別 InformationTypes
若要實現上述功能列表中列出來的所有功能,我們先要定義相應的通訊訊息的訊息型別,如下所示:
public static class InformationTypes { /// <summary> /// 文字(表情)聊天資訊 /// </summary> public const int TextChat = 0; /// <summary> /// 文字(表情)聊天資訊 (由服務端轉發給訊息接收方) /// </summary> public const int TextChat4Transit = 1; /// <summary> /// 圖片聊天資訊 /// </summary> public const int ImageChat = 2; /// <summary> /// 收到訊息傳送者 撤回訊息請求 /// </summary> public const int RecallMsg = 3; /// <summary> /// 客戶端非同步呼叫服務端 /// </summary> public const int ClientSyncCallServer = 4; /// <summary> /// 影片請求 5 /// </summary> public const int VideoRequest = 5; /// <summary> /// 回覆影片請求的結果 6 /// </summary> public const int VideoResult = 6; /// <summary> /// 通知對方 結束通話 影片連線 7 /// </summary> public const int CloseVideo = 7; /// <summary> /// 通知好友 網路原因,導致 影片中斷 8 /// </summary> public const int NetReasonCloseVideo = 8; /// <summary> /// 通知對方(忙線中) 結束通話 影片連線 9 /// </summary> public const int BusyLine = 9; /// <summary> /// 收到遠端協助請求 /// </summary> public const int AssistReceive = 10; /// <summary> /// 協助方拒絕遠端協助 /// </summary> public const int AssistGusetReject = 11; /// <summary> /// 協助方同意遠端協助 /// </summary> public const int AssistGusetAgree = 12; /// <summary> /// 請求方關閉遠端協助 /// </summary> public const int AssistOwnerClose = 13; /// <summary> /// 協助方關閉遠端協助 /// </summary> public const int AssistGusetClose = 14; }
在約定好訊息型別之後,我們就可以實現業務邏輯功能了。
2.定義協議類
資訊型別定義好後,我們接下來定義資訊協議。
對於聊天訊息(InformationTypes.EmotionTextChat),專門定義了一個協議類:ChatMessageRecord。
public class ChatMessageRecord { public string Guid { get; set; } public DateTime MessageTime { get; set; } public string SpeakerID { get; set; } public string ListenerID { get; set; } public ChatMessageType ChatMessageType { get; set; } public string ContentStr { get; set; } public byte[] ImgData { get; set; } public string FilePath { get; set; } }
對於同步呼叫(InformationTypes.ClientSyncCallServer),我們示例的是向伺服器請求加法運算的結果,協議類用的是MathModel。
3.實現自定義資訊處理器
客戶端的MainForm實現了ICustomizeHandler介面,其主要實現HandleInformation方法,來處理收到的聊天資訊和振動提醒。
void HandleInformation(string sourceUserID, int informationType, byte[] info);
服務端的CustomizeHandler實現了服務端的ICustomizeHandler介面,其主要實現HandleQuery方法來處理來自客戶端的同步呼叫(InformationTypes.ClientCallServer)。
byte[] HandleQuery(string sourceUserID, int informationType, byte[] info);
4.服務端驗證使用者登入的帳號
服務端的BasicHandler類實現IBasicHandler介面,以驗證登入使用者的賬號密碼。
public class BasicHandler : IBasicHandler { /// <summary> /// 此處驗證使用者的賬號和密碼。返回true表示透過驗證。 /// </summary> public bool VerifyUser(ClientType clientType, string systemToken, string userID, string password, out string failureCause) { failureCause = ""; return true; } public string HandleQueryBeforeLogin(AgileIPE clientAddr, int queryType, string query) { return ""; } }
本demo中,假設所有的驗證都透過,所以驗證方法直接返回true。
5.客戶端實現文字聊天功能
透過IRapidPassiveEngine的 CustomizeOutter 的 Send 方法來傳送文字表情聊天訊息。
在傳送文字聊天訊息時,有兩個傳送按鈕,“傳送1”和“傳送2”,分別演示了兩種傳送訊息給對方的方式:
(1)直接發給對方。(若P2P通道存在,則經由P2P通道傳送)
internal static void SendTextMsgToClient(ChatMessageRecord record) { try { string cont = JsonConvert.SerializeObject(record); byte[] recordInfo = Encoding.UTF8.GetBytes(cont); //使用Tag攜帶 接收者的ID App.PassiveEngine.CustomizeOutter.Send(record.ListenerID, InformationTypes.TextChat4Transit, recordInfo); } catch (Exception e) { logger.Log(e, "GlobalHelper.SendTextMsgToClient", ErrorLevel.Standard); } }
聊天訊息 ChatMessageRecord 物件先由JSON序列化成字串,然後在使用UTF-8轉成位元組陣列,然後透過通訊引擎的CustomizeOutter傳送出去。
(2)先發給伺服器,再由伺服器轉發給對方。
具體實現,大家去參看原始碼,這裡就不再贅述了。
6.客戶端實現語音視訊通話功能
語音視訊通話實際執行起來後的效果如下所示:
我們先簡單描述一下實現影片對話流程的要點,更詳細的細節請查閱原始碼。
(1)發起方傳送InformationTypes.VideoRequest型別的資訊給對方,以請求影片對話。
程式中是在 VideoChatWindow 視窗顯示的時候,來做這件事的:
protected override void OnInitialized() { base.OnInitialized(); this.SetWindowStats(); if (!this.IsWorking) { VideoController.Singleton.SendMessage(this.DestID, InformationTypes.VideoRequest, null); CommonHelper.AddSystemMsg(this.DestID, "向對方發起視訊通話邀請"); } }
(2)接收方收到請求後,介面提示使用者是同意還是拒絕,使用者選擇後,將傳送InformationTypes.VideoResult型別的資訊給請求方,資訊的內容是一個bool值,true表示同意,false表示拒絕。
(3)發起方收到回覆,如果回覆為拒絕,則介面給出對應的提示;如果回覆為同意,則進入(4)。
(4)先說接收方,如果同意影片,則傳送回覆後,立即呼叫DynamicCameraConnector和MicrophoneConnector的Connect方法,連線到對方的攝像頭、麥克風。
internal void BeginConnect() { UiSafeInvoker.ActionOnUI(() => { string tip = this.IsWorking ? "已同意對方的視訊通話" : "對方同意了你的視訊通話請求"; CommonHelper.AddSystemMsg(this.DestID, tip); this.IsWorking = true; this.NotifyOther = true; this.Title = this.title.Text = this.RepeatedCallTip(false); this.startTime = DateTime.Now; this.timer.Start(); this.otherCamera.Core.DisplayVideoParameters = true; this.otherCamera.Core.VideoDrawMode = VideoDrawMode.ScaleToFill; this.otherCamera.Core.ConnectEnded += DynamicCameraConnector_ConnectEnded; this.otherCamera.Core.Disconnected += DynamicCameraConnector_Disconnected; this.microphoneConnector.ConnectEnded += MicrophoneConnector_ConnectEnded; this.microphoneConnector.Disconnected += MicrophoneConnector_Disconnected; this.otherCamera.BeginConnect(this.DestID); this.microphoneConnector.BeginConnect(this.DestID); }); }
(5)對於發起方,當收到對方同意的回覆後,也立即呼叫DynamicCameraConnector和MicrophoneConnector的Connect方法,連線到接收方的攝像頭、麥克風。
(6)當一方點選結束通話的按鈕時,就會傳送InformationTypes.CloseVideo型別的資訊給對方,並呼叫DynamicCameraConnector和MicrophoneConnector的Disconnect方法斷開到對方裝置的連線。
(7)另一方接收到InformationTypes.CloseVideo型別的資訊時,也會呼叫DynamicCameraConnector和MicrophoneConnector的Disconnect方法以斷開連線。
protected override void OnClosing(CancelEventArgs e) { base.OnClosing(e); this.otherCamera?.Disconnect(); this.otherCamera?.Dispose(); this.microphoneConnector?.Disconnect(); this.microphoneConnector?.Dispose(); this.selfCamera?.Disconnect(); this.selfCamera?.Dispose(); }
(8)如果接收到自己掉線的事件或好友掉線的事件,也採用類似結束通話對話的處理。
四.下載
Avalonia 版本即時通訊原始碼: IM_VideoChat.Avalonia.rar
該原始碼中包括如下專案:
(1)Oraycn.Demos.VideoChat.LinuxServer : 該Demo的Linux服務端(基於.NetCore)。
(2)Oraycn.Demos.VideoChat.ClientAvalonia : 該Demo的 Avalonia 客戶端。
注: Linux客戶端內建的是x86/x64非託管so庫,若需要其它架構的so,請聯絡我們免費獲取。