前言
一畢業第一個項目就做了OPC數據轉發,面向API編程,調用固定接口,定時器輪詢從OPC DA2.0 Server中獲取數據寫到數據庫,定時器5分鍾寫一次,數據量比較大,有幾千個點,當時是數據庫有一個主表,通過主表的觸發器,會為每個點生成一個子表,數據都往子表里寫。可以說非常low了。有時候數據庫和server需要走Network,DA是需要配置COM的,非常的麻煩。就開始研究OPCUA,也是面向API編程,調包程序員。再后來項目不需要就不研究了。在Github上找過其源碼但是沒有看過覺得真是一大遺憾,有一種只問其聲,未見其人的感覺,非常的不爽。最近要寫一個組件用到OPCUA,所以仔細看了它的源碼。OPC的網上資料很少,有一本書寫的太過理論,學組件還是直接看源碼,源碼看懂了再看概念理論就會非常清晰了。
底層通訊
ApplicationInstance:該類是服務的包裝類,提供了加載參數,啟動服務的方法。相當於服務入口。
Server:最重要的服務類:
Server的接口非常多,但是擴展卻非常容易,自己擴展只需要繼承StandardServer就可以了。它是怎么實現各種訂閱,瀏覽,事件,方法等功能的呢,這里面最重要的是用了一個Manager的思想。關注ServerInternal屬性,其是ServerInternalData類,這個類非常像外觀模式,包含了很多Manager。每個Manager是相當於一個組件管理器。比如NodeManager,所有的節點管理都在這個Manager中,還有SubscriptionManager,SessionManager,EventManager等。服務除了Manager,還有Certificate,OPCUA的安全這一塊做的真好,TransportListeners管理所有的Host。
Server中TransportListeners,底層通訊。
OPCUA默認實現了三種Host:UaHttpsChannelListener,UaTcpChannelListener,NullListener,這邊用到了空對象模式,從名字就可以看出來Listener的職責就說監聽連接,TCP監聽實現是通過完成端口模型。
在OnAccept中:一旦有鏈接,就創建Channel來處理數據請求
channel內部對Socket完成端口模型進行了封裝,TcpMessageSocket
在TcpMessageSocket內部:
接受數據完成端口模型,最終調用了Channel的OnMessageReceived方法
在Channel中處理消息,在ProcessRequestMessage方法內部調用了m_RequestReceived回調函數,其實是調用了Listener中的OnRequestReceived方法,該方法實際上是將請求給了專門ReceiveDataHandle類:SessionEndpoint類來處理請求,這個類其實對請求數據的一種管理,所有的請求都給了SessionEndpoint。BeginProcessRequest方法,這個SessionEndpoint管理器會創建一些具體解析請求的類,來處理請求ProcessRequestAsyncResult,
在具體處理數據內部肯定是調用管理器類SessionEndpoint來具體請求數據,這邊將找到的Service保存到m_Service中,只不過這邊又做了一個請求處理隊列,ServerForContext就是Server,server最后還是調用這個類的CallSynchronously方法,然后調用m_service的
ProcessRequestAsyncResult調用m_service
這個m_service就是一開始在SessionEndpoint管理的服務,這些服務最后都指向Server中的一個方法。
比如Browse服務就是Server中的Browse方法。
通訊框架總結
- Server管理所有的Listener集合
- Listener負責監聽
- Channel負責處理鏈接,解析消息結構
- TcpMessageSocket負責封裝Socket來收發消息
- SessionEndpoint封裝所有對消息體處理的服務,這些服務其實都是Server中方法委托。
- TcpMessageSocket接受到消息最終調用SessionEndpoint中的BeginProcessRequest方法,然后生成了消息處理類ProcessRequestAsyncResult,該類將請求給了server中的RequestQueue,Server處理隊列最終是調用ProcessRequestAsyncResult中的方法找到SessionEndpoint中對應的處理服務,調用server中對應的方法。
- Server負責創建SessionEndpoint實例,Listen負責調用SessionEndpoint實例方法解析數據,channel負責解析數據類型調用Listen方法。
協議格式
在Channel的OnMessageReceived方法中,協議頭中前4個字節是消息的類型:也就是消息ID,詳情可以看TcpMessageType類
channel中的ReadSymmetricMessage方法,可以消息頭:一共24個字節
MessageType;MessageSize;channelID;tokenId;sequenceNumber;requestId都是4個字節
channel解密TokenID
通過Token解密消息,還可以看出消息的最后字節是Signature。
還是在Channel的ReadSymmetricMessage 方法中
消息的最后還有一些沒有用的填充消息,在簽名的前一個字節為填充長度
協議內容:
MessageType;MessageSize;channelID;tokenId;sequenceNumber;requestId;MessageBogy;Padding;Signature.
總結
整個協議的格式都是在Channel的ReadSymmetricMessage方法中解析的,主要解析了協議頭,簽名和獲取MessageBody並進行解密。
消息體
在channel的ProcessRequestMessage方法中看到對消息體的解析,將MessageBody解析成ServiceRequest
整個解析職責是交給了BinaryDecoder類,Decoder實際是調用IEncodeable來進行解碼,這邊有點像迭代器模式,集合實現IEnumerable,而調用者使用IEnumator進行遍歷。
MessageBody:NodeId+NodeValue+IEncodeable。,通過NodeId獲取實際是IEncodeableType,調用其Decode方法,獲取IEncodeable,然后顯示轉化為IServiceRequest接口
通過ID來獲取解碼類,進行解碼:
解碼類從哪里來呢?通過工廠,工廠通過反射獲取程序集中所有IEncodeable
至此解析出具體請求類進行回調,也就是將請求類交給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這個人(有點像總經理秘書),這個人將負責管理所有的請求文件種類。它收到請求文件其實就把請求給了總經理,總經理根據不同的請求將請求轉發給其他部門的人干。