测试小站: 处理网 回收帮 培训网 富贵论坛 老富贵论坛

基于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语言方式的完美结合,提供了基础数据结构。

  上述特点在锐英源研究时,印象非常深刻,我们锐英源也重视这些特点,制作了完美的剖析视频,这些视频对初学者适合,也对入门进阶者适用,是提升能力的好选择。

posted @ 2021-12-14 12:45  linjingyg  阅读(900)  评论(0)    收藏  举报