SCTP是一个较新的传输协议,于2000年在IETF(Internet Engineering Task Force,互联网工程任务组)得到标准化(而TCP是在1981年标准化的)。它最初是为满足不断增长的IP电话市场而设计,即穿越因特网传输电话信令。它设计实现的需求在RFC 2719中说明。SCTP是一个可靠的面向消息的协议,在端点之间提供多个流,并为多宿提供传输级支持。作为一个新协议,它没有TCP和UDP那样无处不在,但它提供了一些可能简化特定应用程序设计的新特性。
尽管SCTP和TCP之间存在一些本质性区别,但SCTP的一到一接口与TCP提供的应用接口很接近,这一点可以轻易地移植应用程序,但不能使用SCTP的某些高级特性。SCTP的一到多接口提供了这些特性的完全支持,但可能需要费时费力地重新编写已有应用。对大多数使用SCTP开发的新应用而言,推荐使用一到多接口。
用SCTP开发的应用如果想在各种平台上使用,需要同时考虑使用TCP,以应对SCTP不可用的系统。
SCTP套接字分为一到一套接字和一到多套接字。一到一套接字对应一个单独的SCTP关联,一个SCTP关联是两个系统之间的连接,但可能由于多宿原因,每个端点涉及不止一个IP地址,类似于TCP套接字和TCP连接的对应关系。对于一到多套接字,一个给定套接字上可以同时有多个活跃的SCTP关联,类似于绑定了特定端口的UDP套接字能从若干个同时在发送数据的远程UDP端点接收数据报。
决定使用哪种接口形式时,需考虑的因素:
1.所编写的服务器是迭代的还是并发的。
2.服务器希望管理多少套接字描述符。
3.优化关联建立的四路握手过程,使其能在第三个或第四个分组交换用户数据是否是重要的。
4.应用希望维护多少个连接状态。
开发SCTP的套接字API期间,这两种形式的套接字曾用过别的称谓,有时可能会碰到旧名称,一到一套接字曾被称为TCP风格套接字,一到多套接字曾被称为UDP风格套接字。
这些旧的名字被取消了,因为它们会造成混淆,即SCTP可能被误解成其行为更像TCP或UDP,事实上这些旧名字仅仅说明了TCP套接字和UDP套接字在一个方面的差异(即是否支持多个并发的传输层关联)。它们目前的名字(一到一、一到多)集中体现了这两种套接字形式之间的关键差异。有些人用多到一代替一到多,两者可以互换。
一到一形式的目的是方便将现有TCP应用移植到SCTP上,以下是一到一形式的SCTP和TCP的差异:
1.任何TCP套接字选项必须转换成等效的SCTP套接字选项,较常见的选项是TCP_NODELAY和TCP_MAXSEG,它们应映射成SCTP_NODELAY和SCTP_MAXSEG。
2.SCTP保存消息边界,因此应用层消息边界非必需。例如,基于TCP的某应用协议可能先执行一个双字节write调用,给出消息长度x,再调用一个x字节的write系统调用,读出数据本身。改用SCTP后,接收端SCTP将收到两个独立的消息,需要两次read系统调用才能返回全部数据:第一次返回一个双字节数据,第二次返回一个x字节的消息。
3.有些TCP应用使用半关闭来告知对端去往它的数据流已经结束,这样的应用移植到SCTP需要额外重写应用层协议,让应用在应用数据流中告知对端传输数据流已结束。
4.send函数能以普通TCP方式使用,使用sendto或sendmsg函数时,指定的任何地址都将覆盖原目的地主地址。
一到一式SCTP套接字是一个类型为SOCK_STREAM,协议为IPPROTO_SCTP的网际网套接字(即协议族为AF_INET或AF_INET6)。
一到多形式编写的服务器程序无需管理大量的套接字描述符,单个套接字描述符将代表多个关联,就像一个UDP套接字能从多个客户接收消息那样。一到多式套接字上,用于标识单个关联的是一个关联标识,关联标识是一个类型为sctp_assoc_t的值,通常是一个整数,它是一个不透明的值,应用不应使用不是由内核先前给予的任何关联标识。
一到多式套接字用户应掌握:
1.当一个客户关闭其关联时,其服务器也将自动关闭同一个关联,服务器主机内核中不再有该关联的状态。
2.只有一到多式套接字才能在四路握手的第三个或第四个分组中捎带用户数据。这是因为一到一式套接字客户使用connect函数显式建立关联,但该函数没有指定要发送数据的参数,因此无法在四路握手的第三个分组中随COOKIE ECHO消息携带数据。而一到多式套接字客户不必先建立连接再发送数据,而是可以调用sctp_sendto函数同时完成两者,这样发送的数据会随COOKIE ECHO消息送到对端,这样的关联是隐式建立的。对于第四次握手中的数据,需要对端在连接建立前就准备好要发送的数据,即两端都使用一到多式套接字几乎同时发送数据隐式建立连接,这种关联建立称为INIT冲突。
3.对于一个还没有与其进行关联的IP地址,任何以它为目的地的sendto、sendmsg、sctp_sendmsg调用将导致对主动打开的尝试,如果成功的话,会建立一个与该地址的新关联。即使应用已经调用过listen函数来请求被动打开,主动调用send也会出现此行为。
4.用户必须使用sendto、sendmsg、sctp_sendmsg这三个分组发送函数,而不能使用send或write这两个分组发送函数,除非已经使用sctp_peeloff函数从一个一到多式套接字剥离出一个一到一式套接字。
5.调用分组发送函数时,所用的目的地址是由系统在关联建立阶段选定的主目的地址,除非调用者在所提供的sctp_sndrcvinfo结构中设置了MSG_ADDR_OVER标志,为提供这个标志,调用者必须使用伴随辅助数据的sendmsg函数或sctp_sendmsg函数。
6.关联事件(众多SCTP通知之一)可能被启用,如果应用进程不希望收到这些事件,得使用SCTP_EVENTS套接字显式禁止它们。默认情况启用的唯一事件是sctp_data_io_event,它给recvmsg和sctp_recvmsg函数提供辅助数据,这个默认设置同时适用于一到一式和一到多式。
最初开发SCTP的套接字API时,一到多式接口被定义成默认情况下开启所有关联事件通知,后续版本禁止了一到一式和一到多式接口处sctp_data_io_event外的所有事件通知。并非所有实现都具有这样的行为,最好显式禁止或启用不想要的或想要的通知,以确保良好的移植性。
上图中,服务器调用listen以允许客户建立关联,然后服务器就调用sctp_recvmsg阻塞于等待消息到达,客户启动后也打开一个套接字,并调用sctp_sendto,它导致隐式建立关联,而用户请求数据由四路握手的第三个分组捎带给服务器,服务器收到请求后进行处理,然后向该客户发回一个应答。客户收到应答后关闭其套接字,从而终止其上的关联。然后服务器循环等待接收下一个消息。
上图是一个迭代服务器,来自许多关联(即客户)的可能交错的消息由单个控制线程处理。一个一到多套接字也能结合sctp_peeloff函数以允许组合迭代服务器模型和并发服务器模型,两个服务器模型的关系如下:
1.sctp_peeloff函数用于从一个一到多套接字剥离出某个特定关联(如一个长期持续的会话),独自构成一个一到一式套接字。
2.剥离出的关联所在的一到一套接字可以送给它自己的线程,或为它派生一个进程。
3.此时,主线程继续在原来的套接字上以迭代方式处理剩余关联。
一到多式SCTP套接字是一个类型为SOCK_SEQPACKET,协议为IPPROTO_SCTP的网际网套接字(即协议族为AF_INET或AF_INET6)。
SCTP服务器可能希望捆绑所在主机所有IP地址的一个子集,TCP或UDP服务器要么捆绑所在主机的某个地址,要么捆绑所有地址,而不能捆绑这些地址的一个子集,这种情形发生在:
1.
sctp_bindx函数允许SCTP套接字捆绑一个特定地址子集:
sockfd参数是由socket函数返回的套接字描述符。addrs参数是一个指向紧凑的地址列表的指针,其中每个套接字地址结构紧跟在前一个套接字地址结构之后,中间没有填充字节:
addrcnt参数指定传递给sctp_bindx函数的地址个数。
flags参数指定是添加操作还是删除操作:
sctp_bindx函数既可用于已绑定套接字,也可用于未绑定套接字。对于未绑定的套接字,sctp_bindx函数将把给定的地址集合捆绑到其上;对于已绑定套接字,若指定SCTP_BINDX_ADD_ADDR则把额外的地址加入到套接字描述符,若指定SCTP_BIND_REM_ADDR则从套接字描述符的已加入地址中移除给定地址。如果在一个监听套接字上调用sctp_bindx,那么将来产生的关联将使用新的地址配置,已经存在的关联不受影响(如果两端点不支持动态地址特性)。传递给sctp_bindx函数的两个标志时互斥的,如果同时指定,调用会失败,返回的错误码是EINVAL。所有套接字地址结构的端口号必须相同,且必须与已经绑定的端口号相匹配,否则调用会失败,返回EINVAL错误码。
如果一个端点支持动态地址特性,指定SCTP_BINDX_ADD_ADDR或SCTP_BINDX_REM_ADDR标志调用sctp_bindx会导致该端点向对端发送一个消息,以修改对端的地址列表。增减一个已连接关联的地址只是一个可选功能,因此不支持本功能的实现将返回EOPNOTSUPP,本功能要求两端都支持这个特性。本特性对于支持动态接口供给的系统可能有用,如果调出一个新的以太网接口,则应用进程可以指定SCTP_BINDX_ADD_ADDR标志在已经存在的连接上启动这个接口。
sctp_connectx函数用于连接到一个多宿对端主机,该函数在addrs参数中指定addrcnt参数个全部属于同一对端的地址。addrs参数是一个紧凑的地址列表。SCTP栈使用其中一个或多个地址建立关联,在addrs参数中的所有地址都被认为是有效的经过证实的地址。
getpeername函数不是为支持多宿概念的传输协议设计的,当用于SCTP时,它仅返回主目的地址,如果要知道对端的所有地址,需要使用sctp_getpaddr函数:
sockfd参数是由socket函数返回的套接字描述符。id参数是一到多式套接字的关联标识,函数会忽略一到一式套接字的此字段。addrs参数是一个地址指针,地址内容是由本函数动态分配并填入的紧凑的地址列表,用完地址后,调用者应使用sctp_freepaddrs函数释放所分配的资源。
addrs参数是由sctp_getpaddrs函数通过addrs参数返回的指针指向的指针。
sctp_getladdrs函数用于获取属于某个关联的所有本地地址:
sockfd参数是由socket函数返回的套接字描述符。id参数是一到多式套接字的关联标识,函数会忽略一到一式套接字的此参数。addrs参数是一个地址指针,地址内容是由本函数动态分配并填入的紧凑的地址列表。用完获取的地址后,调用者应调用sctp_freeladdr释放所分配的资源。
addrs参数是指向由sctp_getladdrs函数通过addrs参数返回的指针指向的指针。
通过使用伴随辅助数据的sendmsg函数,应用进程能控制SCTP的各种特性,但使用辅助数据不太方便,许多SCTP实现提供了一个辅助的函数库调用(有可能作为系统调用实现),以方便使用SCTP的高级特性:
sctp_sendmsg函数以指定更多参数为代价简化了发送方法。sockfd参数是由socket函数返回的套接字描述符。msg参数指向一个长度为msgsz参数字节的缓冲区,其中的内容会发送给对端端点参数to,tolen参数指定存放在参数to中的地址长度。ppid参数会与消息一起传递到远端,用于指定消息的协议类型或其他应用定义的含义。flags参数将传给SCTP栈,用以标识SCTP选项。
调用者需要在stream参数中指定一个SCTP流号,流是SCTP关联中的逻辑通道,可以保证同一流中的消息按序到达。可以在lifetime参数中以毫秒为单位指定消息的生命期,0表示无限生命期。context参数用于指定可能有的用户上下文,用户上下文可通过消息通知机制获取某次发送失败的消息的该上下文值,以确定是什么消息发送失败了。
如果要发送一个消息到流号1,发送标志设为MSG_PR_SCTP_TTL,生命期设为1000毫秒,消息净荷协议标识符为24,上下文为52,则调用格式为:
这种方法比分配辅助数据空间并在msghdr结构中设置合适的结构要简单。如果实现把sctp_sendmsg函数映射为sendmsg函数,则sendmsg函数的flags参数被设为0。
与sctp_sendmsg函数一样,sctp_recvmsg函数也为SCTP的高级特性提供了一个更方便的用户接口,用此函数不仅能获取对端地址,也能获取通常伴随recvmsg函数返回的msg_flags参数(如MSG_NOTIFICATION、MSG_EOR等)。本函数也能获取已读入消息缓冲区中的伴随所接收消息的sctp_sndrcvinfo结构(要想获取sctp_sndrcvinfo结构,必须使用SCTP_EVENTS套接字选项预定sctp_data_io_event,默认情况下是开启的)。
sctp_recvmsg函数返回时,msg参数所指缓冲区中最多填入msgsz参数字节的数据。消息发送这的地址存在from参数中,地址结构大小存放在fromlen参数中。msg_flags参数存放可能有的消息标志。如果通知的sctp_data_io_event被启用(默认情形),则会填充sctp_sndrcvinfo结构。如果实现把sctp_recvmsg函数映射为recvmsg函数,则recvmsg的flags参数被设为0。
sctp_opt_info函数是为无法对SCTP套接字使用getsockopt函数的那些实现实提供的,getsockopt函数不支持SCTP的原因在于,有些SCTP套接字选项(如SCTP_STATUS)需要一个入出变量(in_out variable,是一个指针或引用,可以既接收又返回值)传递关联标识,对于无法为getsockopt函数提供入出变量的系统来说,只能用sctp_opt_info函数,对于FreeBSD之类允许在套接字选项中使用入出变量的系统来说,sctp_opt_info函数是一个调用getsockopt函数的库函数,从可移植性考虑,应用应对需要入出变量的所有选项使用sctp_opt_info函数。
sockfd参数是要获取套接字选项的套接字描述符。assoc_id参数给出可能存在的关联标识。opt参数是SCTP套接字选项。arg参数给出套接字选项参数,siz参数用于存放参数大小。
从一个一到多式套接字中抽取一个关联,构成一个一到一式套接字,调用者需要把一到多式套接字的sockfd和要抽取的关联标识id传给sctp_peeloff函数,它将返回一个新的套接字描述符,该描述符是与所请求关联对应的一到一式套接字描述符。
shutdown函数可用于一到一式接口的SCTP端点。由于SCTP不提供半关闭状态,SCTP端点对shutdown函数的反应不同于TCP端点。当相互通信的两个SCTP端点中任何一个发起关联终止序列时,这两个端点都要把已排队的所有数据发送掉,然后关闭关联。关联主动打开的端点使用shutdown函数而非close函数的原因可能是:同一端点可用于连接到一个新的对端端点。与TCP不同,SCTP允许一个端点调用shutdown,shutdown函数结束后,这个端点就可重用原套接字连接到一个新的对端,但如果这个端点没有等到SCTP关联终止序列结束,新的连接会失败。
上图中标出用户接收MSG_NOTIFICATION事件,如果用户不预定接收这些事件,则read函数返回0。对于SCTP,shutdown函数的howto参数语义如下:
1.SHUT_RD:与用于TCP的语义相同,没有任何SCTP协议行为发生。
2.SHUT_WR:禁止后续发送操作,激活SCTP关联终止过程,允许本地端点读取已经排队的数据,这些数据是对端在收到SCTP的SHUTDOWN消息前发送给本端的。
3.SHUT_RDWR:禁止所有read和write操作,激活SCTP关联终止过程,传送到本地端点的排队数据都将得到确认,然后悄然丢弃。
SCTP为应用程序提供了多种通知,SCTP用户可经由这些通知追踪相关关联的状态。通知传递的是传输级事件,包括网络状态变动、关联启动、远程操作错误、消息不可递送。不论是一到一式接口还是一到多式接口,默认情况下除sctp_data_io_event外的所有事件都是被禁止的。
使用SCTP_EVENTS套接字选项可以预定8个事件,其中7个事件会产生称为通知的额外数据,通知本身可通过普通的套接字描述符获取。当产生通知的事件发生时,用户数据和通知将在套接字缓冲区中交错出现。为了区分来自对端的数据和时间产生的通知,用户应使用recvmsg和sctp_recvmsg函数,如果返回的数据是一个通知,那么这两个函数返回的msg_flags参数将含有MSG_NOTIFICATION标志,这个标志告知应用进程刚刚读入的消息不是来自对端的数据,而是本地SCTP栈的通知。
每种通知都采用标签-长度-值(tag-length-value)格式,其中前8个字节给出通知的类型和总长度。开启sctp_data_io_event事件将导致每次读入用户数据都收到一个sctp_sndrcvinfo结构,此结构可通过recvmsg函数的辅助数据获取,也可通过sctp_recvmsg函数的sinfo指针参数获取。
含有SCTP错误起因代码字段的通知有两种,该字段值列在RFC 2960中。
SCTP通知格式:
sctp_tlv.sn_header字段用来告诉我们怎样解释接下来的值,从而解码发送的消息结构,下图是该字段的取值和SCTP_EVENTS套接字选项要预定的消息的字段值的对应关系:
每种通知都有各自对应的结构。
1.SCTP_ASSO_CHANGE:本通知告知应用进程关联本身发生变动,如已开始一个新关联,或已结束一个现有关联。本事件提供的信息结构为:
sctp_assoc_change.sac_state字段给出关联上发生的时间类型,它的取值如下:
(1)SCTP_COMM_UP:某个新的关联刚启动,sac_out_bound_streams和sac_inbound_streams字段指出每个方向有多少可用流的数量。sac_assoc_id字段是新关联在本地SCTP栈的唯一访问标识。
(2)SCTP_COMM_LOST:由sac_assoc_id关联标识字段给出的关联已关闭,原因可能是触发了某个不可达门限(如本地SCTP端点多次超时,触及门限表明对端不再可达),也可能是对端执行了对该关联的中止性关闭(abortive close,通常用SO_LINGER套接字选项将l_onoff设为非0值且把l_linger设为0或MSG_ABORT标志使用sendmsg函数)。特定于用户的信息存放在sac_info字段。
(3)SCTP_RESTART:指示对端已重启,本通知出现的原因最可能是对端主机崩溃并重新启动了。应用进程应验证每个方向流的数目,因为这些值可能在重启后发生变动。
(4)SCTP_SHUTDOWN_COMP:指示本地端点的主动关联终止过程已经结束(可通过调用shutdown或以MSG_EOF标志调用sendmsg)。对于一到一式接口,收到本通知后,相应套接字描述符可再次用于连接到另一个对端。
(5)SCTP_CANT_STR_ASSOC:指示对端对于本端尝试建立关联(如INIT消息)未给出响应。
sac_error字段存放导致本关联变动的SCTP协议错误起因代码。sac_outbound_streams和sac_inbound_streams字段存放本关联上每个方向协定的流数目。sac_assoc_id字段存放本关联的唯一句柄,用它可在套接字选项或以后的通知中关联此关联。sac_info字段存放用户可用的其他信息,如某个关联被对端某个用户自定义错误中止,该错误会存放在该字段中。
2.SCTP_PEER_ADDR_CHANGE:本通知告知对端的某个地址经历了状态变动,这种变动可能是失败性质(如目的地不对所发送的消息作出响应),也可以是恢复性质(如早先处于故障状态的某个目的地恢复正常),伴随地址变动的结构如下:
spc_addr字段存放本事件所影响的对端地址。spc_state字段存放下图中的值:
当一个地址被声明为SCTP_ADDR_UNREACHABLE时,发送到该地址的数据将被重新路由到一个候选地址。上图中的一些状态仅适用于支持动态地址选项的SCTP实现(如SCTP_ADDR_ADDED、SCTP_ADDR_REMOVED)。
spc_error字段存放通知错误代码,用来提供关于事件的更详细信息。spc_assoc_id存放关联标识。
3.SCTP_REMOTE_ERROR:远程端点可能给本地端点发送一个操作性的错误信息,这个信息可以指示有关此关联的错误情形,整个错误消息以wire format(有线格式,广义上指的是用于在内存或硬盘上表达数据的格式)形式传递给应用进程(即将错误消息存放在以下格式中的sre_date字段)。消息格式如下:
其中sre_error字段存放SCTP协议错误起因代码。sre_assoc_id字段存放关联标识。sre_data字段以wire format存放完整的错误。
4.SCTP_SEND_FAILED:无法递送到对端的消息通过本通知送回用户,本通知之后不久通常跟有一个关联故障通知,大多数情况下一个消息不能被递送的原因是关联已经失效。关联有效前提下消息递送失败的唯一情况是使用了SCTP的部分可靠性扩展。本通知提供的结构如下:
其中ssf_flags字段可取以下两个值之一:
(1)SCTP_DATA_UNSENT:指示相应消息无法发送到对端(如流控导致该消息无法在其生命期终止前送出),对端不可能收到该消息。
(2)SCTP_DATA_SENT:指示相应消息已经至少发送到对端一次,但对端一直没有确认,这种情况下,对端可能收到了消息,但无法给出确认。
以上两种取值的区分对于事务性协议可能比较重要,因为这样的协议可能在broken的连接恢复时需要基于对端是否可能收到消息执行不同的恢复操作。
ssf_error字段若不为0,则存放一个特定于本通知的错误代码。
ssf_info字段若有,则提供的是发送数据时传递给内核的信息,如流数目、上下文等。
ssf_assoc_id字段存放关联标识。
ssf_data字段存放未能递送的消息本身。
5.SCTP_SHUTDOWN_EVENT:当对端发送一个SHUTDOWN块到本端点时,本通知被传递给应用进程。本通知告知应用进程在相应套接字上不再接受新的要发送的数据,所有当前已排队的数据将发送出去,发送完毕后关联就被终止。本通知格式:
其中sse_assoc_id存放在正在关闭中,不再接受数据的那个关联的关联标识。
6.SCTP_ADAPTION_INDICATION:有些实现支持适应层指示参数,该参数在INIT和INIT-ACK块中交换,用于通知对端将执行什么类型的应用适用行为。本通知格式如下:
其中sai_assoc_id字段给出关联标识。
sai_adaption_ind字段给出对端在INIT或INIT-ACK消息中传递给本地主机的32位整数。可用SCTP_ADAPTION_LAYER套接字选项设置发送给对端的适配层指示。
7.SCTP_PARTIAL_DELIVERY_EVENT:有些应用会经由套接字缓冲区向用户传送大消息,如一个用户写一个大小为4MB的消息,可能会耗尽系统资源,如果SCTP实现不能在整个消息都写入前就开始把已写入部分递送给对端的机制,就无法处理这样的大消息,能够这样递送消息的机制称为部分递送API,部分递送API由SCTP这样调用:置空msg_flags字段发送一个消息的除最后一部分数据外的各部分数据,发送最后一部分数据时把msg_flags字段设为MSG_EOR。如果应用进程准备接收大消息,就应该使用recvmsg或sctp_recvmsg函数,以便查看msg_flgas字段的值。
有时部分递送API需要向接收的对端传递状态信息,如需要中止一次部分递送API调用,SCTP_PARTIAL_DELIVERTY_EVENT通知就要送给接收应用进程。本通知格式如下:
其中pdapi_assoc_id字段标识部分递送API事件发生的关联标识。
pdapi_indication字段存放发生的事件,目前该字段的唯一有效值是SCTP_PARTIAL_DELIVERY_ABORTED,它指出正在进行的部分递送已被终止。
以下情形可能使用sctp_peeloff函数:大多接受短期请求但偶尔需要长期会话的应用可以用此函数,一旦长期会话请求到来时,可用该函数把长期会话请求剥离出来给专门的线程或进程处理。
当客户关闭关联时,服务器也关闭该关联,这是因为SCTP不支持半关闭状态,客户调用close时,关联终止序列把来自服务器的已排队但未处理的数据冲刷掉,达到关闭关联的目的。