上一篇我们讲了 如何创建一个基本的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. Codeforces 835E. The penguin's game

    http://codeforces.com/problemset/problem/835/E 题意: 这是一道交互题 有n个数,其中有2个y,n-2个x 每次你可以询问若干个数的异或和,从而得出y的位 ...

  2. [国家集训队2012]middle

    http://cogs.pro:8080/cogs/problem/problem.php?pid=1763 二分答案x 把区间内>=x的数设为1,<x的数设为-1 左端点在[a,b]之间 ...

  3. JavaScript继承详解(一)

    面向对象与基于对象 几乎每个开发人员都有面向对象语言(比如C++.C#.Java)的开发经验. 在传统面向对象的语言中,有两个非常重要的概念 - 类和实例. 类定义了一类事物公共的行为和方法:而实例则 ...

  4. py-faster-rcnn代码阅读2-config.py

    简介  该文件指定了用于fast rcnn训练的默认config选项,不能随意更改,如需更改,应当用yaml再写一个config_file,然后使用cfg_from_file(filename)导入以 ...

  5. 洛谷 2257 - YY的GCD

    莫比乌斯反演半模板题 很容易可以得到 \[Ans = \sum\limits_{p \in prime} \sum\limits_{d = 1}^{\min (\left\lfloor\frac{a} ...

  6. %08lx

    u-boot中代码如下: debug ("Now running in RAM - U-Boot at: %08lx\n", dest_addr); 对应设备上的打印消息如下: N ...

  7. Kali Linux上安装SSH服务

    安装 SSH 从终端使用 apt-get 命令安装 SSH 包: # apt-get update # apt-get install ssh 启用和开始使用 SSH 为了确保安全 shell 能够使 ...

  8. spring boot JPA中实体类常用注解

    spring boot jpa中的注解很多,参数也比较多.没必要全部记住,但是经常查看官方文档也比较麻烦,记录一下一些常用的注解.通过一些具体的例子来帮助记忆. @Entity @Table(name ...

  9. Android实现手机摄像头的自动对焦

    如何实现Android相机的自动对焦,而且是连续自动对焦的.当然直接调用系统相机就不用说了,那个很简单的.下面我们主要来看看如如何自己实现一个相机,并且实现自动连续对焦. 代码如下: public c ...

  10. Tomcat不同版本所对应的Servlet/JSP规范

    上午在别人机器上做演示,写好 Servlet居然访问不了.后来回想叻下,觉得应该是Tomcat版本不一致的问题,我用的是Tomcat 7.0建的Project,他们大多数都是用的6.0的版本.回来又仔 ...