UNIX网络编程读书笔记:基本SCTP套接口编程
概述
SCTP是一个较新的传输协议,于2000年在IETF得到标准化(TCP是在1981年标准化的)。它最初是为满足不断增长的IP电话市场设计的;具体地说,就是穿越因特网传输电话信令。
SCTP是一个可靠的面向消息的协议,在端点之间提供多个流,并为多宿提供传输级支持。
尽管SCTP和TCP之间存在一些本质性的差别,然而SCTP的一到一(one-to-one)接口与TCP提供的应用接口非常接近。这一点允许轻而易举地移植应用程序,不过没法使用SCTP的某些高级特性。SCTP的一到多(one-to-many)接口提供了这些特性的完全支持,然而可能需要费时费力地重新编写已有的应用程序。对于使用SCTP开发的大多数新的应用程序而言,推荐使用一到多接口。
接口模型
SCTP套接口分为两类:一到一套接口和一到多套接口。一到一套接口相应于单独一个SCTP关联。这种映射类似于TCP套接口和TCP连接的对应关系。对于一到多套接口,一个给定套接口上可以同时有多个活跃的SCTP关联。这种映射类似于绑定了某个特定端口的UDP套接口能够从若干个同时在发送数据的远地UDP端点接收彼此交错的数据报。
一到一形式
开发一到一形式的目的是方便移植现有TCP应用程序到SCTP上。它提供的模型与http://www.cnblogs.com/nufangrensheng/p/3586562.html中介绍的几乎一样。以下是两者之间必须搞清的差异,特别是在把现有TCP应用程序移植到SCTP的这种形式上时:
(1)任何TCP套接口选项必须转换成等效的SCTP套接口选项。两个较常见的选项是TCP_NODELAY和TCP_MAXSEG,它们应该映射成SCTP_NODELAY和SCTP_MAXSEG。
(2)SCTP保存消息边界,因而应用层消息边界并非必需。
(3)有些TCP应用进程使用半关闭来告知对端去往它的数据流已经结束。移植这样的应用协议SCTP需要额外重写应用层协议,让应用进程在应用数据流中告知对端该传输数据流已经结束。
(4)send函数能够以普通方式使用。
下图为SCTP一到一形式的套接口典型用法的时间线图:
一到一式SCTP套接口是一个类型为SOCK_STREAM,协议为IPPROTO_SCTP的网际套接口(即协议族为AF_INET或AF_INET6)。
一到多形式
一到多形式给应用程序开发人员提供这样的能力:编写的服务器程序无需管理大量的套接口描述字。单个套接口描述字将代表多个关联,就像一个UDP套接口能够从多个客户接收消息那样。在一到多式套接口上,用于标识单个关联的是一个关联标识(association identifier)。关联标识是一个类型为sctp_assoc_t的值,通常是一个整数。它是一个不透明的值;应用进程不应该使用不是由内核早先给予的任何关联标识。一到多套接口的用户应该掌握以下几点:
(1)当一个客户关闭其关联时,其服务器也将自动关闭同一个关联,服务器主机内核中不再有该关联的状态。
(2)可用于致使在四路握手的第3个或第4个分组中捎带用户数据的唯一办法就是使用一到多形式。
(3)对于一个与它还没有关联存在的IP地址,任何以它为目的地址的sendto、sendmsg或sctp_sendmsg将导致尝试主动打开,从而(如果成功的话)建立一个与该地址的新关联。这种行为的发生与执行分组发送的这个应用进程是否曾调用过listen函数以请求被动打开无关。
(4)用户必须使用sendto、sendmsg或sctp_sendmsg这3个分组发送函数,而不能使用send或write这2个分组发送函数,除非已经使用sctp_peeloff函数从一个一到多形式套接口剥离出一个一到一式套接口。
(5)任何时候调用其中任何一个分组发送函数时,所用的宿地址是由系统在关联建立阶段选定的主宿地址,除非调用者在所提供的sctp_sndrcvinfo结构中设置了MSG_ADDR_OVER标志。为了提供这个结构,调用者必须使用伴随辅助数据的sendmsg函数,或者是sctp_sendmsg函数。
(6)关联事件可能被开启,因此要是应用进程不希望接收到这些事件,就得使用SCTP_EVENTS套接口选项显式禁止它们。缺省情况下开启的唯一事件是sctp_data_io_event,它给recvmsg和sctp_recvmsg调用提供辅助数据。这个缺省设置同时适用于一到一形式和一到多形式。
下图为一到多套接口典型用法的时间线图:
一到多式SCTP套接口是一个类型为SOCK_SEQPACKET,协议为IPPROTO_SCTP的网际套接口(即协议族为AF_INET或AF_INET6)。
在SCTP中,一个一到多套接口也能够结合使用sctp_peeloff函数以允许组合迭代服务器模型和并发服务器模型:
(1)sctp_peeloff函数用于从一个一到多套接口剥离出某个特定的关联(例如一个长期持续的会话),独自构成一个一到一式套接口。
(2)剥离出的关联所在的一到一套接口随后就可以遣送给它自己的线程,或者遣送给为它派生的进程(就像在并发模型中那样)。
(3)与此同时,主线程继续在原来的套接口上以迭代方式处理来自任何剩余关联的消息。
sctp_bindx函数
SCTP服务器可能希望捆绑与所在主机系统相关IP地址的一个子集。传统意义上,TCP服务器或UDP服务器要么捆绑所在主机的某个地址,要么捆绑所有地址,而不能捆绑这些地址的一个子集。sctp_bindx函数允许SCTP套接口捆绑一个特定的地址子集。
#include <netinet/sctp.h> int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags); 返回值:0——成功,-1——出错
sockfd是由socket函数返回的套接口描述字。
addrs是一个指向紧凑的地址清单的指针。每个套接口地址结构紧跟在前一个套接口地址结构之后,中间没有填充字节。如下图所示:
传递给sctp_bindx的地址个数由addrcnt参数指定。
flags参数指导sctp_bindx调用执行如下表所示的两种行为之一:
flags | 说明 |
SCTP_BINDX_ADD_ADDR SCTP_BINDX_REM_ADDR |
把地址加入套接口中
从套接口中移除地址 |
sctp_bindx既可用于已绑定的套接口,也可用于未绑定的套接口。
对于未绑定的套接口,sctp_bindx调用将把给定的地址集合捆绑到其上。对于已绑定的套接口,若指定SCTP_BINDX_ADD_ADDR则把额外的地址加入到套接口描述字,若指定SCTP_BINDX_REM_ADDR则从套接口描述字的已加入地址中移除给定的地址。
如果在一个监听套接口上执行sctp_bindx调用,那么将来产生的关联将使用新的地址配置;已经存在的关联不受影响。
传递给sctp_bindx的两个标志是互斥的;如果同时指定,调用就会失败,返回错误码为EINVAL。
所有套接口地址结构的端口号必须相同,而且必须与已经绑定的端口号相匹配;否则调用就会失败,返回EINVAL错误码。
sctp_connectx函数
#include <netinet/sctp.h> int sctp_connectx(int sockfd, const struct sockaddr *addrs, int addrcnt); 返回值:0——成功,-1——出错
sctp_connectx函数用于连接到一个多宿对端主机。该函数在addrs参数中指定addrcnt个全部属于同一对端的地址。addrs参数是一个紧凑的地址列表。SCTP栈使用其中一个或多个地址建立关联。列在addrs参数中的所有地址都被认为是有效的经过证实的地址。
sctp_getpaddrs函数
getpeername函数不是为支持多宿概念的传输协议设计的;当用于SCTP时它仅仅返回主宿地址。如果需要知道对端的所有地址,那么应该使用sctp_getpaddrs函数。
#include <netinet/sctp.h> int sctp_getpaddrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs); 返回值:存放在addrs中的对端地址数,-1——出错
sockfd参数是由socket函数返回的套接口描述字。
id参数对于一到多式套接口是它的关联标识;对于一到一套接口则被忽略。
addrs参数是一个地址指针,而地址内容是由本函数动态分配并填入的紧凑的地址清单。用完之后,调用者使用sctp_freepaddrs释放所分配的资源。
sctp_freepaddrs函数
sctp_freepaddrs函数释放由sctp_getpaddrs函数分配的资源。
#include <netinet/sctp.h> void sctp_freepaddrs(struct sockaddr **addrs);
addrs参数是指向由sctp_getpaddrs返回的地址数组的指针。
sctp_getladdrs函数
sctp_getladdrs函数用于获取属于某个关联的本地地址。当需要知道一个本地端点究竟在使用哪些本地地址时(它们可能是主机所有地址的某个子集),可以调用本函数。
#include <netinet/sctp.h> int sctp_getladdrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs); 返回值:存放在addrs中的本端地址数,-1——出错
sockfd参数是由socket函数返回的套接口描述字。
id参数对于一到多式套接口是它的关联标识;对于一到一套接口则被忽略。
addrs参数是一个地址指针,而地址内容是由本函数动态分配并填入的紧凑的地址清单。用完之后,调用者使用sctp_freeladdrs释放所分配的资源。
sctp_freeladdrs函数
sctp_freeladdrs函数释放由sctp_getladdrs函数分配的资源。
#include <netinet/sctp.h> void sctp_freeladdrs(struct sockaddr **addrs);
addrs参数是指向由sctp_getladdrs返回的地址数组的指针。
sctp_sendmsg函数
ssize_t sctp_sendmsg(int sockfd, const void *msg, size_t msgsz, const struct sockaddr *to, socklen_t tolen, uint32_t ppid, uint32_t flags, uint16_t stream, uint32_t timetolive, uint32_t context); 返回值:写出的字节数,-1——出错
sockfd参数是由socket函数返回的套接口描述字。
msg参数指向一个msgsz字节长度的缓冲区,其中内容将发送给对端端点to。tolen参数指定存放在to中的地址长度。
ppid参数指定将随数据块传递的净荷协议标识符。
flags参数将传递给SCTP栈,用以标识任何SCTP选项。
调用者在stream参数中指定一个SCTP流号。
调用者可以在timetolive参数中以毫秒为单位指定消息的生命期,其中0表示无限生命期。
context参数用于指定可能有的用户上下文。
sctp_recvmsg函数
ssize_t sctp_recvmsg(int sockfd, void *msg, size_t msgsz, struct sockaddr *from, socklen_t *fromlen, struct sctp_sndrcvinfo *sinfo, int *msg_flags); 返回值:读入的字节数,-1——出错
注意,如果应用进程想要接收sctp_sndrcvinfo信息,那么必须使用SCTP_EVENTS套接口选项预定sctp_data_io_event(缺省情况下开启)。
本函数调用返回时,msg参数所指缓冲区中被填入最多msgsz字节数的数据。消息发送者的地址存放在from参数中,地址结构大小存放在fromlen参数中。msg_flags参数中存放可能有的消息标志。注意,如果实现把sctp_recvmsg映射成recvmsg函数,那么recvmsg的flags参数被设为0.
sctp_opt_info函数
sctp_opt_info函数是为getsockopt函数无法支持SCTP的那些实现提供的。
int sctp_opt_info(int sockfd, sctp_assoc_t assoc_id, int opt, void *arg, socklen_t *siz); 返回:0——成功,-1——出错
sockfd参数给出获取其上套接口选项信息的套接口描述字。
assoc_id参数给出可能存在的关联标识。
opt参数是SCTP的套接口选项。
arg给出套接口选项参数。
siz是一个socklen_t类型指针,用于存放参数的大小。
sctp_peeloff函数
int sctp_peeloff(int sockfd, sctp_assoc_t id); 返回:新的套接口描述字——成功,-1——出错
其语义很像带有一个额外参数的accept函数。调用者把一到多式套接口的sockfd和待抽取的关联标识id传递给函数调用。调用结束时将返回一个新的套接口描述字,它是一个与所请求关联对应的一到一式套接口描述字。
通知
SCTP为应用程序提供了多种可用的通知。SCTP用户可以经由这些通知追踪相关关联的状态。通知传递的是传输级的事件,包括网络状态变动、关联启动、远地运作错误以及消息不可递送。不论是一到一式接口还是一到多式接口,缺省情况下除sctp_data_io_event以外的所有事件都是被禁止的。
使用SCTP_EVENTS套接口选项可以预订8个事件。其中7个事件产生称为通知(notification)的额外数据,通知本身可经由普通的套接口描述字获取。当产生它们的事件发生时,这些通知内嵌在数据中加入到套接口描述字。在预订相应通知的前提下读取某个套接口时,用户数据和通知将在套接口缓冲区中交错出现。为了区分来自对端的数据和由事件产生的通知,用户应该使用recvmsg函数或sctp_recvmsg函数。如果返回的数据是一个事件通知,那么这两个函数返回的msg_flags参数将含有MSG_NOTIFICATION标志。这个标志告诉应用进程刚刚读入的消息不是来自对端的数据,而是来自本地SCTP栈的一个通知。