首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >使用Snet.TEP(TCP扩展插件)来进行非标数据的高效采集,任何语言都可对接

使用Snet.TEP(TCP扩展插件)来进行非标数据的高效采集,任何语言都可对接

作者头像
Shunnet
发布2025-02-20 14:34:13
发布2025-02-20 14:34:13
17200
代码可运行
举报
运行总次数:0
代码可运行

TEP是:TCP Extend Plugin ,中译:TCP 扩展 插件,用于对接非标或第三方的数据采集软件

底层采用SocketTCP来进行开发的,数据交互都是Byte[],所以说任何语言都可以对接并采集上传到(主端)

Master:主端,SocketTCP服务端,也就是接收数据,与DAQ函数一致,支持多个 从端 数据上传

Slave:从端,SocketTCP客户端,把非标数据通过网络传输到 主端

解释一下为什么这个设计

  1. 通过SocketTCP通讯是为了适应所有的编程语言,因为SocketTCP属于最基本数据传输协议
  2. 为什么会有主从端一说法呢,主端是继承DAQ接口来进行开发与其他的通信库一样的操作流程,为了可以以插件的形式动态加载
  3. 从端提供C#的库,可以在使用C#开发时快速对接,接口功能都已实现
  4. 如 Python C/C++ JAVA  等等一系列语言都可通过如下会介绍的文档对接,从而实现一个闭环

缺点简单介绍一下

  1. 由于是使用的TCP通信,TCP底层默认会执行握手操作,而我在此基础上又封装了一层自定义的命令,从而导致第一次加载连接会慢
  2. 每一条命令数据的交互都会进行验证,这是为了保证数据的准确性,也会把效率拉低(我以极致优化C#的从端库对接,下方会放上传输的效率对比图)

[ Snet.TEP ] 接入规范 ( SocketTcp )

主端简称:S - 0x43

从端简称:C - 0x53

包体

名称

固定字节

长度

描述

针头

0x7b 0x5b 0x28 0x3c

4个字节

用于解包封包使用

命令

1个字节

请看下方接口描述

方向

C > S:0x43 0x53S > C:0x53 0x43

2个字节

客户端 往 服务端 发送数据服务端 往 客户端 发送数据

数据包长度

4个字节

也就是如下数据包的字节长度

数据包

N

数据包为JSON数据,把JSON数据通过UTF8转换成字节

CRC

2个字节

用于判断如上数据包是否正常,最下面为CRC C#验证代码

针尾

0x3e 0x29 0x5d 0x7d

4个字节

用于解包封包使用

注意:收到数据进行针头、命令、方向、针尾判断与CRC校验+数据长度判断,来确保数据的完整性与正确性

命令

命令

名称

模式

描述

0x30

握手

被动

发送到客户端的命令,表示客户端可以发数据给服务端了

0x31

身份验证

主动

握手后5秒内必须发送身份认证,不然会被服务端强制踢出

0x32

会话心跳包

主动

认证后5秒内必须主动请求一次,不然会被服务端强制踢出

0x33

获取客户端状态

被动

服务端通过设备名设备ID获取设备状态

0x34

数据上传

主动

客户端主动上传设备采集的数据

0x35

数据写入

被动

服务端下发至客户端写入数据,无法写入直接在Message中表现

数据包字段

名称

描述

DevName

设备名称

DevID

设备ID,此字段属于标识符,必须唯一

Message

消息

State

状态;-1 身份尚未认证;0 未响应;1 成功/正常;2 异常;

AddressValue

地址键值集合,注意 Key 不要存在[ . ] 点,用别的符号代替

UserName

用户名

Password

密码

握手【被动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求:S>C

0x30

0x53 0x43

握手

身份验证【主动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求:C>S

0x31

0x43 0x53

{ "UserName": "test", "Password": "test", "DevName": "F100透气度仪", "DevID": "0001" }

响应:S>C

0x31

0x53 0x43

成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message ": "身份验证成功 ", "State ": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "身份验证失败,****", "State": 2 }

心跳包 间隔 5秒一次【主动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求C>S

0x32

0x43 0x53

{ "DevName": "F100透气度仪", "DevID": "0001" }

发送S>C

0x32

0x53 0x43

成功 { "DevName": "F100透气度仪", "DevID": "0001", "Message": "通信正常", "State": 1 }失败 { "DevName": "F100透气度仪", "DevID": "0001", "Message": "通信异常,******", "State": 2 }无响应:C 没有收到 S 的响应,S问题 ,C直接日志输出无响应:S 5秒内没有收到 C 的请求,C问题,强制关闭C的连接

获取客户端状态【被动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求:S>C

0x33

0x53 0x43

{ "DevName": "F100透气度仪", "DevID": "0001" }

响应:C>S

0x33

0x43 0x53

正常 { "DevName": "F100透气度仪", "DevID": "0001", "State": 1, "Message": "状态正常", }异常 { "DevName": "F100透气度仪", "DevID": "0001", "State": 2, "Message": "状态异常,****", }

数据上传【主动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求:C>S

0x34

0x43 0x53

{ "DevName": "F100透气度仪", "DevID": "0001", " AddressValue": [{ "Key": "a", "Value": 6.6 }, { "Key": "b", "Value": "hello" } ] }

响应:S>C

0x34

0x53 0x43

成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据上传成功", "State": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据上传失败,****", "State": 2 }

数据写入【被动】

类型

命令

方向

数据内容:通过UTF8编码格式转换成字节数组,响应时反转即可

请求:S>C

0x35

0x53 0x43

{ "DevName": "F100透气度仪", "DevID": "0001", "AddressValue": [{ "Key": "a", "Value": 6.6 }, { "Key": "b", "Value": "hello" }] }

响应:C>S

0x35

0x43 0x53

成功: { "DevName": "F100透气度仪", "DevID": "0001", "Message": "数据写入成功", "State": 1 }失败: { "DevName": "F100透气度仪", "DevID": "0001" "Message": "数据写入失败,****", "State": 2 } 注意:如果被服务端关闭,说明你返回的数据存在问题

[ Snet.TEP ] 接入规范 ( C#动态库 )

  1. C# 动态库版本对接较为简单,只需要执行打开关闭数据上传,以及传入数据存储方法与状态获取方法
  2. C# 动态库中已经实现了身份认证、会话心跳包、以及自动重连功能
  3. 先到NuGet上搜索 Snet.TEP点击安装,再来进行如下
代码语言:javascript
代码运行次数:0
运行
复制
//先虚拟出 设备名称与ID
string DevName = "测试设备";
string DevID = new Random().Next(10000, 999999).ToString();

//第一步,先实例化对象,用户账号密码就是服务端内置的账号密码,做身份认证使用,其余参数为默认值即可
TepSlaveData.Basics basics = new TepSlaveData.Basics()
{
    IpAddress = "127.0.0.1",
    DevName = DevName,
    DevID = DevID,
    HalfTime = 1,
    UserName = "samples",
    Password = "samples"
    ViolenceUpload = false
};

//第二步,设置方法
bool 服务端获取状态会进入到这里面()
{
    return true;
}
OperateResult 服务端往客户端写入点位数据会进入到这里面(List<CoreBasicsData.KeyValue> keyValues)
{
    //可以写入的返回
    return new OperateResult(true, "写入成功", 1);

    //不可以写入的返回
    return new OperateResult(true, "写入失败,不支持写入功能", 1);
}
clientOperate.SetBaseStateFunc(服务端获取状态会进入到这里面);
clientOperate.SetBaseWriteFunc(服务端往客户端写入点位数据会进入到这里面);

//第三步,打开
OperateResult result = clientOperate.On();

//输出结果
Console.WriteLine(result.ToJson().JsonFormatting());
//状态判断
if (result.State)
{
    //第四步,事件注册
    clientOperate.OnEvent += ClientOperate_OnEvent;
}

//当你要上传数据时就执行数据上传方法,注意 Key 不要存在[ . ] 点,用别的符号代替
result = clientOperate.DataUpload(new List<KeyValue>() { new KeyValue { Key = "键", Value = "值" } });

//输出结果
Console.WriteLine(result.ToJson().JsonFormatting());

//事件接收
private static void ClientOperate_OnEvent(object? sender, Model.data.EventResult e)
{
    Console.ForegroundColor = ConsoleColor.Green;
    Console.WriteLine(e.ToJson());
}

实例参数

代码语言:javascript
代码运行次数:0
运行
复制
/// <summary>
/// tep 从端数据
/// </summary>
public class TepSlaveData
{
    /// <summary>
    /// 基础数据
    /// </summary>
    public class Basics : TcpClientData.Basics
    {
        /// <summary>
        /// 设备名称
        /// </summary>
        [Description("设备名称")]
        public string? DevName { get; set; } = "samples";

        /// <summary>
        /// 设备ID
        /// </summary>
        [Description("设备ID")]
        public string? DevID { get; set; } = "10001";

        /// <summary>
        /// 用户名
        /// </summary>
        [Description("用户名")]
        public string? UserName { get; set; } = "samples";

        /// <summary>
        /// 密码
        /// </summary>
        [Description("密码")]
        public string? Password { get; set; } = "samples";

        /// <summary>
        /// 休息时间;<br/>
        /// 越小CPU占用越高,速度越快
        /// </summary>
        [Description("休息时间")]
        public int HalfTime { get; set; } = 5;

        /// <summary>
        /// 暴力上传<br/>
        /// 只管上传,不处理服务端的返回的结果
        /// </summary>
        [Description("暴力上传")]
        public bool ViolenceUpload { get; set; } = false;
    }
}

CRC校验

代码语言:javascript
代码运行次数:0
运行
复制
#region CRC16查表法
       #region 高位表
       /// <summary>
       /// CRC高位校验码表
       /// </summary>
       readonly static byte[] cRCHighArray =
{
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};
        #endregion 高位表

        #region 低位表
        /// <summary>
        /// CRC低位校验码表
        /// </summary>
        readonly static byte[] cRCLowArray =
 {
0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
        #endregion 低位表

        /// <summary>
        /// 计算CRC16循环校验码
        /// ① 支持按位异或校验(XOR)
        /// ② 支持CRC16查表法校验
        /// ③ 支持CRC16带多项式计算法校验
        /// </summary>
        /// <param name="cmd">字节数据</param>
        /// <param name="isHighBefore">是否高位在前,默认false</param>
        /// <returns></returns>
        public static byte[] GetCRC16(byte[] cmd, bool isHighBefore = false)
        {
            int index;
            int crc_Low = 0xFF;
            int crc_High = 0xFF;

            for (int i = 0; i < cmd.Length; i++)
            {
                index = crc_High ^ (char)cmd[i];
                crc_High = crc_Low ^ cRCHighArray[index];
                crc_Low = (byte)cRCLowArray[index];
            }
            if (isHighBefore == true)
            {
                return new byte[2] { (byte)crc_High, (byte)crc_Low };
            }
            else
            {
                return new byte[2] { (byte)crc_Low, (byte)crc_High };
            }
        }
        #endregion CRC16查表法

关闭暴力上传的效率(单机测试 1s/150+)

开启暴力上传的效率(单机测试 1s/1500+)

TEP的主端从端示例代码已打包

https://shunnet.lanzouv.com/b0mbde3ih  密码:9air

感谢观看

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-02-16,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档