Tcp网络编程,必须要解决的一个问题就是粘包,尽管解决办法有很多,这里讲一个比较简单的方法。

老规矩,先上代码:https://github.com/NewLifeX/NewLife.Net

一、管道处理器

新建管道处理器项目HandlerTest,源码复制自第一节课的EchoTest项目,增加一个管道处理器类

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()); // 把收到的数据发回去
session.Send(pk); return null;
}
}

EchoHandler继承自处理器基类Handler,重载Read方法,当网络层收到数据包时,会调用该方法。

这里我们实现了Echo功能,并打印日志。返回null告知不再执行管道上的后续处理器。

既然有了处理器,第一节课中的MyNetServer就用不上啦,在TestServer中改回来标准的NetServer

// 实例化服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听
var svr = new NetServer
{
Port = ,
Log = XTrace.Log
};
svr.Add<EchoHandler>();
svr.Start();

这里的svr.Add<EchoHandler>()把上面的处理器给注册进去,大意就是由这个处理器来负责处理收到的网络数据包。

跑起来服务端和客户端看看效果:

可以看到,收发正常!

二、粘包的产生

真实应用场景中,不可能允许我们间隔1秒才发出一个网络包,直接就不该有等待。连续发送多个数据包,就很容易产生粘包。

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(); // 定时显示性能数据
_timer = new TimerX(ShowStat, client, , ); // 循环发送数据
for (var i = ; i < ; i++)
{
//Thread.Sleep(1000); var str = "你好" + (i + );
client.Send(str);
} //client.Dispose();
}

这里注释了睡眠语句,让它紧密发出5个数据包。注释后面的Dispose,让其有机会收到响应包。

跑起来看到,粘包了!!!

客户端发送5次,服务端作为一个包给接收了,整体处理,然后返回给客户端。

粘包的解决办法很多,一般是加头部长度或者分隔符,也有取巧的办法直接设置NoDelay。

从使用上来讲,相对可靠的做法是加头部长度。因为除了多个包粘在一起,还可能出现一个包被拆成两半,分别在前后两个包里面。

三、普通粘包解法

我们加上头部长度来解决解包问题。

修改一下服务端,增加一个处理器

static void TestServer()
{
// 实例化服务端,指定端口,同时在Tcp/Udp/IPv4/IPv6上监听
var svr = new NetServer
{
Port = ,
Log = XTrace.Log
};
//svr.Add(new LengthFieldCodec { Size = 4 });
svr.Add<StandardCodec>();
svr.Add<EchoHandler>(); // 打开原始数据日志
var ns = svr.Server;
ns.LogSend = true;
ns.LogReceive = true; svr.Start(); _server = svr; // 定时显示性能数据
_timer = new TimerX(ShowStat, svr, , );
}

StandardCodec处理器是新生命团队标准封包。https://github.com/NewLifeX/X/tree/master/NewLife.Core/Net

其固定4字节作为头部,其中后面两个字节标识负载长度。

也可以使用LengthFieldCodec编码器(如上注释部分),并制定头部加4字节作为长度。

编码器顺序非常重要,网络层收到数据包以后,会从前向后走过每一个处理器;SendAsync/SendMessage发送消息时,会从后向前走过每一个过滤器,逆序。

客户端也要增加相应过滤器

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>(); // 打开原始数据日志
var ns = client;
ns.LogSend = true;
ns.LogReceive = true; client.Open(); // 定时显示性能数据
_timer = new TimerX(ShowStat, client, , ); // 循环发送数据
for (var i = ; i < ; i++)
{
var str = "你好" + (i + );
var pk = new Packet(str.GetBytes());
client.SendAsync(pk);
}
}

发送函数改为SendAsync,原来的Send(Packet pk)会绕过管道处理器。

客户端接收时,e.Message表示经过处理器处理得到的消息,e.Packet表示原始数据包。

同时,通过LogSend/LogReceive打开收发数据日志。

上图效果,客户端发出第5个包,头部多了4个字节,其中07-00表示后续负载数据长度为7字节(NewLife)。

服务端先收到第一个包11字节,然后收到44字节,这是4个包粘在一起。

然后StandardCodec编码器成功将其拆分成为4个,并依次通过EchoHandler。

到了客户端这边,也是后面4个粘在一起,并且也得到了正确拆分。

如果一个大包被拆分为几个,StandardCodec也能缓冲合并,半包超过500~5000ms仍未能组合完整时将抛弃。

四、总结

借助管道处理器架构,我们轻易解决了粘包问题!

显然,管道架构并非单纯为了粘包问题而设计,它有着非常重要的意义,加解密、压缩、各种协议处理,等等。

管道架构的设计,参考了Netty,因此大部分Netty的编解码器都可以在此使用。

NewLife.Net——管道处理器解决粘包的更多相关文章

  1. Netty解决粘包和拆包问题的四种方案

    在RPC框架中,粘包和拆包问题是必须解决一个问题,因为RPC框架中,各个微服务相互之间都是维系了一个TCP长连接,比如dubbo就是一个全双工的长连接.由于微服务往对方发送信息的时候,所有的请求都是使 ...

  2. Netty入门系列(2) --使用Netty解决粘包和拆包问题

    前言 上一篇我们介绍了如果使用Netty来开发一个简单的服务端和客户端,接下来我们来讨论如何使用解码器来解决TCP的粘包和拆包问题 TCP为什么会粘包/拆包 我们知道,TCP是以一种流的方式来进行网络 ...

  3. python 31 升级版解决粘包现象

    目录 1. recv 工作原理 2.升级版解决粘包问题 3. 基于UDP协议的socket通信 1. recv 工作原理 1.能够接收来自socket缓冲区的字节数据: 2.当缓冲区没有数据可以读取时 ...

  4. 网络编程之模拟ssh远程执行命令、粘包问题 、解决粘包问题

    目录 模拟ssh远程执行命令 服务端 客户端 粘包问题 什么是粘包 TCP发送数据的四种情况 粘包的两种情况 解决粘包问题 struct模块 解决粘包问题 服务端 客户端 模拟ssh远程执行命令 服务 ...

  5. c# socket 解决粘包,半包

    处理原理: 半包:即一条消息底层分几次发送,先有个头包读取整条消息的长度,当不满足长度时,将消息临时缓存起来,直到满足长度再解码 粘包:两条完整/不完整消息粘在一起,一般是解码完上一条消息,然后再判断 ...

  6. Socket解决粘包问题1

    粘包是指发送端发送的包速度过快,到接收端那边多包并成一个包的现象,比如发送端连续10次发送1个字符'a',因为发送的速度很快,接收端可能一次就收到了10个字符'aaaaaaaaaa',这就是接收端的粘 ...

  7. python3全栈开发-什么是粘包、粘包现象、如何解决粘包

    一.粘包现象 让我们基于tcp先制作一个远程执行命令的程序(1:执行错误命令 2:执行ls 3:执行ifconfig) 注意注意注意: res=subprocess.Popen(cmd.decode( ...

  8. Python开发【socket篇】解决粘包

    客户端 import os import json import struct import socket sk = socket.socket() sk.connect(('127.0.0.1',8 ...

  9. 网络编程基础【day09】:socket解决粘包问题之MD5(八)

    本节内容 1.概述 2.代码实现 一.概述 上一篇博客讲到的用MD5来校验还是用的之前解决粘包的方法,就是客户端发送一个请求,等待服务端的确认的这样的一个笨方法.下面我们用另外一种方法:就是客户端已经 ...

随机推荐

  1. org.apache.poi.ss.usermodel 类操作excel数据遗漏

    直接上图. 错误程序: 循环读取每一行的单元格数据部分 //for (int j = row.getFirstCellNum(); j < row.getLastCellNum(); j++) ...

  2. 漫谈程序员(十二)IT程序猿之猿体是革命的本钱

    IT程序猿之猿体是革命的本钱 前言 程序猿的一大特点就是加班.加班.不停地加班.... 为了美好的生活,为了生活的更加美好.我们选择勤勤恳恳,踏踏实实. 但是,工作只是生活的一部分.生命中最重要的莫过 ...

  3. C++ Primer 有感(类)

    1.在类内部,声明成员函数时必需 的,而定义成员函数则是可选的.在类内部定义的函数默认为inline. 2.const成员函数不能改变其所操作的对象的数据成员.const必须同时出现在声明和定义中,若 ...

  4. unity UGUI实现类似NGUI切换Sprite的方式

    很多都是使用NGUI的习惯,因为在NGUI中所有图片都打包在一个图集中,通过更改SpriteName就可以更改图片,so,为了方便调用UGUI的sprite,我们也同样需要为其创建一个asset文件. ...

  5. UGUI实现NGUI的UIEventListener功能

    在unity中处理UI事件时,习惯了使用NGUI的UIEventListener,虽然UGUI也有AddListener,但是一个组件只能对应一个函数,不能在一个函数中同时处理多个事件,显得有些麻烦 ...

  6. Mahout fp-growth

    FP-growth Apriori算法的一个主要瓶颈在于,为了获得较长的频繁模式,需要生成大量的候选短频繁模式.FP-Growth算法是针对这个瓶颈提出来的全新的一种算法模式.目前,在数据挖掘领域,A ...

  7. hibernate关联对象的增删改查------查

    本篇博客是之前博客hibernate关联对象的增删改查------查 的后继,本篇代码的设定都在前文已经写好,因此读这篇之前,请先移步上一篇博客 //代码片5 SessionFactory sessi ...

  8. MySQL正则表达式初步

    如果想要了解完整的MySQL手册, 请访问: MySQL 5.1参考手册 你还可以学习: MySQL学习精粹 我们知道,在SQL之中,可以用 like 这个谓词(表达式) 来进行模糊检索,并支持 %, ...

  9. LDA主体模型

    一)LDA作用 传统判断两个文档相似性的方法是通过查看两个文档共同出现的单词的多少,如TF-IDF等,这种方法没有考虑到文字背后的语义关联,可能在两个文档共同出现的单词很少甚至没有,但两个文档是相似的 ...

  10. RTMPdump(libRTMP) 源代码分析 10: 处理各种消息(Message)

    ===================================================== RTMPdump(libRTMP) 源代码分析系列文章: RTMPdump 源代码分析 1: ...