分享一个与硬件通讯的分布式监控与远程控制程序的设计(上:自动升级与异步事件)

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;
        }
    }
}

 

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

 

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

posted @ 2012-12-27 17:01  道法自然  阅读(3015)  评论(6编辑  收藏  举报