上一篇我们讲了 如何创建一个基本的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. 何凯文每日一句打卡||DAY5

  2. [整理]Win下好用的Markdown工具

    用过haroopad,MarkPad,Sublime + markdown插件,前2款勉强能用,都处于继续开发中,haroopad支持的语法相对较少,提示也不明显,MarkPad还是有不少bug. 后 ...

  3. Spark笔记之使用UDF(User Define Function)

    一.UDF介绍 UDF(User Define Function),即用户自定义函数,Spark的官方文档中没有对UDF做过多介绍,猜想可能是认为比较简单吧. 几乎所有sql数据库的实现都为用户提供了 ...

  4. kernel——make menuconfig的实现原理【转】

    转自:https://blog.csdn.net/hpr1992/article/details/41048693 .系统移植可以分为配置系统和编译系统两大块,其中通过命令makemenuconfig ...

  5. 百度编辑器ueditor 字符限制

    百度编辑器ueditor 字符限制 默认只能输入10000个字符 修改 ueditor.config.js // ,wordCount:true //是否开启字数统计 // ,maximumWords ...

  6. 在Docker中运行EOS(MAC版)

    在Docker中运行EOS(MAC版) 在Docker中也可以简单快速的构建EOS.IO.笔者在Mac平台下参考官方文档躺了一次河.记录如下: 安装依赖 Docker 版本 17.05或者更高 tes ...

  7. SSH2框架搭建 和 配置文件详解

    -----------补充说明----------- 文章中所列出的struts2的2.2jar包已经不是最新的了,这个版本有严重漏洞, 现在最新版本为2.3.15,所以.你懂的http://stru ...

  8. Spring加载XML配置文件

    原创链接:http://www.cnblogs.com/yanqin/p/5282929.html(允许转载,但请注明原创链接) BeanFactory加载单个文件 当使用beanfactory去获取 ...

  9. opencv 彩色图像分割(inrange)

    灰度图像大多通过算子寻找边缘和区域生长融合来分割图像. 彩色图像增加了色彩信息,可以通过不同的色彩值来分割图像,常用彩色空间HSV/HSI, RGB, LAB等都可以用于分割! 笔者主要介绍inran ...

  10. Android中如何在代码中设置View的宽和高?

    Android中如何在代码中设置View的宽和高?https://zhidao.baidu.com/question/536302117.htmlhttps://blog.csdn.net/u0141 ...