zoukankan      html  css  js  c++  java
  • 分享一个与硬件通讯的分布式监控与远程控制程序的设计(上:自动升级与异步事件)

    1 远程分布式监控与控制系统设计概述

    1.1 概述

    该系统由通讯服务器、Web管理平台、消息队列服务器和数据库服务器构成。这里,我将在本文要描述的内容摘要如下。

    (1) 基于OSGi.NET的动态部署(自动升级)、模块化开发方法:整个系统由Web管理平台(这个文章不详细描述)和通讯服务器组成,由5个软件开发人员协作开发(硬件开发工程师若干),目前有多个地域的部署。

    (2) 基于异步事件的可视化跟踪消息显示:在通讯方式中,有一个用于显示系统通讯过程中的监控信息,这个监控信息对于软件开发/调试以及硬件开发/调试有极大的帮助,它能够详细显示每一个通讯指令的执行过程,方便软件开发工程师和硬件开发工程师的协作与排除错误;

    (3) 基于RoundTrip(往返)的通讯协议设计:通讯协议层仅仅实现服务器与硬件的通讯,硬件与服务器的通讯是由一个一个的RoundTrip组成,RoundTrip表示一次往返的会话,即要么是服务器发送给硬件然后获取响应,要么就是硬件发送给服务器获取响应,RoundTrip会话是一个原子会话,即通讯服务插件必须有一个RoundTrip队列用于排序通讯的规则,确保RoundTrip不能发生交叉的情况;

    (4) COM/VPN/局域网/GPRS/3G通讯方式的实现:通讯方式基于通讯协议层之上,也就是说,通讯协议是通用的。这几种通讯方式的实现个不相同,我将在后面描述各自实现的算法,并且如何保证数据是线程安全的。

    (5) 基于RabbitMQ的远程控制的实现:整个监控平台的负载非常大,因此我们采用分布式/多服务的方式来实现,我在这里将描述一下系统的架构以及分布式远程通讯的实现;

    (6) 通讯协议的测试:这也是本通讯服务器的亮点之一,通讯协议的测试一般都需要与硬件关联,这就很麻烦了~,在这里我使用单元测试 + 模拟串口的方式来保证通讯协议的正确性,并且使用不同的通讯方式时,仍然可以使用同一套单元测试来保证。

    1.2 通讯服务器界面

    (1)SplashForm,检查插件更新,加载启动插件

    image

    (2)VPN/局域网通讯/GPRS/3G通讯/COM通讯

    image

    1.3 Web管理平台界面

    clip_image012[4]

    2 基于OSGi.NET的动态部署

    2.1 基于OSGi.NET的项目概述

    以下是通讯服务器程序的项目,基于OSGi.NET框架(你可以通过iOpenWorks.com下载该SDK,免费的哦)开发,采用插件方式来构建,由OSGi.NET自带的系统插件和一个Outlook风格通用WinForm界面框架插件(您可以同以下地址来获取该插件,免费且开源,http://www.iopenworks.com/Products/ProductDetails/Introduction?proID=8)和自定义的插件构成。

    自定义的插件有:

    (1) 日志插件,基于Log4net实现详细日志;

    (2) 消息队列契约/消息队列服务插件,基于消息队列实现通讯服务器的分布式通讯,可以接收来自Web管理平台的远程控制指令;

    (3) 通讯服务器插件,这是整个系统核心,它定义了通讯协议的基类,实现了基于COM、VPN/局域网、GPRS/3G等通讯,这个通讯插件的设计非常的巧妙,它采用同一个协议来兼容多种通讯方式,并基于分层方式来隔离不同通讯方式的差异。

    image

    以下是Web管理平台项目,也是基于OSGi.NET构建,由25个自定义插件项目组成,包含了若干系统插件和通讯契约与服务插件。Web管理平台主要实现硬件方面的配置、管理、数据采集、报表分析、远程控制。

    image

    2.2 动态部署

    在这个系统中,涉及的人员有硬件开发工程师、硬件工程师、测试人员、现场实施人员,系统基于各种通讯方式部署若干,未来将会大规模部署。在没有使用动态部署之前,每次程序更新,开发人员都需要单独发布插件,然后将插件发给硬件开发工程师、测试人员和现场实施人员,整个过程将需要重复N次,不仅繁琐还容易出错。此外,项目正式部署后,进行了若干次改动,每次改动都需要远程登录到N个服务器进行程序升级。因此,我们使用了OSGi.NET的动态部署技术,基于开放工厂iOpenWorks.com来实现通过插件仓库统一发布软件的各个测试版本。一旦插件更新发布后,若干个测试/部署的应用程序将得到同步更新。下面我来介绍我们是如何通过iOpenWorks.com来实现这种动态部署的。

    2.2.1 发布插件

    (1)进入http://www.iopenworks.com网站,然后以admin身份登录。

    clip_image020[4]

    (2)点击“插件仓库“,在这里,你可以选择添加插件、编辑/删除/升级插件、编辑插件分类。

    image

    (3)在这个页面中,点击添加插件图标,使用说明如下。

    image

    2.2.2 发布插件更新

    进入http://www.iopenworks.com网站,然后以admin身份登录,进入“插件仓库“,在这个页面中,点击升级插件图标,在这里上传新的插件版本文件并点击保存。

    image

    2.2.3 发布OSGi.NET内核更新

    以管理员登录,点击“插件仓库“。在界面中,点击“内核文件“链接,在这里可以修改内核文件。

    image

    点击“编辑”图标后,你可以上传一个新的内核文件,这样基于OSGi.NET的应用可以实现自动更新。

    2.2.4 程序执行OSGi.NET内核更新

    OSGi.NET应用程序通过UIShell.iOpenWorks.Bootstrapper程序集来提供内核更新功能,如下所示。

    image

    内核文件的更新代码如下。

    image

    2.2.5 程序实现插件自动更新

    插件的升级可以有两种:(1)通过插件中心来执行升级;(2)新建一个插件实现自动更新。

    您可以通过插件中心来查看可以升级的插件,并执行升级动作。

    image

    此外,您还可以新建一个插件实现自动更新,这个方法描述如下:

    (1)添加对UIShell.BundleManagementService依赖。

    (2)添加对UIShell.BundleManagementService.dll程序集的引用,将拷贝本地属性改为false。

    (3)在插件激活器中实现自动更新操作,首先创建服务跟踪器接着监听服务变更事件,当服务可用时,执行自动升级。

    image

    image

    3 基于异步事件的可视化跟踪消息显示

    通讯服务器插件实现了与硬件系统的通讯,与硬件通讯,在初期调试非常麻烦,为了便于寻找问题和查看系统的运行状态,我们有必要实时显示系统的执行情况,如下所示。

    clip_image045[4]

    在这里有一个监控控制台,它将显示每个会话的消息包,按照正确的时序来展现与硬件的通讯过程。在这里,我设计了一个跟踪相关的类和一个异步事件分配器,设计的类图如下。

    image

    首先,我设计了一个ITrackable接口,表示实现该接口的类型能以同步/异步的方式输出消息。其具体实现如下。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace UIShell.CommServerService
    {
        public class TraceEventArgs : EventArgs
        {
            public string Message { get; private set;}
    
            public TraceEventArgs(string message)
            {
                Message = message;
            }
        }
    
        /// <summary>
        /// 收集会话跟踪调试信息。
        /// </summary>
        public interface ITrackable
        {
            /// <summary>
            /// 调试信息添加事件。
            /// </summary>
            event EventHandler<TraceEventArgs> OnTraceMessageAdded;
            bool Trackable { get; set; }
            /// <summary>
            /// 会话跟踪ID。
            /// </summary>
            int TrackerId { get; set; }
            /// <summary>
            /// 父会话跟踪调试器。
            /// </summary>
            ITrackable ParentTracker { get; set; }
            void Trace(string message);
            void DispatchAsyncTraceMessageAddedEvent(object sender, TraceEventArgs e);
            void DispatchSyncTraceMessageAddedEvent(object sender, TraceEventArgs e);
        }
    }
    

      

    接着设计了一个事件分配器。事件分配器可以用于分发一个同步/异步事件,如果是同步事件,事件发生后,分配器将直接调用事件处理函数,如果是异步的,则将改调用作为一个异步调用项,添加到异步事件调用线程的队列中,其关键代码是一个EventThread,负责异步事件的排队和调用,其实现如下。

    /// <summary>
    /// 异步事件派发线程。
    /// </summary>
    class EventThread
    {
        private Thread _dispatcherThread;
        private Queue<EventThreadItem> _queue;
        private object _lockObject;
        private AutoResetEvent _waitEvent;
        private bool _isExited;
    
        /// <summary>
        /// 启动一个线程执行事件派发。
        /// </summary>
        public EventThread()
        {
            _lockObject = new object();
            _queue = new Queue<EventThreadItem>();
            _waitEvent = new AutoResetEvent(false);
            _dispatcherThread = new Thread(new ThreadStart(Dispatch));
            _dispatcherThread.Name = "EventDispatcherThread";
            _dispatcherThread.Start();
        }
                
        /// <summary>
        /// 如果派发列表为空,则暂停线程。
        /// </summary>
        public void Dispatch()
        {
            EventThreadItem item;
    
            while (!_isExited)
            {
                Monitor.Enter(_lockObject);
                if ((item = Pop()) == null)
                {
                    Monitor.Exit(_lockObject);
                    //如果没有要处理的事件了,阻塞在此,一旦阻塞返回,则继续Peek,那么下一次Peek将会取到数据
                    _waitEvent.WaitOne();
                    continue;
                }
                Monitor.Exit(_lockObject);
                if (item != null)
                {
                    SyncDispatchEventItem(item);
                }
            }
        }
    
        /// <summary>
        /// 插入一个事件项,唤醒线程。
        /// </summary>
        /// <param name="item"></param>
        public void Push(EventThreadItem item)
        {
            lock (_lockObject)
            {
                _queue.Enqueue(item);
            }
    
            try
            {
                _waitEvent.Set();
            }
            catch
            {
            }
        }
    
        /// <summary>
        /// 取出一个事件项。
        /// </summary>
        /// <returns></returns>
        public EventThreadItem Pop()
        {
            lock (_lockObject)
            {
                return _queue.Count > 0 ? _queue.Dequeue() : null;
            }
        }
    }
    

    这个事件的分配是一个异步的,即如果监听到有消息添加时,刷新界面时,不需要阻塞原通讯线程。

    限于篇幅本文的上部分描述到这,在下文将继续描述的中下部分将接着介绍通讯协议等内容的设计,通讯协议也是本文的核心部分。

     

    Creative Commons License 关于iOpenWorksSDK下载和疑问,访问:https://www.cnblogs.com/baihmpgy/p/11818026.html

    本文基于Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名道法自然(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
  • 相关阅读:
    把CentOS改成中文
    String,StringBuffer,StringBuilder三者性能对比
    在Linux上部署安装C/C++开发环境
    Kali Linux安装ssh服务
    Kali VirtualBox安装增强功能
    CentOS安装docker
    CentOS安装jdk11
    Java基本数据类型
    奥卡姆剃刀定律在Java代码中的体现——记一次LeetCode刷题心得
    Java 实现简易登录注册功能
  • 原文地址:https://www.cnblogs.com/baihmpgy/p/2836016.html
Copyright © 2011-2022 走看看