在 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,請聯繫我們免費獲取。