1. 程式人生 > 其它 >基於UDP高效能可靠傳輸協議UDT-銳英源軟體經驗

基於UDP高效能可靠傳輸協議UDT-銳英源軟體經驗

  一、 概述

  UDT是一個高效能的基於UDP的資料傳輸協議,它是為支援高速廣域網上海量資料傳輸而設計,為解決TCP的效率和公平問題,同時提供可靠的資料流和報文傳輸。

  UDT是C++庫,幾乎類同於BSD socket APIs。

  UDT是多執行緒安全的,但並不是多程序共享。

  二、 原理

  UDT有兩種傳輸模式:資料流模式(SOCK_STREAM)和資料報模式(SOCK_DGRAM)

  資料流模式類似於傳統的BSD套接字,這種模式下不能保證任何一端一個呼叫就把所有的資料傳送了,因為在資料流中沒有邊界資訊,程序需用loop來發送和接收。

  資料報模式會將資料作為整個單元來傳送,不需要迴圈來接收和傳送資料,要麼全部發送,要麼一點也不傳送。在接收端如果緩衝區不夠大,則只會接收到部分資料,其他的將被丟棄。

  UDT傳送資料有阻塞與非阻塞方式,在阻塞方式下,會直到把需要傳送的資料傳送完再返回,而非阻塞方式下,會根據socket底層的可用緩衝的大小,將緩衝區中的資料拷貝過去,有多大緩衝就拷貝多少,緩衝區滿了就立即返回,這個時候的返回值只是拷貝了多少,不代表傳送了多少,同時剩下的部分需要再次呼叫send

  UDT增加了rendezvous模式,這是一種連線模式,用來穿透防火牆。這種模式下,UDT不能呼叫listen和accept,而是兩端bind後同時建立連線。

  UDT允許使用者自己定義擁塞控制。可以繼承DUT/CCC下的CCC類來改變一些變數,如擁塞視窗,./app/cc.h下的例項是學習的快速途徑。

  三、 安裝及平臺

  UDT是基於原始碼的庫,所以沒有安裝檔案工具,我們只需要根據不同的系統和CPU架構使用命令來make相應的庫即可。

  UDT支援的系統:linux,BSD,OSX

  UDT支援的架構:IA32,IA64,POWERPC,AMD64

  命令: make –e os=XXX arch=XXX

  UDT來源於BSD socket API只有一個頭檔案,一些繼續使用BSDAPI 另一些需要加標示符UDT::

  庫:libudt.h udt.dll udt.dylib libudt.a udt.lib

  四、 配置設定

  讀取和設定選項通過getsockopt和setsockopt方法,一般不要修改預設選項除非應用不能正常執行。

  UDT_MSS用來設定包的大小,一般情況下最佳的UDT包的大小是網路MTU(預設1500位元組)的大小,連線的兩端都要設定這個值,傳輸時取兩端的較小者。

  UDT用不同的同步方式語義UDT_SNDSYN和UDT_RCVSYN,它可以獨立的設定傳送和接收同步,具有更多的靈活性。它不允許在連線建立和關閉的時候進行非阻塞操作。

  UDT緩衝區的大小理論上越大越好,要執行的好兩端buffer至少為【頻寬*RTT】

  UDT使用UDP資料通道,所以UDP緩衝大小影響程式執行,但隨著buffer變大效果也會越來越不明顯。一般來說傳送端的buffer小一點,因為包的傳送沒有限制太多,但太大會增加端到端的延時。

  UDT_LINGER是設定socket關閉時是否立即停止傳送緩衝區的資料。

  UDT_RENDEZVOUS設定集合點模式,在穿越防火牆時很有用。

  UDT_SNDTIMEO和UDT_RCVTIMEO是timeout值

  UDT_REUSEADDR設定UDP埠是否可以給其他UDT使用,預設值是true。

  以下情況需設定false

  1,兩個UDT socket不能在同一埠監聽。

  2,兩個UDT socket繫結在同一IP同一埠而不能建立連線。

  傳送發有兩種選擇:

  1,TTL(預設無限)為timeout時間。

  2,訊息有序到達,直到上一個訊息到達或被丟棄才發下一個。

  UDT提供檔案傳輸,UDT::sendfile和UDT::recvfile這種傳送接收方式跟

  UDT::send和UDT::recv是正交的。也就是說用sendfile傳送不一定要用recvfile接收。另外,sendfile和recvfile不受SNDSYN,RCVSYN,SNDTIMEO,RCVTIMEO影響。它使用C++ fstream進行檔案IO。

  UDT打洞,在傳統方式下,穿越防火牆時是用SO_REUSEADDR選項去開啟兩個socket繫結同一個埠,一個監聽一個建立連線。而UDT提供直接相連的方式。

  UDT允許一個程序中的所有socket繫結到同一埠但只允許一個監聽。

  UDT允許繫結已經存在的UDP埠有兩個好處:

  1,當應用程式向伺服器傳送一個空包去獲得它的地址(尤其是在NAT防火牆下)時,使用者會建立一個UDP包傳送個server確定繫結的埠,然後UDP埠可以順便給UDT使用。

  2,一些本地防火牆在關閉“打洞”時會改變對映埠,新的UDT繫結的埠將失效,此時用UDP的是必須的。

  錯誤處理:所有的UDT API在遇到錯誤時都會返回error UDT定義兩種錯誤,

  UDT::INVALID_SOCK和UDT::ERROR。可以用getErrorCode和getErrorMessage方法檢視存放在ERRORINFO資料結構中的錯誤程式碼及資訊。

  成功的呼叫不會清楚錯誤,所以應用程式應該利用返回值檢查呼叫結果,可以呼叫個體lasterror().clean()來清除錯誤日誌。

  五、 介面描述

  socket方法

  方法名

  socket方法

  功能

  用於建立一個新的socket

  詳細介面

  UDTSOCKET socket(

  int af,

  int type,

  int protocol

  );

  返回值

  成功:返回socket描述符

  錯誤:返回UDT::INVALID_SOCK

  描述

  type:指SICK_STREAM或SOCK_DARAM

  protocol:忽略

  af:AF_INET(IPv4)或者AF_INET(IPv6)

  UDT支援IPv4和IPv6可以通過af引數選擇。

  accept方法

  方法名

  accept方法

  功能

  用於接收一個進來的連線

  詳細介面

  UDTSOCKET accept(

  UDTSOCKET u,

  struct sockaddr* addr,

  int* addrlen

  );

  返回值

  成功:返回新連線的描述符

  錯誤:返回UDT::INVALID_SOCK

  描述

  當一個UDT socket處於監聽狀態,它將接收到的連線存放在一個佇列裡,一個accept呼叫會依次從佇列裡首先取出對頭的連線,並將其移出佇列。

  當佇列裡沒有連線請求時,一個阻塞式的socket就會等待下一個連線,遇到錯誤時會有一個非阻塞式的socket立即返回。

  接收到的socket會繼承監聽socket的屬性。

  bind方法

  方法名

  bind方法

  功能

  用於繫結一個埠

  詳細介面

  int bind(

  UDTSOCKET u,

  struct sockaddr* name,

  int* namelen

  );

  int bind(

  #ifndef WIN32

  int udpsock

  #else

  SOCKET udpsock

  #endif

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR

  描述

  繫結內容包括IP地址和埠,如果設定了INADDR_ANY,則表示0.0.0.0任意地址(多網絡卡時),當埠使用0則會使用一個隨機的可用埠,用getsockname可以獲得埠號,另一種方式UDT允許繫結在已存在的UDP埠(前面已寫)此時需注意程式碼的健壯性,一旦UDP描述符被UDT使用,不要再隨便使用除非你對系統非常瞭解。

  繫結過程是必須的,除了在監聽下,如果沒有繫結,UDT會自動隨機繫結一個地址

  cleanup方法

  方法名

  cleanup方法

  功能

  用於釋放UDT庫

  詳細介面

  int cleanup(

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR

  描述

  當它被呼叫有如下兩個操作:

  1,所有存在的開啟的連線都會被關閉

  2,後臺垃圾回收也被關閉。

  這個方法有效的前提是:startup方法在之前呼叫。

  startup方法

  方法名

  startup方法

  功能

  用於初始化UDT庫

  詳細介面

  int startup(

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR(此版本總會成功)

  描述

  初始化庫,開啟垃圾回收執行緒,必須在所有UDT呼叫之前呼叫它,否則會造成記憶體洩露。

  若多次呼叫則只有第一次有效。

  close方法

  方法名

  close方法

  功能

  用於關閉一個UDT連線

  詳細介面

  int close(

  UDTSOCKET u

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR

  描述

  它溫和地關閉UDT連線並釋放有關的資料結構,如果沒有連線,則只釋放與socket相關的資源。

  在阻塞方式下,如果UDT_LINGER設為非0,則會等待發送快取傳送完或者時間到。非阻塞下立即關閉。

  關閉socket會發送一個關閉訊息給另一端,通知其關閉連線,如果沒有成功傳送,對方也會在一個TIME_OUT的時間後關閉。

  不用的socket都應該被關掉。

  connect方法

  方法名

  connect方法

  功能

  用於連線到服務端socket(常規)或者peer side(集合點模式)

  詳細介面

  int connect(

  UDTSOCKET u,

  const struct sockaddr* name,

  int* namelen

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR

  描述

  UDT是面向連線的。有兩種模式:SOCK_STREA,和SOCK_DARAM模式,

  name是需要建立連線的服務端或者peer side。

  C/S模式下服務端要呼叫bind和listen,在rendezvous模式下,兩端都要bind並同時連線。

  UDT至少需要一個來回建立連線,這對於頻繁建立連線又斷開的應用是一個瓶頸。

  當UDT_RCVSYN=false(接收不同步)connect會立即返回,並在後臺執行建立連線,執行緒可以用epoll來等待連線完成。

  當失敗是sicket可以重連,失敗的socket也要用close來關閉。

  epoll方法

  方法名

  epoll方法

  功能

  用於有效地對大量的socket輪詢IO事件

  詳細介面

  #ifndef WIN32

  typedef int SYSSOCKET;

  #else

  typedef SOCKET SYSSOCKET;

  #endif

  int epoll_create();

  int epoll_add_usock(const int eid, const UDTSOCKET usock, const int* events=NULL);

  int epoll_add_ssock(const int eid, const UDTSOCKET ssock, const int* events=NULL);

  int epoll_remove_usock(const int eid, const UDTSOCKET usock, const int* events=NULL);

  int epoll_remove_ssock(const int eid, const UDTSOCKET ssock, const int* events=NULL);

  int epoll_wait(const int eid, std::set* readfds, std::set* writefds, int64_t msTimeOut, std::set* lrfds=NULL, std::set* wrfds=NULL);

  int epoll_release(const int eid);

  返回值

  成功:epoll_create返回epoll ID,epoll_wait返回準備好IO的socket數量

  其他三個函式返回0.

  錯誤:返回UDT::ERROR

  描述

  當執行緒需等待很多socket時,應該用epoll代替select和selectEx來查詢。

  它也提供等待系統socket,這樣應用就可以同時接受UDT和TCP/UDP。

  執行緒可以用epoll_create去建立一個epoll ID用epoll_add_usock和epoll_remove_usock去新增和刪除socket,如果已經存在,新增會被忽略。

  新增無效的或者關閉的socket會引發錯誤。刪除不存在的則不會錯誤(忽略)

  在linux上,開發者可以用EPOLLIN(read)和EPOLLOUT(write)以及EPOLLERR(異常)來檢視具體事件。可以建立多個epoll。

  epoll_wait是一個timeout值

  getlasterror方法

  方法名

  getlasterror方法

  功能

  用於獲得一個執行緒最近一次的UDT錯誤

  詳細介面

  ERRORINFO& getlasterror(

  );

  返回值

  成功:返回0

  錯誤:返回UDT::ERROR

  描述

  讀出執行緒中最近一次的錯誤。

  getpeername方法

  方法名

  getpeername方法

  功能

  用於獲得peer side的地址

  詳細介面

  int getpeername(

  UDTSOCKET u,

  struct sockaddr* name,

  int* namelen

  );

  返回值

  成功:返回0,並將地址儲存在name變數中。

  錯誤:返回UDT::ERROR

  描述

  前提是:UDT socket已經建立了連線。

  getsockname方法

  方法名

  getsockname方法

  功能

  用於獲得相關DUT的本地地址

  詳細介面

  int getsockname(

  UDTSOCKET u,

  struct sockaddr* name,

  int* namelen

  );

  返回值

  成功:返回0,並將地址儲存在name變數中。

  錯誤:返回UDT::ERROR

  描述

  呼叫該方法前,UDT socket必須明確地綁定了或隱式地連線了。

  如果該呼叫在bind之後且在connect之前,IP地址會返回用於繫結的地址,如果在連線之後,返回的是peer side看到的地址。

  例如:

  有一個代理地址,連線後,將返回代理IP地址,不是本地地址,但不管哪種情況,返回的埠號是一樣的。

  因為UDP是無連線的,用此呼叫返回0.0.0.0作為IP地址,而UDT是面向連線的,UDT會返回一個有效的IP地址(如果沒有代理)。

  UDT暫時還沒有多重連線功能,當有多個網絡卡時會出錯。

  getsockopt方法和setsockopt方法

  方法名

  getsockopt方法和setsockopt方法

  功能

  用於獲得和設定UDT的配置選項

  詳細介面

  int getsockopt(

  UDTSOCKET u,

  int level,

  SOCKOPT optname,

  char* optval,

  int* optlen

  );

  int setsockopt(

  UDTSOCKET u,

  int level,

  SOCKOPT optname,

  const char* optval,

  int optlen

  );

  返回值

  成功:返回0。

  錯誤:返回UDT::ERROR

  描述

  optname選項描述符

  optval存放設定選項的指標

  optlen是optval的長度

  不是任何時候都可以設定這些選項

  perfmon方法

  方法名

  perfmon方法

  功能

  用於取得內部協議引數和執行追蹤

  詳細介面

  int perfmon(

  UDTSOCKET u,

  TRACEINFO* perf,

  bool clear=true

  );

  返回值

  成功:返回0。並且將追蹤結果存放在trace中。

  錯誤:返回UDT::ERROR

  描述

  追蹤最近一次的記錄,結果寫進TRACEINFO結構中。

  有三種資訊可讀:

  1, 自連線以來的連線總數

  2, 自上次清除counts後的數

  3, 立即引數值

  recv方法和send方法

  方法名

  recv方法和send方法

  功能

  用於讀取資料到本地緩衝區和將執行緒緩衝區中的資料發出去。

  詳細介面

  int recv(

  UDTSOCKET u,

  char* buf,

  int len,

  int flags

  );

  int send(

  UDTSOCKET u,

  const char* buf,

  int len,

  int flags

  );

  返回值

  成功:返回傳送或接受資料的大小。

  錯誤:返回UDT::ERROR

  描述

  recv從協議buf讀len長度的資料,不夠的話,讀取存在的,在阻塞方式下recv等待buf中存入資料,非阻塞方式下立即返回error。

  可以給recv設定UDT_RCVTIMEO

  同理傳送一個大小一定的資料,如果傳送緩衝buff不夠存入一定len長度的資料,則傳送一部分。在阻塞方式下send等待buffer有空餘再發,非阻塞方式下返回錯誤。

  sendfile方法和recvfile方法

  方法名

  sendfile方法和recvfile方法

  功能

  用於傳送部分或全部本地檔案資料和讀出一定數量資料到本地檔案

  詳細介面

  int64_t recvfile(

  UDTSOCKET u,

  fstream& ofs,

  int64_t& offset,

  int64_t size,

  int block=366000

  );

  int64_t sendfile(

  UDTSOCKET u,

  fstream& ifs,

  const int64_t& offset,

  const int64_t size,

  const int block=7320000

  );

  返回值

  成功:返回傳送或接收的資料大小。

  錯誤:返回UDT::ERROR

  描述

  sendfile通常是阻塞方式傳送,UDT_SNDSYN和UDTTIMEO都不影響它的傳送。

  在peer side不一定非要用recvfile接收sendfile

  recvfile呼叫前必須知道資料的大小,否則可能會造成死鎖。

  recvmsg方法和sendmsg方法

  方法名

  recvmsg方法和sendmsg方法

  功能

  用於傳送和接收有效的資料報

  詳細介面

  int recvmsg(

  UDTSOCKET u,

  char* msg,

  int len

  );

  int sendmsg(

  UDTSOCKET u,

  const char* msg,

  int len,

  int ttl=-1,

  bool inorder=false

  );

  返回值

  成功:返回傳送或接收的資料大小。

  錯誤:返回UDT::ERROR

  描述

  recvmsg和sendmsg只能在SOCK_DGRAM模式下。

  接收不夠的message會被丟棄。

  在阻塞方式下和send跟recv一樣需等待buffer

  TTL限定了送達的時間,時間到沒送達將被丟棄。

  inorder引數決定了資料報的有序到達,先前的報文到達後來的才能發。

  銳英源軟體開發經驗

  UDT管理網路通訊,是UDP的增強版本,裡面有可靠的演算法解決了UDP得不可靠性。這些通用的介紹在這裡不詳細講,UDT原始碼特點是本段文章關注的重點。它有如下特點:

  1、用C++方式實現了高效演算法,同時避免了C++的缺點。

  2、合理地設計了類和類關係,是初學者學習面向物件的好目標。

  3、用編譯巨集方式同時支援了Linux和Windows平臺。

  4、指標的巧妙使用和C語言方式的完美結合,提供了基礎資料結構。

  上述特點在銳英源研究時,印象非常深刻,我們銳英源也重視這些特點,製作了完美的剖析視訊,這些視訊對初學者適合,也對入門進階者適用,是提升能力的好選擇。