1. 程式人生 > >如何使用網絡庫實現應用級消息收發

如何使用網絡庫實現應用級消息收發

驅動 ont handle 響應 ron lan boolean remote urn

網絡客戶端ISocketClient和網絡會話ISocketSession都繼承了ISocketRemoteISocketRemote表示遠程通信,核心就是收發數據。
下面是ISocketRemote接口的主要實現

/// <summary>遠程通信Socket,僅具有收發功能</summary>
public interface ISocketRemote : ISocket
{
    #region 屬性
    /// <summary>遠程地址</summary>
    NetUri Remote { get; set; }

    
/// <summary>通信開始時間</summary> DateTime StartTime { get; } /// <summary>最後一次通信時間,主要表示會話活躍時間,包括收發</summary> DateTime LastTime { get; } /// <summary>緩沖區大小</summary> Int32 BufferSize { get; set; } #endregion #region 發送 /// <summary>
發送數據</summary> /// <remarks> /// 目標地址由<seealso cref="Remote"/>決定 /// </remarks> /// <param name="pk">數據包</param> /// <returns>是否成功</returns> Boolean Send(Packet pk); #endregion #region 接收 /// <summary>接收數據。阻塞當前線程等待返回</summary>
/// <returns></returns> Packet Receive(); /// <summary>數據到達事件</summary> event EventHandler<ReceivedEventArgs> Received; /// <summary>消息到達事件</summary> event EventHandler<MessageEventArgs> MessageReceived; #endregion #region 數據包處理 /// <summary>粘包處理接口</summary> IPacket Packet { get; set; } /// <summary>異步發送數據並等待響應</summary> /// <param name="pk"></param> /// <returns></returns> Task<Packet> SendAsync(Packet pk); /// <summary>發送消息並等待響應</summary> /// <param name="msg"></param> /// <returns></returns> Task<IMessage> SendAsync(IMessage msg); #endregion }



一、同步收發
一般小型網絡應用,或者個人學習程序,都會使用同步收發。
Send(xxx);
var buf = Receive();
這樣向對服務端發一個數據包,然後同步阻塞等待接收一個響應數據。
同步收發最大的優點就是簡單,容易理解;
最大的缺點是性能極其底下,並且很大的幾率會失敗拋出異常,特別是離開本機或者局域網以後。
除非網絡很幹凈,客戶端服務端只進行很簡單的通信,否則出錯崩潰就是家常便飯!
並且,這個階段的工程師,一般認為只能客戶端向服務端發數據,而不知道服務端可以主動向客戶端發數據。

因此,15年經驗表明,同步收發根本不適合做產品級應用!

二、事件驅動
中大型網絡應用,一般采用事件驅動,特別是多並發服務端。
不管是APM還是SAEA,絕大多數網絡框架都會包裝成為事件,或者路由分發架構。
正如前文接口圖黃色箭頭所示,事件驅動一般用法:
client.Received += OnReceive;
client.Send(xxx);
先建立接收事件,然後發送數據,如果對方有響應,就會觸發OnReceive函數,對響應結果進行處理。

事件驅動(包括路由分發)是當下網絡框架主流,占比超過70%
幾乎所有框架都會在此之外再包裝一層,Send一個業務對象,內部序列化為數據後發出,OnReceive後反序列化得到業務對象,返回給上層。

事件驅動跟同步業務需求是相背而行的。
如果業務需要向服務端發送一個請求,然後等待響應結果,那麽事件驅動甚至還不如同步操作好用!
一般做法是Send裏面做堵塞等待,然後OnReceive裏面做攔截。
這也是事件驅動無法進一步擴大比例的根本原因。

事件驅動很好很強大,只是特別不適應業務上的同步操作需求!

三、異步請求響應
近20年的軟件發展史,無一例外等同於Web發展史。
除了技術的發展,Web思維影響了幾乎所有軟件工程師。哪怕初學者,也很清楚HTTP是請求響應模型。在Web開發裏面,所有的業務都要基於請求與響應。


於是我們網絡庫有了第三種選擇。(前文接口圖紫色箭頭)
Task<Packet> SendAsync(Packet pk);
Task<IMessage> SendAsync(IMessage msg);
event EventHandler<MessageEventArgs> MessageReceived;


異步發送SendAsync,可以像事件模型那樣在MessageReceived裏面處理,也可以 var rs = await SendAsync(pk); 把異步轉為同步操作,滿足同步業務需求。


更為重要的是,SendAsync支持單連接通道並行多異步請求
也就是說,在一個網絡連接上,第一個請求的響應還沒有收到之前,業務邏輯可以連續發出更多的請求,不管這些請求的響應包先後順序以後,網絡庫都能夠準確配對,讓await SendAsync得到正確的結果。
這就解決了一個極為常見的問題,一個業務應用裏面,可能多個線程需要向服務端請求數據,而傳統做法只能是加鎖,在第一個請求響應完成之前,阻塞其它請求。


實際上,HTTP 1.0/1.1正是傳統做法,前一個請求完成之前,不能發起新的請求,導致瀏覽器不得不建立多個Tcp連接。


因此,異步請求響應的架構設計,讓請求響應準確配對,支持並行請求,並且解決一切粘包問題!


應用級消息收發偽代碼:

var str = "{action:Open,args:{index:3},remark:打開3號燈}";
var client = new NetUri("tcp://127.0.0.1:1234").CreateRemote();
client.Packet = new DefaultPacket();
var rs = await client.SendAsync(str.GetBytes());
// rs = "{result:true,data:3號燈已打開}"

上面的DefaultPacket正是 新生命團隊標準網絡封包協議
請求響應包的頭部,都會增加4字節,Json字符串作為負載數據。
正是增加的這4字節,確保了請求響應的準確配對(序列號匹配),解決了粘包問題(頭部長度)


即使沒有默認封包DefualtPacket,上面代碼也是可以工作的,只是這樣就失去了準確配對和粘包拆分,要求業務層不能頻繁收發。


End.

如何使用網絡庫實現應用級消息收發