上一篇我们讲了 如何创建一个基本的Newlife网络服务端 这边我们来讲一下如何解决粘包的问题

在上一篇总我们注册了Newlife的管道处理器 ,我们来看看他是如何实现粘包处理的

svr.Add<ReciveFilter>();//粘包处理管道

首先看一下我们设备的上传数据协议

设备上报的数据包头包含了固定的包头包尾,整个包的数据长度,设备编号。

包头:板卡类型,帧类型 2个字节 0x01 0x70

帧长度: 为两个字节 并且数据的字节序为  高字节在前 ,C#正常默认为低字节在前。

设备号:15位的ASCII 字符串

包尾: 两个字节 0x0D 0x0A  固定

下面来解决粘包的问题

Newlife网络库提供了几种常见的封包协议来解决粘包的问题,其中有一个 LengthFieldCodec解码器 这个解码器以长度字段作为头部 恰好符合我们的需求,我们就以这个解码器稍作改造来解决我们的粘包问题吧

由于这个解码器是适用于 只包含包头和包体的数据结构,且长度为包体的长度,而我们的协议 是包含包头包体包尾,并且帧长度为整个包的长度,长度为高位在前的数据结构,所以我们需要对整个解码器稍微做一些改造来符合我们的数据结构 。

我们来看下代码 其中

        #region 属性
/// <summary>长度所在位置</summary>
public Int32 Offset { get; set; }=2; /// <summary>长度占据字节数,1/2/4个字节,0表示压缩编码整数,默认2</summary>
public Int32 Size { get; set; } = ; /// <summary>过期时间,超过该时间后按废弃数据处理,默认500ms</summary>
public Int32 Expire { get; set; } = ;
#endregion

在我们的协议中可以看到 设置了数据包的长度位置,长度占据的字节数,下面我们来获取一下整个包的长度

/// <summary>从数据流中获取整帧数据长度</summary>
/// <param name="pk"></param>
/// <param name="offset"></param>
/// <param name="size"></param>
/// <returns>数据帧长度(包含头部长度位)</returns>
protected Int32 GetLength(Packet pk, Int32 offset, Int32 size)
{
if (offset < ) return pk.Total - pk.Offset;
// 数据不够,连长度都读取不了
if (offset >= pk.Total) return ; // 读取大小
var len = ;
switch (size)
{
case :
var lenArry = pk.ReadBytes(offset, );
//高位在前,反转数组,获取长度
Array.Reverse(lenArry);
len = lenArry.ToUInt16();
break;
default:
throw new NotSupportedException();
} // 判断后续数据是否足够
if (len > pk.Total) return ; return len;
}

获取长度后我们就可以从数据流中读取一个完整的包了

        /// <summary>解码</summary>
/// <param name="context"></param>
/// <param name="pk"></param>
/// <returns></returns>
protected override IList<Packet> Decode(IHandlerContext context, Packet pk)
{
var ss = context.Owner as IExtend;
var mcp = ss["CodecItem"] as CodecItem;
if (mcp == null) ss["CodecItem"] = mcp = new CodecItem(); var pks = ParseNew(pk, mcp, , ms => GetLength(ms, Offset, Size), Expire); // 跳过头部长度
var len = Offset + Math.Abs(Size);
foreach (var item in pks)
{
item.Set(item.Data, item.Offset + len, item.Count - len);
//item.SetSub(len, item.Count - len);
} return pks;
} #region 粘包处理
/// <summary>分析数据流,得到一帧数据</summary>
/// <param name="pk">待分析数据包</param>
/// <param name="codec">参数</param>
/// <param name="getLength">获取长度</param>
/// <param name="expire">缓存有效期</param>
/// <returns></returns>
protected IList<Packet> ParseNew(Packet pk, CodecItem codec, int startIndex, Func<Packet, Int32> getLength, Int32 expire = )
{
var _ms = codec.Stream;
var nodata = _ms == null || _ms.Position < || _ms.Position >= _ms.Length; var list = new List<Packet>();
// 内部缓存没有数据,直接判断输入数据流是否刚好一帧数据,快速处理,绝大多数是这种场景
if (nodata)
{
if (pk == null) return list.ToArray(); var idx = ;
while (idx < pk.Total)
{
//var pk2 = new Packet(pk.Data, pk.Offset + idx, pk.Total - idx);
var pk2 = pk.Slice(idx);
var len = getLength(pk2);
if (len <= || len > pk2.Count) break; pk2.Set(pk2.Data, startIndex, len);
//pk2.SetSub(0, len);
list.Add(pk2);
idx += len;
}
// 如果没有剩余,可以返回
if (idx == pk.Total) return list.ToArray(); // 剩下的
//pk = new Packet(pk.Data, pk.Offset + idx, pk.Total - idx);
pk = pk.Slice(idx);
} if (_ms == null) codec.Stream = _ms = new MemoryStream(); // 加锁,避免多线程冲突
lock (_ms)
{
// 超过该时间后按废弃数据处理
var now = TimerX.Now;
if (_ms.Length > _ms.Position && codec.Last.AddMilliseconds(expire) < now)
{
_ms.SetLength();
_ms.Position = ;
}
codec.Last = now; // 合并数据到最后面
if (pk != null && pk.Total > )
{
var p = _ms.Position;
_ms.Position = _ms.Length;
pk.WriteTo(_ms);
_ms.Position = p;
} // 尝试解包
while (_ms.Position < _ms.Length)
{
//var pk2 = new Packet(_ms.GetBuffer(), (Int32)_ms.Position, (Int32)_ms.Length);
var pk2 = new Packet(_ms);
var len = getLength(pk2); // 资源不足一包
if (len <= || len > pk2.Total) break; // 解包成功
pk2.Set(pk2.Data, startIndex, len);
//pk2.SetSub(0, len);
list.Add(pk2); _ms.Seek(len, SeekOrigin.Current);
} // 如果读完了数据,需要重置缓冲区
if (_ms.Position >= _ms.Length)
{
_ms.SetLength();
_ms.Position = ;
} return list;
}
}

粘包处理管道完成后,就可以在Recive中去处理一个完整的数据包啦,我来解析一下这个状态的数据并且来保存设备连接

首先定义一个字典项用来保存设备的连接信息.设备号,连接的SessionId

   /// <summary>
/// newLife连接保持
/// </summary>
private Dictionary<string, int> OnLineClients = new Dictionary<string, int>();

由于我们的数据中 帧类型不同的请求中帧类型是不一样的 所以解析数据需要做区分处理 我们来或者状态上传信息中的设备号并且和连接关联

 private Dictionary<string, int> OnLineClients = new Dictionary<string, int>();
private object _lock=new object();
private void Recive(object sender, ReceivedEventArgs e)
{ INetSession session = (INetSession)sender;
var pk = e.Message as Packet;
if (pk.Count == )
{
XTrace.WriteLine("数据包解析错误");
return; }
try
{
//数据包
var respBytes = pk.Data;
//获取帧类型
var dataTypeBytes = respBytes[]; if (dataTypeBytes == 0x70)
{
//数值
byte[] deviceNoByte = new byte[];
Buffer.BlockCopy(respBytes, , deviceNoByte, , ); //从缓冲区里读取包头的字节
string deviceNo = Encoding.ASCII.GetString(deviceNoByte);
XTrace.WriteLine("设备编号:" + deviceNo);
//保存连接信息
SaveClientConnection(deviceNo, session.ID);
//获取设备号后保存连接信息
} //支付宝
}
catch (Exception ex)
{
XTrace.WriteLine(ex.Message);
}
} /// <summary>
/// 保存在线信息
/// </summary>
/// <param name="deviceNo"></param>
/// <param name="sessionId"></param>
private void SaveClientConnection(string deviceNo, int sessionId)
{
lock (_lock)
{
if (OnLineClients.ContainsKey(deviceNo))
{
OnLineClients[deviceNo] = sessionId;
}
else
{
OnLineClients.Add(deviceNo,sessionId);
}
} }

好了数据粘包问题解决啦同时保存了设备连接信息,下面来解决如何定时检查测试在线状态。

使用Newlife网络库管道模式解决数据粘包(二)的更多相关文章

  1. 使用NewLife网络库构建可靠的自动售货机Socket服务端(一)

    最近有个基于tcp socket 协议和设备交互需求,想到了新生命团队的各种组件,所以决定用NewLife网络库作为服务端来完成一系列的信息交互. 第一,首先说一下我们需要实现的功能需求吧 1,首先客 ...

  2. python套接字解决tcp粘包问题

    python套接字解决tcp粘包问题 目录 什么是粘包 演示粘包现象 解决粘包 实际应用 什么是粘包 首先只有tcp有粘包现象,udp没有粘包 socket收发消息的原理 发送端可以是一K一K地发送数 ...

  3. Netty使用LineBasedFrameDecoder解决TCP粘包/拆包

    TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...

  4. C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)

    介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...

  5. 解决Socket粘包问题——C#代码

    解决Socket粘包问题——C#代码 前天晚上,曾经的一个同事问我socket发送消息如果太频繁接收方就会有消息重叠,因为当时在外面,没有多加思考 第一反应还以为是多线程导致的数据不同步导致的,让他加 ...

  6. 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?

    前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...

  7. 粘包处理现象及其解决方案——基于NewLife.Net网络库的管道式帧长粘包处理方法

    [toc] #1.粘包现象 每个TCP 长连接都有自己的socket缓存buffer,默认大小是8K,可支持手动设置.粘包是TCP长连接中最常见的现象,如下图 socket缓存中有5帧(或者说5包)心 ...

  8. 网络编程基础【day09】:解决socket粘包之大数据(七)

    本节内容 概述 linux下运行效果 sleep解决粘包 服务端插入交互解决粘包问题 一.概述 刚刚我们在window的操作系统上,很完美的解决了,大数据量的数据传输出现的问题,但是在Linux环境下 ...

  9. 解决socket粘包的两种low版模式 os.popen()和struct模块

    os.popen()模式 server端 import socket import os phone = socket.socket() # 实例化一个socket对象 phone.bind((&qu ...

随机推荐

  1. Lua程序设计(二)面向对象概念介绍

    ----------------------------------------------------------- Lua面向对象3 local smartMan = { name = " ...

  2. [转载]Windows 8 VHD 概述与使用

    http://www.cnblogs.com/tonycody/archive/2012/11/30/2796858.html

  3. Spring: 读取 .properties 文件地址,json转java对象,el使用java类方法相关 (十三)

    1. 在Java中获取 .properties 文件的路径 (src/main/resources 下) ProjectName |---src/main/java |---src/main/reso ...

  4. AngularJs -- 内置指令

    AngularJS提供了一系列内置指令.其中一些指令重载了原生的HTML元素,比如<form>和<a>标签, 当在HTML中使用标签时,并不一定能明确看出是否在使用指令. 其他 ...

  5. sequelize初使用

    官网地址:Sequelize Sequelize is a promise-based ORM for Node.js v4 and up. It supports the dialects Post ...

  6. 图文详解 解决 MVC4 Code First 数据迁移

    在使用Code first生成数据库后 当数据库发生更改时 运行程序就会出现数据已更改的问题  这时可以删除数据库重新生成解决 但是之前的数据就无法保留  为了保留之前的数据库数据  我们需要使用到C ...

  7. QTP图片验证/图片比较/二进制流对比法

    图片验证主要是文件对比,其中我们可以利用二进制的方法读取图片信息,然后进行对比,达到对比的效果,本例子利用fso对象的文件流的方法实现,代码如下: Public Function CompareFil ...

  8. TP-Link wr703N 使用华为HiLink系列上网卡的设置【转】

    转自:http://www.right.com.cn/forum/thread-103082-1-1.html 最近买了一个华为的E303s的上网卡,之前703的openwrt是按照需要拨号和使用U盘 ...

  9. linux批量关闭进程

    ps aux | grep gunicorn_api | awk '{print $2}' | xargs kill -9 gunicorn 换成你的关键字即可.

  10. stdole.dll

    迁移至win1064位后,发布提示stdole.dll错误,查找半天,是因为引用了office组件问题,将其注释掉.解决.因为此块代码无用,但是对有用的代码如何解决发布问题,未找到合适解决方法.