日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

UDT 最新协议分析

發布時間:2023/12/14 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 UDT 最新协议分析 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

UDT4 最新協議分析

  • 背景
  • 協議
    • 與IETF草案版本差異
    • 簡介
    • 數據結構
      • 數據包
      • 控制包
    • 定時器
    • 兩種連接模式
    • 數據發送與接收
      • 發送端算法
        • 發送端數據結構
        • 數據發送算法
      • 接收端算法
        • 接收端數據結構
        • 數據接收算法
      • 流控
      • 丟包信息壓縮
    • 可配置的擁塞控制
      • CCC接口
      • 原生控制算法
        • 當ACK包被接收
        • 當NAK包被接收
    • 效率與安全

背景

網絡帶寬占用與實際物理管道通信能力之間的矛盾愈加突出,TCP越來越不能滿足需求,傳輸效率提升需求很迫切。如果對這個領域細分的話,其實每個細分領域的需求各不一樣,比如分布式計算,實時通信,直播,在線游戲等。有些對實時性要求更高,也有些對數據完整性要求更高,當然也有兩者兼而有之。

為什么會出現網絡傳輸效率降低呢?這有網絡擁塞的原因,也有傳輸協議的問題。從網絡擁塞上來說,最根本還是中間網關或管道的限制。當大量數據涌入一個流量限制通路,路由器排隊丟失導致丟包;如果路由器性能不足,或者出現故障,也會出現大量的數據丟失。如果采用TCP傳輸,會導致數據重傳,以及傳輸窗口降低影響效率,也存在重新選路的可能。

UDT(UDP-based Data Transfer Protocol)主要針對當前TCP進行長距離傳輸大量數據時的性能表現較差而提出,建立在UDP之上,引入新的擁塞控制以及可靠性,支持可靠的流式傳輸(類似TCP),以及部分可靠的數據報傳輸(增強UDP)。除了高速數據傳輸之外的其它應用領域,例如點到點技術(P2P),防火墻穿透,多媒體數據傳輸等等。

協議

與IETF草案版本差異

協議內容以代碼UDT4中包含的draft-gg-udt-xx為準,更新時間為2013年。在IETF中的協議版本為draft-gg-udt-03,更新時間為2010年。

簡介

UDT是面向連接的雙工協議,每個UDT實體有兩個部分:發送和接收。發送者根據流量控制和速率控制來發送(和重傳)應用程序數據。接收者接收數據包和控制包,并根據接收到的包發送控制包。發送和接收程序共享同一個UDP端口來發送和接收。

接收者也負責觸發和處理所有的控制事件,包括擁塞控制和可靠性控制和他們的相對機制,例如RTT估計、帶寬估計、應答和重傳。所以整個系統依靠接收者對網絡的統計與計算進行調節。

因為使用UDP傳輸,所以可能需要對應用層數據進行分包與重組,這樣就決定了需要有字節包頭格式。流式傳輸時需要填滿上一分包,這一點與TCP類似。從傳輸效率來看,使用流式傳輸效率更高,尤其針對大量的數據或者大塊數據。

UDT在傳輸效率優化時,同樣希望可以兼顧到公平,所以也有自己的擁塞控制算法和流控措施。速率控制調整包的發送周期(RTT估計),擁塞窗口限制傳輸到網絡的數據量(接收方數據到達速度,接收緩沖窗口大小)。

數據結構

數據包

第一個bit為0,標識數據包。
FF標識一個消息中各個包的位置,11:單獨的數據包,10:一個數據流中的第一個數據包,01:一個數據流中的最后一個數據包。緊隨其后的bit位標記是否立即發送數據。
Destination Socket ID是在端口復用時用來區分哪一個連接的數據。因為UDT支持多個連接使用同一個端口。

控制包

第1 bit為1,標識控制包。第2-16bit標識控制報的類型,UDT定義了8種數據類型,其余暫時保留,根據不同的類型,Additional Info與control Information內容也會不同。

  • TYPE 0x0: Protocol Connection Handshake

Additional Info:

Undefined

Control Info:

  • 32 bits: UDT version
  • 32 bits: Socket Type (STREAM or DGRAM) 32 bits: Socket Type (STREAM or DGRAM)
  • 32 bits: initial packet sequence number 32 bits: initial packet sequence number
  • 32 bits: maximum packet size (including UDP/IP headers) 32 bits: maximum packet size (including UDP/IP headers)
  • 32 bits: maximum flow window size 32 bits: maximum flow window size
  • 32 bits: connection type (regular or rendezvous) 32 bits: connection type (regular or rendezvous)
  • 32 bits: socket ID 32 bits: socket ID
  • 32 bits: SYN cookie 32 bits: SYN cookie
  • 128 bits: the IP address of the peer’s UDP socket128 bits: the IP address of the peer’s UDP socket
    • TYPE 0x1: Keep-alive

    Additional Info:

    Undefined

    Control Info:

    None

    • TYPE 0x2: Acknowledgement (ACK)

    Additional Info:

    ACK sequence number

    Control Info:

  • 32 bits: The packet sequence number to which all the previous packets have been received (excluding)
    [The following fields are optional]
  • 32 bits: RTT (in microseconds)
  • 32 bits: RTT variance
  • 32 bits: Available buffer size (in bytes)
  • 32 bits: Packets receiving rate (in number of packets per second)
  • 32 bits: Estimated link capacity (in number of packets per second)
    • TYPE 0x3: Negative Acknowledgement (NAK)

    Additional Info:

    Undefined

    Control Info:

  • 32 bits integer array of compressed loss information
    • TYPE 0x4: Congestion/Delay Warning

    Additional Info:

    Undefined

    Control Info:

    None

    • TYPE 0x5: Shutdown
      Additional Info:

    Undefined

    Control Info:

    None

    • TYPE 0x6: Acknowledgement of Acknowledgement (ACK2)

    Additional Info:

    ACK sequence number

    Control Info:

    None

    • TYPE 0x7: Message Drop Request:

    Additional Info:

    Message ID

    Control Info:

  • 32 bits: First sequence number in the message
  • 32 bits: Last sequence number in the message
  • 定時器

    協議定義了四個定時器ACK,NAK,EXP和SND。SND在發送端,其余在接收端。不同定時器觸發不同的周期事件,包括速率控制、應答、丟失報告(negative應答)和重傳/連接維護。定時器使用系統時間,并主動查詢是否到期。

    SND定時器用來觸發周期性的速率控制。

    ACK定時器周期被CC控制,周期性的有選擇的發送ACK包,檢查周期不超過一個SYN時間 0.01秒。

    NAK被用來觸發發送NAK包。重傳定時器被用來觸發一個數據包的重傳和維護連接狀態。他們周期依賴于對于RTT的估計,周期動態更新為4 * RTT + RTTVar + SYN,RTTVar為RTT樣本方差。

    EXP被用于觸發丟包重傳和維護連接狀態。周期動態更新為 N * (4 * RTT + RTTVar + SYN), N 為連續超時的次數,且周期最小值被設定,默認為0.5秒,以防止太頻繁被觸發。

    兩種連接模式

    C/S模式:客戶端服務器模式,傳統模式。由客戶端向服務器端發起連接請求。而服務器端需要先建立監聽,以便接受連接請求。

    Rendezvous模式:會和模式,通信雙方同時向對方發送連接請求。這種情況,在NAT穿越后較多用到。不能接受C/S模式下的連接請求。

    對于已經建立UDT連接的雙方來說,發送方發送一個關閉請求,且只發送一次。如果接受沒有接收到,需要等到16*EXP后再自行關閉。總超時有最小最大閾值,參考值設置為3秒到30秒。

    數據發送與接收

    發送端根據擁塞控制和流量控制,決定如何發送數據包或者重傳可能丟失的數據包。接收端負責觸發控制事件NAK、ACK和EXP,處理所有相關控制機制。

    發送端算法

    發送端數據結構

    • 發送丟失鏈表(Sender’s Loss List)
      記錄從接收端返回的NAK包記錄的丟包信息以及觸發超時事件時的包序號(發生超時事件時會插入鏈表)。

    數據發送算法

  • 如果發送端丟失鏈表非空,重傳發送丟失鏈表第一個序號的包,并刪除該節點。 Goto 5。
  • 在消息模式下,如果包已經在發送丟失鏈表中滯留了超過應用設定的生存時間TTL值,發送消息丟棄請求,并移除發送丟失鏈表中所有關聯包。Goto 1。
  • 等待,直到有應用數據被發送。
  • 如果未確認的數據包數量已經超過流量/擁塞窗口,將停下來等待ACK到來。Goto 1。否則,將打包一個新的數據包并發送出去。
  • 如果當前包的序號為 16 n(可能超時),其中n是一個整數,Goto 2。
  • 等待(SND - t),Goto 1。其中SND為由CC更新的包間時間間隔,t是從1)到5)的總時間。
  • 接收端算法

    接收端數據結構

    • 接收端丟失鏈表
      包括一個二元組和一個參數。二元組為檢測到丟包的序號,和最新反饋的時間。參數k是每一個包被NAK反饋的次數。丟失鏈表中序號升序排列。
    • ACK 歷史窗口
      記錄每個發送的ACK,以及發送時間。循環數組。
    • PKT 歷史窗口
      記錄每個數據包的到達時間。循環數組。
    • 包對窗口
      記錄每個探測包隊的間隔時間。循環數組。
    • LRSN
      記錄接收到的數據包最大序號,初始化為1。
    • ExpCount
      記錄連續發生EXP超時時間的次數。

    數據接收算法

    • 數據接收算法
  • 查詢系統時間,檢查ACK,NAK,EXP定時器是否已經過期。如果存在任意一個定時器過期,都需要處理該定時器到期的對應事件,并重置關聯時間變量。對于ACK,也會檢查ACK 包間隔。
  • 開始存在時間限制的UDP接收過程。如果沒有包到達,Goto 1。
  • 重置ExpCount 為1。如果沒有未確認數據,或者收到ACK,NAK控制包,重置EXP 定時器。
  • 檢查包頭的標志位,如果是控制包,按照類型處理,并 Goto 1。
  • 如果當前包的序號是16n + 1,記錄這個包和在包對窗口記錄的最近一個數據包的時間間隔。其中n是一個整數。
  • 在PKT歷史窗口中記錄包到達時間。
  • 如果當前數據包的序號sn大于 LRSN + 1,將所有介于(LRSN+1, sn)區間的所有報序號加入接收丟失鏈表。
    如果當前數據包的序號小于LRSN,從接收丟失鏈表中刪除。
  • 更新LRSN,Goto 1。
    • ACK定時器事件處理過程
  • 接收端根據如下規則查找接收到所有包之前的序號(最后確認的最大ACK序號,即為ACK number):
    如果接收丟失鏈表非空,ACK 序號為 LRSN + 1;否則,ACK 序號為接收丟失鏈表中最小序號。
  • 如果ACK number等于被ACK2 確認的最大ACK序號,或者等于不超過2 RTT的上一個ACK的最大序號,停止處理(不發送這個ACK)。
  • 每個ACK序號為唯一單調遞增的,將RTT,RTTVar,流窗口大小(可用接收緩沖大小)填充到ACK包中。如果這個ACK不是被ACK定時器觸發的,發送ACK并停止處理。
  • 包到達速度計算:
    計算存儲在PKT歷史窗口中最近16個包到達時間的中值AI。去掉大于 AI * 8或者AI/8的值。如果剩余超過8個,計算均值 AI’和包到達速率 1/AI’(每秒包到達個數),否則,小于8個則返回0。
  • 鏈路容量估計:
    計算包對窗口中最近的16個包對時間間隔的中值PI,鏈路容量值為1/PI(每秒包數量)。
  • 將包到達菽粟和鏈路容量估計值打包到ACK包中發送。
  • 記錄當前ACK序號,ACK確認前的最大ACK序號以及ACK發送時間將被記錄在ACK歷史窗口中。
    • ACK定時器事件處理過程
  • 接收端根據如下規則查找接收到所有包之前的序號(最后確認的最大ACK序號,即為ACK number):
    如果接收丟失鏈表非空,ACK 序號為 LRSN + 1;否則,ACK 序號為接收丟失鏈表中最小序號。
  • 如果ACK number等于被ACK2 確認的最大ACK序號,或者等于不超過2 RTT的上一個ACK的最大序號,停止處理(不發送這個ACK)。
  • 每個ACK序號為唯一單調遞增的,將RTT,RTTVar,流窗口大小(可用接收緩沖大小)填充到ACK包中。如果這個ACK不是被ACK定時器觸發的,發送ACK并停止處理。
  • 包到達速度計算:
    計算存儲在PKT歷史窗口中最近16個包到達時間的中值AI。去掉大于 AI * 8或者AI/8的值。如果剩余超過8個,計算均值 AI’和包到達速率 1/AI’(每秒包到達個數),否則,小于8個則返回0。
  • 鏈路容量估計:
    計算包對窗口中最近的16個包對時間間隔的中值PI,鏈路容量值為1/PI(每秒包數量)。
  • 將包到達菽粟和鏈路容量估計值打包到ACK包中發送。
  • 記錄當前ACK序號,ACK確認前的最大ACK序號以及ACK發送時間將被記錄在ACK歷史窗口中。
    • NAK事件處理過程

    搜索接收丟失鏈表,尋找最后的反饋時間在 k*RTT的所有包序號,k初始化為2,并且每次反饋增加1。通過NAK將所有包反饋給發送端。

    • EXP事件處理過程
  • 將所有未確認的包添加到發送丟失鏈表中。
  • 如果ExpCount > 16,并且ExpCount被重置為1至少過去3秒,或者3分鐘過去了,關閉UDT連接并退出。
  • 如果發送丟失鏈表為空,發送keep-alive包到對端。
  • ExpCount自增1.
    • 接收到ACK 控制包的處理過程
  • 更新最大確認序號。
  • 使用相同的ACK序號返回一個ACK2作為確認的確認。
  • 更新RTT與RTTVar。
  • 更新ACK和NAK周期為 4 * RTT + RTTVar + SYN。
  • 更新流窗口大小。
  • 如果是一個輕量級ACK,終止處理。(light ACK)
  • 更新包到達速率為:A = (A * 7 + a) / 8,其中a為ACK中攜帶的相應值。
  • 更新鏈路容量估計值:B = (B * 7 + b) / 8,其中b為ACK中攜帶的相應值。
  • 更新發送端緩沖,釋放已經被確認的緩沖。
  • 更新發送丟失鏈表,移除已經被確認的所有包序號。
    • 接收到NAK 控制包的處理過程
  • 將NAK中攜帶的所有序號添加到發送丟失鏈表中。
  • 通過碼率控制(rate control)更新SND周期。
  • 重置EXP時間變量。
    • 接收到ACK2控制包的處理過程
  • 根據ACK2中的ACK序號,在ACK歷史窗口中找到關聯的ACK。
  • 更新被確認的最大ACK序號 。
  • 根據ACK2到達時間和ACK離開時間,計算新的RTT:RTT = (RTT * 7 + rtt) / 8。
  • 更新RTTVar:RTTVar = (RTTVar * 3 + abs(RTT - rtt)) / 4。
  • 更新ACK 與 NAK周期為: 4 * RTT + RTTVar + SYN。
    • 接收到消息丟失請求的處理過程
  • 在接收緩沖中標記所有屬于同一個消息的包,使得不再可讀。
  • 在接收丟失鏈表中移除所有對應的包。
  • 流控

    流量控制窗口初始值設置為16。
    接收到ACK消息時,流窗口大小更新為接收端當前的可用緩沖大小。

    丟包信息壓縮

    NAK包中攜帶的丟失信息是一個32-bit整數的數組。如果數組的第一位為0,表示這個序號的包丟失,如果第1位是1,意味著從這個號碼開始(包括該號碼)到下一個數組中的元素(包括這個元素值)之間的包(它的第1位必須是0)都丟失。例如,下面的NAK中攜帶的信息:
    0x00000002, 0x80000006, 0x0000000B, 0x0000000E
    上面的信息表明序號為:2,6,7,8,9,10,11,14的包都丟了

    可配置的擁塞控制

    UDT中的擁塞控制使用一種開放式的框架,用戶可以較容易的實現自定義的算法,或者在多個算法之間切換。在UDT的控制算法框架中,已經實現了原生的控制算法。

    用戶定義的算法可能重定義許多控制流程以及適配一些UDT參數。這些流程將在某一時間出現時被調用,例如,當ACK接收到時,控制算法可能增加擁塞窗口大小。

    CCC接口

    UDT允許用戶接入兩個擁塞控制參數:擁塞窗口大小和包間發送時間間隔。用戶可以通過調整參數適配這兩個參數來實現基于窗口的控制,基于速率的控制或者混合方法。另外,下列參數也將被公開:

  • 往返時延(RTT)。
  • 最大包大小(Maximum Segment/Packet Size)。
  • 估計帶寬。
  • 目前為止被發送的最新的包序號。
  • 接收端的包到達速率。
  • UDT實現同樣可以公開一些其他的參數,這些信息被在用戶自定義的擁塞控制算法中使用,以挑戰包發送速率。

    下列的控制事件能通過CCC進行重定義(比如通過回調的方式):

  • Init:當UDTsocket被連接時。
  • close:當UDT socket被關閉時。
  • onACK:當接收到ACK時。
  • onLOSS:當接收到NACK時。
  • onTimeout:發生一個超時時。
  • onPktSent:當一個數據包被發送時;
  • onPktRecv:當一個數據包到達時。
  • 用戶還可以在自定義算法中調整下列參數:

  • ACK interval:ACK可能每間隔固定數量包進行發送。用戶可以自定義時間間隔。如果值為-1,意味著在包到達時沒有ACK被發送。
  • ACK Timer:ACK也可以以固定的時間間隔進行發送,在UDT中是強制的。最大和缺省ACK時間間隔為SYN。
  • RTO:UDT用 4 * RTT + RTTVar 來估計RTO,用戶可以重定義。
  • 原生控制算法

    如果用戶沒有自定義控制算法,那么UDT缺省使用原生的控制算法,并被CCC執行。

    UDT原生算法是一種混合型的擁塞控制算法,因此需要調整擁塞窗口大小和包間間隔。原生算法使用基于定時器的ACK,ACK之間的時間間隔為SYN。

    出視擁塞控制窗口大小為16個包,并且包間間隔為0,算法以慢啟動開始,直到第一個ACK或NAK到來。

    當ACK包被接收

  • 如果當前處于慢啟動階段,設置擁塞窗口大小為 包到達速率與(RTT+SYN)的乘積,慢啟動結束。停止處理。
  • 設置擁塞窗口大小:CWND = A * (RTT + SYN) + 16。
  • 下一個SYN周期內需要發送的包數量增加值為:
    if (B <= C) inc = min_inc
    else inc = max(10^(ceil( log10( (B-C) * PS * 8 ) )) * Beta/PS, min_inc)
    其中,B 為鏈路容量估計,C為當前發送速率估計,單位 包數/秒。
    Beta 是常數 0.0000015,min_inc 為最小增加值,0.01,表示每秒至少增加一個包(SYN)。
  • SND 周期更新為:
    SND = (SND * SYN) / (SND * inc + SYN)
  • 在碼率降低時有四個參數將被使用,初始值為圓括號內值:

  • AvgNAKNum (1):一個擁塞周期內的平均NAK包數。
  • NAKCount (1):當前周期內的NAK目前的數量。
  • DecCount(1):一個擁塞周期的平均NAK數量。
  • LastDecSeq(初始序號-1):上次包發送速率降低時的最大包序號。
  • 一個擁塞周期被定義為介于 2個NAK包之間,并且第一個最大的丟包序號大于LastDecSeq。

    當NAK包被接收

  • 如果處于慢啟動階段,慢啟動結束。
    如果被觀察到接收速率被從包間間隔設置為 1/recvrate,停止。
    否則,根據當前窗口大小(cwnd / rtt + SYN)設置發送速率,繼續以步驟2中方法減少窗口。
  • 如果這個NAK開始一個新的擁塞周期,增加包間時間間隔snd: snd = snd * 1.125。
    更新AvgNAKNum,重置NAKCount為1,計算DecRandom為1到AvgNAKNum之間的一個平均分布的隨機數,更新LastDecSeq。停止。
  • 如果DecCount <= 5,并且NAKCount == DecCount * DecRandom:
    a. 更新SND period: SND = SND * 1.125;
    b. DecCount以增加值1進行增加;
    c. 記錄當前最大發送序號LastDecSeq。
  • 效率與安全

    UDT原生控制算法被設計給海量數據在高帶寬時延積網絡傳輸數據的。這也就解釋了為什么在日常使用中有些應用批判UDT效果不佳的問題,尤其在無線網絡。

    所以永遠不要期望存在一種算法適合所有的網絡狀態,只能與具體的業務進行分析,才能優化出更好的效果。與其抱怨算法的太差,不如回頭梳理自己的需求是否一致以及如何修改或者創新。

    UDT安全機制類似與TCP。

    總結

    以上是生活随笔為你收集整理的UDT 最新协议分析的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 久国产精品 | 4438五月天| 男人插入女人下面视频 | 日本欧美韩国国产精品 | 日韩成人在线影院 | 亚洲经典视频在线观看 | 国产综合视频在线 | 人妻少妇精品一区二区 | 免费av影视 | 少妇裸体淫交视频免费看高清 | 无码国产精品96久久久久 | caoporn成人| 台湾佬美性中文娱乐 | 伊人久久中文 | 日韩一区二区三区av | 亚洲19p | 国产在线观看中文字幕 | 国产91清纯白嫩初高中在线观看 | 中文字幕日本在线观看 | 99热这里只有精品9 日韩综合在线 | 久久精品视频一区二区三区 | 色屁屁ts人妖系列二区 | 深夜福利网站在线观看 | 国产成年无码久久久久毛片 | 中文精品久久久久人妻不卡 | 日韩色一区 | 欧美aaa在线观看 | 国产精品免费看 | 精品国产一区二区三区噜噜噜 | 日本天堂一区 | 成人黄色免费网 | 婷婷网址| 久久久在线免费观看 | 亚洲成在线观看 | 国产福利免费在线观看 | 国产肉体ⅹxxx137大胆 | 97免费看| 中文字幕资源在线 | 国产高清视频网站 | av不卡网| 成人黄色激情 | 男人激烈吮乳吃奶爽文 | 国产精品午夜未成人免费观看 | 无码人妻av免费一区二区三区 | 97视频在线观看免费高清完整版在线观看 | 天天夜夜骑 | 色妞综合| av天堂永久资源网 | 国产草草草 | 天堂99| 久久久精品福利 | 午夜影院污 | 欧洲熟妇的性久久久久久 | 国产大奶在线 | 亚洲亚洲人成综合网络 | 在线看av网址 | 久久中文字幕电影 | 天天躁夜夜躁狠狠躁 | 欧美性视频一区二区 | a在线免费观看 | aaa国产精品 | 久久久亚洲天堂 | 91综合精品 | 91天天色| 午夜亚洲av永久无码精品 | av不卡网站| 岛国精品视频 | 国产精品黑人一区二区三区 | 午夜免费福利视频 | 中文字幕第7页 | 粉嫩av一区二区三区四区五区 | 亚洲爆乳无码精品aaa片蜜桃 | 黄色字幕网 | 人妻丰满熟妇av无码久久洗澡 | 成人在线视频观看 | 久久久视频在线 | 天堂在线国产 | 久久久精选 | 精品日韩制服无码久久久久久 | 亚洲香蕉网站 | 四虎少妇做爰免费视频网站四 | 手机在线亚洲 | 久久这里只精品 | 日本在线观看免费 | 久久久777| 欧美日韩免费在线观看 | 国产精品一国产精品 | 成年精品| 亚洲一级中文字幕 | 国产精品无码在线播放 | 国产色| 色婷婷av久久久久久久 | 久久精品这里只有精品 | 日韩性欧美 | 少妇裸体挤奶汁奶水视频 | 精品久久久久久久久久岛国gif | 美色视频 | 亚洲色图小说 | 中文字幕无线精品亚洲乱码一区 |