在一般的Socket應用裡面,很多時候數據的發送和接收是分開處理的,也就是我們發送一個消息,不知道這個請求消息什麼時候得到應答消息,而且收到對應的應答消息的時候,如果操作界面的內容,也是需要特別處理的,因為它們和界面線程是不在一起的。如果我們在發送消息的時候,能夠給一段回調的代碼給收到應答消息的時候處理,那麼就會方便很多。本文主要介紹如何在Socket應用裡面,通過回調函數的處理,實現收到應答消息的時候能夠調用對應的函數。
在上一篇的隨筆裡面,介紹了基於Json的Socket消息的實體類設計,其中包括了消息回調ID和是否在調用後移除回調兩個屬性,這個是用來為回調處理服務的,如下所示。
也就是在通用消息對象BaseMessage類裡面添加下面兩個屬性。
但我們需要發送消息的時候,我們把回調的ID添加到本地集合裡面,得到應答的時候,在從集合裡面提出來,執行就可以了。
/// <summary> /// 發送通用格式的數據對象 /// </summary> /// <param name="data">通用的消息對象</param> /// <returns></returns> public bool SendData(BaseMessage data, Delegate callBack = null) { AddCallback(callBack, data);//添加回調集合 var toSendData = PackMessage(data); var result = SendData(toSendData); return result; }
/// <summary> /// 記錄回調的函數信息 /// </summary> /// <param name="callBack"></param> /// <param name="msg"></param> private void AddCallback(Delegate callBack, BaseMessage msg) { if (callBack != null) { Guid callbackID = Guid.NewGuid(); ResponseCallbackObject responseCallback = new ResponseCallbackObject() { ID = callbackID, CallBack = callBack }; msg.CallbackID = callbackID; callBackList.Add(responseCallback); } }
在服務端,需要根據請求的消息構建應答內容,因此我們在應答請求的時候,需要把請求的回調ID給復制到應答的消息體裡面,如下所示。
/// <summary> /// 封裝數據進行發送(復制請求部分數據) /// </summary> /// <returns></returns> public BaseMessage PackData(BaseMessage request) { BaseMessage info = new BaseMessage() { MsgType = this.MsgType, Content = this.SerializeObject(), CallbackID = request.CallbackID }; if(!string.IsNullOrEmpty(request.ToUserId)) { info.ToUserId = request.FromUserId; info.FromUserId = request.ToUserId; } return info; }
調用方在收到服務器的應答消息的時候,會根據回調的ID ,從本地集合裡面調出來並執行處理,實現了我們回調的操作。
var md5 = MD5Util.GetMD5_32(message.Content); if (md5 == message.MD5) { InvokeMessageCallback(message, message.DeleteCallbackAfterInvoke);//觸發回調 OnMessageReceived(message);//給子類重載 }
/// <summary> /// 執行回調函數 /// </summary> /// <param name="msg">消息基礎對象</param> /// <param name="deleteCallback">是否移除回調</param> private void InvokeMessageCallback(BaseMessage msg, bool deleteCallback) { var callBackObject = callBackList.SingleOrDefault(x => x.ID == msg.CallbackID); if (callBackObject != null) { if (deleteCallback) { callBackList.Remove(callBackObject); } callBackObject.CallBack.DynamicInvoke(msg); } }
這樣,我們在調用的時候,傳入一個回調的Action,讓收到消息後進行動態執行就可以了。例如在登陸的時候,我們如果需要在登陸成功後顯示主窗體,那麼可以執行下面的處理代碼。
var request = new AuthRequest(userNo, password); var message = request.PackData(); Singleton<CommonManager>.Instance.Send(message, (msg) => { var instance = Singleton<CommonManager>.Instance; try { var response = JsonTools.DeserializeObject<AuthResponse>(msg.Content); if (response.ValidateResult == 0) { instance.ShowLoadFormText("登錄成功,加載基礎數據。。。。"); //放置初始化代碼 Portal.gc.User = new User(userNo); instance.SetClientId(userNo); instance.ShowMainForm(); instance.CloseLoadForm(); } else { instance.CloseLoadForm(); instance.ShowMessage("登錄失敗:" + response.Message); instance.ShowLogin(); } } catch (Exception ex) { instance.ShowMessage("初始化異常:" + ex.Message); instance.Exit(); } });
或者我們來看看另外一個例子,這個例子是在用戶登陸的時候,請求一次在線用戶列表,如果用戶在線,那麼在界面上展示列表,具體操作代碼如下所示,也是利用了回調函數的處理方式。
/// <summary> /// 發送獲取在線用戶列表的請求,並在收到應答數據後進行本地界面更新 /// </summary> private void RefreshUser() { CommonRequest request = new CommonRequest(DataTypeKey.UserListRequest); var data = request.PackData(); Singleton<CommonManager>.Instance.Send(data, (msg) => { UserListResponse response = JsonTools.DeserializeObject<UserListResponse>(msg.Content); if (response != null) { this.InvokeUI(() => { this.listView1.Items.Clear(); foreach (CListItem item in response.UserList) { if (item.Value != Portal.gc.User.UserNo) { this.listView1.Items.Add(item.Text, 0); } } }); } }); }
例如,客戶端登陸幾個用戶後,用戶可以獲得在線用戶列表,界面展示如下所示。
以上就是我們在Socket應用裡面處理回調函數的實現過程,這樣處理可以很好利用回調代碼來封裝處理的細節,對於理解相關的應答操作也是很直觀的。