OPCUA底層通訊源碼解析


前言

一畢業第一個項目就做了OPC數據轉發,面向API編程,調用固定接口,定時器輪詢從OPC DA2.0 Server中獲取數據寫到數據庫,定時器5分鍾寫一次,數據量比較大,有幾千個點,當時是數據庫有一個主表,通過主表的觸發器,會為每個點生成一個子表,數據都往子表里寫。可以說非常low了。有時候數據庫和server需要走Network,DA是需要配置COM的,非常的麻煩。就開始研究OPCUA,也是面向API編程,調包程序員。再后來項目不需要就不研究了。在Github上找過其源碼但是沒有看過覺得真是一大遺憾,有一種只問其聲,未見其人的感覺,非常的不爽。最近要寫一個組件用到OPCUA,所以仔細看了它的源碼。OPC的網上資料很少,有一本書寫的太過理論,學組件還是直接看源碼,源碼看懂了再看概念理論就會非常清晰了。

底層通訊

ApplicationInstance:該類是服務的包裝類,提供了加載參數,啟動服務的方法。相當於服務入口。

Server:最重要的服務類:

1603974747879

Server的接口非常多,但是擴展卻非常容易,自己擴展只需要繼承StandardServer就可以了。它是怎么實現各種訂閱,瀏覽,事件,方法等功能的呢,這里面最重要的是用了一個Manager的思想。關注ServerInternal屬性,其是ServerInternalData類,這個類非常像外觀模式,包含了很多Manager。每個Manager是相當於一個組件管理器。比如NodeManager,所有的節點管理都在這個Manager中,還有SubscriptionManager,SessionManager,EventManager等。服務除了Manager,還有Certificate,OPCUA的安全這一塊做的真好,TransportListeners管理所有的Host。

1603975341434

Server中TransportListeners,底層通訊。

OPCUA默認實現了三種Host:UaHttpsChannelListener,UaTcpChannelListener,NullListener,這邊用到了空對象模式,從名字就可以看出來Listener的職責就說監聽連接,TCP監聽實現是通過完成端口模型。

1603976653100

1604057776912

在OnAccept中:一旦有鏈接,就創建Channel來處理數據請求

1604057874649

1604057634344

channel內部對Socket完成端口模型進行了封裝,TcpMessageSocket

1604058221546

在TcpMessageSocket內部:

1604058282130

接受數據完成端口模型,最終調用了Channel的OnMessageReceived方法

1604058308648

1604058510245

在Channel中處理消息,在ProcessRequestMessage方法內部調用了m_RequestReceived回調函數,其實是調用了Listener中的OnRequestReceived方法,該方法實際上是將請求給了專門ReceiveDataHandle類:SessionEndpoint類來處理請求,這個類其實對請求數據的一種管理,所有的請求都給了SessionEndpoint。BeginProcessRequest方法,這個SessionEndpoint管理器會創建一些具體解析請求的類,來處理請求ProcessRequestAsyncResult,

1604058741455

1604059150058

1604059169202

1604059187168

1604059880031

在具體處理數據內部肯定是調用管理器類SessionEndpoint來具體請求數據,這邊將找到的Service保存到m_Service中,只不過這邊又做了一個請求處理隊列,ServerForContext就是Server,server最后還是調用這個類的CallSynchronously方法,然后調用m_service的

1604060145717

1604060447298

1604060501554

ProcessRequestAsyncResult調用m_service

1604061001059

這個m_service就是一開始在SessionEndpoint管理的服務,這些服務最后都指向Server中的一個方法。

比如Browse服務就是Server中的Browse方法。

1604061123338

通訊框架總結

  1. Server管理所有的Listener集合
  2. Listener負責監聽
  3. Channel負責處理鏈接,解析消息結構
  4. TcpMessageSocket負責封裝Socket來收發消息
  5. SessionEndpoint封裝所有對消息體處理的服務,這些服務其實都是Server中方法委托。
  6. TcpMessageSocket接受到消息最終調用SessionEndpoint中的BeginProcessRequest方法,然后生成了消息處理類ProcessRequestAsyncResult,該類將請求給了server中的RequestQueue,Server處理隊列最終是調用ProcessRequestAsyncResult中的方法找到SessionEndpoint中對應的處理服務,調用server中對應的方法。
  7. Server負責創建SessionEndpoint實例,Listen負責調用SessionEndpoint實例方法解析數據,channel負責解析數據類型調用Listen方法。

協議格式

在Channel的OnMessageReceived方法中,協議頭中前4個字節是消息的類型:也就是消息ID,詳情可以看TcpMessageType類

1604109961342

1604110030696

channel中的ReadSymmetricMessage方法,可以消息頭:一共24個字節

MessageType;MessageSize;channelID;tokenId;sequenceNumber;requestId都是4個字節

1604110800288channel解密TokenID

1604111015281

通過Token解密消息,還可以看出消息的最后字節是Signature。

還是在Channel的ReadSymmetricMessage 方法中

1604111191527

消息的最后還有一些沒有用的填充消息,在簽名的前一個字節為填充長度

1604112388066

協議內容:

MessageType;MessageSize;channelID;tokenId;sequenceNumber;requestId;MessageBogy;Padding;Signature.

1604111829721

總結

整個協議的格式都是在Channel的ReadSymmetricMessage方法中解析的,主要解析了協議頭,簽名和獲取MessageBody並進行解密。

消息體

在channel的ProcessRequestMessage方法中看到對消息體的解析,將MessageBody解析成ServiceRequest

1604113227288

整個解析職責是交給了BinaryDecoder類,Decoder實際是調用IEncodeable來進行解碼,這邊有點像迭代器模式,集合實現IEnumerable,而調用者使用IEnumator進行遍歷。

1604114314385

1604115066158

MessageBody:NodeId+NodeValue+IEncodeable。,通過NodeId獲取實際是IEncodeableType,調用其Decode方法,獲取IEncodeable,然后顯示轉化為IServiceRequest接口

1604113359651

1604113459405

通過ID來獲取解碼類,進行解碼:

解碼類從哪里來呢?通過工廠,工廠通過反射獲取程序集中所有IEncodeable

1604113802227

1604113822171

1604113846559

1604114209704

1604116408322

1604116611351

1604116673012

至此解析出具體請求類進行回調,也就是將請求類交給SessionEndpoint進行處理。

總結:Channel調用Decoder,這邊Decoder負責解析MessageBody,具體的解析操作職責給IEncodeable的子類。解析完后將IEncodeable顯示轉化為IServiceRequest交給SessionEndpoint處理。

感悟

軟件中的架構大體和現實世界差不多,如果一個需求有多個不同的操作,就將其抽象為子類,一個管理器Manager或者XXXer負責管理所有的子類集合,子類可以由專門的工廠類創建也可以是由管理器創建。

現實生活中一個公司就相當於一個軟件,有一個總經理(Master/Server/Instance)總經理負責協調各個部門,每個部門的部長就相當於(Manager/Presenter/Controller...),它負責安排這個部門的職責,也就是管理部門里面的員工集合,員工就是具體干活的類,這些類可以由基類派生或實現統一的接口。派生類如何生成可以由部長負責,也可以由部長請求公司的HR相當於工廠生成派生類。工廠可以是抽象工廠也可以工廠模式,為每個部門生成一個工廠子類。

以OPCUA的通訊為例:

Server就相當於總經理,通訊組件(銷售)就相當於一個通訊部門這邊部長和總經理和二為一,可能這個部門非常重要,總經理想親自管理,Listeners就相當於具體干活的員工,每個Listener(銷售)負責一個通訊線的對接,每當有通訊連接就派Channel(具體負責哪個廠)去負責對接內容,Channel說對接內容還需要一個人去專門的地方去就派了MessageSocket通過完成端口模型去取通訊消息,MessageSocket每次取到消息就把消息給Channel解析,Channel先解析這是上面類型的消息,將其具體解析為一個請求文件,這個請求文件都匯總給了由Listener指派的SessionEndpoint這個人(有點像總經理秘書),這個人將負責管理所有的請求文件種類。它收到請求文件其實就把請求給了總經理,總經理根據不同的請求將請求轉發給其他部門的人干。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM