程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> C#入門知識 >> [連載]《C#通訊(串口和網絡)框架的設計與實現》-4.設備驅動管理器的設計,

[連載]《C#通訊(串口和網絡)框架的設計與實現》-4.設備驅動管理器的設計,

編輯:C#入門知識

[連載]《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:[email protected]

QQ:504547114

.NET開發技術聯盟:54256083

文檔下載:http://pan.baidu.com/s/1pJ7lZWf

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

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved