使用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 ...
随机推荐
- Flex 笔记整理 三
1. Panel, TitleWindow PopUpManager 透明 用一个类,这个类里引用一个组件, P如 Panel, TitleWindow等, 利用PopUpManager来弹出显示. ...
- 20155230 2016-2017-2 《Java程序设计》第九周学习总结
20155230 2016-2017-2 <Java程序设计>第九周学习总结 教材学习内容总结 第十六章 statement在不使用时所关联的resultset也会自动关闭. 要让SQL执 ...
- 第12月第30天 love2d
1. Linux On Linux, you can use one of these command lines: love /home/path/to/gamedir/ love /home/pa ...
- bash脚本:集群资源争夺战crazy-killer
背景 公司的集群很多人一起用,有时候就难免资源紧张,某次需要用的时候没资源等了半天还是没资源,再等半天还是没资源,于是就写了个脚本泄愤,建议看到的人拷走放在自己公司集群上长期运行 :) 实现 此脚本运 ...
- webpack react 错误整理
1.ERROR in ./src/entry.js Module build failed: SyntaxError 解决方法: 安装babel-preset-react, npm install ...
- Basic(消息)的一些属性及方法
AMQP协议:是一个金融级的消息队列,确保消息万无一失 1.消息发布端的确认 手动确认消息是否已经发送 场景:发布消息到RabbitMQ中,我们需要知道这个消息是否发布成功了. *发布确认影响性能 c ...
- 分析占用了大量CPU处理时间的是Java进程中哪个线程
下面是详细步骤: 1. 首先确定进程的 ID ,可以使用 jps -v 或者 top 命令直接查看 2. 查看该进程中哪个线程占用大量 CPU,执行 top -H -p [PID] 结果如下: 可以发 ...
- jquery-easyui:格式化列
主框架页面: 在主界面区会加载西区菜单点击的URL内容. <!DOCTYPE html> <html> <head> <meta charset=" ...
- python抓取内涵段子文章
# coding:utf-8 from urllib.request import urlretrieve import threading import requests from bs4 impo ...
- jQuery下的onChange事件在某些情况下无效
onChage无效的原因: 虽然表面上感觉是当内容发生变化时,就会触发onchange事件,但是那只能在页面上操作.而如果通过dom对象去修改它的value则什么事也不会发生. onchange触发原 ...