第四章           设备驱动管理器的设计… 2,第六章           通信控制器的设计… 2

目       录

目       录

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

第六章           通信控制器的设计… 2

4.1           接口定义… 2

6.1           控制器接口… 2

4.2           设备容器… 7

6.2           串口控制器… 3

4.3           生成设备ID.. 7

6.3           互联网决定器… 5

4.4           对配备容器操作的互斥… 8

6.4           通信控制管理器… 9

4.5           得到装备列表… 8

6.5           远程交互… 9

4.6           设备计数器的异样用处… 8

6.6           小结… 10

4.7           小结… 10

 

 

第六章     通信控制器的设计

    
经过前几章的介绍,那章介绍通信控制器。主要担负对配备驱动(IRunDevice)、IO通道(IIOChannel)举办和谐、调度、以及事件做出响应,在此基础上达成轮询通信形式、并发通信形式和约束通信形式的任务调度。由于串口和互联网硬件链路特性的来头以及通信机制不等同,所以通信控制器的兑现上也有很大差异。

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

   
设备驱动管理器是对IRunDevice设备驱动接口的管制,是框架主要的组成部分之一。不管设备驱动管理器怎么规划、以怎么样花样存在,在概念上必将是存在的。设计好的装备驱动管理器对于框架平台的安澜运转重点。

  
在介绍装备驱动管理器从前,先简单介绍一下IO控制器(IIOController),它最首要担负对IO和配备举行调度,并驱动装置运行,在《第5章
串口和网络的IO设计》举办详尽的牵线。也就是说一个IO控制器可能会对应八个设施驱动(插件)。

  
早期的时候,每个IO控制器都有一个设施驱动管理器。在框架平台启动的时候,根据设备驱动的广播发表参数把相应的装置驱动分配到相应的IO管理器;当IO参数暴发变化的时候,会触发事件,把该装备驱动从脚下IO控制器移动到另一个IO控制器。

  
从作业角度来设想,这样做并没有怎么难点,并且一贯运行的很平静。可是,从模块化、增加性角度来设想,不是太美好,若是在任什么地点方调用某一个设备驱动时,不可以直接、很快的找到该设施驱动,必要遍历IO控制器再同盟相应的配备驱动,并且操作麻烦以及功用不高。

  
在对框架平台拓展重构的时候,把该难点展开了重新考虑,并把相关联的难题一起化解。把每个IO控制器中的设备驱动管理器举行了咬合,用一个装备驱动管理器来形成框架平台的调和工作。

  
那块涉及到的技巧并不难,也很不难通晓,可是在筹划进程中要求注意一些细节难题,这个题材可能影响框架平台的稳定。

6.1    控制器接口

    
控制器内置一个线程负责对配备驱动和IO实例进行职务协调、调度,相当于在《第4章
设备管理器的筹划》、《第5章
串口和网络的IO设计》完毕的根底上创设了一个更高层次的和谐机制,并完成设备与IO的合作、区其他通信机制。

    
不管串口通信控制器和网络通信控制器如何完毕,都会持续自统一的(IIOController)接口,接口定义的代码如下:

public interface IIOController
{
       /// <summary>
       /// 当然是否工作
       /// </summary>
       bool IsWorked { set; get; }

       /// <summary>
       /// IO控制器的关键字。
       /// </summary>
       string Key { get; }

       /// <summary>
       /// 启动服务
       /// </summary>
       void StartService();

       /// <summary>
       /// 停止服务
       /// </summary>
       void StopService();

       /// <summary>
       /// IO控制器类型
       /// </summary>
       CommunicationType ControllerType { get; }
}

     控制层次结构图如下:

图片 1 

4.1    接口定义

    先定义一个接口(IDeviceManager<TKey,
电视alue>),确定设备驱动管理器都要落成什么功效,扩张设备、删除设备、获得装备和列表、以及其它的功效。接口代码如下:

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,
电视alue)字典类,那么些类的持有国有和受有限帮忙的积极分子都是线程安全的,使用原子性操作,适合七个线程之间同时利用。再重构时可以动用ConcurrentDictionary类代替Dictionary类,因为ConcurrentDictionary的装有操作使用到了Monitor线程同步类,不需求协调再举办包装。

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

6.2    串口控制器

   
每个(硬件)串口都对应一个串口控制器,每个串口控制器里都会有一个单独的线程,也就是说用到有些个串口号就会有微微个控制器和线程。框架平台可能会挂载七个装备驱动(插件),有可能一个装置驱动对应一个串口,也可能多少个设备驱动共用一个串口,那么也就是说串口控制器和设施驱动之间存在1对1或1对N的关联。结构示意图如下:

 图片 2

   
一个串口控制器内的拥有设备安装的串口通信参数都是同样的,所以设备驱动(IRunDevice)接口的COM中的Port属性、IO接口(IIOChannel)的Key属性、以及串口控制器(IIOController)接口Key属性是一致的,都用于标识串口号。既然一个串口控制器中的所有设施都共用一个硬件串口,就决定了装有装备驱动之间的职责调度只可以采用轮询格局,一个设施发送和收取操作完之后,再调度下一个装置驱动,设备驱动之间就是串行工作格局,防止一个串口控制器内多个装备驱动同时操作串口IO导致数据错乱,影响正常通信。

   
一个串口控制器内的配备驱动是串行工作格局,如若把富有设施驱动都设置成一个串口号,在一个串口控制器下串行调度,那么就会影响设备驱动的简报效能,某个设备的调度周期的公式如下:

某个设备调度周期=(串口控制器所有设备数-1)* 单个设备驱动执行耗时

     
那仅是一个驳斥值,实际运用中要比这些理论值要大,因为涉嫌到不类型的设施驱动共用一个串口号,在一个串口控制器下办事,处理的数量流程、形式各异,例如:有可能数量保存在TXT文件中、有可能保留在SQL数据库中、有可能保存在NoSQL数据库中等等。

    
有人会想,岂不是在一个串口下挂载的设施越来越多功效越低,的确是如此的。可是,多少个串口控制器之间是互相工作方式。如若当场条件对报纸发布效用有须要的话,可以增添串口服务器,也就是充实可用的串口硬件电路,把N个设备驱动平衡负载到不一致的串口上,扩张互动运行的串口控制器的节点,进而压实框架平台的运作功用。

   
不过,那样解决未来也牵动一定的高危机和瓶颈,就是对此数据的仓储,借使多个相互的数据流同时向一个单线程的存储介质写多少,那么又会导致互斥的场景,甚至造成意外的结果或尤其,如下图:

图片 3 

     如若同时向Sql
Server、Oracle、Mysql等数据库存储数据,那么是绝非难点的;如若使用文本文件、桌面数据库等储存数据,那么可能存在难题,可以分八个公文进行保存操作。DCS系统大多使用PI(Plant
Information
System)数据库。不问可知,作为一个系统来讲,须求完整设计、考虑,那块必要尤其注意。

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,一般选取手动扩展设备驱动,所以在那块不必要加线程同步锁。

6.3    互联网控制器

    
框架平台唯有一个互连网控制器,网络控制器内有一个独立的线程负责对拥有互连网设施驱动进行轮询、并发、自控情势通信调度。轮询通信方式与串口控制器类似,只是串行的调度所有网络设施驱动,不过框架惟有一个网络控制器,不可以透过增添网络控制器来增强通信作用,那种方式是互联网通讯调度鸡肋;并发通信格局,线程会通过控制器中的线程集中发送所有设施的请求命令数据,接收数据是经过IO异步监听来落成,异步接收到数码后再把数量分发到装备驱动的RunIODevice接口,举办数据处理;自控通信方式,发送命令数据的功效移交给了配备驱动本身,可以透过定时器来完结发送命令数据的功力,线程不再负责发送命令数据,接收数据与产出通信形式一样。互联网控制器的里边示意图如下:

 图片 4

    
针对互联网通信,轮询通信形式是无法揭橥其优势的,所以增添了产出通讯方式和约束通信格局。后三种简报情势会用到《第4章
设备驱动管理器的筹划》的“4.6  
设备计数器的与众差距用途”的规划,设备驱动计数器如果过量等于某个值的时候,就会经过RunIODevice(new
byte[]{})驱动当前配备,执行总体设施处理流程,以转移设备驱动的运转意况,实际上当前装备驱动处于“通信中断”状态。

    发送数据代码如下:

public void ControllerSend(IRunDevice dev, byte[] data)
{
       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);
}

     异步接收、分多少的代码如下:

private void NETDeviceController_ReceiveSocketData(object source, ReceiveSocketDataArgs e)
{
       if (GlobalProperty.GetInstance().ControlMode == ControlMode.Parallel || GlobalProperty.GetInstance().ControlMode == ControlMode.Self)
       {
              int counter = 0;
              IRunDevice dev = null;
              IRunDevice[] list = DeviceManager.GetInstance().GetDevices(e.RemoteIP, CommunicationType.NET);
              for (int i = 0; i < list.Length; i++)
              {
                     dev = list[i];
                     if (String.CompareOrdinal(dev.DeviceParameter.NET.RemoteIP, e.RemoteIP) == 0)
                     {
                            dev.ShowMonitorIOData(e.ReceiveData, "接收");
                            dev.AsyncRunIODevice(e.ReceiveData);
                            counter = DeviceManager.GetInstance().GetCounter(dev.DeviceParameter.DeviceID.ToString());
                            Interlocked.Decrement(ref counter);
                            if (counter < 0)
                            {
                                   Interlocked.Exchange(ref counter, 0);
                            }                  DeviceManager.GetInstance().SetCounter(dev.DeviceParameter.DeviceID.ToString(), counter);
                     }
              }
       }
}

4.4    对配备容器操作的排外

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

是当集合暴发变动的时候,可能会出现很是。例如:启动框架平台的时候,IO控制器已经起步,IO控制器从设备驱动管理器提取自己的装备列表,不过此时有可能还尚无加载完设备驱动,当有新的装置驱动增添到设备驱动管理时,可能会抓住冲突。

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

6.4    通信控制管理器

   
通信控制管理器负责对串口控制器和互联网控制器进行管制,实际上是对Dictionary<Key,Value>进行的包裹,所有涉及到操作控制器的地点都是通过控制管理器来形成的。IIOControllerManager<TKey,
电视alue>通信控制管理器的接口定义如下:

图片 5 

4.5    获得装备列表

有八个得到装备的构造函数(GetDevices),紧如果满意不一致的行使场景。

请参见“4.1接口定义”。

   
此外,得到高优先运行设备的GetPriorityDevice函数在上一章节一度介绍了。

6.5    远程交互

    
在摸底串口控制器和互联网控制器的基本原理和法力后,还要考虑到一个运用场景:控制器不仅仅要与硬件进行数量交互,还有可能要把采访上来的多少转载到任何服务器或节点上,也就是框架平台要负有路由的功效,整合设备驱动采集上来的数量,举行打包、转载。

   
从那么些利用场景来看,在开发设备驱动的时候,不符合在设备驱动的拍卖流程中开展转载、多事情处理,受条件、互连网、业务复杂度的影响或者会阻塞控制器的调度,影响框架的完全运行功效。

   
在物联网建设中,多级互联、逐层转载是很常见技术要求。为驾驭决那个实际题材,框架平台提供了IAppService应用服务接口,二次开发者可以把设备驱动中的数据新闻打包后传出到IAppService接口中,可以在那边落成缓存、转载等切实的事情服务。那样设计的关键目标是不影响框架平台实时的数量搜集,有限支撑数据源的稳定性。

    IApp瑟维斯具体的安插性和选择将来《第7章
外部接口的布置》中展开详细介绍。

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类,用于八个线程共享的变量提供原子操作,幸免在多处理器上并行操作时或者引发的非常或数量遭到破坏。

6.6    小结

    
通讯控制器完结那后,理论上框架平台就可见跑起来了,但是距离大家开头布置的目的还差很多干活要做,还不可能为二次开发提供很大的便宜。在继续的安排中,逐渐的会把框架平台丰硕起来。

 

作者:唯笑志在

Email:504547114@qq.com

QQ:504547114

.NET开发技术联盟:54256083

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

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

4.7    小结

  
那样改造后,不仅可以在IO控制器对装备举行引用,也得以在其他零件使用。就算碰着类似的情景,希望拔取ConcurrentDictionary类。

 

作者:唯笑志在

Email:504547114@qq.com

QQ:504547114

.NET开发技术联盟:54256083

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

合法网址:http://www.bmpj.net

相关文章