简单可靠传输协议(SRT)

本篇文章主要介绍简单可靠传输的原理实现,我们只考虑由连接管理使用的控制,其它部分将在另外几篇博文中介绍。为了完成SRT的测试,我们需要编写一个客户端和一个服务器。

就像TCP一样,SRT提供按顺序字节流的可靠传递。这里SRT做出了一些假设,以简化协议的设计和实现。

  • 单向传输,即DATA段从客户端流向接受端
  • 连接管理:客户端发起和断开连接
  • 不支持流量控制或拥塞控制

SRT拥有以下机制

  • 连接管理:连接和断开
  • 校验和用于检测接受的数据是否被破坏
  • 回退N步用于在接收端可靠地传送数据,序列号用于检测丢失的数据和将数据重新排序
  • 重传:客户端使用超时和ACK来重传丢失的数据和控制分组(例如SYN)

API:SRT,SNP和覆盖控制

上一篇博文中,我们看到在DartNet协议栈的不同层之间有许多API。在实现SRT过程中,我们不使用SNP层转发,专注于客户端和服务器的实现与SRT API相关联的一组有限功能 。只考虑客户端和服 务器之间的连接相关的函数调用。SRT像TCP一样能够支持多个同时连接。我们将讨论数据结构和有限状态机(FSM)需要在本文后面支持这一点。

没有SNP时,SRT直接位于覆盖层上。在这种情况下,覆盖层简单地实现客户端和服务器SRT传输协议之间的TCP连接。我们的覆盖层也只包含客户端和服务器之间的直接TCP连接。

overlay_start()和 overlay_stop()函数创建客户端和服务器之间的TCP直接连接。overlay_start()返回TCP套接字描述符。overlay_stop()关闭TCP连接。

首先需要实现的是snp _sendseg()和 snp_recvseg()函数,以使用覆盖层的TCP连接发送和接段
。在TCP数据作为字节流发送的情况下。为了通过覆盖层TCP连接发送数据段,我们需要在字节流中添加分隔符。

在SRT中,使用特殊字符“!&”表示段的开始,特殊字符“!#”表示段的结束。这些分隔符不应该出现在客户端和服务之间发送的数据中。这是一个限制(并且如果“!&”或“!#”发生在客户端的字节流中,协议将不正确地操作,因此应该确保数据不包括这些特殊控制字符对)。分隔符是必要的,因为STR分组的接收侧(即STR标头+ STR段)需要知道它何时成功接收到SRT分组,这是一种方法。
可能是最简单的方法。

因为覆盖层是从TCP构建的,而不是说UDP,我们没有任何损失。为了允许覆盖看起来像在有损链路上运行,并且分组可以在因特网中丢失,我们在接收到SRT分组时在snp _recvseg()中调用丢失 loss()函数,以丢弃具有概率PROBABILITY_LOSS的分段来模拟分组丢失以迫使SRT恢复丢失的分段,其可以是SRT DATA分组 ,SYN, SYNACK,FIN和FINACK。 SRT协议必须能够正确地从任何这些数 据包的丢失中恢复。

图3:这些是DartNet API函数调用的子集
Lab4 API

功能的流程如下
app_client.c和app_server.c,应用程序层代码首先通过在overlay_start()中的客户端和服务器之间创建一个直接TCP链接来启动覆盖层。在app _client和 app _server中实 现overlay_start()。 服务器通过调用srt _svr _init()初始化SRT服务器,客户机通过调用srt _client _init()初始化 SRT客户机。

然后服务器调用srt _svr _sock()创建服务器端套接字,srt _svr _accept()接受来自客户端的连 接请求(SRT层的控制消息SYN)客户端创建一个套接字,并分别通过调用srt _client _sock() 和 srt _client _connect()连接到服务器套接字。

客户端和服务器在不同端口上建立两个连接,所以SRT可以处理多个连接:服务器创建另一个套接字并等待传入连接,客户端创建另一个套接字并连接到新的服务器套接字。通过这样做,SRT可以同时有多个连接。

客户端在等待时间之后通过为每个连接调用srt _client _disconnect()断开与服务器的连接。最 后,服务器通过调用srt _svr _close()关闭套接字,客户端通过调用srt _client _close()关闭 套接字。

该app_client和app_server通过调用终止其进程之前停止叠加层over_end()在客户端和服务器。实现了overlay_stop(),而且还实现了srt˙client.c和 srt_server.c中使用的所有SRT 函数。

数据结构

SRT定义了实现将数据控制和分组成段的多个重要数据结构。
SRT分组包括SRT头(参见下面的srt_hdr_t)和SRT段(参见seg_t-数据字节流在这里)。SRT分组被封装到SNP分组中,当我们进入SNP时我们将讨论该SNP分组。注意,SRT报头包括与分组相关联的重要信息,包括例如无符号短整型类型; 。

报头中的type字段定义数据包的类型:

#define  SYN  0 
#define  SYNACK  1 
#define  FIN  2 
#define  FINACK  3 
#define  DATA  4 
#define  DATAACK  5

SRT协议以段为单位发送和接收数据。
图4:段头格式
SRT Segment Header

它在seg.h中定义:

typedef struct srt_hdr {
  unsigned int src_port;        //source port number
  unsigned int dest_port;       //destination port number
  unsigned int seq_num;         //sequence number
  unsigned short int length;    //segment data length
  unsigned short int  type;     //segment type
  unsigned short int  rcv_win;  //currently not used
  unsigned short int checksum;  //currently not used
} srt_hdr_t;
typedef struct segment {
  srt_hdr_t header;
  char data[MAX_SEG_LEN];
} seg_t;

SRT使用传输控制块(TCB)维护与连接相关联的所有状态。对于每个连接,客户端和服务器端初始化并维护TCB。当连接关闭时,TCB返回到未使用的TCB池。下图显示了为每个连接维护的服务器端TCB。当srt _svr _init()和 srt _client _init()分别由server_App 和client_App 调用时,TCB表 在启动时在客户端和服务器上初始化。在编码SRT时,始终对数据结构和数据结构表使用malloc()/ free()。

typedef struct svr_tcb {
  unsigned int svr_nodeID;        //node ID of server, similar as IP address
  unsigned int svr_portNum;       //port number of server
  unsigned int client_nodeID;     //node ID of client, similar as IP address
  unsigned int client_portNum;    //port number of client
  unsigned int state;             //state of server
  unsigned int expect_seqNum;     //sequence number of the data seg server is expecting
  char* recvBuf;                  //a pointer pointing to a receive Buffer
  unsigned int  usedBufLen;       //size of received data in receiver buffer
  pthread_mutex_t* bufMutex;      //a pointer pointing to a mutex for receive buffer access
} 
svr_tcb_t;

对于每个连接,SRT客户端维护客户端和服务器TCB

typedef struct client_tcb {
  unsigned int svr_nodeID;        //node ID of server, similar as IP address
  unsigned int svr_portNum;       //port number of server
  unsigned int client_nodeID;     //node ID of client, similar as IP address
  unsigned int client_portNum;    //port number of client
  unsigned int  state;            //state of client
  unsigned int next_seqNum;       //next sequence number
  pthread_mutex_t* bufMutex;      //a pointer pointing to a mutex for send buffer access
  segBuf_t* sendBufHead;          //used by send buffer
  segBuf_t* sendBufunSent;        //used by send buffer
  segBuf_t* sendBufTail;          //used by send buffer
  unsigned int unAck_segNum;      //number of unAcked segments
} client_tcb_t;

连接管理

SRT具有与TCP类似的连接设置和断开。SRT只是使用两次握手。

连接设置
当客户端发送SYN并且服务器用SYNACK响应时建立连接

图5:SRT连接设置
SRT Connection Setup

But life is not that simple: corruption and loss of control messages get in the way

设置和断开时可能发生的以下问题:在SYN中发生丢失或损坏;客户端已经发送,服务器却不知道。这个问题如何解决?如果服务器接收到SYN,在发送SYNACK过程中丢失,会发生什么?

这些异常的解决方案如下:在丢失SYN的情况下,str_client需要设置SYN_TIMEOUT,如果在该周期内没有收到SYN ACK,则它会发送另一个SYN并再次设置定时器。它重复此操作,直到它获得SYNACK或超过SYN_MAX_RETRY。在该状态下,它很可能接收SYN。

SRT连接断开
当客户端发送FIN并且服务器相应地使用FINACK进行响应时,采取(断开)连接

图6:SRT连接断开
SRT SRT Connection Teardown

但是我们可以在这里丢包。在FIN中发生了什么?客户端需要在FIN_TIMEOUT之后发送另一个FIN,直到FIN_MAX_RETRY。如果FINACK丢失会发生什么?客户端必须等待重传FIN_MAX_RETRY以尝试获得FINACK。如果它没有得到FINACK,它在等待期后转换到CLOSE状态,并假定连接有时间以有序的方式关闭。

有限状态机(FSM)

客户端和服务器端实现FSM。
FSM由有限数量的状态组成。状态转换由状态机和EVENT / ACTION控制。基本上,在例如在客户端 FSM上的状态下,例如,假设其处于状态 = SYNSENT并且发生事件 SYN_TIMEOUT,则适当的ACTION 发送SYN。检查客户端FSM,看看是否是这种情况。

客户端FSM
在客户端TCB中在结构的“状态”元素中维护的状态如下:

//states of client
#define CLOSED 1
#define SYNSENT 2
#define CONNECTED 3
#define FINWAIT 4

当实现协议时,您将需要咨询这里所示的状态机,以确定客户端和服务器的状态转换,因为在连接建立和拆除期间交换段。这里SRT服务器状态机如图2所示。8。SRT客户端状态机如图1所示。
SRT客户端FSM
SRT Client FSM

在服务器TCB中在结构的“状态”元素中维护的状态如下:

//states of server
#define CLOSED 1
#define LISTENING 2
#define CONNECTED 3
#define CLOSEWAIT 4

服务器端FSM
SRT Server FSM