您现在的位置是:首页
>
中美协定 流控制传输协定详细资料大全
流控制传输协定详细资料大全 流控制传输协定(SCTP,Stream Co trol Tra mi io Protocol)是一种在网路连线两端之间同时传输多个数据流的协定。SCTP提供的服务于UDP和

流控制传输协定详细资料大全
流控制传输协定(SCTP,Stream Control Transmission Protocol)是一种在网路连线两端之间同时传输多个数据流的协定。SCTP提供的服务于UDP和TCP类似。
基本介绍
中文名:流控制传输协定外文名:Stream Control Transmission Protocol 介绍,报文格式,特点,工作过程,建立连线,下线,同时打开连线,同时下线,状态变化图,C++实例,介绍
SCTP提供的服务于UDP和TCP类似。SCTP在RFC2960中详细说明,并有RFC3309加以更新。RFC 3286给出了SCTP的简要介绍。SCTP在客户和伺服器之间提供关联(association),并像TCP那样给套用提供可靠性、排序、流量控制以及全双工的数据传输。SCTP中使用“关联”一词替代“连线”是为了避免这样的内涵:一个连线只涉及两个IP位址间的通信。一个关联指代两个系统之间的一次通信,它可能因为SCTP支持多宿而涉及不止两个地址。 与TCP不同的是,SCTP是面向讯息的(message-oriented)。它提供各个记录的按序递送服务。与UDP一样,由传送端写入的每一条记录的长度随数据一道传递给接收端套用。 SCTP能给在所连线的端点之间提供多个流,每个流各自可靠地按序递送讯息。一个流上某个讯息的丢失不会阻塞同一关联其他流上讯息的投递。这种做法与TCP正好相反,就TCP而言,在单一位元组流中任何位置的位元组丢失都将在阻塞该连线上其后所有数据的递送,直到该丢失被修复为止。 SCTP还提供多宿特性,使得单个SCTP端点能够支持多个IP位址。该特性可以增强应对网路故障的健壮性。一个端点可能有多个冗余的连线,每个网路又可能有各自接入网际网路基础设施的连线。当该端点与另一个端点建立一个关联之后,如果它的某个网路或某个跨域网际网路的通路发生故障,SCTP就可以通过切换到使用已与该关联的另一个地址来避免发生的故障。报文格式
一个SCTP分组含了一个公共的分组头(Common Header)和若干数据块(Chunk),每个数据块中既可以包含控制信息,也可以包含用户数据。SCTP报文格式 除了INIT、INIT ACK和SHUTDOWN COMPLETE数据块外,其他类型的多个数据块可以捆绑在一个SCTP分组中,以满足对MTU大小的要求。当然,这些数据块也可以不与其他数据块捆绑在一个分组中。如果一个用户讯息不能放在一个SCTP分组中,这个讯息可以被分成若干个数据块。 Source Port Number:16比特的无符号整数,源连线埠号,识别SCTP传送端点的SCTP连线埠号。接收方可以使用源连线埠号、源IP位址、目的连线埠号和目的IP位址标识该SCTP分组所属的偶联。 Destination Port Number:16比特的无符号整数,目的连线埠号,为目的端点的SCTP连线埠号。接收主机可以使用目的连线埠号将SCTP分组复用到正确的端点或套用中。 Verification Tag:32比特的无符号整数,验证标签是偶联建立时,本端端点为这个偶联生成一个随机标识。偶联建立过程中,双方会交换这个TAG,到了数据传递时,传送端必须在公共分组头中带上对端的这个TAG,以备校验。包含INIT数据块的分组中验证标签必须为0。在包含SHUTDOWN-COMPLETE数据块且设定了T比特的分组中,验证标签必须要从包含 SHUTDOWN-ACK数据块的分组中复制。在包含ABORT数据块的分组中,验证标签必须要从触发这个ABORT传送的分组中复制。 Checksum:32比特的无符号整数,SCTP通过对用户数据使用ADLER-32算法,计算出一个32位的校验码,带在数据报中,在接收端进行同样的运算,通过检查校验码是否相等来验证用户数据是否遭到破坏。 Chunk Type:8比特的无符号整数,块类型定义在块值(Chunk Value)中讯息所属的类型。包括:INIT、INIT ACK、SACK、ABORT、ERROR、SHUTDOWN、COOKIE ACK等13种数据块类型。该参数的取值范围为0~254,255留作今后的扩展。数据块类型栏位的编码分配如下:
0:净荷数据(DATA)
1:启动(INIT)
2:启动证实 (INIT ACK)
3:选择证实 (SACK)
4:Heartbeat请求(HEARTBEAT)
5:Heartbeat证实(HEARTBEAT ACK)
6:中止 (ABORT)
7:关闭(SHUTDOWN)
8:关闭证实(SHUTDOWN ACK)
9:操作差错(ERROR)
10:状态Cookie(COOKIE ECHO)
11:Cookie证实(COOKIE ACK)
12:为明确拥塞通知回响(ECNE)预留
13:为降低拥塞视窗(CWR)预留
14:关闭完成(SHUTDOWN COMPLETE)
15~62:IETF预留
63:IETF定义的数据块扩展
64~126:IETF预留
127:IETF定义的数据块扩展
128~190:IETF预留
191:IETF定义的数据块扩展
192~254:IETF预留
255:IETF定义的数据块扩展
Chunk type的高两位bit指示了收端不认识对应的chunk type的处理原则:
00:停止处理数据报并丢弃,不再处理报中的其他Chunk。
01:与00相同处理外,还要在ERROR或INIT ACK中上报,原因为不认识的参数类型。
10:忽略该Chunk ,继续处理数据报中的其他Chunk。
11:同10相同处理外,还要在ERROR中上报,原因为不认识的Chunk类型。 Chunk Flags:8比特的无符号整数,块标志位用法由块类型决定。除非被置为其他值,块标记在传送过程中会被置0而且接收端点会忽视块标记。 Chunk Length:16比特的无符号整数,块长度用来表示包括块类型、块标记、块长度和块值在内的位元组数,长度使用二进制表示。 Chunk Value:变长,块值栏位是在该数据块中真正传送的信息,内容由数据块类型决定。块值的长度为不定长。
特点
和TCP类似,SCTP是面向连线、端到端、全双工、带有流量和拥塞控制的可靠传输协定。SCTP的连线称为关联。SCTP的关联通过4次握手建立。相对于TCP的3次握手建立连线,SCTP的关联能够抵御拒绝服务(DoS)攻击,从而提高了安全性。数据只有在关联建立之后与关联关闭之前才可传送。SCTP的关联通过3次握手关闭,不支持类似TCP的半关闭连线。也就是在任何一方关闭关联后,对方即不再传送数据。面向讯息的传输SCTP是一种面向讯息的传输协定,从上层套用传递下来的数据以讯息的形式传输。SCTP提供讯息的定界功能。在接收端,数据以讯息的形式递交。为便于传输,SCTP提供讯息的拆分和组装,以及讯息的捆绑传输功能。SCTP的数据传输基本单位是块。每个SCTP包包括一个SCTP公共头部、一个或多个块。块有两种基本类型:控制块和数据块。控制块用于SCTP的连线控制,包括连线的建立、关闭、传输路径的维护等;数据块传送套用层的用户数据。上层用户的每一个讯息均被封装在一个数据块中,如果讯息长度大于传输路径的最大传输单元(MTU),讯息将被拆分成多个数据块传输,在接收端再组装起来向上层提交,这样每一个SCTP包封装一个数据块。如果讯息长度较小,在1个MTU大小的限制下,在同一个SCTP包里可以捆绑多个讯息,也即多个数据块共用一个公共头部,从而提高传输效率。数据块可以和控制块封装在同一个SCTP包里传输,这种捆绑受MTU大小的限制。
RFC2960定义了13种块类型,包括1种数据块和12种控制块。为实现新功能扩展,可以定义新的块类型。块包括块参数,用于协助完成块功能。块和块参数都是采用类型-长度-值(TLV)的结构定义的。用这种结构定义新的块类型及块参数类型来实现SCTP新功能非常方便。SCTP包括较完善的容错机制,如果通信双方的某一方不支持对端的某项扩展功能,可以通过容错和报错机制保证关联的健壮性。体现了SCTP的良好可扩展性。多穴主机SCTP的一个主要特点是支持多穴主机。SCTP关联的每个端点都可以拥有多个网路层地址。SCTP可以支持不同的网路层协定,为描述方便,本文以IP作为网路层协定来说明,即每个SCTP端点可以拥有多个IP位址用于数据传输。
对多穴主机的支持是为了在网路级提高容错能力。如果接收端是多穴主机,那么对于传送端来说每一个接收端的IP位址代表着一条通往对端的路径,这样传送端可以选择任一条路径来传送数据。SCTP规定任何时间都有一条路径作为首选路径来传送数据,其他路径作为备份路径。如果首选路径因接口故障或者网路拥塞等原因而失效,SCTP可以自动切换到另外一条路径来传送,避免单点失效,从而提高整个关联的容错能力。多穴主机之间的SCTP关联如图所示。主机A到主机B有两条路径,A是多穴主机,这里只考虑不同的IP位址对应不同的网路接口的情况,A有两个网路接口,选择哪个接口来传送数据是A自身的路由策略问题(源地址选择问题)。多流 SCTP的另一主要特点是多流。SCTP讯息在不同的流内传送,这也是流传输控制协定名称的由来。从传送端到接收端可以有多个流,在同一流内传送的讯息有序,而不同流之间的讯息无序,因此不同流之间的讯息传输是相对独立的。在某一个流内由于数据传输失败而引起的阻塞不会影响其他流的讯息递交。多流特性可以帮助解决TCP中的队头阻塞(HOL)问题。因为TCP传输是按位元组严格有序的,先行传送的位元组如果丢失或损坏,即使后续的位元组正确地被接收到也不能向上层递交,必须在接收端缓冲起来,直到先行位元组由于重传而全部正确接收到后才可以提交,并且释放缓冲区。 图描述了一个用3个流传送讯息的SCTP实例。传送端有3个出流,相应的接收端就有3个入流。图2中给出了传送从1到9的9个讯息实例。其中讯息1、2、3在同一个流,4、5、6在同一个流,7、8、9在同一个流,分别在各自流内有序。由于讯息1没有正确接收,造成讯息2、3不能向上层协定(ULP)提交。然而从4到9的讯息由于在不同的流中,则可以提交给ULP。这种流机制提供了无序递交功能,提高了传输效率。 此外,SCTP还定义了无序讯息。如果讯息带有无序标志,则不论它在哪个流中(在具体实现中,数据块中的流号不被解析),只要被正确接收,都提交给ULP,从而实现和流无关的无序递交。流量、拥塞和错误的控制 SCTP仍然采用类似TCP的流量控制和拥塞控制机制,但又有所增强。整个传输分为慢启动阶段和拥塞避免阶段。与TCP不同的是,SCTP的拥塞视窗初始值可以是2个MTU,可以比TCP获得更快的视窗增长。SCTP的拥塞控制采用了选择确认(SACK)快速重传和快速恢复机制,是TCP各种主流改进机制的集成。但是由于SCTP采用了块结构和控制块机制,可以比TCP更大地提升传输性能。例如SCTP在移动通信的切换中表现得比TCP SACK更优越[4]。 由于SCTP有多个通往对端的路径,在传送端对每一个路径都有一套拥塞控制参数和控制用的数据结构。这类似于有多个通往对端的TCP连线,SCTP为多条路径的流量控制和拥塞控制提供统一的管理机制。讯息可以在不同的路径上传输,流管理和路径管理是正交的,即相对独立。
每个路径有一个错误计数器,当某一路径上的错误达到一个门限时,该路径将会被标记为不活动的(Inactive),SCTP把传输转移到另一条路径上进行。同时SCTP对整个关联设定一个错误计数器,每个路径上的错误计数时,整个关联的错误计数也要增加,只要对端返回确认,则关联错误计数器清零(不管是对哪条路径返回的确认)。如果关联错误计数器达到一个门限值,则整个关联被非正常关闭。由此可见,多路径带来比TCP更好的网路级容错机制。
工作过程
建立连线
不同于TCP,SCTP通过四次握手来完成连线的建立: 连线发起者(一般为客户端)SCTP传送一个INIT讯息(初始化)。该讯息包括了连线发起者的IP位址清单、初始序列号、用于标识本耦联中所有报文的起始标记、客户请求的外出流的数目以及客户能够支持的外来流的数目 对端(伺服器)传送一个INITACK讯息确认连线发起者的INIT讯息,其中含有伺服器的IP位址清单、初始序列号、起始标记、伺服器请求的外出流的数目、伺服器能够支持的外来流的数目以及一个状态cookie,状态cookie包含伺服器用于确信本耦联有效所需的所有状态,cookie是经过数字签名的,因而可以确保其有效性 客户以一个COOKIEECHO讯息返回伺服器的状态cookie,除COOKIEECHO外,该讯息可能在同一个报文中捆绑一个用户数据 伺服器以一个COOKIEACK讯息确认客户返回的cookie是正确的,到此时该耦联就建立成功了。该讯息也可能在同一个报文中捆绑一个用户数据。 在一次SCTP四次握手中,INIT讯息的接收端不必保存任何状态信息或者分配任何资源,这样就可防范SYNFlooding等DoS攻击。它在传送INIT-ACK讯息时,采用了一种机制——“状态Cookie”,该Cookie具有传送端要建立自己状态所需的全部信息。 用于建立连线的INIT ACK只能在COOKIE WATI状态收到,在其它状态收到该报文时都会直接丢弃,类似的,COOKIE ACK只能在COOKIE ECHOED状态接收。 在常规的握手中,主动发起方的本地tag在发起握手时产生,主动发起方的对端tag在收到INIT ACK时产生。而连线的被动方的本地tag和对端tag都在收到INIT时产生,但是最终要到收到了COOKIE ECO后才确定并保存下来。 SCTP产生一个状态Cookie的过程如下: 使用收到的INIT和发出的INIT-ACK块中的信息创建一个关联的TCB(传输控制块)。 在TCB中,将当前日期设为创建日期,将协定参数“有效Cookie时间”设为生存期间。 根据TCB,收集重建TCB所需的最小信息子集,将该子集和密钥产生一个MAC(信息认证编码)。 结合上述最小信息子集和MAC产生状态Cookie。 在传送完INITACK(包含状态Cookie参数)后,传送方必须删除TCB以及任何与新关联有关的本地资源。 INIT和INIT-ACK都必须包含建立初始状态所需的参数:一组IP位址,保证可靠传输的初始序列号,每个被接收的SCTP报文中必须含有的验证标签,每一端请求发出的流数目和每一端能支持接收的流数目。交换完这些讯息之后,INIT的传送端以COOKIE-ECHO讯息的方式传送回状态Cookie。接收端根据所接收到的COOKIE-ECHO中的状态Cookie,完整地重建自己的状态,并回送COOKIE-ACK来确认关联已建立。 因此对于SCTP,即使接收再多的INIT讯息,接收端也没有任何资源的消耗:它既不分配任何系统资源,也不保存此次新关联的状态,它只是把相应重建状态所用的状态Cookie作为参数,包含在每一个回送的INIT-ACK讯息中,最后该状态Cookie会被COOKIE-ECHO讯息传送回来。 类似于TCP,SCTP也多由客户端执行主动打开,而伺服器执行被动打开。下线
与TCP不同,SCTP使用三次握手来关闭一个耦联。而且SCTP不支持TCP所支持的“半关闭”状态。典型的SCTP关闭一个耦联的过程如下: 应用程式发出关闭请求,SCTP耦联进入SHUTDOWN-PENDING状态,并且不再接收应用程式的数据,只传送伫列中还未传送的数据,再伫列中没有待传送数据后,传送SHUTWODN并进入SHUTDOWN-SENT状态。这一方被称为主动关闭。 执行被动关闭的一方在接收到主动关闭一方的SHUTWODN讯息时,进入SHUTDOWN-RECEIVED状态,此时执行被动关闭一方不再接受上层套用的数据,只传送伫列中剩余的数据。在传送伫列中的数据被传送完后,执行被动关闭一方传送SHUTDOWN-ACK并进入SHUTDOWN-ACK-SENT状态。 执行主动关闭一方收到SHUTDOWN-ACK后就传送SHUTDOWN-COMPLETE,并进入CLOSE状态。 执行主动关闭一端接收到SHUTDOWN-COMPLETE后就进入close状态。同时打开连线
RFC规定,如果SCTP在COOKIE-WAIT或者COOKIE-ECHOED状态接收到INIT报文。则: INIT报文的接收者产生一个INIT-ACK,该INIT-ACK使用的本端参数和自己传送的那个INIT报文的相同 执行状态COOKIE的计算过程,产生一个状态COOKIE 不允许修改SCTP的状态 状态COOKIE相关的TCB不能删除 不关闭T1-init定时器 如果SCTP在非COOKIE-WAIT状态接收到了INIT-ACK,则丢弃它。同时下线
极少数情况下,耦联的双发可能同时执行主动关闭,即同时进入传送SHUTWODN并进入SHUTDOWN-SENT状态。在这种情况下关闭的流程为: 两端都传送SHUTWODN并进入SHUTDOWN-SENT状态 两端都收到对方的SHUTDOWN讯息,并传送SHUTDOWN-ACK,然后进入SHUTDOWN-ACK-SENT状态 两端都收到对方的SHUTDOWN-ACK,并传送SHUTDOWN-COMPLETE,然后就进入close状态状态变化图
SCTP的状态迁移图如下所示。C++实例
下面展示一个通过C++编写的、用SCTP的一到多实现的一个回显伺服器。 1、服务端 Server.h#pragma once#include <sys/socket.h>#include <i/in.h>#include <i/sctp.h>#define SERVER_PORT 6666#define BUFFER_SIZE 1024#define LISTEN_QUEUE 100class SctpServer { public: SctpServer(); void start(void); private: 开启监听socket void listenSocket(void); 循环处理请求 void loop(void); int sockFd_; 用来接受的套接字 int messageFlags_; 讯息类型 char readBuf_[BUFFER_SIZE]; 接受缓冲区 struct sockaddr_in clientAddr_; 用来保存客户端地址 struct sockaddr_in serverAddr_; 用来保存服务端地址 struct sctp_sndrcvinfo sri_; 讯息相关细节信息 struct sctp_event_subscribe events_; 事件集 int streamIncrement_; 流号 socklen_t len_; 地址长度 size_t readSize_; 读到的大小}; Server.cpp#include "server.h"#include <unistd.h>#include <ftl.h>#include <string.h>#include <stdio.h>#include <arpa/i.h>SctpServer::SctpServer() :streamIncrement_(1){}void SctpServer::listenSocket(void){ 创建SCTP套接字 sockFd_ = socket(AF_INET,SOCK_SEQPACKET,IPPROTO_SCTP); bzero(&serverAddr_,sizeof(serverAddr_)); serverAddr_.sin_family = AF_INET; serverAddr_.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr_.sin_port = htons(SERVER_PORT); i_pton(AF_INET,"127.0.0.1",&serverAddr_.sin_addr); 地址绑定 bind(sockFd_,(struct sockaddr *)&serverAddr_,sizeof(serverAddr_)); 设定SCTP通知事件(此处只设定了I/O通知事件) bzero(&events_,sizeof(events_)); events_.sctp_data_io_event = 1; setsockopt(sockFd_,IPPROTO_SCTP,SCTP_EVENTS,&events_,sizeof(events_)); 开始监听 listen(sockFd_,LISTEN_QUEUE);}void SctpServer::loop(void){ while(true) { len_ = sizeof(struct sockaddr_in); 从socket读取内容 readSize_ = sctp_recvmsg(sockFd_,readBuf_,BUFFER_SIZE, (struct sockaddr *)&clientAddr_,&len_,&sri_,&messageFlags_); 增长讯息流号 if(streamIncrement_) { sri_.sinfo_stream++; } sctp_sendmsg(sockFd_,readBuf_,readSize_, (struct sockaddr *)&clientAddr_,len_, sri_.sinfo_ppid,sri_.sinfo_flags,sri_.sinfo_stream,0,0); }}void SctpServer::start(void){ listenSocket(); loop();} main.cpp#include "server.h"int main(int argc,char **argv){ SctpServer server; server.start(); return 0;} 2、客户端 Client.h#pragma once#include <sys/socket.h>#include <i/in.h>#include <i/sctp.h>#include <string.h>#include <unistd.h>#include <ftl.h>#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <arpa/i.h>#define SERVER_PORT 6666#define MAXLINE 1024void sctpstr_cli(FILE *fp,int sock_fd,struct sockaddr *to,socklen_t tolen);class SctpClient{ public: SctpClient():echoToAll_(0) { } ~SctpClient() { close(sockFd_); } 启动客户端 void start(void) { makeSocket(); } private: void makeSocket(void) { sockFd_ = socket(AF_INET,SOCK_SEQPACKET,IPPROTO_SCTP); bzero(&serverAddr_,sizeof(serverAddr_)); serverAddr_.sin_family = AF_INET; serverAddr_.sin_addr.s_addr = htonl(INADDR_ANY); serverAddr_.sin_port = htons(SERVER_PORT); i_pton(AF_INET,"127.0.0.1",&serverAddr_.sin_addr); bzero(&events_,sizeof(events_)); events_.sctp_data_io_event = 1; setsockopt(sockFd_,IPPROTO_SCTP,SCTP_EVENTS,&events_,sizeof(events_)); if(echoToAll_ == 0) { sctpstr_cli(stdin,sockFd_,(struct sockaddr *)&serverAddr_,sizeof(serverAddr_)); } } int sockFd_; struct sockaddr_in serverAddr_; struct sctp_event_subscribe events_; int echoToAll_;};循环传送并接受讯息void sctpstr_cli(FILE *fp,int sock_fd,struct sockaddr *to,socklen_t tolen){ struct sockaddr_in peeraddr; struct sctp_sndrcvinfo sri; char sendline[MAXLINE]; char recvline[MAXLINE]; socklen_t len; int out_sz,rd_sz; int msg_flags; bzero(&sri,sizeof(sri)); while(fgets(sendline,MAXLINE,fp) != NULL) { if(sendline[0] != '[') { printf("ERRORn"); continue; } sri.sinfo_stream = sendline[1] - '0'; out_sz = strlen(sendline); 传送讯息 int count = sctp_sendmsg(sock_fd,sendline,out_sz,to,tolen,0,0,sri.sinfo_stream,0,0); len = sizeof(peeraddr); rd_sz = sctp_recvmsg(sock_fd,recvline,sizeof(recvline), (struct sockaddr *)&peeraddr,&len,&sri,&msg_flags); printf("From str:%d seq:%d (assoc:0x%x):", sri.sinfo_stream,sri.sinfo_ssn,(u_int)sri.sinfo_assoc_id); printf("%d %sn",rd_sz,recvline); }} Client.cpp#include "client.h"int main(int argc,char **argv){ SctpClient client; client.start(); return 0;} 很赞哦! (1043)