999精品在线视频,手机成人午夜在线视频,久久不卡国产精品无码,中日无码在线观看,成人av手机在线观看,日韩精品亚洲一区中文字幕,亚洲av无码人妻,四虎国产在线观看 ?

DDS分布式系統快速開發

2022-12-30 07:51:36唐江文
計算機工程與設計 2022年12期
關鍵詞:設計

唐江文

(中國電子科技集團公司 電子科學研究院,北京 100041)

0 引 言

數據分發服務(data-distribution service,DDS)是一種發布訂閱式的通信中間件,它由OMG組織于2004年發布為國際開放標準,并不斷擴充發展至今。DDS引入了虛擬全局數據空間的概念,在全局數據空間中,應用可以通過讀寫帶有主題的數據對象的方式進行通信。同時,DDS提供了QoS參數配置功能,可以實現對可靠性、帶寬、通信截止時間以及資源上限的靈活控制。DDS可以實現分布式通信實體間的解耦,發布者和訂閱者可以動態加入和退出,使系統具有高擴展性;另外,DDS采用組播自動發現的機制,DDS應用可以靈活部署在網絡中的任一計算機設備上。

目前,DDS廣泛應用于各種需要實時數據交換的場景和領域。文獻[1]在EXata仿真器構建的子系統間,使用DDS實現數據交互和時間同步,提高了大規模電力通信網的仿真效率;文獻[2]基于DDS實現了組織靈活、支持實時狀態監控與用戶管理的語音通信系統;文獻[3]基于DDS實現了一種分布式的熱處理集散控制監控系統,用于航天火箭發動機制造領域;文獻[4]中的ROS2,是基于DDS開發的多Agent機器人系統開發框架,廣泛應用于機器人研究和仿真;文獻[5]基于DDS實現了航天器軟件通信框架,可以實現靈活便捷的通信過程。

通過前面各領域的工程實踐,已經充分證明了DDS作為分布式系統的通信中間件,具有的高擴展性和高靈活性。但在開發基于DDS的大規模分布式系統時暴露了一項不足之處:缺乏分布式系統建模的能力。這是因為用于DDS開發的IDL語言,只描述接口的數據類型,而沒有描述主題、消息域,更沒有描述應用。這些信息的缺失造成DDS無法像常見的建模語言(例如UML和SysML)那樣方便地進行代碼自動生成[6,7],進而限制了DDS應用的開發效率,不得不使用文檔以文字的形式來描述系統模型,并且編寫大量重復冗余的代碼,易錯難查。

本文擴展了IDL語法,添加了topic和domain關鍵字,從而可以從IDL文件中同時解析出來接口數據類型信息和主題、消息域信息;另外又為應用設計了應用描述XML文件,用于獲取應用對主題的發布訂閱相關信息。實現了編譯器dseappgen,直接為應用生成面向主題發布訂閱的C++代碼文件,并自動編譯成動態庫,去除了重復冗余代碼,大大簡化了開發者的開發工作,并降低了出錯風險。另外,本文還在自動生成的代碼中植入了一些增強功能,比如植入了Lua語言解釋器[8,9],讓開發者可以進行快速仿真;又如Json字符串與DDS消息之間的相互轉換,增強了DDS原有的序列化能力;并且植入了輕量級的Http Server,支持用戶為應用進行B/S架構開發。

1 DDS通信原理簡介

DDS是通過自動發現協議實現與網絡IP的解耦[10]。只要應用位于同一個支持組播的二層網絡中,就可以自動匹配具有相同主題的發布者和訂閱者,所以應用可以部署運行在網絡中的任何一臺計算機設備上,這讓開發者無需關注網絡具體的組網結構。

DDS的發布者和訂閱者的自動匹配能力,源自于DDS的自動發現協議,協議涉及的主要概念和基本過程如圖1所示。DDS中的通信實體稱作“參與者(Participant)”,參與者記錄了當前計算機設備所有的IP地址和端口,以及共享內存的“端口”。當應用中實例化一個參與者時,它會執行參與者發現協議(PDP),用PDP寫者(PDPWriter)通過組播的方式將自己的IP地址以及EDP的發布(Pub)/訂閱(Sub)的讀者(Reader)/寫者(Writer)等信息發送給其它參與者,其它參與者會記錄下這位新參與者的信息,并把自己的相應信息發送回來,從而保證每個參與者都能感知到網絡中其它參與者的存在,并且知曉與其它參與者的通信方法。

圖1 DDS通信原理:自動發現協議支撐發布訂閱通信方式

當用戶使用DDS進行業務數據通信時,首先會定義某個主題的發布者(Publisher)/訂閱者(Subscriber),然后關聯到用戶寫者(UserWriter)/用戶讀者(UserReader)。此時,參與者會執行終端發現協議(EDP),將用戶寫者/用戶讀者的相關信息,比如主題、消息類型等,發送給網絡中需要這些信息的其它參與者,進行用戶讀/寫者的匹配,這樣發布者就知道了其發布的消息應該發送給哪些訂閱者,從而實現了發布訂閱的通信方式。

圖2簡單展示了DDS底層數據緩存及收發模型,DDS在發送消息時,從更改池(CacheChange Pool)中取出一個更改(CacheChange,“更改”可以簡單理解為一個消息),從負載池(Payload Pool)中取出一個負載(Payload),將需要發送的數據序列化后填入負載,然后掛載到更改中。隨后,更改會被添加到寫者歷史(Writer History,可以理解為一個消息隊列)里,發送線程會將寫者歷史中的更改通過指定的傳輸方式(UDP、TCP、共享內存)發送到相應接收方。發送結束后,該更改會從寫者歷史中移除,其負載會在釋放數據后重新放到負載池中,而該更改也會被回收到更改池中。接收消息的一方,其內存也會進行類似的鏈式循環操作。可見,DDS充分利用了內存池技術,最大程度地提高了內存效率。

圖2 DDS底層數據緩存及收發模型

2 DDS分布式系統開發流程的改進

傳統DDS分布式系統的開發流程如圖3所示,設計人員一側,首先撰寫系統軟件設計文檔,然后手動編寫IDL文件,由ddsgen生成各接口正反序列化代碼。開發人員一側,手動編寫主題、消息域、主題與接口相關、主題與發布訂閱相關以及大量業務代碼,最后和ddsgen自動生成的代碼完成編譯鏈接生成DDS應用。

圖3 傳統DDS分布式系統的開發流程

其中存在的問題是,設計文檔是一種供人閱讀的、格式相對自由的文本,無法為開發提供強制約束。設計人員根據設計文檔編寫IDL文件,容易出現謄抄錯誤,后期一旦文檔發生更改需要手動進行同步,費時費力。而且,主題、消息域、主題與接口的關聯、主題與發布訂閱的關聯是設計內容,卻侵入到了開發人員的代碼中,設計和開發出現交叉,導致設計和開發邊界不清,既加大了開發者工作量,也讓代碼容易出錯。事實上,設計一旦固定下來,業務之外的其它代碼基本是固定不變的,完全可以自動生成,避免不必要的出錯風險。

改進后的DDS應用開發流程如圖4所示,由格式化、結構化較強的類代碼文件(IDL文件、應用描述XML文件)替代系統軟件設計文檔作為原始設計輸入,后期系統軟件設計文檔可以由這些文件自動生成,這就解決了文檔和工程實現之間難以同步的問題,而且這些類代碼文件還可以通過一些版本管理工具(如Git)進行歷史版本管理。之前業務代碼之外需要手動編寫的代碼也可以通過語法解析和模板生成的方式,進行自動生成。這樣一來,設計和開發實現完全解耦,業務開發人員只需要關心業務內容,編寫業務代碼即可,大大減少了開發人員的工作量,同時也避免了出錯風險。最后將這些代碼自動編譯鏈接,生成DDS應用。

圖4 DDS分布式系統的快速開發流程

自動生成代碼的另一個優勢是,還可以根據解析出來的接口、主題、消息域等元信息,自動生成各種增強性功能代碼,這里主要添加了Lua解釋器、Json與接口消息的轉換、Http Server、消息記錄、消息統計等功能,進一步增強DDS應用的基礎能力。

3 改進的IDL編譯器dseappgen關鍵技術

前面介紹了DDS分布式系統開發流程的改進,而支撐改進的核心是dseappgen這個改進的IDL編譯器的實現。本部分主要介紹dseappgen的關鍵技術途徑,分為5個方面:①新的設計輸入;②語法解析;③代碼生成;④Lua解釋器;⑤其它增強功能。

3.1 新的設計輸入

前面提到,在新的開發流程中使用格式化、結構化較強的類代碼文件(IDL文件、應用描述XML文件)替代系統軟件設計文檔作為原始設計輸入。

在包含傳統的IDL文件內容,諸如模塊(module)、結構體(struct)、枚舉類型(enum)等等之外,dseappgen又添加了兩個新的語法關鍵字——主題(topic)和消息域(domain),具體語法可以見表1。這兩個關鍵字描述了系統中有哪些主題和消息域,以及主題與哪個結構體進行關聯。從而把主題和消息域的定義,從代碼轉移到了IDL文件。IDL文件還加入了@description的注解語法,可以為各種語法元素添加注解,這些注解經過dseappgen處理后,可以生成為代碼注釋,以及軟件設計文檔,方便閱讀。

表1 topic和domain關鍵字的基本語法

應用描述XML文件,該文件指明了引用哪個IDL文件,并且描述了該應用具體用到了哪些主題,這些主題在哪些消息域中進行發布或是訂閱。dseappgen在獲取這些信息后,一方面可以從IDL中裁減出該應用引用到的主題、消息域和接口,屏蔽掉未引用到的,從而加快代碼生成和編譯鏈接效率;另一方面,可以避免未引用主題和接口的干擾,防止應用開發者錯誤使用非設計的主題和接口。應用描述XML文件的基本格式見表2。其中,本文將主題的發布訂閱方式具體劃分為4種,即INPUT(只訂閱該主題),OUTPUT(只發布該主題),LOOPBACK(在同一消息域中既發布也訂閱該主題),CONVERT(在一個消息域中訂閱該主題,在另一消息域中發布該主題),方便設計者靈活運用。

表2 應用描述XML文件的基本格式

這兩類設計輸入文件,IDL文件是面向系統全局,其內容由系統內所有應用共享,而應用描述XML文件是面向具體應用,每個應用有一份自己的應用描述XML文件,這兩種文件實現了對DDS分布式系統的建模。

3.2 語法解析

語法解析是實現代碼自動生成的前提,是實現用IDL和應用描述XML文件替代設計文檔的關鍵技術。需要用語法解析技術解析出的元信息包括模塊(module)、結構體(struct)、結構體元素(struct element)、枚舉(enum)、枚舉元素(enum element)、內置類型(如short、long等)、主題(topic)、消息域(domain)、應用名等,并且要解析出這些元信息之間的關聯關系。

其中,應用、應用和主題之間的關聯關系可以使用XML解析工具進行解析。而描述其它元信息及關聯關系的IDL文件,使用的是經過關鍵字擴展后的IDL語法,這里采用ANTLR4進行語法分析,該工具在語法分析領域應用廣泛[11,12]。核心的語法結構使用廣泛應用的巴科斯范式(Backus-Naur form[13],BNF)表示見表3。

表3 語法結構核心部分的巴科斯范式

ANTLR4可以對語法元素進行分析和提取,但語法樹的數據結構需要自行設計。dseappgen的語法樹主要包括語法元素哈希表,以及表征語法元素關聯關系的樹形鏈表。語法樹構建成功之后,就可以支撐后續步驟——代碼生成——進行語法元素查找和遍歷。

3.3 代碼生成

在獲取了各種必要的語法元信息之后,就需要對具體的DDS應用進行代碼生成。代碼生成從技術上來說,是通過一種模板引擎,將代碼模板中待替換的內容,替換為相應的信息,在本文所研究的場景中,即替換為各種由語法解析分析出來的語法元信息。這里選用的模板引擎是StringTemplate,該引擎靈活高效,簡單易用,而且能夠滿足代碼遞歸生成的需求,該引擎經常和ANTLR4配合使用。

在選定代碼生成引擎之后,需要對代碼模板的結構進行設計。需要考慮的問題有目標生成的代碼結構,以及暴露給用戶的C++API。圖5展示了代碼模板的基本結構。在代碼模板中,自動生成了與應用描述XML文件內容相對應的參與者、主題、發布者、訂閱者等各種DDS的通信概念實體,從而免去了開發者手動編寫的工作量,而且讓開發者完全不需要了解這些具體概念。并且這些API將主題名具化到函數名里(圖5中“xxx”表示的是某個主題的名字),從而可以讓C++編譯器協助檢查,尤其是在使用IDE開發過程中,會有函數名自動提示,大大降低了開發者出錯風險。與該應用無關的主題不會出現在生成的API中,也防止了開發者錯誤發布或訂閱與其無關的主題。

圖5 代碼模板的基本結構

對于消息結構體的正反序列化代碼,依然調用DDS自帶的生成工具(ddsgen)進行生成。不過由于每個應用只引用了IDL文件中的部分結構體,所以為了避免生成冗余的正反序列化代碼,這里通過對語法樹中語法元素的依賴關系進行分析,只提取引用到的必要的語法信息,生成新的精簡過的IDL文件,然后再交給ddsgen處理。其中,語法元素的依賴關系分析用到了DAG(有向無環圖)的拓撲排序算法[14]。

代碼生成還包括各種增強功能,以及CMake編譯文件和配置文件的生成等等。

3.4 Lua解釋器

Lua是一種可以動態解釋執行的腳本語言,它具有輕量、方便嵌入集成、執行速度快等優點,在許多大型軟件中作為嵌入式腳本語言存在。這里,引入Lua腳本語言,進一步提高DDS分布式系統的開發效率,并賦予DDS應用隨時編寫隨時運行的動態執行能力,方便分布式系統的功能調試和仿真,尤其適用于功能原型的快速搭建,以及消息激勵器、算法仿真器等模擬器的開發。

Lua腳本由Lua解釋器加載執行,本文將Lua解釋器內嵌到DDS應用中,并將DDS消息發布訂閱相關API以Lua語言的形式暴露給用戶。這里需要解決的主要問題是Lua對DDS C++接口的封裝,Lua數據結構與DDS消息結構體的轉換,暴露給用戶的Lua API的設計。圖5也展示了DDS應用中Lua相關部分的代碼模板結構。代碼模板將發布者和訂閱者封裝成Lua對象,并通過Lua Table和C++結構的轉換實現消息在Lua API和DDS C++API之間的傳遞,用戶只需要獲取Lua封裝的相應主題的發布者和訂閱者對象,即可實現消息的發布訂閱操作。

3.5 其它增強功能

考慮到DDS應用開發者在日常開發過程中的一些常用需求,又在生成的代碼中加入了一些增強功能。

3.5.1 Json字符串與DDS消息的轉換

開發者經常需要通過打印消息內容進行功能調試,然而實際系統中用到的DDS消息往往字段較多,逐字段打印會加大開發者工作量。前面通過語法解析,已經獲取了DDS消息每個字段的名稱和類型,因此可以通過代碼生成的方法自動生成DDS消息與Json字符串之間的互相轉換代碼(這里用到了C++Json庫nlohmann)。這樣,需要打印消息內容時就可以將DDS消息以格式化良好的Json字符串形式打印,大大減輕了開發者的工作負擔。另外,開發者也可以在代碼中使用Json字符串作為DDS消息的字面值,然后轉換為DDS消息結構體,進而進行消息發布,這樣具有更好的可讀性。

3.5.2 Http Server的嵌入

DDS應用多是運行在服務器的后臺程序,若想與DDS應用在運行時進行交互,會比較困難,因此在DDS應用中嵌入了輕量級的Http Server——Boost Beast[15],它允許用戶通過瀏覽器加載Web頁面,執行Javascript代碼,可以進行HTTP請求應答通信,支持WebSocket,當用戶有運行時交互或者圖形化交互需求時,可以開啟該輕量級Http Server。

3.5.3 消息記錄和統計

DDS分布式系統中往往記錄DDS消息,以方便對系統進行數據分析或故障排查。由于在代碼生成中已經掌控了DDS消息的發布和訂閱,因此在發布和訂閱過程中加入對消息的記錄和統計。消息記錄的內容包括消息Json字符串、應用名稱、發布訂閱時間等,使用Elasticsearch作為記錄存儲數據庫,以支持快速高效的記錄查詢。消息統計主要統計的是消息的發布訂閱次數、頻率,通過Http Server開放了相應Restful Api,支持當前流行的數據采集平臺Prometheus進行訪問采集。

圖6給出了dseappgen編譯生成的可執行文件內部各功能的組成、協作以及與外部的交互。

圖6 編譯生成的可執行文件內部功能結構

4 簡單實例

這里以一個簡單的發布訂閱實例說明使用dseappgen編譯器后給DDS分布式系統開發帶來的便利。

為了實現如圖7所示的發布訂閱通信功能,表4是該DDS分布式系統的IDL文件,表5是應用pubtest的應用描述XML文件,表6是應用subtest的應用描述XML文件,這3個文件由設計者編寫。

圖7 一個簡單的發布訂閱通信實例

表4 示例IDL文件test.idl

表5 示例應用pubtest的應用描述XML文件pubtest.xml

表6 示例應用subtest的應用描述XML文件subtest.xml

然后設計者使用表7中命令調用dseappgen生成兩個可執行程序pubtest和subtest(dseappgen也可以生成供C++開發者使用的動態庫,這里以生成Lua解釋器為例)。

表7 使用dseappgen編譯生成可執行程序

接下來,開發者可以為pubtest和subtest撰寫Lua腳本實現消息的發布訂閱。發布Lua腳本見表8,訂閱Lua腳本見表9。

表8 示例應用pubtest的腳本pubtest.lua

表9 示例應用subtest的腳本subtest.lua

分別啟動pubtest和subtest,將會看到subtest打印出pubtest發布的消息。從該示例可以直觀看出,設計者和開發者的工作是解耦的。設計者使用IDL和應用描述XML文件就完成了整個分布式系統的應用組成和消息互聯的設計,也就是完成了系統建模。通過dseappgen自動生成可執行文件后,即可將工作轉移到開發者進行業務開發。dseappgen的代碼生成隱藏了DDS諸多概念,使得發布訂閱API變得非常簡單,即使并不了解DDS的各種概念,也可以簡單幾行代碼輕松實現發布訂閱通信。

5 優勢分析

本文在實現改進的IDL編譯器dseappgen的基礎上,提出了新的DDS分布式系統快速開發流程,主要的優勢有以下幾點:

(1)賦予IDL語言進行分布式系統建模的能力,貫徹了基于模型的系統工程化(MBSE)思想,IDL文件和應用描述XML文件完全建模了整個分布式系統的框架和通信結構,方便項目管理者從全局掌握項目拓撲情況。

(2)傳統DDS分布式系統開發方式,主題和消息域的定義是侵入到代碼中的,無法實現設計和開發的解耦,而新的開發流程中,主題和消息域定義在IDL文件中,應用的主題和消息域使用情況定義在應用描述XML文件中,從而使得設計者和開發者完全解耦。

(3)dseappgen生成的動態庫或Lua解釋器,只包含應用描述XML文件中涉及到的主題的API,避免了開發者發布訂閱無關主題,方便項目管理者對主題訪問權限進行管理;而且API是具化到函數名中的,C++編譯器和Lua解釋器會協助檢查,防止開發者錯誤使用。

(4)自動生成的動態庫和Lua解釋器,隱藏了DDS內部復雜概念,使得發布訂閱API更加簡化,一行代碼實現通信,大大減少了開發者的代碼量,尤其是在動輒幾百個主題的大型項目中,大大提高了開發效率,降低了項目風險。

(5)Lua語言的嵌入,以及各種增強功能的加入,讓DDS應用更適合快速開發、快速測試、快速仿真。

(6)與當前熱門的機器人操作系統ROS2[16]相比,ROS2同樣使用DDS進行各應用節點之間的通信,并且定義了參數、服務等概念,并提供了基于Python的腳本開發方式,加上開源社區為ROS2提供了豐富的自動化控制領域的第三方庫,是目前自動化領域設計仿真的首選框架。而本文更關注的是,為大型DDS分布式系統開發,提供一種開發模式,讓設計者和開發者為完成項目更好更高效地進行合作,而ROS2并沒有在這方面提供相應的解決方案,而且本文也提供了基于Lua的腳本開發方式。

6 結束語

本文結合當前DDS分布式系統工程開發現狀,對工程開發實踐中遇到的問題進行了分析,設計實現了改進的IDL編譯器dseappgen,并基于此工具提出DDS分布式系統開發流程,其中涉及到的技術手段包括語法解析、代碼生成、Lua解釋器等等。新方法貫徹了MBSE的思想,將工程實踐中設計和開發進行解耦合,一方面讓設計獨立于開發,不需要將設計侵入代碼,另一方面也節省了開發者的很多工作量,尤其是Lua解釋器的引入,讓DDS應用擁有了隨時修改隨時運行的能力,非常適用于算法驗證、消息激勵等模擬器的開發。

目前dseappgen及其帶來的新開發模式在很多項目中開始了實踐和應用,很大程度加快了項目的設計、開發、測試進度。

為了繼續推進DDS分布式系統開發的工程化,未來還將基于本文提出的開發流程,構建具有圖形界面的“低代碼[17]”開發平臺,可以用于工程的系統原型快速搭建,提升工程開發的自動化,降低工程開發的復雜性。

猜你喜歡
設計
二十四節氣在平面廣告設計中的應用
河北畫報(2020年8期)2020-10-27 02:54:06
何為設計的守護之道?
現代裝飾(2020年7期)2020-07-27 01:27:42
《豐收的喜悅展示設計》
流行色(2020年1期)2020-04-28 11:16:38
基于PWM的伺服控制系統設計
電子制作(2019年19期)2019-11-23 08:41:36
基于89C52的32只三色LED搖搖棒設計
電子制作(2019年15期)2019-08-27 01:11:50
基于ICL8038的波形發生器仿真設計
電子制作(2019年7期)2019-04-25 13:18:16
瞞天過海——仿生設計萌到家
藝術啟蒙(2018年7期)2018-08-23 09:14:18
設計秀
海峽姐妹(2017年7期)2017-07-31 19:08:17
有種設計叫而專
Coco薇(2017年5期)2017-06-05 08:53:16
從平面設計到“設計健康”
商周刊(2017年26期)2017-04-25 08:13:04
主站蜘蛛池模板: 日韩精品资源| A级毛片无码久久精品免费| 在线五月婷婷| 亚洲成人黄色在线| 欧美日韩导航| 欧美精品在线视频观看| 黑人巨大精品欧美一区二区区| 好紧太爽了视频免费无码| 亚洲国产成人久久精品软件 | 999精品视频在线| 日韩欧美综合在线制服| 国产精品亚洲天堂| 午夜老司机永久免费看片| 亚洲精品成人片在线观看| 亚洲色图另类| 中文字幕丝袜一区二区| 国产亚洲精品无码专| 国产成人高清在线精品| 亚洲第一色网站| 亚洲成人在线播放 | 污污网站在线观看| 欧美国产日韩另类| 香蕉久人久人青草青草| 中文国产成人久久精品小说| 国产乱人伦精品一区二区| 91麻豆久久久| 国产91麻豆视频| 日韩高清中文字幕| 久久美女精品| 超级碰免费视频91| a在线亚洲男人的天堂试看| 手机永久AV在线播放| 97se亚洲综合不卡| 亚洲一区二区精品无码久久久| 日韩午夜伦| 久久香蕉国产线看精品| 日韩少妇激情一区二区| 亚洲系列无码专区偷窥无码| 国产精品林美惠子在线观看| 国产精品尤物铁牛tv| 亚洲制服丝袜第一页| 精品无码一区二区三区在线视频| 99热6这里只有精品| 欧美自拍另类欧美综合图区| 国产精品va| 亚洲精品福利视频| 久久熟女AV| 国产日韩欧美一区二区三区在线| 欧美色香蕉| 国产精品第一区| 都市激情亚洲综合久久| 福利姬国产精品一区在线| 国产成人无码播放| 不卡午夜视频| 国产精品观看视频免费完整版| 国产网站免费看| 国产手机在线ΑⅤ片无码观看| 自慰高潮喷白浆在线观看| 在线视频亚洲欧美| 中文成人在线| 国产18页| 日韩国产精品无码一区二区三区| 一级爆乳无码av| 波多野结衣久久高清免费| 国产正在播放| 免费国产一级 片内射老| 成人福利在线视频免费观看| 欧美亚洲国产精品第一页| 欧美日韩在线第一页| 97免费在线观看视频| 亚洲黄网视频| 国产在线八区| 视频一本大道香蕉久在线播放| 国产在线视频导航| 久久99精品国产麻豆宅宅| 极品尤物av美乳在线观看| 国产裸舞福利在线视频合集| 国产精品嫩草影院av| 中字无码av在线电影| 久久网综合| 91丝袜在线观看| 免费看一级毛片波多结衣|