《UNIX 网络编程-卷1》阅读笔记28: 高级SCTP套接字编程
作者: andylin02
学习章节: 第二十七章 高级SCTP套接字编程
关键词: SCTP, 高级SCTP编程, 部分递送, 无序数据, 地址捆绑, 多宿, 头端阻塞, 心搏机制, 关联剥离, 通知事件
一、章节概述
1.1 本章焦点
第二十七章深入探讨SCTP协议的高级编程特性。SCTP是一个较新的传输层协议,由IETF于2000年标准化,最初是为满足IP电话市场而设计,用于穿越因特网传输电话信令。与TCP和UDP相比,SCTP提供了独特的多宿(multi-homing)、多流(multi-streaming)等高级特性。
SCTP提供两种接口形式:一到一(One-to-One) 风格(类似于TCP),主要用于移植现有TCP应用程序到SCTP;一到多(One-to-Many) 风格(类似于UDP),大多数新开发的SCTP应用使用一到多接口,以充分利用SCTP的所有特性。
💡 本章核心价值:读完第二十七章,你将能够——
- 理解SCTP的通知机制和事件驱动编程模型
- 利用SCTP的多流特性解决TCP队头阻塞问题
- 掌握SCTP的部分递送与无序数据发送机制
- 使用
sctp_bindx实现地址子集捆绑- 利用心搏机制监控多宿环境中的路径可达性
- 使用
sctp_peeloff将一到多套接字剥离为一到一套接字- 掌握
getpaddrs和getladdrs获取对端和本端地址信息
1.2 本章内容结构
| 节号 | 标题 | 核心内容 |
|---|---|---|
| 27.1 | SCTP通知 |
SCTP_ASSOC_CHANGE、SCTP_PEER_ADDR_CHANGE等11种通知类型 |
| 27.2 | SCTP多流编程 | 多流特性如何减少头端阻塞,流的创建与管理 |
| 27.3 | SCTP部分递送与无序数据 | 部分递送API、无序消息发送机制 |
| 27.4 | SCTP地址捆绑 |
sctp_bindx捆绑地址子集,sctp_connectx连接到多宿对端 |
| 27.5 | SCTP获取地址信息 |
sctp_getpaddrs/sctp_getladdrs获取对端和本端地址 |
| 27.6 | SCTP的心搏机制 | 地址不可达检测、故障切换、RTT测量 |
| 27.7 | 关联剥离 |
sctp_peeloff将关联从一到多套接字中剥离 |
| 27.8 | SCTP与TCP/UDP对比 | 三种协议的核心特性对比 |
二、SCTP通知机制(Notification)
2.1 什么是SCTP通知
SCTP套接字上的通知机制允许应用程序接收关联状态变化、地址状态变化等异步事件,与TCP完全缺乏此类主动通知机制形成鲜明对比。这使得SCTP非常适用于需要实时监控网络状态的应用场景。
2.2 事件预订
应用程序需要通过SCTP_EVENTS套接字选项预订通知事件。预订sctp_data_io_event后,服务器才能在接收消息时获取流号和struct sctp_sndrcvinfo结构中的信息。
#include <netinet/sctp.h>
struct sctp_event_subscribe events;
bzero(&events, sizeof(events));
events.sctp_data_io_event = 1; // 预订数据I/O事件
events.sctp_association_event = 1; // 预订关联状态变化事件
events.sctp_address_event = 1; // 预订地址状态变化事件
events.sctp_send_failure_event = 1; // 预订发送失败通知
Setsockopt(sockfd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
2.3 11种通知事件类型
| 通知类型 | 事件宏 | 说明 |
|---|---|---|
| 数据I/O | sctp_data_io_event |
允许服务器查看sctp_sndrcvinfo结构,确定消息到达所在的流号 |
| 关联变化 | sctp_association_event |
关联建立、关闭、重启等状态变化 |
| 地址变化 | sctp_address_event |
地址状态变化(如故障或恢复服务) |
| 发送失败 | sctp_send_failure_event |
发送失败通知 |
| 对端错误 | sctp_peer_error_event |
对端错误通知 |
| 关闭事件 | sctp_shutdown_event |
关联关闭事件 |
| 部分递送 | sctp_partial_delivery_event |
部分递送事件 |
| 适配层事件 | sctp_adaption_layer_event |
适配层事件 |
| 认证事件 | sctp_auth_event |
认证相关事件 |
| 流重置事件 | sctp_stream_reset_event |
流重置事件 |
| 关联重置事件 | sctp_assoc_reset_event |
关联重置事件 |
💡 关键理解:
sctp_data_io_event是唯一默认开启的事件,其他所有事件默认都是禁止的,需要显式预订。使用SCTP_EVENTS套接字选项可以精确控制需要接收哪些通知。
三、SCTP多流编程
3.1 多流的核心价值
SCTP的多流特性能够尽可能减少头端阻塞(Head-of-Line Blocking)。在TCP中,单一字节流中任何位置的字节丢失都将阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。而SCTP在关联中提供多个独立的流,同一个流中的数据会延缓,但不影响其他流。
3.2 流与流序列号(SSN)
SCTP的流是关联内部具有先后顺序的消息队列。每个流都有自己的流序列号(Stream Sequence Number, SSN):
- 流序列号仅在对应流内唯一,不同流间可以重复
- 每条进入流的新消息会获得一个递增的流序列号
- 消息的递送顺序在同一个流内保证,不同流之间可以独立排序
3.3 解决头端阻塞的机制
SCTP通过以下机制减少头端阻塞:
| 机制 | 说明 |
|---|---|
| 多流独立排序 | 不同流的消息可以独立地、顺序地传输 |
| 无序递送 | 通过设置U位(unordered bit)允许消息无序递交 |
| 消息边界保留 | SCTP是面向消息的协议,保留记录边界 |
3.4 SCTP回射服务器多流示例
#include "unp.h"
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 events;
int stream_increment = 1;
socklen_t len;
size_t rd_sz;
if (argc == 2)
stream_increment = atoi(argv[1]);
// 创建一到多式SCTP套接字
sock_fd = 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, (SA *)&servaddr, sizeof(servaddr));
// 预订数据I/O事件,获取sctp_sndrcvinfo结构
bzero(&events, sizeof(events));
events.sctp_data_io_event = 1; // 仅预订此事件,获取消息到达所在的流号
Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
Listen(sock_fd, LISTENQ);
for ( ; ; ) {
len = sizeof(struct sockaddr_in);
rd_sz = Sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),
(SA *)&cliaddr, &len, &sri, &msg_flags);
// 流号递增逻辑:输入流号 + 1
if (stream_increment) {
sri.sinfo_stream++;
// 达到最大流号时归零
if (sri.sinfo_stream >= sctp_get_no_strms(sock_fd, (SA *)&cliaddr, len))
sri.sinfo_stream = 0;
}
Sctp_sendmsg(sock_fd, readbuf, rd_sz, (SA *)&cliaddr, len,
sri.sinfo_ppid, sri.sinfo_flags, sri.sinfo_stream, 0, 0);
}
}
💡 代码说明:
SOCK_SEQPACKET是一到多式套接字类型sri.sinfo_stream存储消息到达的流号,服务器将其加1后发回sctp_get_no_strms函数通过SCTP_STATUS套接字选项获取关联中协商的最大流数- 这种流号递增策略展示了SCTP多流的实际应用
四、部分递送与无序数据
4.1 部分递送机制
SCTP支持部分递送(Partial Delivery),允许应用程序在接收大消息时,在消息完全接收之前就开始处理部分数据。这在处理大型数据报时可以提高响应速度。
// 设置部分递送点
int pd_point = 4096; // 每4KB触发一次部分递送
Setsockopt(sockfd, IPPROTO_SCTP, SCTP_PARTIAL_DELIVERY_POINT, &pd_point, sizeof(pd_point));
💡 应用场景:部分递送对于处理大型消息(如大文件传输)特别有用,允许应用程序在处理大数据的同时继续接收新数据,避免了完全阻塞等待完整消息。
4.2 无序数据发送
SCTP支持两种递送模式:
- 有序递送(Ordered Delivery):U位清除(0),同一流内保证顺序
- 无序递送(Unordered Delivery):U位设置(1),消息可以乱序递交
无序数据发送的典型场景:
- 实时性要求高于顺序性的应用
- 音频/视频流中,新数据比等待旧数据更有价值
- 独立的消息交换,不需要顺序保证
// 发送无序数据
sri.sinfo_flags = SCTP_UNORDERED; // 设置无序标志
Sctp_sendmsg(sockfd, data, len, (SA *)&addr, addrlen,
0, sri.sinfo_flags, stream_no, 0, 0);
4.3 消息分片与交错
SCTP根据关联的PMTU自动对数据消息进行分片,以适配底层网络路径。此外,SCTP_FRAGMENT_INTERLEAVE套接字选项控制在部分递送过程中,是否允许来自其他流或无序的消息穿插进来。
五、地址捆绑与多宿
5.1 sctp_bindx——捆绑地址子集
TCP和UDP服务器只能捆绑单个地址或通配地址,无法捆绑地址的子集。而SCTP服务器可以使用sctp_bindx函数捆绑与所在主机系统相关IP地址的一个子集。
#include <netinet/sctp.h>
int sctp_bindx(int sockfd, const struct sockaddr *addrs, int addrcnt, int flags);
// 返回值:若成功,返回0;否则,返回-1
参数说明:
| 参数 | 说明 |
|---|---|
sockfd |
socket函数返回的套接字描述符 |
addrs |
指向紧凑的地址列表的指针,地址结构紧跟在前一个之后,中间没有填充字节 |
addrcnt |
地址的个数 |
flags |
SCTP_BINDX_ADD_ADDR(添加地址)或SCTP_BINDX_REM_ADDR(移除地址) |
使用规则:
| 场景 | 行为 |
|---|---|
| 未绑定的套接字 | 将给定的地址集合捆绑到套接字上 |
已绑定的套接字 + SCTP_BINDX_ADD_ADDR
|
把额外的地址加入到套接字描述符 |
已绑定的套接字 + SCTP_BINDX_REM_ADDR
|
从套接字描述符的已加入地址中移除给定的地址 |
| 监听套接字上调用 | 新的地址配置只影响新产生的关联 |
⚠️ 重要:所有套接字地址结构的端口号必须相同,并且要与已经绑定的端口号相匹配,否则调用将产生
EINVAL错误码。
动态地址特性:
如果一个端点支持动态地址特性,sctp_bindx将导致该端点向对端发送合适的消息,以修改对端的地址列表。如果调出一个新的以太网接口,应用进程可以指定SCTP_BINDX_ADD_ADDR标志在已经存在的连接上启动使用这个接口。
// 示例:将IP地址列表捆绑到套接字
struct sockaddr_in addrs[2];
bzero(&addrs[0], sizeof(struct sockaddr_in));
addrs[0].sin_family = AF_INET;
inet_pton(AF_INET, "192.168.1.100", &addrs[0].sin_addr);
addrs[0].sin_port = htons(SERV_PORT);
bzero(&addrs[1], sizeof(struct sockaddr_in));
addrs[1].sin_family = AF_INET;
inet_pton(AF_INET, "10.0.0.100", &addrs[1].sin_addr);
addrs[1].sin_port = htons(SERV_PORT);
// 捆绑两个地址
sctp_bindx(sockfd, (struct sockaddr *)addrs, 2, SCTP_BINDX_ADD_ADDR);
5.2 sctp_connectx——连接到多宿对端
sctp_connectx函数用于连接到一个多宿对端主机,允许应用程序指定多个属于同一对端的地址,SCTP会尝试连接到这些地址中的任意一个。
int sctp_connectx(int sockfd, const struct sockaddr *addrs, int addrcnt);
// 返回值:若成功,返回0;否则,返回-1
六、获取地址信息
6.1 sctp_getpaddrs——获取对端地址列表
int sctp_getpaddrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);
// 返回值:若成功,返回存放在addrs中的对端地址数;否则,返回-1
💡 关键理解:对于一到一式套接字,
id参数被忽略;对于一到多式套接字,必须指定assoc_id来标识特定的关联。
6.2 sctp_getladdrs——获取本端地址列表
int sctp_getladdrs(int sockfd, sctp_assoc_t id, struct sockaddr **addrs);
// 返回值:若成功,返回存放在addrs中的本端地址数;否则,返回-1
6.3 释放地址列表内存
void sctp_freepaddrs(struct sockaddr *addrs);
void sctp_freeladdrs(struct sockaddr *addrs);
⚠️ 重要:
sctp_getpaddrs和sctp_getladdrs动态分配内存,使用后必须调用相应的释放函数,否则会导致内存泄漏。
6.4 使用示例
#include "unp.h"
void print_peer_addresses(int sockfd, sctp_assoc_t assoc_id)
{
struct sockaddr *addrs;
int i, n;
char abuf[INET6_ADDRSTRLEN];
n = sctp_getpaddrs(sockfd, assoc_id, &addrs);
if (n < 0)
err_sys("sctp_getpaddrs error");
printf("Peer has %d addresses:n", n);
for (i = 0; i < n; i++) {
struct sockaddr_in *sin = (struct sockaddr_in *)&addrs[i];
printf(" %sn", Inet_ntop(AF_INET, &sin->sin_addr, abuf, sizeof(abuf)));
}
sctp_freepaddrs(addrs);
}
七、心搏机制与地址不可达检测
7.1 心搏的基本原理
SCTP使用心搏(Heartbeat)机制来监控对端地址的可达性。在SCTP关联上,心搏默认就在交换。其主要功能包括:
- 监控目的地址的可用状态
- 测量空闲路径的往返时间(RTT)
- 触发故障切换(failover)
7.2 地址可达性检测与故障切换
当SCTP关联建立时,选择一个主目的地址用于发送新数据。如果网络没有丢包,多宿SCTP关联的行为类似于TCP连接。
故障检测机制:
- SCTP发送端为每个目的地址维护一个错误计数器
- 当无法收到对DATA块的SACK或对HEARTBEAT块的HEARTBEAT ACK时,计数器递增
- 如果计数器超过阈值(通常为6),该地址被标记为不活跃(inactive),停止使用
故障切换流程:
- 如果主地址被标记为不活跃,所有数据被切换到备用地址
- 心搏用于监控目的地址的状态
- 当主地址重新响应心搏时,恢复为活跃状态
7.3 心搏与RTT测量
心搏不仅是可达性检测工具,还用于:
- 在空闲路径上获得RTT测量值
- 为后续的拥塞控制和重传提供RTT估计
7.4 心搏检测流程图
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCTP 心搏检测与故障切换流程 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 关联建立 │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 主地址被选定,数据通过主地址发送 │ │
│ └─────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 发送心搏(HEARTBEAT)到目的地址 │ │
│ └─────────────────────────────┬───────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────┴──────────────────┐ │
│ │ │ │
│ ▼ ▼ │
│ 收到HEARTBEAT ACK 未收到应答,计数器递增 │
│ (地址可达) (地址可能不可达) │
│ │ │ │
│ │ ▼ │
│ │ 计数器 > 阈值(通常6次) │
│ │ │ │
│ │ ▼ │
│ │ 标记地址为inactive │
│ │ │ │
│ │ ▼ │
│ │ 切换到备用地址 │
│ │ │ │
│ │ ▼ │
│ │ 发送心搏到原地址 │
│ │ │ │
│ └─────────────────┬───────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────┐ │
│ │ 原地址恢复活动,重新成为主地址 │ │
│ └─────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
八、关联剥离
8.1 sctp_peeloff的功能
sctp_peeloff函数将一到多式套接字中的某个关联剥离出来,创建一个新的、独立的一到一式套接字,专门用于处理该关联。
int sctp_peeloff(int sockfd, sctp_assoc_t id);
// 返回值:若成功,返回一个新的套接字描述符;否则,返回-1
8.2 典型应用场景
- 主线程处理大多数短期请求:使用一到多套接字高效处理大量短连接
- 需要长期处理的关联:将特定的关联剥离后交给专用线程/进程处理
- 需要将套接字传递给其他进程:剥离后的一到一套接字可以安全地通过Unix域套接字传递
8.3 使用示例
#include "unp.h"
int main(int argc, char **argv)
{
int sock_fd, peeled_fd;
struct sockaddr_in servaddr;
struct sctp_event_subscribe events;
sctp_assoc_t assoc_id;
sock_fd = 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, (SA *)&servaddr, sizeof(servaddr));
bzero(&events, sizeof(events));
events.sctp_association_event = 1; // 获取关联ID
Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
Listen(sock_fd, LISTENQ);
// 接受新关联,获取关联ID
// 当需要将某个关联单独处理时
assoc_id = get_assoc_id(...); // 从通知中获得
// 剥离关联为一到一套接字
peeled_fd = sctp_peeloff(sock_fd, assoc_id);
if (peeled_fd < 0)
err_sys("sctp_peeloff error");
// 现在可以独立处理这个套接字
// 可以fork子进程或创建新线程来处理
if (fork() == 0) {
/* 子进程 */
Close(sock_fd);
handle_association(peeled_fd);
Close(peeled_fd);
exit(0);
}
Close(peeled_fd); // 父进程关闭剥离的套接字
}
九、SCTP与TCP/UDP核心对比
9.1 三种协议特性对比表
| 对比维度 | TCP | UDP | SCTP |
|---|---|---|---|
| 连接方式 | 面向连接 | 无连接 | 面向关联(Association) |
| 可靠性 | ✅ 可靠 | ❌ 不可靠 | ✅ 可靠 |
| 消息边界 | ❌ 字节流 | ✅ 数据报 | ✅ 面向消息 |
| 多流支持 | ❌ 无 | ❌ 无 | ✅ 有 |
| 多宿支持 | ❌ 无 | ❌ 无 | ✅ 有(核心特性) |
| 头端阻塞 | ❌ 存在 | ✅ 无 | ✅ 已解决 |
| 心搏机制 | ❌ 需应用层实现 | ❌ 无 | ✅ 内建 |
| 通知机制 | ❌ 无 | ❌ 无 | ✅ 内建 |
| 部分递送 | ❌ 无 | ❌ 无 | ✅ 支持 |
| 无序数据 | ❌ 无 | ✅ 天然无序 | ✅ 支持 |
| 地址捆绑 | 单个/通配 | 单个/通配 | ✅ 子集 |
9.2 协议选择建议
| 场景 | 推荐协议 | 理由 |
|---|---|---|
| 标准Web服务 | TCP | 广泛支持,标准成熟 |
| 广播/多播 | UDP | 唯一支持广播的传输层协议 |
| 多宿高可用性 | SCTP | 内建多宿支持,路径冗余 |
| 独立消息流 | SCTP | 多流特性避免队头阻塞 |
| 实时音视频 | UDP | 低延迟,允许少量丢包 |
| 需要内建通知 | SCTP | 内建关联状态通知 |
| 需要多宿 | SCTP | TCP/UDP都不支持多宿 |
十、关键图表
10.1 SCTP高级特性架构图
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCTP高级特性架构 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 应用程序层 │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │ 数据流1 │ │ 数据流2 │ │ 数据流N │ │ │
│ │ │ (Stream 1) │ │ (Stream 2) │ │ (Stream N) │ │ │
│ │ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘ │ │
│ │ │ │ │ │ │
│ │ └────────────────┼────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────┴────────────────┐ │ │
│ │ │ SCTP套接字API │ │ │
│ │ │ sctp_sendmsg / sctp_recvmsg │ │ │
│ │ └────────────────┬────────────────┘ │ │
│ └──────────────────────────┼───────────────────────────────────────────┘ │
│ │ │
│ ┌──────────────────────────┼───────────────────────────────────────────┐ │
│ │ SCTP内核模块 │ │
│ │ ┌────────────────┴────────────────┐ │ │
│ │ │ 通知机制 │ │ │
│ │ │ SCTP_EVENTS套接字选项 │ │ │
│ │ └────────────────┬────────────────┘ │ │
│ │ ┌────────────────┴────────────────┐ │ │
│ │ │ 多流管理 │ │ │
│ │ │ 流0 流1 流2 ... 流N │ │ │
│ │ └────────────────┬────────────────┘ │ │
│ │ ┌────────────────┴────────────────┐ │ │
│ │ │ 多宿管理 │ │ │
│ │ │ 主地址 备用地址1 备用地址2 │ │ │
│ │ └────────────────┬────────────────┘ │ │
│ │ ┌────────────────┴────────────────┐ │ │
│ │ │ 心搏机制 │ │ │
│ │ │ HEARTBEAT / HEARTBEAT ACK │ │ │
│ │ └─────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
10.2 SCTP多流解决队头阻塞示意图
┌─────────────────────────────────────────────────────────────────────────────┐
│ SCTP多流 vs TCP队头阻塞 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ TCP队头阻塞问题 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 发送端 网络 接收端 │ │
│ │ ┌─────┐ ┌─────┐ │ │
│ │ │流1 │──消息1─────────X(丢失)──────────→│阻塞 │ ❌ 等待重传 │ │
│ │ │ │──消息2──────────────────────────→│阻塞 │ ❌ 后续消息阻塞 │ │
│ │ │ │──消息3──────────────────────────→│阻塞 │ ❌ │ │
│ │ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ SCTP多流:无队头阻塞 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 发送端 网络 接收端 │ │
│ │ ┌─────┐ ┌─────┐ │ │
│ │ │流1 │──消息1─────────X(丢失)──────────→│阻塞 │ ⚠️ 仅流1阻塞 │ │
│ │ │ │──消息2──────────────────────────→│送达 │ ✅ 流2正常递送 │ │
│ │ │流2 │──消息3──────────────────────────→│送达 │ ✅ 流3正常递送 │ │
│ │ │流3 │──消息4──────────────────────────→│送达 │ ✅ │ │
│ │ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
10.3 SCTP高级函数速查表
| 函数 | 用途 | 返回值 |
|---|---|---|
sctp_bindx |
捆绑地址子集 | 0(成功),-1(失败) |
sctp_connectx |
连接到多宿对端 | 0(成功),-1(失败) |
sctp_getpaddrs |
获取对端地址列表 | 地址数,-1(失败) |
sctp_getladdrs |
获取本端地址列表 | 地址数,-1(失败) |
sctp_freepaddrs |
释放对端地址列表 | void |
sctp_freeladdrs |
释放本端地址列表 | void |
sctp_sendmsg |
发送SCTP消息 | 字节数,-1(失败) |
sctp_recvmsg |
接收SCTP消息 | 字节数,-1(失败) |
sctp_opt_info |
获取SCTP选项 | 0(成功),-1(失败) |
sctp_peeloff |
剥离关联 | 新套接字描述符,-1(失败) |
十一、常见问题与注意事项
11.1 常见错误速查表
| 错误 | 原因 | 解决方案 |
|---|---|---|
sctp_bindx返回EINVAL
|
端口号不匹配或地址列表无效 | 确保所有地址端口号相同,且与已绑定的端口匹配 |
sctp_bindx返回EOPNOTSUPP
|
不支持动态地址特性 | 检查系统是否支持动态地址添加 |
sctp_getpaddrs失败 |
关联ID无效或套接字类型错误 | 对于一到多套接字必须指定有效的关联ID |
sctp_peeloff失败 |
关联ID无效 | 确保关联ID来自关联建立通知 |
| 收不到SCTP通知 | 未预订事件 | 使用SCTP_EVENTS套接字选项预订所需事件 |
| 多流不工作 | 使用了一到一式套接字 | 使用一到多式套接字(SOCK_SEQPACKET) |
| 多宿切换不触发 | 未配置备用地址 | 使用sctp_bindx添加多个地址 |
| 心搏未响应 | 网络故障或超时配置不当 | 检查网络连接,调整心搏超时参数 |
11.2 环境准备
在Linux系统上使用SCTP功能,需要安装必要的库和工具:
# 安装SCTP开发库和工具
sudo apt-get install libsctp-dev libsctp1 lksctp-tools
# 编译SCTP程序时需要链接sctp库
gcc -o sctp_program sctp_program.c -lsctp
💡 重要:SCTP的函数是第三方库函数,需要链接
-lsctp。
11.3 可移植性注意事项
| 注意点 | 说明 |
|---|---|
| 头文件差异 | SCTP头文件为<netinet/sctp.h>,在不同系统上可能存在差异 |
| 内核支持 | 需要内核支持SCTP协议,可通过modprobe sctp加载模块 |
| 平台兼容性 | SCTP在Linux、FreeBSD、Solaris等主流平台支持,但Windows支持有限 |
| 辅助数据格式 | 不同系统对辅助数据的定义可能略有差异 |
十二、本章小结
12.1 核心知识点回顾
| 知识点 | 关键要点 |
|---|---|
| SCTP通知机制 | 11种通知类型,通过SCTP_EVENTS套接字选项预订事件 |
| 多流特性 | 在关联中提供多个独立流,避免队头阻塞,每个流有独立的SSN |
| 部分递送 | 允许在完整消息到达前就开始处理部分数据 |
| 无序数据 | 通过设置U位实现消息无序递送,适用于实时性要求高的场景 |
| sctp_bindx | 捆绑IP地址子集,支持动态添加/移除地址 |
| sctp_connectx | 连接多宿对端,可指定多个备选地址 |
| 地址信息获取 |
sctp_getpaddrs/sctp_getladdrs获取对端/本端地址列表 |
| 心搏机制 | 内建可达性检测、RTT测量和故障切换 |
| 关联剥离 |
sctp_peeloff将关联从一到多套接字中剥离为一到一套接字 |
12.2 本章思维导图
第二十七章 高级SCTP套接字编程
├── SCTP通知机制
│ ├── sctp_data_io_event(数据I/O,唯一默认开启)
│ ├── sctp_association_event(关联状态变化)
│ ├── sctp_address_event(地址状态变化)
│ └── sctp_send_failure_event(发送失败)
├── 多流编程
│ ├── 流(Stream):关联内独立顺序的消息队列
│ ├── 流序列号(SSN):流内唯一,流间可重复
│ ├── 解决队头阻塞
│ └── 回射服务器示例
├── 部分递送与无序数据
│ ├── SCTP_PARTIAL_DELIVERY_POINT
│ ├── 有序递送 vs 无序递送
│ └── SCTP_UNORDERED标志
├── 地址捆绑
│ ├── sctp_bindx:捆绑地址子集
│ │ ├── SCTP_BINDX_ADD_ADDR(添加)
│ │ └── SCTP_BINDX_REM_ADDR(移除)
│ ├── sctp_connectx:连接到多宿对端
│ ├── sctp_getpaddrs/sctp_getladdrs:获取地址
│ └── sctp_freepaddrs/sctp_freeladdrs:释放地址
├── 心搏机制
│ ├── 地址可达性检测
│ ├── RTT测量
│ ├── 故障切换
│ └── 错误计数器与阈值(默认6次)
├── 关联剥离
│ ├── sctp_peeloff:一到多 → 一到一
│ └── 适用于长期处理的关联
└── SCTP vs TCP/UDP
├── SCTP独有:多流、多宿、内建心搏、通知机制
└── 协议选择建议
十三、下一章预告
下一章预告
📌 下一篇:《UNIX网络编程》读书笔记(二十八):第二十八章 服务类型
第二十八章将详细讲解:
- 服务类型(Type of Service, ToS)概述:IPv4首部中的ToS字段与IPv6中的流量类别(Traffic Class)字段,用于区分数据包的优先级和处理需求
- 服务类型字段的构成:优先级(Precedence)、延迟(Delay)、吞吐量(Throughput)、可靠性(Reliability)等标志位
-
套接字选项:
IP_TOS和IPV6_TCLASS选项的设置与获取方法 - 服务质量(QoS)与差异化服务(DiffServ):从ToS到DiffServ的演进,区分服务码点(DSCP)的概念
-
编程实现:使用
setsockopt为发送的数据包设置服务类型,以及如何接收携带服务类型标志的入站数据包 -
服务类型的实际应用:
- 交互式应用(如Telnet、SSH)希望低延迟
- 文件传输(如FTP)希望高吞吐量
- 网络管理协议(如SNMP)希望高可靠性
- 多宿与路由决策的影响:服务类型如何影响路由选择和下一跳行为
学习目标:学完第二十八章后,你将能够——
- 理解IPv4 ToS和IPv6 Traffic Class字段的作用
- 使用
IP_TOS/IPV6_TCLASS套接字选项为应用程序设置差异化服务 - 理解DiffServ体系中的DSCP编码
- 在实际网络应用中合理设置服务类型以优化用户体验
敬请期待!
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!