如何使用網(wǎng)絡(luò)庫實(shí)現(xiàn)應(yīng)用級(jí)消息收發(fā)
網(wǎng)絡(luò)客戶端ISocketClient和網(wǎng)絡(luò)會(huì)話ISocketSession都繼承了ISocketRemoteISocketRemote表示遠(yuǎn)程通信,核心就是收發(fā)數(shù)據(jù)。
下面是ISocketRemote接口的主要實(shí)現(xiàn)
/// <summary>遠(yuǎn)程通信Socket,僅具有收發(fā)功能</summary> public interface ISocketRemote : ISocket { #region 屬性 /// <summary>遠(yuǎn)程地址</summary> NetUri Remote { get; set; } /// <summary>通信開始時(shí)間</summary> DateTime StartTime { get; } /// <summary>最后一次通信時(shí)間,主要表示會(huì)話活躍時(shí)間,包括收發(fā)</summary> DateTime LastTime { get; } /// <summary>緩沖區(qū)大小</summary> Int32 BufferSize { get; set; } #endregion #region 發(fā)送 /// <summary>發(fā)送數(shù)據(jù)</summary> /// <remarks> /// 目標(biāo)地址由<seealso cref="Remote"/>決定 /// </remarks> /// <param name="pk">數(shù)據(jù)包</param> /// <returns>是否成功</returns> Boolean Send(Packet pk); #endregion #region 接收 /// <summary>接收數(shù)據(jù)。阻塞當(dāng)前線程等待返回</summary> /// <returns></returns> Packet Receive(); /// <summary>數(shù)據(jù)到達(dá)事件</summary> event EventHandler<ReceivedEventArgs> Received; /// <summary>消息到達(dá)事件</summary> event EventHandler<MessageEventArgs> MessageReceived; #endregion #region 數(shù)據(jù)包處理 /// <summary>粘包處理接口</summary> IPacket Packet { get; set; } /// <summary>異步發(fā)送數(shù)據(jù)并等待響應(yīng)</summary> /// <param name="pk"></param> /// <returns></returns> Task<Packet> SendAsync(Packet pk); /// <summary>發(fā)送消息并等待響應(yīng)</summary> /// <param name="msg"></param> /// <returns></returns> Task<IMessage> SendAsync(IMessage msg); #endregion }
一、同步收發(fā)
一般小型網(wǎng)絡(luò)應(yīng)用,或者個(gè)人學(xué)習(xí)程序,都會(huì)使用同步收發(fā)。
Send(xxx);
var buf = Receive();
這樣向?qū)Ψ?wù)端發(fā)一個(gè)數(shù)據(jù)包,然后同步阻塞等待接收一個(gè)響應(yīng)數(shù)據(jù)。
同步收發(fā)最大的優(yōu)點(diǎn)就是簡單,容易理解;
最大的缺點(diǎn)是性能極其底下,并且很大的幾率會(huì)失敗拋出異常,特別是離開本機(jī)或者局域網(wǎng)以后。
除非網(wǎng)絡(luò)很干凈,客戶端服務(wù)端只進(jìn)行很簡單的通信,否則出錯(cuò)崩潰就是家常便飯!
并且,這個(gè)階段的工程師,一般認(rèn)為只能客戶端向服務(wù)端發(fā)數(shù)據(jù),而不知道服務(wù)端可以主動(dòng)向客戶端發(fā)數(shù)據(jù)。
因此,15年經(jīng)驗(yàn)表明,同步收發(fā)根本不適合做產(chǎn)品級(jí)應(yīng)用!
二、事件驅(qū)動(dòng)
中大型網(wǎng)絡(luò)應(yīng)用,一般采用事件驅(qū)動(dòng),特別是多并發(fā)服務(wù)端。
不管是APM還是SAEA,絕大多數(shù)網(wǎng)絡(luò)框架都會(huì)包裝成為事件,或者路由分發(fā)架構(gòu)。
正如前文接口圖黃色箭頭所示,事件驅(qū)動(dòng)一般用法:
client.Received += OnReceive;
client.Send(xxx);
先建立接收事件,然后發(fā)送數(shù)據(jù),如果對(duì)方有響應(yīng),就會(huì)觸發(fā)OnReceive函數(shù),對(duì)響應(yīng)結(jié)果進(jìn)行處理。
事件驅(qū)動(dòng)(包括路由分發(fā))是當(dāng)下網(wǎng)絡(luò)框架主流,占比超過70%
幾乎所有框架都會(huì)在此之外再包裝一層,Send一個(gè)業(yè)務(wù)對(duì)象,內(nèi)部序列化為數(shù)據(jù)后發(fā)出,OnReceive后反序列化得到業(yè)務(wù)對(duì)象,返回給上層。
事件驅(qū)動(dòng)跟同步業(yè)務(wù)需求是相背而行的。
如果業(yè)務(wù)需要向服務(wù)端發(fā)送一個(gè)請(qǐng)求,然后等待響應(yīng)結(jié)果,那么事件驅(qū)動(dòng)甚至還不如同步操作好用!
一般做法是Send里面做堵塞等待,然后OnReceive里面做攔截。
這也是事件驅(qū)動(dòng)無法進(jìn)一步擴(kuò)大比例的根本原因。
事件驅(qū)動(dòng)很好很強(qiáng)大,只是特別不適應(yīng)業(yè)務(wù)上的同步操作需求!
三、異步請(qǐng)求響應(yīng)
近20年的軟件發(fā)展史,無一例外等同于Web發(fā)展史。
除了技術(shù)的發(fā)展,Web思維影響了幾乎所有軟件工程師。哪怕初學(xué)者,也很清楚HTTP是請(qǐng)求響應(yīng)模型。在Web開發(fā)里面,所有的業(yè)務(wù)都要基于請(qǐng)求與響應(yīng)。
于是我們網(wǎng)絡(luò)庫有了第三種選擇。(前文接口圖紫色箭頭)
Task<Packet> SendAsync(Packet pk);
Task<IMessage> SendAsync(IMessage msg);
event EventHandler<MessageEventArgs> MessageReceived;
異步發(fā)送SendAsync,可以像事件模型那樣在MessageReceived里面處理,也可以 var rs = await SendAsync(pk); 把異步轉(zhuǎn)為同步操作,滿足同步業(yè)務(wù)需求。
更為重要的是,SendAsync支持單連接通道并行多異步請(qǐng)求!
也就是說,在一個(gè)網(wǎng)絡(luò)連接上,第一個(gè)請(qǐng)求的響應(yīng)還沒有收到之前,業(yè)務(wù)邏輯可以連續(xù)發(fā)出更多的請(qǐng)求,不管這些請(qǐng)求的響應(yīng)包先后順序以后,網(wǎng)絡(luò)庫都能夠準(zhǔn)確配對(duì),讓await SendAsync得到正確的結(jié)果。
這就解決了一個(gè)極為常見的問題,一個(gè)業(yè)務(wù)應(yīng)用里面,可能多個(gè)線程需要向服務(wù)端請(qǐng)求數(shù)據(jù),而傳統(tǒng)做法只能是加鎖,在第一個(gè)請(qǐng)求響應(yīng)完成之前,阻塞其它請(qǐng)求。
實(shí)際上,HTTP 1.0/1.1正是傳統(tǒng)做法,前一個(gè)請(qǐng)求完成之前,不能發(fā)起新的請(qǐng)求,導(dǎo)致瀏覽器不得不建立多個(gè)Tcp連接。
因此,異步請(qǐng)求響應(yīng)的架構(gòu)設(shè)計(jì),讓請(qǐng)求響應(yīng)準(zhǔn)確配對(duì),支持并行請(qǐng)求,并且解決一切粘包問題!
應(yīng)用級(jí)消息收發(fā)偽代碼:
var str = "{action:Open,args:{index:3},remark:打開3號(hào)燈}"; 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號(hào)燈已打開}"
上面的DefaultPacket正是 新生命團(tuán)隊(duì)標(biāo)準(zhǔn)網(wǎng)絡(luò)封包協(xié)議
請(qǐng)求響應(yīng)包的頭部,都會(huì)增加4字節(jié),Json字符串作為負(fù)載數(shù)據(jù)。
正是增加的這4字節(jié),確保了請(qǐng)求響應(yīng)的準(zhǔn)確配對(duì)(序列號(hào)匹配),解決了粘包問題(頭部長度)
即使沒有默認(rèn)封包DefualtPacket,上面代碼也是可以工作的,只是這樣就失去了準(zhǔn)確配對(duì)和粘包拆分,要求業(yè)務(wù)層不能頻繁收發(fā)。
End.

浙公網(wǎng)安備 33010602011771號(hào)