在一般的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應用裡面處理回調函數的實現過程,這樣處理可以很好利用回調代碼來封裝處理的細節,對於理解相關的應答操作也是很直觀的。