• 流控制传输协议(SCTP)


    一、SCTP介绍

    1. SCTP在客户和服务器之间提供关联(association),并像TCP那样给应用提供可靠性、排序、流性控制以及全双工的数据传送。

    2. SCTP中使用“关联”一词取代“连接” 是为了避免这样的内涵:一个连接只涉及两个IP地址之间的通信。一个关联指代两个系统之间的一次通信,它可能因为SCTP支持多宿而涉及不止两个地址。

    3. 与TCP不同的是,SCTP是面向消息的(message-oriented)。它提供各个记录的按序递送服务。与UDP一样,由发送端写入的每条记录的长度随数据一道传递给接收端应用。

    4. SCTP能够在所连接的端点之间提供多个流,每个流各自可靠地按序递送消息。一个流上某个消息的丢失不会阻塞同一关联其他流上消息的投递。这种做法与TCP正好相反,就TCP而言,在单一字节流中任何位置的字节丢失都将阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。

    5. SCTP还提供多宿特性,使得单个SCTP端点能够支持多个IP地址。该特性可以增强应对网络故樟的健壮性。一个端点可能有多个冗余的网络连接,每个网络又可能有各自接入因特网基础设施的连接。当该端点与另一个端点建立一个关联后,如果它的某个网络或某个跨越因特网的通路发生故障,SCTP就可以通过切换到使用已与该关联相关的另一个地址来规避所发生的故陓。

    二、SCTP关联的建立和终止

    SCTP的握手过程不同于TCP。

    1.四路握手

    1. 服务器必须准备好接受外来的关联。通常调用socket、bind和listen这3个函数来完成,称为被动打开。

    2. 客户通过调用connect或者发送隐式打开该关联的消息进行主动打开。这使得客户SCTP发送 一个INIT消息(初始化),该消息告诉服务器客户的IP地址清单、初始序列号、用于标识本关联中所有分组的起始标记、客户请求的外出流的数目以及客户能够支持的外来流的数目。

    3. 服务器以 一 个INIT ACK消息确认客户的INIT消息,其中含有服务器的IP地址清单、初始序列号 、 起始标记、 服务器诮求的外出流的数目、服务器能够支持的外来流的数目以及一 个状态cookie状态cookie包含服务器用于确信本关联有效所需的所有状态,它是数字化签名过的,以确保其有效性。

    4. 客户以 一 个COOKIE ECHO消息回射服务器的状态cookie。除COOKIE ECHO外,该消息可能在同 一个分组中还捆绑了用户数据。

    5. 服务器以 一 个COOKIE ACK 消息确认客户回射的cookie是正确的,本关联于是建立。该消息也可能在同 — 个分组中还捆绑了用户数据。
      在这里插入图片描述
      在SCTP中使用四路握手为了避免拒绝服务器攻击。

    2.关联终止

    SCTP不像TCP那样允许**"半关闭”**的关联。当一端关闭某个关联时,另一端必须停止发送新的数据。关联关闭请求的接收端发送完已经排队的数据(如果有的话)后,完成关联的关闭。
    在这里插入图片描述

    1. SCTP没有类似于TCP的TIME_WAlT状态,因为SCTP使用了验证标记。

    2. 所有后续块都在捆绑它们的SCTP分组的公共首部标记了初始的INIT块和INIT ACK块中作为起始标记交换的验证标记;

    3. 由来自旧连接的块通过所在SCTP分组的公共首部间接携带的验证标记对于新连接来说是不正确的。所以SCTP通过放置验证标记值就避免了TCP在TIME_WAIT状态保持整个连接的做法。

    3.SCTP状态转换图

    SCTP涉及关联建立和关联终止的操作状态图如下:
    在这里插入图片描述

    4.观察分组

    下图为SCTP关联所发生的实际分组交换情况,包括关联建立、数据传送和关联终止3个阶段。
    在这里插入图片描述

    1. ,客户在COOKIEECHO块所在分组中捎带了它的第一个DATA块,服务器则在作为应答的COOKIE ACK块所在分组中捎带了数据。

    2. 当网络应用采用一 到多接口式样时,COOKIE ECHO通常捎带一个或多个DATA块。

    3. SCTP分组中信息的单位称为块(chunk)。块是自描述的,包含 一 个块类型、若干个块标记和一 个块长度。这样做方便了多个块的绑缚,只要把它们简单地组合到一个SCTP外出消息中。

    5.接口模型

    SCTP套接字分为:一到一套接字和一到多套接字,一到一套接字对应单独的SCTP关联。一对多套接字,一个给定套接字上可以同时有多个活跃的SCTP关联。

    ①.一到一形式

    一到一形式的目的是将现有TCP应用程序移植到SCTP上。以下是这两者之间必须搞清的差异,特别是在把现有TCP应用程序移植到SCTP的这种形式上时。

    1. 任何TCP套接字选项必须转换成等效的SCTP套接字选项。两个较常见的选项是TCP_NODELAYTCP_MAXSEG, 它们应该映射成SCTP_NODELAYSCTP_MAXSEG

    2. SCTP保存消息边界,因而应用层消息边界并非必需。例:基于TCP的某个应用协议可能先执行一个双字节的write系统调用,给出消息的长度x,再调用一个x字节的write系统调用,写出消息数据本身。改用SCTP后,接收端SCTP将收到两个独立的消息(也就是说得有两次read系统调用才能返回全部数据:第一次返回一个双字节数据,第二次返回一个x字节消息)。

    3. 有些TCP应用进程使用半关闭来告知对端去往它的数据流已经结束。将这样的应用程序移植到SCTP需要额外重写应用层协议,让应用进程在应用数据流中告知对端该传输数据流已经结束。

    4. send函数能够以普通方式使用。使用sendto或sendmsg函数时,指定的任何地址都被认为是对目的地主地址的重写。

    下图所示为一到一套接字典型用法的时间线图。
    在这里插入图片描述

    1. 服务器启动后,打开一个套接字,bind一个地址,然后就等着accept客户关联。

    2. 客户端启动,打开一个套接字,并初始化与服务器的一个关联。

    3. 我们假设客户向服务器发送一个请求,服务器处理该请求后向客户发回一个应答。这个循环待续到客户开始终止该关联为止。这样主动关闭关联之后,服务器或者退出,或者等待新的关联。

    4. SCTP一到一-套接字的交互类似于TCP套接字。

    5. 一到一式SCTP套接字是一个类型为SOCK_STREAM,协议为IPPROTO_SCTP的网际网套接字(即协议族为AF_INET或AF_INET6)。

    6. 关联事件(将在9.14节讨论的众多SCTP通知之 一 )可能被启用,因此要是应用进程不希望收到这些事件,就得使用SCTP_EVENTS套接字选项显式禁止它们。默认情况下启用的唯一事件是sctp_data_io_event它给recvmsg和sctp_recvmsg调用提供辅助数据。这个默认设

    ②.一到多形式

    • 一到多形式:编写的服务器程序无需管理大量的套接字描述符。单个套接字描述符将代表多个关联,就像一个UDP套接字能够从多个客户接收消息那样。

      • 在一到多式套接字上,用于标识单个关联的是一个关联标识。关联标识是一个类型为sctp_assoc_t的值,通常是一个整数,不透明的值,应用进程不应该使用不是由内核先前给予的任何关联标识。

    一到多式套接字的用户应该掌握以下儿点。

    1. 客户关闭其关联时,其服务器也将自动关闭同一个关联,服务器主机内核中不再有该关联的状态。

    2. 可用于致使在四路握手的第三个或第四个分组中捎带用户数据的唯一办法就是使用一到多形式。

    3. 对于一个与它还没有关联存在的IP地址,任何以它为目的地的sendto、senclrnsg或sctp_sendmsg将导致对主动打开的尝试,从而(如果成功的话)建立一个与该地址的新关联。这种行为的发生与执行分组发送的这个应用进程是否曾调用过listen函数以请求被动打开无关。

    4. 用户必须使用sendto、sendmsg或sctp_sendmsg这3个分组发送函数,而不能使用send或write这2个分组发送函数,除非已经使用sctp_peeloff函数从一个一到多式套接字剥离出一个一到一式套接字。

    5. 任何时候调用其中任何一个分组发送函数时,所用的目的地址是由系统在关联建立阶段选定的主目的地址,除非调用者在所提供的sctp_sndrcvinfo结构中设置了MSG_ADDR_OVER标志。为了提供这个结构,调用者必须使用伴随辅助数据的senclrnsg函数或sctp_senclrnsg函数。

    6. 关联事件可能被启用,因此要是应用进程不希望收到这些事件,就得使用SCTP_EVENTS套接字选项显式禁止它们。默认情况下启用的唯一件是sctp_data_io_event,它给recvmsg和sctp_recvmsg调用提供辅助数据。这个默认设置同时适用于 一 到一 形式和一 到多形式。

    一到多套接字示意图:
    在这里插入图片描述

    1. 服务器启动后打开一 个套接字,bind一个地址,调用listen以允许客户建立关联,然后就调用sctp_recvmsg阻塞于等待第个消息的到达。

    2. 客户启动后也打开一个套接字,并调用sctp_sendto, 它导致隐式建立关联,而数据请求由四路握手的第三个分组捎带给服务器。

    3. 服务器收到该请求后进行处理并向该客户发回一个应答。客户收到应答后关闭其套接字,从而终止其上的关联。服务器循环回去接收下一个消息。

    本例子是迭代服务器模型,来自许多关联(许多客户)的(可能交错的)消息能够由单个控制线程处理。在SCTP中, 一 个一 到多套接字也能够结合使用sctp_peeloff函数 以允许组合迭代服务器模型和并发服务器模型。关系如下:

    • sctp_peeloff函数用于从一个 一到多套接字剥离出某个特定的关联(例如一个长期持续的会话),独自构成 一 个一到一 式套接字。

      • 剥离出的关联所在的 一 到一 套接字随后就可以遣送给它自己的线程,或者遣送给为它派生的进程(就像在并发模型中那样).

        • 与此同时,主线程继续在原来的套接字上以迭代方式处理来自任何剩余关联的消息一到多式SCTP套接字是 一 个类型为SOCK_SEQPACKET,协议为IPPROTO_SCTP的网际网套接字(即协议族为AF_INET或AF_INET6)。
    6.SCTP选项
    • SCTP使用参数和块方便增设可选特性。
      • 新的特性通过添加这两个条目之一 加以定义,并允许通常的SCTP处理规则汇报未知的参数和未知的块。
        • 参数类型字段和块类型字段的高两位指明SCTP接收端该如何处置未知的参数或未知的块。

    当前如下两个对SCTP的扩展正在开发中心:

    1. 动态地址扩展,允许协作的SCTP端点从已有的某个关联中动态增删IP地址。

    2. 不完全可靠性扩展,允许协作的SCTP端点在应用进程的指导下限制数据的重传。当 一个消息变得过于·陈旧而无须发送时(按照应用进程的指导),该消息将被跳过而不再发送到对端。

    三、SCTP套接字选项

    1.SCTP_ADAPTION_LAYER套接字选项

    1. 在关联初始化期间,任何一个端点都可能指定一个适配层指示(adaptionlayer indication)。指示为32位无符号整数,可由两端的应用进程用来协调任何本地应用适配层。

    2. 本选项允许调用者获取或设置将由本端提供给对端的适配层指示。

    3. 获取本选项的值时,调用者得到的是本地套接字将提供给所有未来对端的值。要获取对端的适配层指示,应用进程必须预订适配层事件。

    2.SCTP_ASSOCINFO套接字选项

    本套接字选项可用于以下三个目的:

    1. 获取关于某个现有关联的信息。

    2. 改变某个已有关联的参数。

    3. 为未来的关联设置默认信息。

    4. 在获取关于某个现有关联的信息时,应该使用sctp_opt_info函数而不是getsockopt函数。

    sctp_assocparams结构:

    struct sctp_assocparams{
    sctp_assoc_t sasoc_assoc_id; //存放待访问关联的标识(即关联ID);调用setsockopt时置0本字段,郑么sasoc_asocmaxrxt和sasoc_cookie_life字段代表将作为默认信息设置在相应套接字上的值。提供关联ID,返回的就是特定于该关联的信息,否则如果置0本字段,返回的就是默认的端点设置信息。
    u_int16_t   sasoc_asocrnaxrxt; //某个关联在已发送数据没有得到确认的情况下尝试重传的最大次数。
    u_int16_t 	sasoc__number_peer_destinations;//存放对端目的地址数;它不能设置,只能获取。
    u_int32_t 	sasoc_peer_rwnd;//存放对端的当前接收窗口 ;不能设置,只能获取。
    u_int32_t 	sasoc_local_rwnd;//存放本地SCTP协议栈当前通告对端的接收窗口。不能设置,只能获取。
    u_int32_t 	sasoc_cookie_life//存放送给对端的状态cookie以毫秒为单位的有效期
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    3.SCTP _AUTOCLOSE 套接字选项

    1. 选项允许获取或设置一 个SCTP端点的自动关闭时间。自动关闭时间是个SCTP关联在空闲时保持打开的秒数。

    2. SCTP协议栈把空闲定义为一个关联的两个端点都没有在发送或接收用户数据的状态。自动关闭功能默认是禁止的。自动关防选项意在用于一 到多式SCTP接口 。

    3. 设置本选项时,传递给它的整数值为某个空闲关联被自动关防前的持续秒数,值为0表示禁止自动关闭。本选项仅仅影响由相应本地端点将来创建的关联,已有关联保持它们的现行设置不变。

    4. 自动关闭功能可由服务器用来强制关闭空闲的关联,服务器无需为此维护额外的状态。

    5. 使用本特性的服务器应该仔细估算它的所有关联预期的最长空闲时间。自动关闭时间设置过短会导致关联的过早关闭。 .

    4.SCTP _DEFAULT_SEND_PARAM 套接字选项
    1. SCTP有许多可选的发送参数,它们通常作为辅助数据传递,或者由sctp_sendrnsg函数使用 (sctp_sendrnsg通常作为库函数实现,它替用户传递辅助数据)
    2. 希望发送大量消息且所有消息具有相同发送参数的应用进程可以使用本选项设置默认参数,从而避免使用辅助数据或执行sctp_sendrnsg调用。

    sctp_sndrcvinfo结构:

    struct sctp_sndrcvinfo{
    u_int16_t sinfo_stream; //指定新的默认流,所有外出消息将被发送到该流中。
    u_int16_t sinfo_ssn; // 在设置默认发送参数时被忽略。当使用recvmsg或sctp_recvmsg函数接收消息时,本字段将存放由对端置于SCTPDATA块的流序号(SSN)字段中的值。
    u_int16_t sinfo_flags; //指定新的默认标志,它们将应用于所有消息发送。
    u_int32_t sinfo_ppid; // 指定将置于所有外出消息中的SCTP净荷协议标识字段的默认值。
    u_int32_t sinfo_context;//指定新的默认上下文。本字段是个本地标志,用于检索无法发送到对端的消息。
    u_int32_t sinfo_timetolive;//指定新的默认生命期,它将应用于所有消息发送。SCTP协议栈使用本字段判定何时丢弃(尚未执行首次传送就)因过度拖延而失效的外出消息。如果同关联的两个跺点都支待部分可靠性选项,那么本生命期也用千指定完成首次传送后的消息的继续有效期。
    u_int32_t sinfo_tsn; //在设置默认发送参数时被忽略。当使用recvmsg或sctp_recvmsg函数接收消息时,本字段将存放由对端置于SCTPDATA块的传输序(TSN)字段中的值
    u_int32_t sinfo_cumtsn;//在设赏默认发送参数时被忽略。当使用recvmsg或sctp_recvmsg函数接收消息时,本字段将存放本地SCTP协议栈已与对端挂钩的当前累积TSN。
    sctp_assoc_t sinfo_assoc_id;//指定请求者希望对其设置默认参数的关联标识.对于一到一式套接字,本字段被忽略 。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    在这里插入图片描述
    注:所有默认设置只影响没有指定sctp_sndrcvinfo结构的消息发送。指定了该结构的消息发送(例如带辅助数据的sctp_sendmsg或sendmsg函数调用)将覆写默认设置 。除了进行默认设置,通过使用sctp_opt_info函数,本选项也可用于获取当前的默认设置。

    5.SCTP _DISABLE_FRAGMENTS套接字选项

    SCTP通常把太大而不适合置于单个SCTP分组中的用户消息分割成多个DATA块。本选项将在发送端禁止这种行为。SCTP的默认行为与本选项被禁止等效,也就是说,SCTP通常会对用户消息执行分片。

    6.SCTP_EVENTS套接字选项
    1. 本套接字选项允许调用者获取、开启或禁止各种SCTP通知,SCTP通知是由SCTP协议栈发送给应用进程的消息。

    2. 读取只需把recvmsg函数的msghdr结构参数中的msg_flags字段设置为MSG_NOTIFICATION

    3. 不准备使用recvmsgsctp_recvmsg函数应用进程不应该开启串件通知功能。

    4. 使用本选项传递 一 个sctp_event_subscribe结构就可以预订8类事件的通知。其中各0表示不预订1表示预订。

    struct sctp_event_subscribe{
    u_int8_t 	sctp_data_io_event; 
    u_int8_t 	sctp_association_event;
    u_inc8_t    sctp_address_event; 
    u_int8_t 	sctp_send_failure_event;
    u_int8_t	sctp_peer_error_event;
    u_int8_t 	sctp_shutdown_event;
    u_int8_t	sctp_parcial_delivery_event; 
    u_int8_t 	sctp_adaption_layer_event; 
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    在这里插入图片描述

    7.SCTP _GET_PEER_ADDR_工NFO套接字选项
    1. 本选项仅用于获取某个给定对端地址的相关信息,包括拥塞窗口、平滑化后的RTT和MTU等。
    2. 调用者在其中的spinfo_address字段填入待查询的对端地址,并且为了便于移植,应该使用sctp_opt_info函数而不是getsockopt函数。
    3. 用途1:把一个IP地址结构转换成一个可用十其他调用的关联标识。
    4. 用途2:由应用进程跟踪一个多宿对端主机每个地址的性能,并把相应关联的主目的地址更新为其中性能最佳的一个。这些值也同样有利于日志记录和程序调试。

    sctp_paddrinfo结构的格式如下:

    struct  sctp_paddrinfo{
    sctp_assoc_t spinfo_assoc_id; //存放关联标识
    struct sockaddr_storage spinfo_address; //由调用者设置
    int32_t  spinfo_state; 
    u_int32_t spinfo_cwnd;//表示为所指定对端地址维护的当前拥塞窗口。
    u_inc32_t spinfo_srtt; //表示就所指定对端地址而言的平滑化后RTT的当前估计值。
    u_int32_t spinfo_rto; //表示用于所指定对端地址的当前重传超时值。
    u_int32_t spinfo_mtu; //表示由路径MTU发现功能发现的通往所指定对端地址的路径MTU的当前值。
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    spinfo_state存放的常值:
    在这里插入图片描述

    8.SCTP_I_WANT_MAPPED_V4_ADDR套接字选项
    1. 标志套接字选项用于为AF_INET6类型的套接字开启或禁止IPv4映射地址,其默认状态为开启。
    2. 注意,本选项开启时,所有IPv4地址在送往应用进程之前将被映射成一个IPv6地址。
    3. 本选项禁止时,SCTP套接字不会对IPv4地址进行映射,而是作为sockaddr_in结构直接传递。
    9.SCTP_INITMSG套接字选项

    本套接字选项用于获取或设置某个SCTP套接字在发送INIT消息时所用的默认初始参数。sctp_initmsg结构如下:

    struct sctp_initmsg{
    uint16_t sinit_num_ost:reams;//表示应用进程想要请求的外出SCTP流的数目
    uint16_t sinit_max__instreams; //表示应用进程准备允许的外来SCTP流的最大数目。如果该值大千SCTP协议栈所支持的最大允许流数,那么它将被改为这个最大数。
    uint16_t sinit_rnax_atc.empts; //表示SCTP协议栈应该重传多少次初始INIT消息才认为对端不可达。
    uint16_t sinit_rnax_init_timeo;//表示用于INIT定时器的最大RTO值。在初始定时器进行指数退避期间,该值将替代RTO.rnax作为重传RTO极限。该值以毫秒为单位。
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意,当设置这些字段时,SCTP将忽略其中的任何0值。一到多式套接字的用户在关联隐性建立期间也可能在辅助数据中传递一个sctp_initmsg结构。

    10.SCTP _MAXBURST套接字选项
    1. 本套接字选项允许应用进程获取或设置用于分组发送的最大猝发大小。当SCTP向对端发送数据时,一次不能发送多于这个数目的分组,以免网络被分组淹没。

    2. 具体SCTP实现有两种方法应用这个限制:(1)把拥塞窗口缩减为当前飞行大小加上最大猝发大小与路径MTU的乘积;(2)把该值作为一个独立的微观控制量,在任意一个发送机会最多只发送这个数目的分组。

    11.SCTP_MAXSEG套接字选项
    1. 本套接字选项允许应用进程获取或设宜用于SCTP分片的最大片段大小。

    2. 当某个SCTP发送端从其应用进程收到一个大于这个大小的消息时,它将把该消息分割成多个块,以便分别传送到对端。

    3. SCTP发送端通常使用的这个大小是通达它的对端的所有路径各自的MTU中的最小值(每条路径对应一个对端地址)。设置本选项可以把这个大小降低到所指定的值。

    4. 注意,SCTP可能以比本选项所诸求的值更小的边界分割消息。当通达对端的某条路径的MTU变得比本选项所请求的值还要小时,这种偏小的分割就会发生。

    5. 最大片段大小是一个端点范围的设置,在一到多式接口中,它可能影响不止一个关联。

    12.SCTP_NODELAY套接字选项

    开启本选项将禁止SCTP的Nagle算法。本选项默认关闭(也就是说默认情况下Nagle算法是启动的)。
    SCTP的Nagle算法与TCP的Nagle算法工作原理相同,区别在于前者对付多个DATA块,后者对付单个流上的字节

    13.SCTP _PEER_ADDR_PARAMS套接字选项

    本套接字选项允许应用进程获取或设置关于某个关联的对端地址的各种参数。选项sctp_paddrparams结构定义如下:

    //调用者必须在该结构中填写关联标识和/或一个对端地址,
    
    struct sctp_paddrparam{
    sctp_assoc_t spp_assoc_id; //存放在其上获取或设置参数信息的关联标识。如果该值为0,那么所访问的是端点默认参数,而不是特定于关联的参数。
    struct sockaddr_storage spp_address;//指定其参数待获取或待设置的对端IP地址。如果spp_assoc_id字段值为O, 那么本字段被忽略。
    u_int32_t spp_hbinterval; //表示心搏间隔时间。设萱该值为SCTP_NO_HB将禁止心搏,为SCTP_ISSUE_HB将按请求心搏,为其他值则将把心搏间隔重置为以毫秒为单位的新值。设置端点默认参数时,不能使用SCTP_ISSUE_HB这个值。
    u_int16_t spp_pathmaxrxt; //表示在声明所指定对端地址为不活跃之前将尝试的重传次数。当主目的地址被声明为不活跃时,另外一个对端地址将被选为主目的地址。
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    14.SCTP _PRIMARY_ADDR套接字选项

    本套接字选项用于获取或设置本地端点所用的主目的地址。主目的地址是本端发送给对端的所有消息的默认目的地址。

    sctp_setprim结构定义如下:

    //调用者必须在该结构中填写关联标识,若是设置主目的地址则再填写一个将用作主目的地址的对端地址
    struct stcp_setprim{
    	sctp_assoc_t 	ssp_assoc_id;//存放在其上获取或设置当前主目的地址的关联标识。对于一到一式套接字,本字段被忽略。
    	struct sockaddr_storage ssp_addr;//指定主目的地址(主目的地址必须是一个属于对端的地址)。使用setsockopt函数设置本选项时,本字段为请求者要求设置的主目的地址的新值,使用getsockopt函数获取本选项时,本字段为当前所用主目的地址的值。
    	};
    
    //注:在只有一个本地地址与之关联的到一到一式套接字上获取本选项的值跟直接调用getsockname是相同的。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    15.SCTP_RTOINFO套接字选项

    本套接字选项用于获取或设置各种RTO信息,它们既可以是关于某个给定关联的设置,也可以是用于本地端点的默认设置。
    为了便于移植,当获取信息时,调用者应该使用sctp_opt_info函数而不是getsockopt函数。是sctp_rtoinfo结构定义如下:

    struct sctp_rtoinfo{
    	sctp_assoc_t	srto_assoc_id;//存放感兴趣关联的标识或0。若值为0,当前函数调用会对系统的默认参数产生影响。
    	sctp_assoc_t	srto_initial;//存放用于对端地址的初始RTO值。初始RTO值在向对端发送INIT块时使用。该值以亳秒为单位且默认值为3000。
    	uint32_t srto_max;//存放在更新重传定时器时使用的最大RTO值。如果更新后的RTO值大于这个RTO最大值,那就把这个最大值作为新的RTO值。该值默认为60000。
    	uint32_t srto_min;//存放在启动重传定时器时使用的最小RTO值。任何时候RTO定时器一旦更改,就对照这个RTO最小值检查新值。如果新值小于最小值,那就把这个最小值作为新的RTO值。该值默认为1000。
    	}
    //srto_initial、srto_max或srto_min值为0表示当前设定的默认值不应改变。
    //所有时间值都以毫秒为单位。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    16.SCTP _SET_PEER_PRIMARY_ADDR套接字选项

    设置本套接字选项导致发送一个消息:请求对端把所指定的本地地址作为它的主目的地址。

    sctp_setpeerprim结构定义如下:

    
    //用者必须在该结构中填写关联标识和一个请求对端标为其主目的地址的本地地址。这个本地地址必须已经绑定在本地端点。
    
    struct sctp_setpeerprim{
    	sctp_assoc_t	sspp_assoc_id;//指定在其上想要设笠主目的地址的关联标识。对于一到一式套接字,本字段被忽略。
    	struct sockaddr_storage sspp_addr;//r存放想要对端设置为主目的地址的本地地址。
    	};
    
    //本特性是可选的,只有两端均支持才能运作。 如果本地端点不支持本特性,那就给调用者EOPNOTSUPP返回错误。
    //如果远程端点不支持本特性,那就返回调用者EINVAL错误。
    //本套接字选项只能设置,不能获取。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    17.SCTP_STATUS套接字选项

    本套接字选项用于获取某个SCTP关联的状态。为了便于移植,调用者应该使用sctp_opt_info函数而不是getsockopt函数。

    sctp_status结构的格式如下:

    //调用者必须在该结构中填写关联标识,关于这个关联的信息将在返回时被填写到该结构的其他字段中。
    struct sctp_status{
    
    sctp_assoc_t sstat_assoc_id; //存放关联标识。
    int32_t sstac_state; //如下图所示
    u_int32_t sstat_rwnd; //存放本地端点对于对端接收窗口的当前估计。
    u_intl6_t sstat_unackdata; //存放等着对端处理的未确认DATA块数目。
    u_intl6_t sstat_penddata;//存放本地端点暂存并等着应用进程读取的未读DATA块数目 。
    u_intl6_t sstat_instrms; //存放对端用于向本端发送数据的流的数目。
    u_intl6_t sstat_outstrms;//存放本端可用于向对端发送数据的流的数目。
    u_int32_t sstat_fragmentation_point; //存放本地SCTP端点将其用作用户消息分割点的当前值。该值通常是所有目的地址的最小MTU, 或者是由本地应用进程使用SCTP_MAXSEG套接字选项设置的更小的值。
    struct sctp_paddrinfo sstat_primary;//存放当前主目的地址。主目的地址是向对端发送数据时使用的默认目的地址。
    }
    //这些值可用于诊断或确定会话的特征。 
    //sctp_get_no_strms函数将使用sstat_outstrms成员确定有多少外出流可用。
    //sstat_rwnd`值和/或偏高的sstat_unackdata值可用于判定对端的接收套接字缓冲区正在变满,这 一 点又可用作让应用进程尽可能降低发送速率的信号 。
    // 有些应用进程使用sstat_fragmentation_point减少SCTP不得不创建的片段数品,办法就是发送较小的应用消息。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    四、SCTP套接字编程

    1.sctp_bindx函数

    sctp_bindx函数用于SCTP套接字捆绑 一 个IP地址特定地址子集。

    #include
    int sctp_bindx(int sockfd,const struct sockaddr *addrs,int addrcnt, int flags);
    
    //sockfd:套接字描述符
    //addrs:指向紧凑的地址列表指针。
    //addrcnt:地址个数
    //flags:如图所示
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    在这里插入图片描述

    1. sctp_bindx调用既可用于已绑定的套接字,也可用于未绑定的套接字。对于未绑定的套接字,sctp_bindx调用将把给定的地址渠合捆绑到其上 。 对于己绑定的套接字,若指定SCTP_BINDX_ADD_ADDR则把额外的地址加入到套接字描述符,若指定SCTP_BINDX_REM_ADDR则从套接字描述符的已加入地址中移除给定的地址。

    2. 如果在 一个监听套接字上执行sctp_bindx调用,那么将来产生的关联将使用新的地址配置,已经存在的关联则不受影响。传递给sctp_bindx的两个标志是互斥的,如果同时指定,调用就会失败,返回的错误码为`EINVAL。

    3. 所有套接字地址结构的端口号必须相同,而且必须与已经绑定的端口号相匹配,否则调用就会失败,返回EINVAL错误码 。

    4. 若端点支持动态地址特性,指定SCTP_BINDX_ADD_ADDR或SCTP_BINDX_REM_ADDR标志调用sctp_bindx将导致该端点向对端发送 — 个合适的消息,以修改对端的地址列表。 由于增减一个已连接关联的地址只是一个可选的功能,因此不支持本功能的实现将返回EOPNOTSUPP

    5. 注:本功能正确操作要求两个端点都支持这个特性。本特性对于支持动态接口供给的系统可能有用,举例来说,如果调出 一 个新的以太网接口,那么应用进程可以指定SCTP_BINDX_ADD_ADDR标志在已经存在的连接上启动使用这个接口。

    2.sctp_connectx 函数

    用于连接到一个多宿对端主机。

    #include
    int sctp_connectx(int sockfd,const struct sockaddr *addrs,int addrcnt);
    //addrs指定addrcnt个全部属于同一对端的地址。是个紧凑的地址列表。
    //SCTP栈使用其中一个或多个地址建立关联。列在addrs参数中的所有地址都被认为是有效的经过证实的地址 。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3.sctp_getpaddrs函数

    getpeername函数不是为支持多宿概念的传输协议设计的;当用于SCTP时它仅仅返回主目的地址。如果需要知道对端的所有地址,那么应该使用sctp_getpaddrs函数.

    #include
    int sctp_getpaddrs(int sockfd,stcp_assoc_t id,struct sockaddr **addrs);
    //sockfd:套接字描述符
    //id:一对多式套接字的关联标识,一对一式套接字会忽略该字段。
    //addrs:地址指针,地址内容是由本函数动态分配填入的紧凑的地址列表。
    //应使用sctp_freepaddrs释放所分配的资源。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    4.sctp_freepaddrs函数

    函数sctp_freepaddrs函数释放由sctp_getpaddrs函数分配的资源。

    #include
    void sctp_freepaddrs(struct sockaddr *addrs);
    //addrs:指向由sctp_getpaddrs返回的地址数组的指针
    
    • 1
    • 2
    • 3
    5.sctp_getladdrs函数

    sctp_getladdrs函数用于获取属于某个关联的本地地址。

    #include
    int sctp_getladdrs(int sockfd,sctp_assoc_t id,struct sockaddr **addrs);
    //返回:成功:存放addrs的本端地址数,出错-1
    //sockfd:套接字描述符
    //id:一到多式套接字的关联标识,一到一忽略。
    //addrs:地址指针,地址内容由本函数动态分配并填入的紧凑的地址列表
    //sctp_freeladdrs释放所分别的资源
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    6.sctp_freeladdrs函数

    sctp_freeladdrs函数释放由sctp_getladdrs函数分配的资源:

    #include
    void sctp_freeladdrs(struct sockadd *addrs);//addrs:指向由sctp_getladdrs返回的地址数组的指针
    
    • 1
    • 2
    7.sctp_sendmsg函数

    使用伴随辅助数据的sendmsg函数不大方便,许多SCTP实现提供了辅助函数库调用,以方便应用进程使用SCTP的高级特性。

    #include
    ssize_t sctp_sendmsg(int sockfd,const void *msg,sizet_t msgsz,const struct *to,socklen_t toeln,uint32_t ppid,uint32_t flags,uint16_t stream,uint32_t timetolive,uint_t context);
    
    //sockfd:套接字描述符
    //msg:指向长度为msgsz字节缓冲区,其中内容将发送给对端点to。
    //tolen:指定存放在to的地址长度。
    //ppid:指定随数据块传递的净荷协议标识符。
    //flags:将传递给SCTP栈,用以标识SCTP选项。如下图所示为有效值。
    //stream:指定一个SCTP流号。
    //lifetime:以毫秒为单位指定消息的生命周期,0表示无限生命周期。
    //context:用于指定可能有的用户上下文。用户上下文把通过消息通知机制收到的某次失败的消息发送与某个特定于应用的本地上下文关联起来。
    
    
    //发送消息到流号1,发送标志MSG_PR_SCTP_TTL,生命周期为1000毫秒,净荷协议:24,上下文:52
    ret = sctp_sendmsg(sockfd,data,datasz,&dest,sizeof(dest),24,MSG_PR_SCTP_TTL,1,1000,52);
    //这种方法比分配必要的辅助数据空间并在msghdr结构中设置合适的结构容易些。
    //注:如果实现把sctp_sendmsg函数映射成sendmsg函数,那么sendmsg的lflags参数被设为0。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

    8.sctp_recvmsg函数函数
    1. sctp_recvmsg函数为SCTP的高级特性提供用户的接口。使用本函数不仅能获取对端的地址,也能获取通常伴随recvmsg函数调用返回的msg_flags参数(例如MSG_NOTIFICATION和MSG_EOR等)。
    2. 本函数也允许获取已读入消息缓冲区中的伴随所接收消息的sctp_sndrcvinfo结构。注意,如果应用进程想要接收sctp_sndrcvinfo信息,那么必须使用SCTP_EVENTS套接字选项预订sctp_data_io_event (默认情况下开启)。
    #include
    ssize_t sctp_recvmsg(int sockfd,void *msg,size_t msgsz,struct sockaddr *from,socklen_t *fromlen,struct sct_sndrcvinfo *sinfo,int *msg_flags);
    //sockfd:套接字描述符
    //msg参数所指缓冲区中被填入最多msgsz字节的数据。
    //消息发送者的地址存放在from参数中
    //地址结构大小存放在fromlen参数中
    //msg_flags参数中存放可能有的消息标志
    //sinfo指向一个sctp_sndrcvinfo结构体,收到消息的时候会填写这个结构体,是一个输出参数;
    //若sctp_data_io_event被启用(默认情形),就会有与消息相关的细节信息来填充sctp_sndrcvinfo结构。
    //若把sctp_recvmsg函数映射成recvmsg函数,那么recvrnsg的flags参数被设为0。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    9.sctp_opt_info函数

    sctp_opt_info函数是为SCTP无法使用getsockopt函数的那些实现提供的,成功返回0,出错返回-1。

    #include
    int sctp_opt_info(int sockfd,sctp_assoc_t assoc_id,int opt,void *arg,socklen_t *siz);
    //sockfd:套接字描述符
    //assoc_id:给出可能存在的关联标识。
    //opt:SCTP套接字选项
    //arg:t套接字选项参数
    //siz:socklen_t类型指针,存放参数的大小
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    10.sctp_peeloff函数
    1. 从一 个一到多式套接字中抽取一个关联,构成单独一 个一 到一式套接字,其语义很像带有一个额外参数的accept函数。

    2. 调用者把一到多式套接字的sockfd和待抽取的关联标识id传递给函数调用。

    3. 调用结束时将返回 一 个新的套接字描述符,它是一个与所请求关联对应的一到 一 式套接字描述符。

    #include
    int sctp_peeloff(int sockfd,sctp_assoc_t id);
    //成功返回新的套接字描述符,错误-1。
    
    • 1
    • 2
    • 3
    10.shutdown函数

    终止网络连接的通常方法是调用close函数。不过close有2个限制,却可以用shutdown来避免。

    1. close把描述符的引用计数减1,仅在该计数变为0时才关闭套接字。使用shutdown可以不管引用计数就激发tcp的正常连接终止序列。
    2. close终止读和写两个方向的数据传送。既然tcp连接是全双工的,有时候我们需要告知对端我们已经完成了数据发送,即使对端仍有数据要发给我们。如下是这种情况的经典调用。

    在这里插入图片描述

    #include 
    int shutdown(int sockfd, int howto);
    //返回:若成功则为0,若出错则为-1
    
    • 1
    • 2
    • 3

    该函数的行为依赖于howto参数的值:

    1. SHUT_RD 关闭连接的读这一半, 套接字中不再有数据可接收,而且套接字接收缓冲区中的现有数据都被丢弃。进程不能再对这样的套接字调用任何读函数。对一个tcp套接字这样调用shutdown函数后,由该套接字接收的来自对端的任何数据都被确认,然后悄然丢弃。
    2. SHUT_WR 关闭连接的写这一半,对于这样的tcp套接字,这称为半关闭(half-close)。当前留着套接字发送缓冲区的数据将被发送掉,后跟tcp的正常连接终止序列。我们已经说过,不管套接字描述符的引用计数是否为0,这样的写半部照样执行。进程不能再对这样的套接字调用任何写函数。
    3. SHUT_RDWR 连接的读半部和写半部都关闭,这于调用shutdown两次等效:第一次调用指定SHUT_RD,第二次调用指定SHUT_WR。

    在这里插入图片描述

    • stdineof 是一个初始化为0的新标志。只要该标志为0,每次在主循环中我们总是select标准输入的的可读性。
    • 当我们在套接字上读到EOF时,如果我们已经在标准输入上输入了EOF,那就是正常的终止,于是函数返回。如果我们在标准输入上没有遇到EOF,那么服务器进程已过早终止。
      • 当我们在标准输入上碰到EOF时,我们就把stdineof置为1,并把第二个参数指定为SHUT_WR来调用shutdown来发送FIN。

    通知

    1. SCTP用户可经由这些通知追踪相关关联的状态,由套接字描述符获取;

    2. 通知传递的是传输级的事件,包括网络状态变动、关联启动、远程操作错误以及消息不可递送。不论是一到一式还是一到多式接口,默认情况下除 sctp_data_io_event 外的所有事件都是禁止的。

    3. 使用SCTP_EVENTS套接字选项可以预定8个事件。其中7个事件产生为通知的额为数据,通知本身可经由普通的套接字描述符获取。当产生它们的事件发生时,这些通知就会内嵌在数据中加入到套接字描述符。

    4. 在预订相应通知的前提下读取某个套接字时,用户数据和通知将在套接字缓冲区中交错出现,所以为了区分数据和通知,应该使用 recvmsg 或 sctp_recvmsg 函数。若返回的数据是一个事件通知,那么这两个函数返回的 msg_flags 参数将含有 MSG_NOTIFICATION 标志。

    5. 【通知格式】:都是采用标签-长度-值,其中前8个字节给出通知的类型和总长度;

    6. 开启sctp_data_io_event将导致每次读入用户数据都收到一个sctp_sndrcvinfo

    7. 一般信息通过调用recvmsg作为辅助数据获取,应用程序可调用sctp_recvmsg,同样的信息将被填写到由某个指针指出的sctp_sndrcvinfo

    struct sctp_tlv{
    	u_int16_t sn_type;
    	u_int16_t sn_flags;
    	u_int32_t sn_length;
    };
    
    union sctp_notification{
    	struct sctp_tlv sn_header;//用于解释类型值,以便译解出所处理的实际消息
    	struct sctp_assoc_change sn_assoc_change;
    	struct sctp_ paddr__change sn_paddr_change;
    	struct sctp_remote_error sn_remote_error;
    	struct sctp_send__failed sn_send_failed;
    	struct sctp_shutdown_event sn_shutdown_event;
    	struct sctp__adaption_event sn_adaption_event;
    	struct sctp pdapi_event sn_pdapi_event ;
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    下表给出了 sn_header.sn_type 的取值与 SCTP_EVENTS 套接字选项中使用的预订字段之间的对应关系:
    在这里插入图片描述

    1. SCTP_ASSOC_CHANGE:本通知告知应用进程关联本身发生变动:或者已开始一个新的关联,或者已结束一个现有的关联。
    struct sctp_assoc_change{    u_int16_t    sac_type;    u_int16_t    sac_flags;    u_int32_t    sac_length;    u_int16_t    sac_state;    u_int16_t    sac_error;    u_int16_t    sac_outbound_streams;    u_int16_t    sac_inbound_streams;    sctp_assoc_t sac_asoc_id;    u_int8_t    sac_info[];};
    
    //sac_state 给出关联上发生的事件类型状态:
    //SCTP_COMM_UP:某个新的关联刚刚启动
    //SCTP_COMM_LOST:由关联标识字段给出的关联已经关闭
    //SCTP_RESTART:表示对端已经重启
    //SCTP_SHUTDOWN_COMP:表示由本地端点激发的关联终止过程(或者通过调用 shutdown 函数,
    //或者通过以 MSG_EOF 标志使用 sendmsg 函数)已经结束
    
    //SCTP_CANT_STR_ASSOC:对端对于本端的关联建立尝试(例如 INIT 消息)未曾给出响应。
    //sac_error:存放导致本地关联变动的 SCTP 协议错误起因代码
    //sac_outbound_streams 和 sac_inbound_streams :存放本关联上每个方向协定的流数目
    //sac_assoc_id 存放本关联的唯一标识。
    //sac_info 字段存放用户可用的其他信息。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    1. SCTP_PEER_ADDR_CHANGE:本通知告知对端的某个地址经历了状态变动,这种变动既可以是失败性质(例如目的地不对所发送的消息作出响应),也可以是恢复性质(例如早先处于故障状态的某个目的地恢复正常):
    struct sctp_paddr_change{    u_int16_t    spc_type;    u_int16_t    spc_flags;    u_int32_t    spc_length;    struct sockaddr_storage    spc_aaddr;    u_int32_t    spc_state;    u_int32_t    spc_error;    sctp_assoc_t    spc_assoc_id;};
    // spc_aaddr 字段存放本事件所影响的对端地址。
    
    //当一个地址被声明为 SCTP_ADDR_UNREACHABLE 状态时,发送到该地址的任何数据都会被重新路由到一个候选地址。
    //另外,其中一些状态(如 SCTP_ADDR_ADDED 和 SCTP_ADDR_REMOVED)仅仅适用于支持动态地址选项的 SCTP 实现。
    //spc_error 存放用于提供关于事件更详细信息的通知错误代码,spc_assoc_id 存放关联标识。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    spc_state 字段存放下表所示的值之一:
    在这里插入图片描述

    1. SCTP_REMOTE_ERROR:远程端点可能给本端发送一个操作性错误消息,这些消息可以指示当前关联的各种出错条件。当开启本通知时,整个错误块将以内嵌格式传递给应用进程。
    struct sctp_remote_error{    u_int16_t    sre_type;    u_int16_t    sre_flags;    u_int32_t    sre_length;    u_int16_t    sre_error;    sctp_assoc_t    sre_assoc_id;    u_int8_t    sre_data[];};
    //sre_error 存放 SCTP 协议错误起因代码
    //sre_assoc_id 存放关联标识
    //sre_data 以内嵌格式存放完整的错误。
    
    • 1
    • 2
    • 3
    • 4
    1. SCTP_SEND_FAILED:无法递送到对端的消息会通过本通知送还给用户,这之后通常还会跟有一个关联故障通知
    struct sctp_send_failed{    u_int16_t    ssf_type;    u_int16_t    ssf_flags;    u_int32_t    ssf_length;    u_int32_t    ssf_error;    struct sctp_sndrcvinfo    ssf_info;    sctp_assoc_t    ssf_assoc_id;    u_int3_t    ssf_data[];};
    //ssf_flags 可取两个值为:SCTP_DATA_UNSENT:表示相应消息无法发送到对端;SCTP_DATA_SENT:表示相应消息已经至少发送到对端一次,然而对端一直没有确。
    //ssf_error 字段若不为 0
    //ssf_info 若有的话提供的是发送数据时传递给内核的信息(例如流数目、上下文等)
    //ssf_assoc_id 存放关联标识,
    //ssf_data 存放未能递送的消息本身
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. SCTP_SHUTDOWN_EVENT:本通知会在对端发送一个 SHUTDOWN 块到本地端点时传递给应用进程,它告知应用进程在相应套接字上不再接受新的数据。所有当前已排队的数据将被发送出去,发送完毕后相应关联就被终止。
    struct sctp_shutdown_event{    uint16_t    sse_type;    uint16_t    sse_flags;    uint32_t    see_length;    sctp_assoc_t    sse_assoc_id;};
    //sse_assoc_id 存放即将关闭的那个关联的关联标识。
    
    • 1
    • 2
    1. SCTP_ADAPTION_INDICATION:有些实现支持适应层指示参数,该参数在 INIT 和 INIT_ACK 中交换,用于通知对端将执行什么类型的应用适应行为。
    struct sctp_adaption_event{    u_int16_t    sai_type;    u_int16_t    sai_flags;    u_int32_t    sai_length;    u_int32_t    sai_adaption_ind;    sctp_assoc_t    sai_assoc_id;};
    //sai_assoc_id 给出了本适应层通知的关联标识
    //sai_adaption_ind 给出对端在 INIT 或 INIT_ACK 消息中传递给本地主机的 32 为整数。
    //外出适应层使用 SCTP_ADAPTION_LAYER 套接字选项设置。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    1. SCTP_PARTIAL_DELIVERY_EVENT:部分递送应用程序接口用于经由套接字缓冲区向用户传送大消息。
    //一个支持在整个消息到达之前就可以开始递送它的实现称为具备部分递送 API。
    //SCTP 实现如此调用部分递送 API:置空 msg_flags 字段,然后发送一个消息的各部分数据,在发送最后一部分数据时把 msg_flags 字段设置为 MSG_EOR。
    //如果应用进程准备接收大消息,应该使用 recvmsg 或 sctp_recvmsg 函数,以便查看 msg_flags 字段确定是否出现本条件。
    struct sctp_pdapi_event{    uint16_t    pdapi_type;    uint16_t    pdapi_flags;    uint32_t    pdapi_length;    uint32_t    pdapi_indication;    sctp_assoc_t    pdapi_assoc_id;};
    //pdapi_assoc_id 给出部分递送 API 事件发生的关联标识
    //pdapi_indication 存放发生的事件,目前该字段的唯一有效值是 SCTP_PARTIAL_DELIVERY_ABORTED,它指出当前活跃的部分递送已被终止。
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    SCTP客户/服务器

    在这里插入图片描述

    SCTP一到多式流分回射服务器程序

    #include    
    #include    
    #include    
    #include    
    #include    
    #include    
    
    #define     LISTENQ              1024
    #define     BUFFSIZE             4096
    #define     SERV_PORT            9877
    
    int sctp_get_no_strms(int sock_fd,struct sockaddr *to, socklen_t tolen)
    {
        socklen_t retsz;
        struct sctp_status status;
        retsz = sizeof(status);
        bzero(&status,sizeof(status));
    
        status.sstat_assoc_id = sctp_address_to_associd(sock_fd,to,tolen);
        getsockopt(sock_fd,IPPROTO_SCTP, SCTP_STATUS,
                   &status, &retsz);
        return(status.sstat_outstrms);
    }
    
    
    
    sctp_assoc_t sctp_address_to_associd(int sock_fd, struct sockaddr *sa, socklen_t salen)
    {
        struct sctp_paddrparams sp;
        socklen_t siz;
    
        siz = sizeof(struct sctp_paddrparams);
        bzero(&sp,siz);
        memcpy(&sp.spp_address,sa,salen);
        sctp_opt_info(sock_fd,0,
                      SCTP_PEER_ADDR_PARAMS, &sp, &siz);
        return(sp.spp_assoc_id);
    }
    
    
    
    int main(int argc, char **argv) {
        int sock_fd, msg_flags;
        char readBuf[BUFFSIZE];
        struct sockaddr_in servaddr, cliaddr;
        struct sctp_sndrcvinfo sri;
        struct sctp_event_subscribe evnts;
        int stream_increment = 1;
        socklen_t len;
        size_t rd_sz;
    
        if(argc == 2)
            stream_increment = atoi(argv[1]);
        /* 创建一个SCTP一到多式套接字 */
        sfd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
    
        /* 捆绑地址,对于多宿主机,这种捆绑意味着一个远程断点能够与这个本地主机任何一个路由
         * 地址建立关联并发送分组
         * */
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
    
        bind(sock_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    
        /* 预定sctp_data_io_event,从而允许服务器查看sctp_sndrcvinfo结构,
         * 服务器可从结构确定消息所在的流号
         * */
        bzero(&evnts, sizeof(evnts));
        evnts.sctp_data_io_event = 1;
        setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof(evnts));
    
        listen(sock_fd, LISTENQ);
    
        while (1) {
            len = sizeof(struct sockaddr_in);
            /* 等待消息,服务器初始化客户套接字地址结构的大小,后阻塞在等待来自任何一个远程
             * 对端的消息上;
             * */
            rd_sz = sctp_recvmsg(sock_fd, readBuf, sizeof(readBuf),
                                 (struct sockaddr*)&cliaddr, &len, &sri, &msg_flags);
            /* 当有消息到达时,服务器检查stream_increment来确定是否需要增长流号;
             * 若设置了该标志,服务器就把消息的流号增1,若流号增长最大流号时,服务器就把流号重置为0
             * */
            if(stream_increment) {
                sri.sinfo_stream++;
                if(sri.sinfo_stream >=
                    sctp_get_no_strms(sock_fd, (struct sockaddr*)&cliaddr, len))
                    sri.sinfo_stream = 0;
            }
            /* 服务器使用来自sri结构的净荷协议ID、标志一级可能改动过的流号发送回消息本身
             * 服务器不希望得到关联通知,故禁止了向上传递消息到套接字缓冲区的所有事件;
             * 本服务器依赖于sctp_sndrcvinfo结构中的消息和cliaddr返回地址定位对端的关联地址并回射消息;
             * */
            sctp_sendmsg(sock_fd, readBuf, rd_sz,
                         (struct sockaddr*)&cliaddr, len,
                                 sri.sinfo_ppid,
                                 sri.sinfo_flags, sri.sinfo_stream, 0, 0);
        }
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104

    SCTP流分回射客户程序

    #include    
    #include    
    #include    
    #include    
    #include    
    #include    
    #include    
    #include    
    
    #define     MAXLINE             4096
    #define     SERV_PORT           9877
    #define     SCTP_MAXLINE        800
    #define     SERV_MAX_SCTP_STRM  10
    
    void sctpstr_cli(FILE *fp, int sockfd, struct sockaddr *to, socklen_t tolen) {
        struct sockaddr_in peeraddr;
        struct sctp_sndrcvinfo sri;
        char sendline[MAXLINE], recvline[MAXLINE];
        socklen_t len;
        int out_sz, rd_sz;
        int msg_flags;
    
        /* 将sri清0,并循环阻塞读取 */
        bzero(&sri, sizeof(sri));
        while (fgets(sendline, MAXLINE, fp) != NULL) {
            /* 检查输入格式 */
            if(sendline[0] != '[') {
                cout << "Error, line must be of the from '[streamnum]text'\n";
                continue;
            }
            /* 将请求的流号转成sri结构的sinfo_stream */
            sri.sinfo_stream = strtol(&sendline[1], NULL, 0);
    
            /* 初始化目的地址结构的长度以及用户数据的大小后,客户使用sctp_sendmsg函数发送消息 */
            out_sz = strlen(sendline);
            sctp_sendmsg(sockfd, sendline, out_sz, to,
                         tolen, 0, 0, sri.sinfo_stream, 0, 0);
            /* 阻塞等待服务器的回射消息 */
            len = sizeof(peeraddr);
            rd_sz = sctp_recvmsg(sockfd, 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("%.*s", rd_sz, recvline);
        }
    }
    
    
    void sctpstr_cli_echoall(FILE *fp, int sock_fd, struct sockaddr *to,
            socklen_t tolen) {
        struct sockaddr_in peeraddr;
        struct sctp_sndrcvinfo sri;
        char sendline[SCTP_MAXLINE], recvline[SCTP_MAXLINE];
        socklen_t len;
        int rd_sz, i, strsz;
        int msg_flags;
    
        bzero(sendline, sizeof(sendline));
        bzero(&sri, sizeof(sri));
        while (fgets(sendline, SCTP_MAXLINE - 9, fp) != NULL) {
            strsz = strlen(sendline);
            if(sendline[strsz -1] == '\n'){
                sendline[strsz-1] = '\0';
                strsz--;
            }
            for(i=0; i<SERV_MAX_SCTP_STRM; i++) {
                snprintf(sendline + strsz, sizeof(sendline) - strsz,
                         ".msg.%d", i);
                sctp_sendmsg(sock_fd, sendline, sizeof(sendline),
                             to, tolen, 0, 0, i, 0, 0);
            }
            for(i=0; i<SERV_MAX_SCTP_STRM; ++i) {
                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]%s", rd_sz, recvline);
            }
        }
    }
    
    
    
    
    int main(int argc, char **argv) {
    
        int sock_fd;
        struct sockaddr_in servaddr;
        struct sctp_event_subscribe evnts;
        int echo_to_all = 0;
    
        if(argc < 2)
            err_quit("Missing host argument - use '%s host [echo]'\n", argv[0]);
    
        if(argc > 2) {
            err_quit("Echoing messages to all streams\n");
            echo_to_all = 1;
        }
        /* 创建一个sctp一到多式套接字 */
        sfd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
        bzero(&servaddr, sizeof(servaddr));
        servaddr.sin_family = AF_INET;
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        servaddr.sin_port = htons(SERV_PORT);
        inet_pton(AF_INET, argv[1], &servaddr.sin_addr);    // 服务器地址从表达式转化成数值格式
    
        /* 客户显示设置其一到多式SCTP套接字的通知预订*/
        bzero(&evnts, sizeof(evnts));
        evnts.sctp_data_io_event = 1;
        setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof(servaddr));
    
        if(echo_to_all == 0)
            sctpstr_cli(stdin, sfd, (struct sockaddr*)&servaddr, sizeof(servaddr));
        else
            sctpstr_cli_echoall(stdin, sock_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
        close(sock_fd);
    
        return 0;
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    探究头端阻塞
    • SCTP中内部关联具有先后顺序的消息序列;
      • 【避免仅使用单个TCP字节流导致的头端阻塞现象】:以流本身而不是以流所在关联为单位进行消息排序

    现象:

    1. 头端阻塞发送在一个TCP分解丢失,导致后续分解不按序到达接受端;

    2. 当第一张图丢失时,客户将保持已不按序到达的所有数据,直到丢失的分节被重传并成功到达为止;

    在这里插入图片描述

    SCTP该特性能够减少头端阻塞,后续的图不受第一幅的影响:

    在这里插入图片描述

    #define SCTP_MAXLINE 800
    #define SERV_MAX_SCTP_STRM 10
    
    void sctpstr_cli_echoall(FILE *fp, int sock_fd, struct sockaddr *to,
            socklen_t tolen) {
        struct sockaddr_in peeraddr;
        struct sctp_sndrcvinfo sri;
        char sendline[SCTP_MAXLINE], recvline[SCTP_MAXLINE];
        socklen_t len;
        int rd_sz, i, strsz;
        int msg_flags;
    
        bzero(sendline, sizeof(sendline));
        bzero(&sri, sizeof(sri));
        while (fgets(sendline, SCTP_MAXLINE - 9, fp) != NULL) {
            strsz = strlen(sendline);
            if(sendline[strsz -1] == '\n'){
                sendline[strsz-1] = '\0';
                strsz--;
            }
            for(i=0; i<SERV_MAX_SCTP_STRM; i++) {
                snprintf(sendline + strsz, sizeof(sendline) - strsz,
                         ".msg.%d", i);
                sctp_sendmsg(sock_fd, sendline, sizeof(sendline),
                             to, tolen, 0, 0, i, 0, 0);
            }
            for(i=0; i<SERV_MAX_SCTP_STRM; ++i) {
                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]%s", rd_sz, recvline);
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    控制流的数目

    如何在关联初始化阶段控制一个端点请求的流数目;

    if(argc == 2) 
    	stream_increment = atoi(argv[1]);
    sock_fd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
    /* 将sinit_num_ostream设置未期望请求的流数目 */
    bzero(&initm, sizeoof(initm));
    initm.sinit_num_ostream = SERV_MORE_STRMS_SCTP;
    setsockopt(sock_fd, IPPROTO_SCTP, SCTP_INITMSG, &initm, sizeof(initm));
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    控制终结

    如果服务器希望在发送完一个应答消息后终止一个关联

    1. 可在与该消息对应的sctp_sndrcvinfo结构的sinfo_flags字段中设置WSG_EOF标志;该标志使所发送消息被客户确认之后,相应关联也被终止;
    2. MSG_ABORT标志应用于sinfo_flags字段。该标志将以ABORT块迫使立即终止关联;SCTP的ABORT块类似TCP的RST分节,能够无延迟地中止任何关联,尚未发送的任何数据都被丢弃;

    服务器程序应答同时终止关联的改动部分:

    while(1) {
    	len = sizeof(struct sockaddr_in);
    	rd_sz = sctp_recvmgs(sock_fd, readbuf, sizeof(readbuf));
    	if(stream_increment) {
    		sri.sinfo_stream++;
    		if(sri.sinfo_stream >= 
    			sctp_get_no_strms(sock_fd, (struct sockaddr*)&cliaddr, len))
    			sri.sinfo_stream = 0;
    	}
    	sctp_sendmsg(sock_fd, readbuf, rd_sz, (struct sockaddr*)&cliaddr, len, sri.sinfo_ppid
    		, (sri.sinfo_flags | MSG_EOF), sri.sinfo_stream, 0, 0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    客户程序预先终止关联的改动部分:

    if(echo_to_all == 0)
    	sctpstr_cli(stdin, sock_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    else
    	sctpstr_cli_echoall(stdin, sock_fd, (struct sockaddr*)&servaddr, sizeof(servaddr));
    /* 客户准备一个信息座位关联中止的用户错误起因,后以MSG_ABORT调用sctp_sendmsg函数;
    	该标志导致一发送一个ABORT块,从而立即终止当前关联,该ABORT块包含用户发起错误起因代码
     */
    strcpy(byemsg, "goodbye");
    sctp_sendmsg(sock_fd, byemsg, strlen(byemsg), (struct sockaddr*)&servaddr, sizeof(servaddr), 0, MSG_ABORT, 0, 0, 0);
    /* 即使关联中止,仍得关闭套接字描述符以释放与关联的系统资源 */
    close(sock_fd);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
  • 相关阅读:
    Rust 语法笔记
    Python基础入门系列详解20篇
    const关键字
    【MybatisPlus】MP解决四种表与实体的映射问题,以及id自增策略
    dubbo高可用:负载均衡机制(十二)
    【华为OD机试真题 JS】连续字母长度
    docker基础篇:安装mysql单机版
    mysql外键
    HarmonyOS/OpenHarmony原生应用开发-华为Serverless认证服务说明(二)
    Servlet 学习笔记(一)
  • 原文地址:https://blog.csdn.net/weixin_50866517/article/details/126749020