使用Newlife网络库管道模式解决数据粘包(二)
上一篇我们讲了 如何创建一个基本的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网络库管道模式解决数据粘包(二)的更多相关文章
- 使用NewLife网络库构建可靠的自动售货机Socket服务端(一)
最近有个基于tcp socket 协议和设备交互需求,想到了新生命团队的各种组件,所以决定用NewLife网络库作为服务端来完成一系列的信息交互. 第一,首先说一下我们需要实现的功能需求吧 1,首先客 ...
- python套接字解决tcp粘包问题
python套接字解决tcp粘包问题 目录 什么是粘包 演示粘包现象 解决粘包 实际应用 什么是粘包 首先只有tcp有粘包现象,udp没有粘包 socket收发消息的原理 发送端可以是一K一K地发送数 ...
- Netty使用LineBasedFrameDecoder解决TCP粘包/拆包
TCP粘包/拆包 TCP是个”流”协议,所谓流,就是没有界限的一串数据.TCP底层并不了解上层业务数据的具体含义,它会根据TCP缓冲区的实际情况进行包的划分,所以在业务上认为,一个完整的包可能会被TC ...
- C#下利用封包、拆包原理解决Socket粘包、半包问题(新手篇)
介于网络上充斥着大量的含糊其辞的Socket初级教程,扰乱着新手的学习方向,我来扼要的教一下新手应该怎么合理的处理Socket这个玩意儿. 一般来说,教你C#下Socket编程的老师,很少会教你如何解 ...
- 解决Socket粘包问题——C#代码
解决Socket粘包问题——C#代码 前天晚上,曾经的一个同事问我socket发送消息如果太频繁接收方就会有消息重叠,因为当时在外面,没有多加思考 第一反应还以为是多线程导致的数据不同步导致的,让他加 ...
- 深入学习Netty(5)——Netty是如何解决TCP粘包/拆包问题的?
前言 学习Netty避免不了要去了解TCP粘包/拆包问题,熟悉各个编解码器是如何解决TCP粘包/拆包问题的,同时需要知道TCP粘包/拆包问题是怎么产生的. 在此博文前,可以先学习了解前几篇博文: 深入 ...
- 粘包处理现象及其解决方案——基于NewLife.Net网络库的管道式帧长粘包处理方法
[toc] #1.粘包现象 每个TCP 长连接都有自己的socket缓存buffer,默认大小是8K,可支持手动设置.粘包是TCP长连接中最常见的现象,如下图 socket缓存中有5帧(或者说5包)心 ...
- 网络编程基础【day09】:解决socket粘包之大数据(七)
本节内容 概述 linux下运行效果 sleep解决粘包 服务端插入交互解决粘包问题 一.概述 刚刚我们在window的操作系统上,很完美的解决了,大数据量的数据传输出现的问题,但是在Linux环境下 ...
- 解决socket粘包的两种low版模式 os.popen()和struct模块
os.popen()模式 server端 import socket import os phone = socket.socket() # 实例化一个socket对象 phone.bind((&qu ...
随机推荐
- 【转载】视频CDN技术原理与流程说明
视频CDN专为移动互联网视频内容分发量身定做的一套自主研发的分布式平台,该平台以深圳为中心,分布在全国各地BGP机房的服务器为边缘节点,汇聚中国电信.中国联通.中国移动.中国教育网等运营商网络资源,构 ...
- p 最多两行 多的显示省略号
-webkit-line-clamp: 2 -webkit-box-orient: vertical; }
- HDU 3371 Connect the Cities 最小生成树(和关于sort和qsort的一些小发现)
解题报告:有n个点,然后有m条可以添加的边,然后有一个k输入,表示一开始已经有k个集合的点,每个集合的点表示现在已经是连通的了. 还是用并查集加克鲁斯卡尔.只是在输入已经连通的集合的时候,通过并查集将 ...
- Python 入门基础3 --流程控制
今日目录: 一.流程控制 1. if 2. while 3. for 4. 后期补充内容 一.流程控制--if 1.if判断: # if判断 age = 21 weight = 50 if age & ...
- 【项目部署】部署项目以war包部署和解开以目录部署的区别
我们都知道最简单的部署web项目的方式是打成war包直接仍在tomcat的webapps目录下,我上个项目也确实是这样做的,可是这给我们后期的维护带来了极大的不便,下面就简单研究一下以war包部署和解 ...
- elasticsearch代码片段,及工具类SearchEsUtil.java
ElasticSearchClient.java package com.zbiti.framework.elasticsearch.utils; import java.util.Arrays; i ...
- XPATH的几个常用函数
1.contains (): //div[contains(@id,'in')] ,表示选择id中包含有’in’的div节点2.text():由于一个节点的文本值不属于属性,比如“<a clas ...
- window批处理——bat文件的编写
BAT 批处理脚本 教程 第一章 批处理基础第一节 常用批处理内部命令简介 批处理定义:顾名思义,批处理文件是将一系列命令按一定的顺序集合为一个可执行的文本文件,其扩展名为BAT或者CMD.这些命 ...
- 中文分词-jieba
支持三种分词模式: 精确模式,试图将句子最精确地切开,适合文本分析: 全模式,把句子中所有的可以成词的词语都扫描出来, 速度非常快,但是不能解决歧义: 搜索引擎模式,在精确模式的基础上,对长词再次切分 ...
- 【LOJ】#2888. 「APIO2015」巴邻旁之桥 Palembang Bridges
题解 发现我们选择一座桥会选择力\(\frac{s + t}{2}\)较近的一座桥 然后我们只需要按照\(s + t\)排序,然后枚举断点,左边取所有s和t的中位数,右边同理 动态求中位数用平衡树维护 ...