通用访问 - 用“反射”来设计通用的通信协议

1. 效果演示

2. 通信协议

  • 功能介绍
  • 特点
  • TCP协议
  • WebApi协议

3. SDK与工具

4. 应用示例

  • 迷你网管
  • 通用GIS
  • 系统管理

5. 设计初衷与演化

1. 效果演示

服务端代码

    //创建服务器
    var __服务端 = FT通用访问工厂.创建服务端();
    __服务端.端口 = 8888;
    __服务端.开启();
    //实际对象
    var __基本状态 = new M基本状态();
    __基本状态.版本 = "1.0.0.0";
    __基本状态.待处理问题.Add(new M问题 { 等级 = E重要性.普通, 内容 = "xxxx" });
    __基本状态.待处理问题.Add(new M问题 { 等级 = E重要性.重要, 内容 = "yyyy" });
    __基本状态.开启时间 = DateTime.Now;
    __基本状态.连接设备.Add(new M设备连接 { IP = IPAddress.Parse("192.168.0.1"), 标识 = "X1", 类型 = "X", 连接中 = true });
    __基本状态.连接设备.Add(new M设备连接 { IP = IPAddress.Parse("192.168.0.2"), 标识 = "Y1", 类型 = "Y", 连接中 = true });
    __基本状态.位置 = "威尼斯";
    __基本状态.业务状态 = new Dictionary<string, string> { { "参数1", "参数1值" }, { "参数2", "参数2值" } };
    //可通信对象
    var __对象 = new M对象("基本状态", "");
    __对象.添加属性("版本", () => __基本状态.版本, E角色.所有, null);//最后一个参数是元数据定义
    __对象.添加属性("位置", () => __基本状态.位置, E角色.所有, null);
    __对象.添加属性("开启时间", () => __基本状态.开启时间.ToString(), E角色.所有, null);
    __对象.添加属性("待处理问题", () => HJSON.序列化(__基本状态.待处理问题), E角色.所有, null);
    __对象.添加属性("连接设备", () => HJSON.序列化(__基本状态.连接设备), E角色.所有, null);
    __对象.添加属性("业务状态", () => HJSON.序列化(__基本状态.业务状态), E角色.所有, null);
    __对象.添加方法("重启", __实参 => {
        //处理实参
        return string.Empty;  //返回空字符串或者json格式的对象
    }, E角色.所有, null, null); //最后两个参数是对参数和返回值的元数据定义
    __对象.添加事件("发生了重要变化", E角色.所有, null);
    //将对象加入到服务器
    __服务端.添加对象("基本状态", () => __对象);
    //触发服务器端事件
    __服务端.触发事件("基本状态", "发生了重要变化", null, null);//最后两个参数是实参和客户端地址列表(可选)

客户端代码

图片中的客户端是通用的,所以不用写代码,如果用于面向客户的定制界面,示例如下

    //创建客户端
    var __客户端 = FT通用访问工厂.创建客户端();
    __客户端.连接(new IPEndPoint(IPAddress.Any, 8888));
    var __版本 = __客户端.查询属性值("基本状态", "版本");
    var __待处理问题 = HJSON.反序列化<List<M问题>>(__客户端.查询属性值("基本状态", "待处理问题"));
    __客户端.执行方法("基本状态", "重启", null); //最后一个参数是实参
    __客户端.订阅事件("基本状态", "发生了重要变化", __实参 => {
        //处理实参
    });

2. 通信协议

功能介绍

  • 该协议是应用层的底层协议,可以承载各种业务,开发具体业务时,只需定义需要通信的对象元数据
  • 协议支持面向3种角色,开发/工程/客户,可以优雅的解决程序设计时的角色混杂问题
  • 基于配套的SDK开发分布式应用,像开发单机应用一样容易
  • 支持通用客户端(winform/web),面向开发/工程的功能,无需写代码就能访问服务器端对象;面向客户的功能,服务器端开发也能方便的测试

特点

  • 只要定义通信对象就可以开发分布式应用,简单强大
  • 跨平台,通用性强;开发SDK的难度不是很大,可以参考我写的.NET版本的,可以做到深度控制

TCP协议

报文格式


字段


类型


长度(字节)


说明


起始标识


Byte[]


2


固定为0xAAAA,用于识别报文开始


报文内容长度


Int32


4


余下所有字段长度


功能标识


Int16


2


用于识别报文类型


发送方事务标识


Int32


4


由发送方指定。当会话类型为请求型时,用于区别发送方的不同请求;当会话类型为通知型时,固定为0。


接收方事务标识


Int32


4


由接收方指定。当会话类型为请求型时,用于区别接收方的不同请求,特别的,会话开始的第一条报文,发送方的该字段填0;当会话类型为通知型时,固定为0。


负载


Byte[]


*


由报文内容长度字段指定, 推荐编码格式为UTF8文本, 采用json格式, 大数据量时采用压缩算法

功能码


功能



查询对象列表(请求)


1


查询对象列表(响应)


2


查询对象明细(请求)


3


查询对象明细(响应)


4


执行方法(请求)


5


执行方法(响应)


6


查询属性值(请求)


7


查询属性值(响应)


8


订阅事件


9


取消订阅事件


A


接收事件


B

查询对象列表

请求: 无

响应: [{名称, 分类, 角色}]

查询对象明细

请求: {对象名称}

响应: {属性列表[{名称, 元数据, 角色}], 方法列表[{名称, 形参列表[{名称, 元数据}, 角色], 返回值元数据}], 事件列表[{名称, 形参列表[{名称, 元数据}], 角色}]}

元数据: {类型, 结构(单值/对象/单值数组/对象数组), 描述, 默认值, 范围, 子成员列表[{名称, 元数据}]}

执行方法

请求: {对象名称, 方法名称, 实参列表[{名称, 值}]}

响应: {成功, 描述, 返回值}

查询属性值

请求: {对象名称, 属性名称}

响应: {成功, 描述, 返回值}

订阅事件

通知: {对象名称, 事件名称}

取消订阅事件

通知: {对象名称, 事件名称}

接收事件

通知: {对象名称, 事件名称, 实参列表[{名称, 值}]}

WebApi协议

响应数据格式(ContentType)为application/json;charset=utf-8


功能


HTTP方法


地址


查询对象列表


GET/ POST


objects.k


查询对象明细


GET/ POST


object.k


执行对象方法


GET/ POST


method.k


查询对象属性值


GET/ POST


proterty.k


登录


POST


login.k


心跳


GET


heart.k

注:成功后会设置cookies, 键为token

查询对象列表

参数: 无

响应: [{名称, 分类, 角色}]

查询对象明细

参数: 对象名称

响应: {属性列表[{名称, 元数据, 角色}], 方法列表[{名称, 形参列表[{名称, 元数据}], 返回值元数据, 角色}]}

执行方法

参数: 对象名称, 方法名称, 实参列表[{名称, 值}]

响应: {成功, 描述, 返回值}

查询对象属性值

参数: 对象名称, 属性名称

响应: {成功, 描述, 返回值}

登录

参数: password

响应: {成功(true/false), 描述}

心跳

参数: 无

响应: 服务器时间

3. SDK与工具

源码地址:https://github.com/xiaomiwk/K_tongyongfangwen

不实时更新

SDK核心接口

    public interface IT服务端
    {
        int 端口 { get; set; }
        int WebApi端口 { get; set; }
        void 添加对象(string 对象名称, Func<M对象> 获取对象);
        void 删除对象(string 对象名称);
        void 开启();
        event Action<IPEndPoint> 客户端已连接;
        event Action<IPEndPoint> 客户端已断开;
        List<IPEndPoint> 客户端列表 { get; }
        void 关闭();
        void 触发事件(string 对象名, string 事件名, Dictionary<string, string> 实参 = null, List<IPEndPoint> 地址列表 = null);
    }
    public interface IT客户端
    {
        IPEndPoint 设备地址 { get; }
        void 连接(IPEndPoint 设备地址);
        void 断开();
        bool 连接正常 { get; }
        /// <summary>
        /// true:主动断开,false:被动断开
        /// </summary>
        event Action<bool> 已断开;
        bool 自动重连 { get; set; }
        event Action 已连接;
        M对象列表查询结果 查询可访问对象();
        M对象明细查询结果 查询对象明细(string 对象名称);
        string 查询属性值(string 对象名, string 属性名, int 超时毫秒 = 3000);
        string 执行方法(string 对象名, string 方法名, Dictionary<string,string> 参数列表 = null, int 超时毫秒 = 3000);
        void 订阅事件(string 对象名, string 事件名, Action<Dictionary<string, string>> 处理方法);
        void 注销事件(string 对象名, string 事件名, Action<Dictionary<string, string>> 处理方法);
        /// <summary>
        /// 通常用于调试
        /// </summary>
        event Action<M接收事件> 收到了事件;
    }
public class M对象
{
    public string 名称
    {
        get { return 概要.名称; }
        set { 概要.名称 = value; }
    }
    public string 分类
    {
        get { return 概要.分类; }
        set { 概要.分类 = value; }
    }
    public E角色 角色
    {
        get { return 概要.角色; }
        set { 概要.角色 = value; }
    }
    internal M对象概要 概要 { get; set; }
    internal M对象明细查询结果 明细 { get; set; }
    /// <summary>
    /// 字典中的键string:方法名; 值:方法; 方法中的string:返回值
    /// </summary>
    Dictionary<string, Func<Dictionary<string, string>, string>> _所有方法 = new Dictionary<string, Func<Dictionary<string, string>, string>>();
    /// <summary>
    /// 字典中的键string:方法名; 值:方法; 方法中的string:返回值
    /// </summary>
    Dictionary<string, Func<Dictionary<string, string>, IPEndPoint, string>> _所有方法_带地址 = new Dictionary<string, Func<Dictionary<string, string>, IPEndPoint, string>>();
    /// <summary>
    /// 字典中的键string:属性名; 值:属性值计算方法
    /// </summary>
    Dictionary<string, Func<string>> _所有属性方法 = new Dictionary<string, Func<string>>();
    public M对象(string __名称, string __分类)
    {
        this.概要 = new M对象概要(__名称, __分类);
        this.明细 = new M对象明细查询结果();
    }
    public void 添加属性(string __名称, Func<string> __计算值, E角色 __角色 = E角色.开发, M元数据 __元数据 = null)
    {
        this.角色 = this.角色 | __角色;
        this.明细.属性列表.Add(new M属性(__名称, __元数据, __角色));
        _所有属性方法[__名称] = __计算值;
    }
    public void 添加方法(string __名称, Func<Dictionary<string, string>, string> __执行方法, E角色 __角色 = E角色.开发, List<M形参> __参数列表 = null, M元数据 __返回值元数据 = null)
    {
        this.角色 = this.角色 | __角色;
        _所有方法[__名称] = __执行方法;
        this.明细.方法列表.Add(new M方法(__名称, __参数列表, __返回值元数据, __角色));
    }
    public void 添加方法(string __名称, Func<Dictionary<string, string>, IPEndPoint, string> __执行方法, E角色 __角色 = E角色.开发, List<M形参> __参数列表 = null, M元数据 __返回值元数据 = null)
    {
        this.角色 = this.角色 | __角色;
        _所有方法_带地址[__名称] = __执行方法;
        this.明细.方法列表.Add(new M方法(__名称, __参数列表, __返回值元数据, __角色));
    }
    internal string 执行方法(string __名称, Dictionary<string, string> __参数列表, IPEndPoint __来源)
    {
        if (_所有方法.ContainsKey(__名称))
        {
            return _所有方法[__名称].Invoke(__参数列表);
        }
        if (_所有方法_带地址.ContainsKey(__名称))
        {
            return _所有方法_带地址[__名称].Invoke(__参数列表, __来源);
        }
        throw new ApplicationException("执行方法失败: 无此方法");
    }
    internal string 计算属性(string __属性名称)
    {
        if (_所有属性方法.ContainsKey(__属性名称))
        {
            return _所有属性方法[__属性名称]();
        }
        throw new ApplicationException("计算属性失败: 无此属性");
    }
    public void 添加事件(string __名称, E角色 __角色 = E角色.开发, List<M形参> __参数列表 = null)
    {
        this.角色 = this.角色 | __角色;
        this.明细.事件列表.Add(new M事件(__名称, __参数列表, __角色));
    }
}

工具

4. 应用示例

迷你网管

协议(对象)

名片

属性

名称

描述

版本号

版本时间

方法

查询

返回值: {名称, 描述, 版本号, 版本时间}

查询参数

返回值: [{名称, 值}]

状态

属性

启动时间

未清除告警数量

健康状态(优/良/中/差)

状态开始时间

注: 健康状态推荐规则是存在紧急告警为差, 存在重要告警为中, 存在次要告警为良, 否则为优

方法

查询概要状态

返回值: {启动时间, 健康状态, 状态开始时间, 未清除告警数量}

查询业务概要

参数: 类别(可为空), 属性(可为空)

返回值: [{类别, 属性, 当前值, 正常范围, 正常, 单位, 描述}]

查询未清除告警

参数: 条件{每页数量, 页数, 来源设备类型(可选), 来源设备标识(可选), 类别(可选), 重要性(可选), 关键字(可选)}

返回值: {总数量, 列表[{标识, 来源设备类型, 来源设备标识, 产生时间, 类别, 重要性, 描述, 原因, 解决方案}]}

重要性:


一般(1)


正常通知, 无需排除告警


次要(2)


非直接影响业务的使用, 不需要立即处理, 但还是需要排除告警


重要(3)


严重的故障, 可能会影响业务, 需要用户处理


紧急(4)


系统级故障, 严重影响业务, 需要用户立即处理

事件

上报告警

参数: 事件参数{标识, 来源设备类型, 来源设备标识, 产生时间, 类别, 重要性, 描述, 原因, 解决方案}

上报清除

参数: 事件参数{标识, 来源设备类型, 来源设备标识}

健康状态变化

参数: 事件参数{启动时间, 健康状态, 状态开始时间, 未清除告警数量}

系统

方法

重启

返回值: 无

关闭

返回值: 无

查询版本记录

返回值: [{版本号, 标签[], 修改记录}]

FTP

属性

支持(true/false)

运行中(true/false)

端口号

目录路径

编码(ASCII/UTF8/GB2312)

方法

设置端口号

参数: 端口号

返回值: 无

开启

返回值: 无

关闭

返回值: 无

客户端界面

下图模拟500个客户端连接




通用GIS

重量级作品,单独写文章介绍

系统管理

5. 设计初衷与演化

初衷

公司的界面软件内容非常混杂,充斥了大量的面向开发人员的元素,与其他设备间的协议,太多的地方让人恶心,耦合度极高,稳定性和可维护性极差。年前讨论重构,其中最基本的一条思路就是区别客户、工程、开发三种角色,面向最终客户的界面软件与其他设备间的协议,应该以应用为导向设计协议(上层决定下层,下层提供可行性约束,协议接口必须抽象)。将面向开发人员的内容与面向最终客户的软件分离开后,需要重新编写面向开发人员的界面与协议,加上我之前一直重视和建议面向工程(及测试)人员开发工具类软件,这就是设计通用访问协议的初衷,因为工程和开发人员不需要最终客户那样的定制化的界面。经过反复的推敲,最终以“自描述”为核心的界面以及协议诞生了。

演化

以后设计分布式的软件,首先完全面向业务的设计出需要的对象及其成员元数据,形成一份简单的但是纯粹的协议,无需定义额外的功能码、报文头、结构体;然后服务端基于通用访问SDK(与平台和开发语言相关),注入对象及其成员的实现(类似于和程序内部的模块、对象挂钩子或是牵线),通过通用访问工具可以直接测试协议接口,无需最终的客户端,同时提供一些面向工程和开发人员自身的对象(这部分对象易变性很高,以往常寄生在面向客户的界面软件中,严重加大无谓的工作量),以便部署和调试;客户端方面同样基于通用访问SDK实现访问各种设备的协议约定的对象;最后如果一个设备需要与多个其他类型的设备互联,也无需定义多套功能码、报文编解码方案,只需使用对象来提供统一的服务。似乎看到了蓝天白云,鸟语花香,人人和睦相处,高效自动化工作的画面。

来自为知笔记(Wiz)

时间: 2024-10-03 23:53:57

通用访问 - 用“反射”来设计通用的通信协议的相关文章

解析大型.NET ERP系统 设计通用Microsoft Excel导入功能

做企业管理软件很难避免与Microsoft Excel打交道,常常是软件做好了,客户要求说再做一个Excel导入功能.导入Excel数据的功能的难度不大,从Excel列数据栏位的取值,验证值,再导入到数据库表中.然而一直是在做重复工作,写过不计其数的Excel导入程序,每次只是满足于问题解决,后来终于找到一个方法,实现通用的Excel数据导入. 设计通用的Excel导入功能,第一个实现要求是不能依赖Excel,客户的电脑或服务器很有可能没有安装Excel,所以微软的Office Interop一

通用的业务编码规则设计实现

一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的应该如何编码的,具体的编码规则我们很多时候都是直接写在程序当中 常用的的编码有: 1.数据库自增长ID或最大值加1 2.GUID 3.时间戳 4.常量+自增长 5.常量+时间戳+自增长 6.根据单据属性编码 比如商品编码:第X是代码商品颜色,第Y位是代码商品产地 7.自定义函数处理返回 8.其它 添

C#:数据库通用访问类 SqlHelper

using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; using System.Data.SqlTypes; using System.Globalization; using System.IO; using System.Text.RegularExpressions; using System.Xml; using System.Reflection;

《通用试题库管理系统的设计与实现》9

一:基本信息 1标题:<通用试题库管理系统的设计与实现> 2时间:2013 3来源:电子科技大学硕士学位论文 4关键词:试题库,ASP.NET,随机算法.二:研究内容 1:研究背景. 2:技术介绍:XML.ASP/NET.ADO/NET.JAVAWEB数据库. 3:系统分析: A:需求分析. B:数据业务流程分析. C:功能分析. D:运行分析. 三:流程图: 系统功能设计流程图: 用户管理流程图: 四:总结:       比较了随机组卷算法.回溯试探算法和遗传算法的优点和缺点,命题实际需求,

《通用试题库管理系统的设计与实现 》19

一.基本信息 标题:<通用试题库管理系统的设计与实现 > 时间:2014 来源:承德石油高等专科学校 关键字:试题库; 设计; 实现 二.研究内容 问题定义:本文献设计的通用试题库管理系统,重点要解决传统试题库的通 用性差.交互性弱和界面缺乏友好等问题,同时对存放试题的数据库进行合理的设计,提高扩展性和并 发控制的能力,为以后试题库改版为 C/S 或 B/S 模式做好准备,同时对传统的组卷算法进行改进,提高 组卷的速度和合理性. 三.流程图 四.总结 本通用题库管理系统具有良好的通用性;题库管

《基于 VBA 技术的通用试题库管理系统的设计与实现》20

一.基本信息 标题:<基于 VBA 技术的通用试题库管理系统的设计与实现> 时间:2017 来源:吉 林 大 学 关键字:VBA,试题库,随机抽题,自动组卷 二.研究内容 问题定义:该系统主要包括三个模块.题库维护模块:在题库文档中利用Word的图文混排功能,对试题和答案进行增加.删除和修改,并且“题库文档”提供了“参数检测”.“标题涂色”和“查找重复题”的功能,“标题涂色”使试题看起来更醒目,“查找重复题”使查找重复题目更轻松,“参数检测”避免人为错误,提高程序的实用性.信息统计模块:可以对

用泛型和反射实现函数通用

使用泛型和反射机制事项函数的通用,写下来,欢迎吐槽 代码示例使用vb. net Imports System.Reflection Module Module1 Sub Main() Dim lst1 As List(Of Person) = New List(Of Person)() Dim lst2 As List(Of Person) = New List(Of Person)() Dim lstT As List(Of Person) = New List(Of Person)() Fo

利用反射实现类通用的DAO层

public void add(Object object) throws SQLException { ResultSet rs=null; PreparedStatement ps=null; Connection con=null; //获取表名 Class c= object.getClass(); String className=c.getName(); String declareName=className.substring(className.lastIndexOf(".&q

通用的业务编码规则设计实现[转:http://www.cnblogs.com/xqin/p/3708367.html]

一.背景 每一个企业应用中不可避免的都会涉及到业务编码规则的问题,比如订单管理系统中的订单编号,比如商品管理系统中的商品编码,比如项目管理系统中的项目编码等等,这一系列的编码都需要管理起来,那么它们的应该如何编码的,具体的编码规则我们很多时候都是直接写在程序当中 常用的的编码有: 1.数据库自增长ID或最大值加1 2.GUID 3.时间戳 4.常量+自增长 5.常量+时间戳+自增长 6.根据单据属性编码 比如商品编码:第X是代码商品颜色,第Y位是代码商品产地 7.自定义函数处理返回 8.其它 添