NewLife.Net——管道处理器解决粘包
Tcp網(wǎng)絡(luò)編程,必須要解決的一個(gè)問題就是粘包,盡管解決辦法有很多,這里講一個(gè)比較簡(jiǎn)單的方法。
?
老規(guī)矩,先上代碼:https://github.com/nnhy/NewLife.Net.Tests
?
一、管道處理器
新建管道處理器項(xiàng)目HandlerTest,源碼復(fù)制自第一節(jié)課的EchoTest項(xiàng)目,增加一個(gè)管道處理器類
class EchoHandler : Handler {public override Object Read(IHandlerContext context, Object message){var session = context.Session;var pk = message as Packet;session.WriteLog("收到:{0}", pk.ToStr());// 把收到的數(shù)據(jù)發(fā)回去 session.Send(pk);return null;} }EchoHandler繼承自處理器基類Handler,重載Read方法,當(dāng)網(wǎng)絡(luò)層收到數(shù)據(jù)包時(shí),會(huì)調(diào)用該方法。
這里我們實(shí)現(xiàn)了Echo功能,并打印日志。返回null告知不再執(zhí)行管道上的后續(xù)處理器。
?
既然有了處理器,第一節(jié)課中的MyNetServer就用不上啦,在TestServer中改回來標(biāo)準(zhǔn)的NetServer
// 實(shí)例化服務(wù)端,指定端口,同時(shí)在Tcp/Udp/IPv4/IPv6上監(jiān)聽 var svr = new NetServer {Port = 1234,Log = XTrace.Log }; svr.Add<EchoHandler>(); svr.Start();這里的svr.Add<EchoHandler>()把上面的處理器給注冊(cè)進(jìn)去,大意就是由這個(gè)處理器來負(fù)責(zé)處理收到的網(wǎng)絡(luò)數(shù)據(jù)包。
?
跑起來服務(wù)端和客戶端看看效果:
可以看到,收發(fā)正常!
?
二、粘包的產(chǎn)生
真實(shí)應(yīng)用場(chǎng)景中,不可能允許我們間隔1秒才發(fā)出一個(gè)網(wǎng)絡(luò)包,直接就不該有等待。連續(xù)發(fā)送多個(gè)數(shù)據(jù)包,就很容易產(chǎn)生粘包。
static void TestClient() {var uri = new NetUri("tcp://127.0.0.1:1234");//var uri = new NetUri("tcp://net.newlifex.com:1234");var client = uri.CreateRemote();client.Log = XTrace.Log;client.Received += (s, e) =>{XTrace.WriteLine("收到:{0}", e.Packet.ToStr());};client.Open();// 定時(shí)顯示性能數(shù)據(jù)_timer = new TimerX(ShowStat, client, 100, 1000);// 循環(huán)發(fā)送數(shù)據(jù)for (var i = 0; i < 5; i++){//Thread.Sleep(1000);var str = "你好" + (i + 1);client.Send(str);}//client.Dispose(); }這里注釋了睡眠語句,讓它緊密發(fā)出5個(gè)數(shù)據(jù)包。注釋后面的Dispose,讓其有機(jī)會(huì)收到響應(yīng)包。
跑起來看到,粘包了!!!
客戶端發(fā)送5次,服務(wù)端作為一個(gè)包給接收了,整體處理,然后返回給客戶端。
粘包的解決辦法很多,一般是加頭部長度或者分隔符,也有取巧的辦法直接設(shè)置NoDelay。
從使用上來講,相對(duì)可靠的做法是加頭部長度。因?yàn)槌硕鄠€(gè)包粘在一起,還可能出現(xiàn)一個(gè)包被拆成兩半,分別在前后兩個(gè)包里面。
?
三、普通粘包解法
我們加上頭部長度來解決解包問題。
修改一下服務(wù)端,增加一個(gè)處理器
static void TestServer() {// 實(shí)例化服務(wù)端,指定端口,同時(shí)在Tcp/Udp/IPv4/IPv6上監(jiān)聽var svr = new NetServer{Port = 1234,Log = XTrace.Log};//svr.Add(new LengthFieldCodec { Size = 4 });svr.Add<StandardCodec>();svr.Add<EchoHandler>();// 打開原始數(shù)據(jù)日志var ns = svr.Server;ns.LogSend = true;ns.LogReceive = true;svr.Start();_server = svr;// 定時(shí)顯示性能數(shù)據(jù)_timer = new TimerX(ShowStat, svr, 100, 1000); }StandardCodec處理器是新生命團(tuán)隊(duì)標(biāo)準(zhǔn)封包。https://github.com/NewLifeX/X/tree/master/NewLife.Core/Net
其固定4字節(jié)作為頭部,其中后面兩個(gè)字節(jié)標(biāo)識(shí)負(fù)載長度。
也可以使用LengthFieldCodec編碼器(如上注釋部分),并制定頭部加4字節(jié)作為長度。
編碼器順序非常重要,網(wǎng)絡(luò)層收到數(shù)據(jù)包以后,會(huì)從前向后走過每一個(gè)處理器;SendAsync/SendMessage發(fā)送消息時(shí),會(huì)從后向前走過每一個(gè)過濾器,逆序。
?
客戶端也要增加相應(yīng)過濾器
static void TestClient() {var uri = new NetUri("tcp://127.0.0.1:1234");//var uri = new NetUri("tcp://net.newlifex.com:1234");var client = uri.CreateRemote();client.Log = XTrace.Log;client.Received += (s, e) =>{var pk = e.Message as Packet;XTrace.WriteLine("收到:{0}", pk.ToStr());};//client.Add(new LengthFieldCodec { Size = 4 });client.Add<StandardCodec>();// 打開原始數(shù)據(jù)日志var ns = client;ns.LogSend = true;ns.LogReceive = true;client.Open();// 定時(shí)顯示性能數(shù)據(jù)_timer = new TimerX(ShowStat, client, 100, 1000);// 循環(huán)發(fā)送數(shù)據(jù)for (var i = 0; i < 5; i++){var str = "你好" + (i + 1);var pk = new Packet(str.GetBytes());client.SendAsync(pk);} }發(fā)送函數(shù)改為SendAsync,原來的Send(Packet pk)會(huì)繞過管道處理器。
客戶端接收時(shí),e.Message表示經(jīng)過處理器處理得到的消息,e.Packet表示原始數(shù)據(jù)包。
?
同時(shí),通過LogSend/LogReceive打開收發(fā)數(shù)據(jù)日志。
上圖效果,客戶端發(fā)出第5個(gè)包,頭部多了4個(gè)字節(jié),其中07-00表示后續(xù)負(fù)載數(shù)據(jù)長度為7字節(jié)(NewLife)。
服務(wù)端先收到第一個(gè)包11字節(jié),然后收到44字節(jié),這是4個(gè)包粘在一起。
然后StandardCodec編碼器成功將其拆分成為4個(gè),并依次通過EchoHandler。
到了客戶端這邊,也是后面4個(gè)粘在一起,并且也得到了正確拆分。
?
如果一個(gè)大包被拆分為幾個(gè),StandardCodec也能緩沖合并,半包超過500~5000ms仍未能組合完整時(shí)將拋棄。
?
四、總結(jié)
借助管道處理器架構(gòu),我們輕易解決了粘包問題!
顯然,管道架構(gòu)并非單純?yōu)榱苏嘲鼏栴}而設(shè)計(jì),它有著非常重要的意義,加解密、壓縮、各種協(xié)議處理,等等。
管道架構(gòu)的設(shè)計(jì),參考了Netty,因此大部分Netty的編解碼器都可以在此使用。
總結(jié)
以上是生活随笔為你收集整理的NewLife.Net——管道处理器解决粘包的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 代码规范之eslint+prettier
- 下一篇: ora-01017 invalid us