通用访问 - 用“反射”来设计通用的通信协议,以及配套的SDK、工具

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与工具

不实时更新
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实现访问各种设备的协议约定的对象;最后如果一个设备需要与多个其他类型的设备互联,也无需定义多套功能码、报文编解码方案,只需使用对象来提供统一的服务。似乎看到了蓝天白云,鸟语花香,人人和睦相处,高效自动化工作的画面。

 





posted @ 2016-08-01 22:03  K.NET  阅读(475)  评论(0编辑  收藏  举报