• [连载]《C#通讯(串口和网络)框架的设计与实现》-4.设备驱动管理器的设计


    目       录

    第四章           设备驱动管理器的设计... 2

    4.1           接口定义... 2

    4.2           设备容器... 7

    4.3           生成设备ID.. 7

    4.4           对设备容器操作的互斥... 8

    4.5           获得设备列表... 8

    4.6           设备计数器的特殊用处... 8

    4.7           小结... 10

     

    第四章     设备驱动管理器的设计

        设备驱动管理器是对IRunDevice设备驱动接口的管理,是框架重要的组成部分之一。不管设备驱动管理器怎么设计、以什么形式存在,在概念上肯定是存在的。设计好的设备驱动管理器对于框架平台的稳定运行至关重要。

       在介绍设备驱动管理器之前,先简单介绍一下IO控制器(IIOController),它主要负责对IO和设备进行调度,并驱动设备运行,在《第5章 串口和网络的IO设计》进行详细的介绍。也就是说一个IO控制器可能会对应多个设备驱动(插件)。

       早期的时候,每个IO控制器都有一个设备驱动管理器。在框架平台启动的时候,根据设备驱动的通讯参数把相应的设备驱动分配到相应的IO管理器;当IO参数发生变化的时候,会触发事件,把该设备驱动从当前IO控制器移动到另一个IO控制器。

       从业务角度来考虑,这样做并没有什么问题,并且一直运行的很稳定。但是,从模块化、扩展性角度来考虑,不是太理想,如果在其他地方调用某一个设备驱动时,不能直接、很快的找到该设备驱动,必要遍历IO控制器再匹配相应的设备驱动,并且操作麻烦以及效率不高。

       在对框架平台进行重构的时候,把该问题进行了重新考虑,并把相关联的问题一起解决。把每个IO控制器中的设备驱动管理器进行了整合,用一个设备驱动管理器来完成框架平台的协调工作。

       这块涉及到的技术并不难,也很容易理解,但是在设计过程中需要注意一些细节问题,这些问题可能影响框架平台的稳定性。

    4.1    接口定义

        先定义一个接口(IDeviceManager<TKey, TValue>),确定设备驱动管理器都要完成什么功能,增加设备、删除设备、获得设备和列表、以及其他的功能。接口代码如下:

    public interface IDeviceManager<TKey, TValue> : IEnumerable<TValue> where TValue : IRunDevice
    {
           /// <summary>
           /// 新建设备的ID,且唯一
           /// </summary>
           /// <returns></returns>
           string BuildDeviceID();
     
           /// <summary>
           /// 增加设备
           /// </summary>
           /// <param name="key"></param>
           /// <param name="val"></param>
           void AddDevice(TKey key, TValue val);
    
           /// <summary>
           /// 删除设备
           /// </summary>
           /// <param name="key"></param>
           void RemoveDevice(TKey key);
    
           /// <summary>
           /// 移除所有设备
           /// </summary>
           void RemoveAllDevice();
    
           /// <summary>
           /// 获得值集合
           /// </summary>
           /// <returns></returns>
           List<TValue> GetValues();
    
           /// <summary>
           /// 获得关键字集合
           /// </summary>
           /// <returns></returns>
           List<TKey> GetKeys();
    
           /// <summary>
           /// 获得设备的ID和名称
           /// </summary>
           /// <returns></returns>
           Dictionary<int, string> GetDeviceIDAndName();
     
           /// <summary>
           /// 获得高优先运行设备
           /// </summary>
           /// <param name="vals"></param>
           /// <returns></returns>
           TValue GetPriorityDevice(TValue[] vals);
    
           /// <summary>
           /// 获得单个设备
           /// </summary>
           /// <param name="key"></param>
           /// <returns></returns>
           TValue GetDevice(TKey key);
    
           /// <summary>
           /// 获得设备数组
           /// </summary>
           /// <param name="para">IP或串口号</param>
           /// <param name="ioType">通讯类型</param>
           /// <returns></returns>
           TValue[] GetDevices(string para, CommunicationType ioType);
    
           /// <summary>
           /// 获得指定IP和工作模式的网络设备
           /// </summary>
           /// <param name="remoteIP"></param>
           /// <param name="workMode"></param>
           /// <returns></returns>
           TValue[] GetDevices(string remoteIP, WorkMode workMode);
    
           /// <summary>
           /// 获得指定工作模式的网络设备
           /// </summary>
           /// <param name="workMode"></param>
           /// <returns></returns>
           TValue[] GetDevices(WorkMode workMode);
    
           /// <summary>
           /// 获得设备数组
           /// </summary>
           /// <param name="ioType"></param>
           /// <returns></returns>
           TValue[] GetDevices(CommunicationType ioType);
    
           /// <summary>
           /// 按设备类型获得设备
           /// </summary>
           /// <param name="devType"></param>
           /// <returns></returns>
           TValue[] GetDevices(Device.DeviceType devType);
    
           /// <summary>
           /// 判断设备是否存在
           /// </summary>
           /// <param name="key"></param>
           /// <returns></returns>
           bool ContainDevice(TKey key);
    
           /// <summary>
           /// 根据输入参数,判断是否包括设备
           /// </summary>
           /// <param name="para">IP或串口号</param>
           /// <param name="ioType">设备通讯类型</param>
           /// <returns></returns>
           bool ContainDevice(string para, CommunicationType ioType);
    
           /// <summary>
           /// 设置用户级别
           /// </summary>
           /// <param name="userlevel"></param>
           void SetUserLevel(UserLevel userlevel);
    
           /// <summary>
          /// 设置是否注册
           /// </summary>
           /// <param name="isreg"></param>
           void SetIsRegLicense(bool isreg);
    
           /// <summary>
           /// 获得可用设备数
           /// </summary>
           int Count { get; }
    
           /// <summary>
           /// 获得设备的计数器的值
           /// </summary>
           /// <param name="key"></param>
           ///<returns></returns>
           int GetCounter(TKey key);
    
           /// <summary>
           /// 设置计数器的值
           /// </summary>
           /// <param name="key"></param>
           /// <param name="val"></param>
           void SetCounter(TKey key, int val);
    }
    

     4.2    设备容器

       设备驱动管理器是对Dictionary<Key,Value>的封装,Key是设备驱动的ID,Value是IRunDevice设备驱动接口。设备驱动管理器需要跨线程应用,所以对Dictionary操作要加线程同步锁。

       当时使用的是.NET Framework 2.0框架,没有ConcurrentDictionary(Of TKey, TValue)字典类,这个类的所有公共和受保护的成员都是线程安全的,使用原子性操作,适合多个线程之间同时使用。再重构时可以使用ConcurrentDictionary类代替Dictionary类,因为ConcurrentDictionary的所有操作使用到了Monitor线程同步类,不需要自己再进行封装。

       不贴ConcurrentDictionary类的源代码了,具体使用参考MSDN。

    4.3    生成设备ID

        查寻设备驱动管理器中最大的设备ID,并在此基础上加1。这块代码很简单,

    如下:

    public string BuildDeviceID()
    {
           if(_dic.Count>0)
           {
              int maxID=_dic.Max(d => d.Value.DeviceParameter.DeviceID);
              return (++maxID);
           }
           else
           {
                  return 0;
           }
    }
    

        增加设备驱动是需要生成设备ID,一般采用手动增加设备驱动,所以在这块不需要加线程同步锁。

    4.4    对设备容器操作的互斥

    框架平台所有组件要共享设备驱动管理器,所以会涉及到跨线程应用,特别

    是当集合发生改变的时候,可能会出现异常。例如:启动框架平台的时候,IO控制器已经启动,IO控制器从设备驱动管理器提取自己的设备列表,但是这时有可能还没有加载完设备驱动,当有新的设备驱动增加到设备驱动管理时,可能会引发冲突。

        所以,在增加设备、删除设备和获得设备列表的时候增加了线程同步锁,例如:lock (_SyncLock)。

    4.5    获得设备列表

    有多个获得设备的构造函数(GetDevices),主要是满足不同的应用场景。

    请参考“4.1接口定义”。

        另外,获得高优先运行设备的GetPriorityDevice函数在上一章节已经介绍了。

    4.6    设备计数器的特殊用处

        在接口定义中有SetCounter和GetCounter两个函数,用在通讯过程中。

        应用场景是这样的,在并发和自控通讯模式中,设备驱动一直处于在通讯正常的情况下,但是突然发生线路中断或其他原因导致无法接收到数据时,那么设备驱动一直无法接收到数据,也无法对通讯状态进行检测以及改变相应的数据信息,也就是说现实情况已经发生改变,但是设备驱动却无法得到响应。

        为了防止这种情况的出现,设备驱动每次发送数据时,通过GetCounter函数获得当前设备驱动的计数器,对计数器(变量)+1操作,并通过SetCounter函数把计数器(变量)再写到设备驱动管理器中。在异常接收数据的时候,执行相同的流程,但是执行-1操作。如果一直发送数据,而没有接收到数据时,当前设备驱动的计数器就会一直在累加。如果大于等于某个值的时候,就会通过RunIODevice(new byte[]{})驱动当前设备,执行整个设备处理流程,二次开发的代码块就会被调用,来完成此类应用场景的状态改变和数据变化。代码如下:

    int counter = DeviceManager.GetInstance().GetCounter(dev.DeviceParameter.DeviceID.ToString());
    int sendNum = SessionSocketManager.GetInstance().Send(dev.DeviceParameter.NET.RemoteIP, data);
    if (sendNum == data.Length && sendNum != 0)
    {
           DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, "发送请求数据");
           Interlocked.Increment(ref counter);
    }
    else
    {
           Interlocked.Increment(ref counter);
           DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, "尝试发送数据失败");
    }
    dev.ShowMonitorIOData(data, "发送");
    if (counter >= 3)
    {
           try
           {
                  dev.RunIODevice(new byte[] { });
           }
           catch (Exception ex)
           {
                  DeviceMonitorLog.WriteLog(dev.DeviceParameter.DeviceName, ex.Message);
                  GeneralLog.WriteLog(ex);
           }
           Interlocked.Exchange(ref counter, 0);
    }
    DeviceManager.GetInstance().SetCounter(dev.DeviceParameter.DeviceID.ToString(), counter);
    

       对于发送和接收数据会在不同的线程上完成,在对计数器(变量)进行+1和-1操作的时候使用到了Interlocked类,用于多个线程共享的变量提供原子操作,防止在多处理器上并行操作时可能引发的异常或数据遭到破坏。

    4.7    小结

       这样改造后,不仅可以在IO控制器对设备进行引用,也可以在其他组件使用。如果遇到类似的情况,希望使用ConcurrentDictionary类。

    作者:唯笑志在

    Email:504547114@qq.com

    QQ:504547114

    .NET开发技术联盟:54256083

    文档下载:http://pan.baidu.com/s/1pJ7lZWf

    官方网址:http://www.bmpj.net

  • 相关阅读:
    Java面向对象(02)--封装
    Java面向对象(01)--初识
    Java基础(10)--数组
    Java基础(09)--方法
    python中format输出常用的3种格式
    python 查找列表中重复元素以及重复元素的次数
    HttpRunner六:创建run.py文件,执行套件并生成测试报告
    HttpRunner五:关联参数的应用,获取上一个接口的返回值,用于当前接口的请求值
    HttpRunner四:testcases、testsuites以及参数化的使用
    HttpRunner中在case2中,使用作为请求参数和预期结果,预期结果中值显示是:LazyString($变量key)
  • 原文地址:https://www.cnblogs.com/lsjwq/p/4995233.html
Copyright © 2020-2023  润新知