linux网络编程之SCTP套接字常用接口

本文详细介绍了SCTP套接字编程接口,包括sctp_bindx、sctp_opt_info、sctp_recvmsg、sctp_sendmsg等关键函数的使用方法,并给出了客户端和服务端的实现示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前面文章我们总结了基本sctp套接字编程及实例讲解,基本跑通了sctp网络编程,但其中使用到的套接字常用接口并不十分完善,今天咱们就顺藤摸瓜,看看到底sctp常用套接字都有什么不一样。
废话不多说,开干!

一、SCTP套接字接口

​ 当 socket() 调用为 IPPROTO_SCTP 创建套接字时,它会调用特定于 SCTP 的套接字创建例程。针对 SCTP 套接字执行的套接字调用会自动调用相应的 SCTP 套接字例程。在一对一套接字中,每个套接字都对应一个 SCTP 关联。可以通过调用以下函数来创建一对一套接字:
socket(AF_INET[6], SOCK_STREAM, IPPROTO_STCP);

​ 在一对多风格套接字中,每个套接字都处理多个 SCTP 关联。每个关联都具有一个名为 sctp_assoc_t 的关联标识符。可以通过调用以下函数来创建一对多套接字:
socket(AF_INET[6], SOCK_SEQPACKET, IPPROTO_STCP);

1、sctp_bindx()

*int sctp_bindx(int sock , void addrs , int addrcnt , int flags );

​ 如果成功,则 sctp_bindx() 函数将返回 0。如果失败,则 sctp_bindx() 函数将返回 -1,并将 errno 的值设置为相应的错误代码。

2、sctp_opt_info()

**int sctp_opt_info(int sock , sctp_assoc_id_t id , int opt , void arg , socklen_t len );

​ sctp_opt_info() 函数将返回与 sock 参数中所述的套接字关联的 SCTP 级别选项。如果此套接字为一对多风格 SCTP 套接字,则 id 参数的值是某个特定关联。对于一对一风格 SCTP 套接字,将忽略 id 参数。opt 参数的值指定要获取的 SCTP 套接字选项。arg 参数的值是为调用程序而分配的特定于选项的结构缓冲区。*len 参数的值为选项长度。

​ 如果成功,则 sctp_opt_info() 函数将返回 0。如果失败,则 sctp_opt_info() 函数将返回 -1,并将 errno 的值设置为相应的错误代码。如果 sock 参数中传递给 sctp_opt_info() 的文件描述符无效,则 sctp_opt_info() 函数将失败并返回 EBADF。如果 sock 参数中传递给 sctp_opt_info() 函数的文件描述符没有描述套接字,则 sctp_opt_info() 函数将失败并返回 ENOTSOCK。如果关联 ID 对于一对多风格 SCTP 套接字而言无效,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EINVAL。如果输入缓冲区长度对于指定的选项而言过短,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EINVAL。如果对等地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_opt_info() 函数将失败,并将 errno 的值设置为 EAFNOSUPPORT。

opt 参数可以采用以下值:

(1)SCTP_RTOINFO

//返回用于初始化和绑定重新传输超时 (retransmission timeout, RTO) 可调参数的协议参数。

struct sctp_rtoinfo {
       sctp_assoc_t srto_assoc_id;	//调用程序提供此值,它指定所关注的关联。
       uint32_t     srto_initial;	//此值为初始 RTO 值。
       uint32_t     srto_max; 		//此值为最大 RTO 值。
       uint32_t     srto_min;		//此值为最小 RTO 值。
};
(2)SCTP_ASSOCINFO

//返回特定于关联的参数。这些参数使用以下结构:

struct sctp_assocparams {
     sctp_assoc_t sasoc_assoc_id;					//调用程序提供此值,它指定所关注的关联。
     uint16_t     sasoc_asocmaxrxt;					//此值指定关联的最大重新传输计数。
     uint16_t     sasoc_number_peer_destinations;	//此值指定对等方具有的地址数。
     uint32_t     sasoc_peer_rwnd;					//此值指定对等方接收窗口的当前值。
     uint32_t     sasoc_local_rwnd;					//此值指定对等方传输到的上一个已报告的接收窗口。
     uint32_t     sasoc_cookie_life;				//此值指定关联 cookie 的生命周期。可在发出 cookie 时使用此值。
}; //所有使用时间值的参数均以毫秒为单位。
(3)SCTP_DEFAULT_SEND_PARAM

//返回 sendto(3SOCKET) 函数调用在此关联中使用的缺省参数集

struct sctp_sndrcvinfo {
     uint16_t     sinfo_stream;		//此值指定 ​sendmsg()​ 调用的缺省流。
     uint16_t     sinfo_ssn;		//此值始终为 0。
     uint16_t     sinfo_flags;		//此值包含 ​sendmsg()​ 调用的缺省标志。此标志可以采用以下值:MSG_UNORDERED、MSG_ADDR_OVER、MSG_ABORT、MSG_EOF、MSG_PR_SCTP
     uint32_t     sinfo_ppid;		//此值为 ​sendmsg()​ 调用的缺省有效负荷协议标识符。
     uint32_t     sinfo_context;	//此值为 ​sendmsg()​ 调用的缺省上下文
     uint32_t     sinfo_timetolive;	//此值指定时间段(以毫秒为单位)。在此时间段过后,如果消息传输尚未开始,则消息将过期。值为 0 指示消息尚未过期。如果设置了 MSG_PR_SCTP 标志,当消息传输未在 sinfo_timetolive 所指定的时间段内成功完成时,消息将过期。
     uint32_t     sinfo_tsn;		//此值始终为 0。
     uint32_t     sinfo_cumtsn;		//此值始终为 0。
     sctp_assoc_t sinfo_assoc_id;	//此值由调用程序填充。它指定所关注的关联。
};
(4)SCTP_PEER_ADDR_PARAMS

//返回所指定对等地址的参数

struct sctp_paddrparams {
     sctp_assoc_t            spp_assoc_id;		//调用程序提供此值,它指定所关注的关联。
     struct sockaddr_storage spp_address;		//此值指定所关注的对等地址。
     uint32_t                spp_hbinterval;	//此值指定心跳间隔(以毫秒为单位)。
     uint16_t                spp_pathmaxrxt;	//此值指定在认为地址不可访问之前针对此地址尝试的最大重新传输数。
};
(5)SCTP_STATUS

//返回有关关联的当前状态信息

struct sctp_status {
     sctp_assoc_t          sstat_assoc_id;				//调用程序提供此值,它指定所关注的关联。
     int32_t               sstat_state;					//此值为关联的当前状态。关联可以采用以下状态:
    //SCTP_IDLE:SCTP 端点没有任何与其关联的关联。一旦 ​socket()​ 函数调用打开一个端点或端点关闭,则端点便会处于此状态。
    //SCTP_BOUND:SCTP 端点在调用 ​bind()​ 之后绑定到一个或多个本地地址。
    //SCTP_LISTEN:此端点在等待来自任何远程 SCTP 端点的关联请求。
    //SCTP_COOKIE_WAIT:此 SCTP 端点已发送 INIT 块并在等待 INIT-ACK 块。
    //SCTP_COOKIE_ECHOED:此 SCTP 端点已将从其对等方的 INIT-ACK 块接收的 cookie 回显到对等方。
    //SCTP_ESTABLISHED:此 SCTP 端点可以与其对等方交换数据。
    //SCTP_SHUTDOWN_PENDING:此 SCTP 端点已从其上层接收了 SHUTDOWN 元语。此端点不再从其上层接受数据。
    //SCTP_SHUTDOWN_SEND:处于 SCTP_SHUTDOWN_PENDING 状态的 SCTP 端点已向其对等方发送了 SHUTDOWN 块。仅在确认所有从此端点到其对等方的未完成数据之后,才发送 SHUTDOWN 块。当此端点的对等方发送 SHUTDOWN ACK 块时,此端点会发送 SHUTDOWN COMPLETE 块并认为关联已关闭。
    //SCTP_SHUTDOWN_RECEIVED:SCTP 端点已从其对等方接收了 SHUTDOWN 块。此端点不再从其用户接受新数据。
    //SCTP_SHUTDOWN_ACK_SEND:处于 SCTP_SHUTDOWN_RECEIVED 状态的 SCTP 端点已向其对等方发送了 SHUTDOWN ACK 块。此端点仅在其对等方确认来自此端点的所有未完成数据之后发送 SHUTDOWN ACK 块。当此端点的对等方发送 SHUTDOWN COMPLETE 块时,将关闭关联。
     uint32_t              sstat_rwnd;					//此值为关联对等方的当前接收窗口。
     uint16_t              sstat_unackdata;				//此值为未确认的 DATA 块数。
     uint16_t              sstat_penddata;				//此值为等待接收的 DATA 块数。
     uint16_t              sstat_instrms;				//此值为传入的流数。
     uint16_t              sstat_outstrms;				//此值为外发的流数。
     uint32_t              sstat_fragmentation_point;	//如果消息、SCTP 数据包头和 IP 数据包头的组合大小超出 sstat_fragmentation_point 的值,则消息会分段。此值等于包目标地址的路径最大传输单元 (Path Maximum Transmission Unit, P-MTU)。
     struct sctp_paddrinfo sstat_primary;
};
(6)sstat_primary

// 此值包含有关主要对等地址的信息

struct sctp_paddrinfo {
     sctp_assoc_t            spinfo_assoc_id;	//调用程序提供此值,它指定所关注的关联。
     struct sockaddr_storage spinfo_address;	//此值为主要对等地址。
     int32_t                 spinfo_state;		//此值可用SCTP_ACTIVE 或 SCTP_INACTIVE 两个值中的任意一个。
     uint32_t                spinfo_cwnd;		//此值为对等地址的拥塞窗口。
     uint32_t                spinfo_srtt;		//此值为对等地址的当前平滑往返时间计算结果,以毫秒为单位。
     uint32_t                spinfo_rto;		//此值为对等地址的当前重新传输超时值,以毫秒为单位。
     uint32_t                spinfo_mtu;		//此值为对等地址的 P-MTU。
};

3、sctp_recvmsg()

**ssize_t sctp_recvmsg(int s , void *msg , size_t len , struct sockaddr *from , socklen_t *fromlen , struct sctp_sndrcvinfo sinfo , int msg_flags );

​ 使用 sctp_recvmsg() 函数,可以从 s 参数所指定的 SCTP 端点接收消息。调用程序可以指定以下属性:

msg  	//此参数为消息缓冲区的地址。
len		//此参数为消息缓冲区的长度。
from	//此参数为指向包含发送主机地址的地址的指针。
fromlen //此参数为与 from 参数中的地址关联的缓冲区的大小。
sinfo 	//此参数仅在调用程序启用 sctp_data_io_events 时处于活动状态。要启用 sctp_data_io_events,请使用套接字选项 SCTP_EVENTS 调用 setsockopt() 函数。如果启用了 sctp_data_io_events,则应用程序将接收每个传入消息的 sctp_sndrcvinfo 结构的内容。此参数为指向 sctp_sndrcvinfo 结构的指针。此结构将在接收消息时进行填充。
msg_flags //此参数包含所有存在的消息标志。

sctp_recvmsg() 函数将返回其接收的字节数。sctp_recvmsg() 函数将在出现错误时返回 -1。

​ 如果在 s 参数中传递的文件描述符无效,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 msg_flags 参数包括值 MSG_OOB,则 sctp_recvmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_recvmsg() 函数将失败,并将 errno的值设置为 ENOTCONN。

4、sctp_sendmsg()

**ssize_t sctp_sendmsg(int s , const void msg , size_t len , const struct sockaddr to , socklen_t tolen , uint32_t ppid , uint32_t flags , uint16_t stream_no , uint32_t timetolive , uint32_t context );

​ sctp_sendmsg() 函数在发送来自 SCTP 端点的消息时启用高级 SCTP 功能。

s			//此值指定发送消息的 SCTP 端点。
msg			//此值包含 sctp_sendmsg() 函数所发送的消息。
len			//此值为消息的长度,以字节为单位。
to			//此值为消息的目标地址。
tolen		//此值为目标地址的长度。
ppid		//此值为应用程序指定的有效负荷协议标识符。
stream_no	//此值为此消息的目标流。
timetolive	//此值为消息未能成功发送到对等方的情况下消息过期之前可以等待的时间段,以毫秒为单位。
context 	//如果在发送消息时出现错误,则返回此值。
flags 		//此值在将逻辑运算 OR 以按位形式应用于以下零个或多个标志位时形成:
MSG_UNORDERED //设置此标志之后,sctp_sendmsg() 函数将无序传送消息。
MSG_ADDR_OVER //设置此标志之后,sctp_sendmsg() 函数将使用 to 参数中的地址,而不使用关联的主要目标地址。此标志仅用于一对多风格 SCTP 套接字。
MSG_ABORT 	//设置此标志之后,指定的关联将异常中止,同时向其对等方发送 ABORT 信号。此标志仅用于一对多风格 SCTP 套接字。
MSG_EOF 	//设置此标志之后,指定的关联将进入正常关机状态。此标志仅用于一对多风格 SCTP 套接字。
MSG_PR_SCTP //设置此标志之后,如果消息传输未在 timetolive 参数所指定的时间段内成功完成,则消息将过期。

sctp_sendmsg() 函数将返回其发送的字节数。sctp_sendmsg() 函数将在出现错误时返回 -1。

​ 如果在 s 参数中传递的文件描述符无效,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 flags 参数包括值 MSG_OOB,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果一对一风格套接字的 flags 参数包括 MSG_ABORT 或 MSG_EOF值,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 ENOTCONN。如果套接字关闭,禁止进一步写入,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EPIPE。如果套接字为非阻止套接字并且传输队列已满,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EAGAIN。

​ 如果控制消息长度不正确,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果指定的目标地址不属于关联,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果 stream_no 的值不在关联所支持的外发流数之内,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。如果所指定的目标地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_sendmsg() 函数将失败,并将 errno 的值设置为 EINVAL。

5、sctp_send()

**ssize_t sctp_send(int s , const void msg , size_t len , const struct sctp_sndrcvinfo sinfo , int flags );

​ sctp_send() 函数可供一对一及一对多风格套接字使用。sctp_send() 函数在发送来自 SCTP 端点的消息时启用高级 SCTP 功能。

s		//此值指定 ​socket()​ 函数所创建的套接字。
msg		//此值包含 ​sctp_send()​ 函数所发送的消息。
len		//此值为消息的长度,以字节为单位。
sinfo	//此值包含用于发送消息的参数。对于一对多风格套接字,此值可以包含消息所发送到的关联 ID。
flags	//此值与 ​sendmsg()​ 函数中的标志参数相同。

sctp_send() 函数将返回其发送的字节数。sctp_send() 函数将在出现错误时返回 -1。

​ 如果在 s 参数中传递的文件描述符无效,则 sctp_send() 函数将失败,并将 errno 的值设置为 EBADF。如果在 s 参数中传递的文件描述符没有描述套接字,则 sctp_send() 函数将失败,并将 errno 的值设置为 ENOTSOCK。如果 sinfo 参数的 sinfo_flags 字段包括值 MSG_OOB,则 sctp_send() 函数将失败,并将 errno 的值设置为 EOPNOTSUPP。如果一对一风格套接字中 sinfo 参数的 sinfo_flags 字段包括 MSG_ABORT 或 MSG_EOF 值,则 sctp_send() 函数将失败,并将 errno 值的设置为 EOPNOTSUPP。如果没有建立关联,则 sctp_send() 函数将失败,并将 errno 的值设置为 ENOTCONN。如果套接字关闭,禁止进一步写入,则 sctp_send() 函数将失败,并将 errno 的值设置为 EPIPE。如果套接字为非阻止套接字并且传输队列已满,则 sctp_send() 函数将失败,并将 errno 的值设置为 EAGAIN。

​ 如果控制消息长度不正确,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果指定的目标地址不属于关联,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果 stream_no 的值不在关联所支持的外发流数之内,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。如果所指定的目标地址的地址族不是 AF_INET 或 AF_INET6,则 sctp_send() 函数将失败,并将 errno 的值设置为 EINVAL。

6、分叉关联

​ 应用程序可以将一对多风格套接字上已建立的关联分叉为独立的套接字和文件描述符。对于具有多个偶发消息发送者或接收者的应用程序,如果这些发送者或接收者需要存在于原始一对多风格套接字之下,则独立的套接字和文件描述符非常有用。应用程序会将传输大量数据通信流量的关联分叉为独立的套接字描述符。应用程序使用 sctp_peeloff() 调用将关联分叉为独立的套接字。新套接字为一对一风格套接字。sctp_peeloff() 函数的语法如下:

int sctp_peeloff(int sock , sctp_assoc_t id );

sock	//从 ​socket()​ 系统调用返回的原始一对多风格套接字描述符
id		//要分叉为独立的文件描述符的关联的标识符

​ 如果在 sock 参数中传递的套接字描述符不是一对多风格 SCTP 套接字,则 sctp_peeloff() 函数将失败并返回 EOPTNOTSUPP。如果 id 的值为 0 或者 id 的值大于在 sock 参数中传递的套接字描述符的最大关联数,则 sctp_peeloff() 函数将失败并返回 EINVAL。如果 sctp_peeloff()函数无法创建新的用户文件描述符或文件结构,则此函数将失败并返回 EMFILE。

7、sctp_getpaddrs()

// 返回关联中的所有对等地址。

**int sctp_getpaddrs(int sock , sctp_assoc_t id , void addrs );

​ 当 sctp_getpaddrs() 函数成功返回时,**addrs 参数的值将指向每个地址相应类型的动态分配的压缩 sockaddr 结构数组。调用线程使用 sctp_freepaddrs() 函数释放内存。**addrs 参数的值不能为 NULL。如果 sock 中给定的套接字描述符用于 IPv4 套接字,则 sctp_getpaddrs() 函数将返回 IPv4 地址。如果 sock 中给定的套接字描述符用于 IPv6 套接字,则 sctp_getpaddrs() 函数将同时返回 IPv4 和 IPv6 地址。对于一对多风格套接字,id 参数指定要查询的关联。对于一对一风格套接字,sctp_getpaddrs() 函数将忽略 id 参数。当 sctp_getpaddrs() 函数成功返回时,它将返回关联中的对等地址数。如果此套接字上没有关联,则 sctp_getpaddrs() 函数将返回 0,并且不定义 **addrs 参数的值。如果出现错误,则 sctp_getpaddrs() 函数将返回 -1,并且不定义 **addrs 参数的值。

​ 如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符无效,则 sctp_getpaddrs() 函数将失败并返回 EBADF。如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符没有描述套接字,则 sctp_getpaddrs() 函数将失败并返回 ENOTSOCK。如果 sock 参数中传递给 sctp_getpaddrs() 函数的文件描述符描述了未连接的套接字,则 sctp_getpaddrs() 函数将失败并返回 ENOTCONN。

8、sctp_freepaddrs()

// 将释放所有由之前的 sctp_getpaddrs() 调用所分配的资源

*void sctp_freepaddrs(void addrs );

*addrs 参数为包含 ​sctp_getpaddrs()​ 函数所返回的对等地址的数组。

9、sctp_getladdrs()

// 将返回套接字上的所有本地绑定的地址

**int sctp_getladdrs(int sock , sctp_assoc_t id , void addrs );

​ 当 sctp_getladdrs() 函数成功返回时,addrs 的值将指向动态分配的压缩 sockaddr 结构数组。sockaddr 结构为每个本地地址的相应类型。调用应用程序使用 sctp_freeladdrs() 函数释放内存。addrs 参数的值不能为 NULL。

​ 如果 sd 参数引用的套接字为 IPv4 套接字,则 sctp_getladdrs() 函数将返回 IPv4 地址。如果 sd 参数引用的套接字为 IPv6 套接字,则 sctp_getladdrs() 函数将同时返回相应的 IPv4 或 IPv6 地址。

​ 针对一对多风格套接字调用 sctp_getladdrs() 函数时,id 参数的值指定要查询的关联。sctp_getladdrs() 函数在一对一套接字上运行时将忽略 id 参数。

​ 当 id 参数的值为 0 时,无论为何种特定关联,sctp_getladdrs() 函数都将返回本地绑定的地址。当 sctp_getladdrs() 函数成功返回时,它将报告绑定到套接字的本地地址数。如果未绑定套接字,则 sctp_getladdrs() 函数将返回 0,并且不定义 *addrs 的值。如果出现错误,则 sctp_getladdrs() 函数将返回 -1,并且不定义 *addrs 的值。

10、sctp_freeladdrs()

// 将释放所有由之前的 sctp_getladdrs() 调用所分配的资源

*void sctp_freeladdrs(void addrs );

*addrs 参数为包含 ​sctp_getladdrs()​ 函数所返回的对等地址的数组。

二. SCTP客户端/服务端实现

Linux内核从2.6已经支持SCTP协议栈了,而且也提供了套接口(socket), SCTP的套接口两类:一对一(类似TCP)和一对多(类似UDP)

一对一

​ 客户端可以用connect()连接服务器, write(), read()读写,close()关闭套接口;服务器端用bind()绑定端口,listen()监听,accept()接受连接,write()/read()读写,close() 关闭,这和普通TCP程序是相同的。

一对多

​ 一对多方式的SCTP编程和UDP类似,打开的是SCTP的有序分组接口 :

​ socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)

​ 用的socket(), bin(), listen(), close()等函数和原来一样,但发送接收数据是用sctp_sendto(),sctp_sendmsg()和sctp_recvmsg()这些SCTP专用函数。

具体实例

客户端源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
 
#define MAX_BUFFER  1024
#define MY_PORT_NUM 19000
#define LOCALTIME_STREAM    0
#define GMT_STREAM   1  
/*
struct sctp_sndrcvinfo {
    __u16 sinfo_stream;  //目标流
    __u16 sinfo_ssn;     //流序号
    __u16 sinfo_flags;   //标识符
    __u32 sinfo_ppid;    //有效负荷协议标识符
    __u32 sinfo_context; //出错返回值
    __u32 sinfo_timetolive;  //等待时间
    __u32 sinfo_tsn;         //传输序号
    __u32 sinfo_cumtsn;      //累积TSN
    sctp_assoc_t sinfo_assoc_id;  //关联ID
};
*/

int main()
{
  int connSock, in, i, flags;
  struct c servaddr;
  struct sctp_status status;			//返回有关关联的当前状态信息
  struct sctp_sndrcvinfo sndrcvinfo;	//消息相关细节信息
  struct sctp_event_subscribe events;	//事件集
  struct sctp_initmsg initmsg;
  char buffer[MAX_BUFFER+1];
 
  /* Create an SCTP TCP-Style Socket */
  connSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
 
  /* Specify that a maximum of 5 streams will be available per socket */
  memset( &initmsg, 0, sizeof(initmsg) );
  initmsg.sinit_num_ostreams = 5;
  initmsg.sinit_max_instreams = 5;
  initmsg.sinit_max_attempts = 4;
  setsockopt( connSock, IPPROTO_SCTP, SCTP_INITMSG,
                     &initmsg, sizeof(initmsg) );
 
  /* Specify the peer endpoint to which we'll connect */
  bzero( (void *)&servaddr, sizeof(servaddr) );
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(MY_PORT_NUM);
  servaddr.sin_addr.s_addr = inet_addr( "127.0.0.1" );
 
  /* Connect to the server */
  connect( connSock, (struct sockaddr *)&servaddr, sizeof(servaddr) );
 
  /* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */
  memset( (void *)&events, 0, sizeof(events) );
  events.sctp_data_io_event = 1;
  setsockopt( connSock, SOL_SCTP, SCTP_EVENTS,
                     (const void *)&events, sizeof(events) );
 
  /* Read and emit the status of the Socket (optional step) */
  in = sizeof(status);
  getsockopt( connSock, SOL_SCTP, SCTP_STATUS,
                     (void *)&status, (socklen_t *)&in );
 
  printf("assoc id = %d\n", status.sstat_assoc_id );
  printf("state = %d\n", status.sstat_state );
  printf("instrms = %d\n", status.sstat_instrms );
  printf("outstrms = %d\n", status.sstat_outstrms );
 
  /* Expect two messages from the peer */

  for (i = 0 ; i < 2 ; i++) {
 
/*
int sctp_recvmsg(int sd, void * msg, size_t len,
                struct sockaddr * from, socklen_t * fromlen,
                struct sctp_sndrcvinfo * sinfo, int * msg_flags);
    sd :    socket描述符
    msg:   消息指针
    len:    消息长度
    from:   源地址
    fromlen:    源地址长度
    sinfo:  消息的选项信息, 需要启用套接字选项 sctp_data_io_event
    msg_flags:  消息标识符
*/

    in = sctp_recvmsg( connSock, (void *)buffer, sizeof(buffer),
                        (struct sockaddr *)NULL, 0, &sndrcvinfo, &flags );
 
    if (in > 0) {
      buffer[in] = 0;
      if (sndrcvinfo.sinfo_stream == LOCALTIME_STREAM) {
        printf("(Local) %s\n", buffer);
      } else if (sndrcvinfo.sinfo_stream == GMT_STREAM) {
        printf("(GMT ) %s\n", buffer);
      }
    }
 
  }
 
  /* Close our socket and exit */
  close(connSock);
 
  return 0;
}

服务端源码:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
 
#define MAX_BUFFER  1024
#define MY_PORT_NUM 19000
#define LOCALTIME_STREAM    0
#define GMT_STREAM   1  
 

int main()
{
  int listenSock, connSock, ret;
  struct sockaddr_in servaddr;
  struct sctp_initmsg initmsg;
  char buffer[MAX_BUFFER+1];
  time_t currentTime;
 
  /* Create SCTP TCP-Style Socket */
  listenSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
 
  /* Accept connections from any interface */
  bzero( (void *)&servaddr, sizeof(servaddr) );
  servaddr.sin_family = AF_INET;
  servaddr.sin_addr.s_addr = htonl( INADDR_ANY );
  servaddr.sin_port = htons(MY_PORT_NUM);
 
  ret = bind( listenSock, (struct sockaddr *)&servaddr, sizeof(servaddr) );
 
  /* Specify that a maximum of 5 streams will be available per socket */
  memset( &initmsg, 0, sizeof(initmsg) );
  initmsg.sinit_num_ostreams = 5;
  initmsg.sinit_max_instreams = 5;
  initmsg.sinit_max_attempts = 4;
  ret = setsockopt( listenSock, IPPROTO_SCTP, SCTP_INITMSG,
                     &initmsg, sizeof(initmsg) );
 
  /* Place the server socket into the listening state */
  listen( listenSock, 5 );
 
  /* Server loop... */
  while( 1 ) {
 
    /* Await a new client connection */
    printf("Awaiting a new connection\n");
    connSock = accept( listenSock, (struct sockaddr *)NULL, (int *)NULL );
 
    /* New client socket has connected */
 
    /* Grab the current time */
    currentTime = time(NULL);
 
/*
int sctp_sendmsg(int sd, const void * msg, size_t len,
                struct sockaddr *to, socklen_t tolen,
                uint32_t ppid, uint32_t flags,
                uint16_t stream_no, uint32_t timetolive,
                uint32_t context);
    sd :    socket描述符
    msg:   消息指针
    len:    消息长度
    to:    目的地址
    tolen: 目的地址长度
    ppid:   应用指定的有效负荷协议标识符
    flags:  发送标识符
    stream_no: 目标流
    timetolive: 等待时间,此值为消息未能成功发送到对等方的情况下消息过期之前可以等待的时间段,以毫秒为单位。
    context:    出错返回值,如果在发送消息时出现错误,则返回此值。
*/

    /* Send local time on stream 0 (local time stream) */
    snprintf( buffer, MAX_BUFFER, "%s\n", ctime(&currentTime) );
    ret = sctp_sendmsg( connSock, (void *)buffer, (size_t)strlen(buffer),
                         NULL, 0, 0, 0, LOCALTIME_STREAM, 0, 0 );
 
    /* Send GMT on stream 1 (GMT stream) */
    snprintf( buffer, MAX_BUFFER, "%s\n", asctime( gmtime( &currentTime ) ) );
    ret = sctp_sendmsg( connSock, (void *)buffer, (size_t)strlen(buffer),
                         NULL, 0, 0, 0, GMT_STREAM, 0, 0 );
 
    /* Close the client connection */
    close( connSock );
 
  }
 
  return 0;
}

[推荐一个零声学院免费公开课程,个人觉得老师讲得不错,分享给大家:Linux,Nginx,ZeroMQ,MySQL,Redis,
fastdfs,MongoDB,ZK,流媒体,CDN,P2P,K8S,Docker,TCP/IP,协程,DPDK等技术内容,点击立即学习:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值