作者: andylin02
学习章节: 第九章 基本SCTP套接字编程
关键词: SCTP, 流控制传输协议, 多宿, 多流, 关联, 消息边界, 一到一, 一到多, 四路握手, Cookie机制, sctp_recvmsg, sctp_sendmsg
一、章节概述
1.1 本章焦点
SCTP是一个较新的传输层协议,由IETF于2000年标准化(TCP是1981年)。它最初是为满足不断增长的IP电话市场而设计,用于穿越因特网传输电话信令。SCTP是一个可靠的面向消息的协议,在端点之间提供多个流,并为多宿提供传输级支持。
SCTP借鉴了TCP和UDP的精华,同时又提供了独有的特性。它提供了类似UDP的面向消息的特性,同时又具有TCP协议的可靠性、顺序传输和拥塞控制的功能,还提供了多宿主和冗余路径的能力,从而提高弹性和可靠性。
本章的核心是介绍SCTP套接字编程的基本知识,包括两种编程接口(一到一与一到多)、核心函数以及SCTP特有的通知机制和多宿支持。
💡 本章核心价值:读完第九章,你将能够——
- 理解SCTP相对于TCP/UDP的三大核心优势(多流、多宿、消息边界)
- 区分一到一和一到多两种编程范式,知道如何选择
- 使用sctp_recvmsg和sctp_sendmsg编写SCTP客户/服务器程序
- 理解SCTP事件通知机制及其使用
- 了解SCTP四路握手和Cookie机制如何抵御DoS攻击
1.2 本章内容结构
| 9.1 | 概述 | SCTP协议的诞生背景和设计目标 |
| 9.2 | SCTP与TCP/UDP的核心区别 | 多宿、多流、消息边界三大特性 |
| 9.3 | 一到一与一到多套接字 | 两种编程接口的设计哲学和选择指南 |
| 9.4 | SCTP套接字函数 | socket、bind、listen、connect、close等API详解 |
| 9.5 | sctp_recvmsg和sctp_sendmsg | SCTP核心收发函数的完整使用说明 |
| 9.6 | SCTP回射服务器程序 | 一到多式SCTP回射服务器的完整实现 |
| 9.7 | SCTP通知(Notification) | 事件通知机制、8种通知类型、SCTP_EVENTS选项 |
| 9.8 | sctp_getpaddrs和sctp_getladdrs | 获取对端和本端的地址信息 |
| 9.9 | sctp_bindx | 绑定特定地址子集 |
| 9.10 | sctp_peeloff | 将一到多套接字剥离为一到一套接字 |
二、SCTP vs TCP/UDP:核心区别
2.1 SCTP的三大核心特性
在开始编程之前,先来理解SCTP为什么被誉为“下一代传输协议”。以下是SCTP相对于TCP和UDP的三大核心优势:
1. 消息边界(Message-Oriented)
与UDP一样,SCTP是面向消息的协议,由发送端写入的每一条记录的长度随数据一道传递给接收端应用。发送端调用一次sctp_sendmsg发送一条消息,接收端调用一次sctp_recvmsg读取整条消息。
2. 多流(Multi-streaming)
SCTP能够给在端点之间提供多个流,每个流各自可靠地按序递送消息。一个流上某个消息的丢失不会阻塞同一关联其他流上消息的投递。这种做法与TCP正好相反——在TCP单一字节流中,任何位置的字节丢失都将阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。
这一特性解决了TCP中著名的“队头阻塞(Head-of-line blocking)”问题,对于需要并行传输多个独立数据流的应用(如VoIP信令与媒体流同时传输)意义重大。
3. 多宿(Multi-homing)
单个SCTP端点能够支持多个IP地址,这使得当某个网络路径发生故障时,SCTP可以自动切换到另一个地址继续传输,增强了应对网络故障的健壮性。由于SCTP支持多宿,SCTP使用“关联(Association)”一词替代“连接”,以避免“连接只涉及两个IP地址”的误解。
💡 为什么SCTP可以支持多宿,而TCP不能?
SCTP在设计之初就考虑了多宿场景,它的关联建立过程(四路握手)会交换双方的IP地址列表。TCP的设计没有这种机制,因此在连接建立后无法动态添加或切换IP地址。
2.2 三大传输层协议对比表
| 面向连接 | ✅ 面向连接 | ❌ 无连接 | ✅ 面向关联 |
| 可靠性 | ✅ 可靠 | ❌ 不可靠 | ✅ 可靠 |
| 消息边界 | ❌ 字节流(无边界) | ✅ 数据报(有边界) | ✅ 面向消息(有边界) |
| 流控制 | ✅ 有 | ❌ 无 | ✅ 有 |
| 拥塞控制 | ✅ 有 | ❌ 无 | ✅ 有 |
| 多流支持 | ❌ 无 | ❌ 无 | ✅ 有 |
| 多宿支持 | ❌ 无 | ❌ 无 | ✅ 有(核心特性) |
| 队头阻塞 | ⚠️ 存在 | ❌ 无 | ✅ 已解决 |
| 防DoS攻击 | ❌ 较弱(SYN Flood) | — | ✅ Cookie机制 |
2.3 SCTP与TCP的对比图
┌─────────────────────────────────────────────────────────────────────────────────┐
│ TCP(字节流) vs SCTP(多流)对比图 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ TCP单流:队头阻塞问题 │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 发送端 网络 接收端 │ │
│ │ ┌─────┐ ┌─────┐ │ │
│ │ │流1 │──消息1─────────X(丢失)──────────→│阻塞 │ ❌ 等待重传,无法递送 │ │
│ │ │ │──消息2──────────────────────────→│阻塞 │ 后续消息 │ │
│ │ │ │──消息3──────────────────────────→│阻塞 │ │ │
│ │ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ SCTP多流:无队头阻塞 │
│ ┌─────────────────────────────────────────────────────────────────────────┐ │
│ │ 发送端 网络 接收端 │ │
│ │ ┌─────┐ ┌─────┐ │ │
│ │ │流1 │──消息1─────────X(丢失)──────────→│阻塞 │ ⚠️ 仅流1等待重传 │ │
│ │ │ │──消息2──────────────────────────→│送达 │ ✅ 流2正常递送 │ │
│ │ │流2 │──消息3──────────────────────────→│送达 │ ✅ 流3正常递送 │ │
│ │ └─────┘ └─────┘ │ │
│ └─────────────────────────────────────────────────────────────────────────┘ │
│ │
│ 💡 多流的实际意义:在VoIP应用中,信令和语音数据可以走不同的流,信令的丢失或 │
│ 重传不会阻塞语音数据的实时递送,保证了通话质量。 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
三、SCTP套接字编程范式:一到一 vs 一到多
3.1 两种编程模式的核心区别
SCTP套接字分为一到一套接字和一到多套接字。选择哪种接口形式,需考虑以下因素:
| 对应套接字类型 | SOCK_STREAM | SOCK_SEQPACKET |
| 关联与套接字关系 | 一个套接字对应一个关联 | 一个套接字对应多个关联 |
| 服务器管理 | 每个关联需要独立套接字 | 单个套接字管理所有关联 |
| 适合场景 | 现有TCP应用移植 | 新应用开发 |
| 并发模型 | 需要fork/线程 | 单进程迭代处理 |
| 高级特性支持 | 有限 | 完全支持 |
| 旧名称 | TCP风格套接字(已弃用) | UDP风格套接字(已弃用) |
💡 历史说明:这两种形式的套接字曾用过别的称谓,一到一套接字曾被称为TCP风格套接字,一到多套接字曾被称为UDP风格套接字。这些旧名字容易造成SCTP行为更像TCP或UDP的误解,目前的名字集中体现了关键差异。
3.2 一到一套接字(One-to-One Style)
一到一形式的目的是方便将现有TCP应用移植到SCTP上,以下是迁移时需要注意的差异:
| 套接字选项 | TCP_NODELAY等 | 需转换为等效选项(SCTP_NODELAY、SCTP_MAXSEG) |
| 消息边界 | 字节流,应用需自行划分消息边界 | 保存消息边界,接收端可能收到独立消息 |
| 半关闭 | 支持shutdown | 需要额外重写应用层协议来模拟 |
| 发送地址 | 连接时固定 | sendto/sendmsg可覆盖目的地址 |
3.3 一到多套接字(One-to-Many Style)
一到多式编写的服务器程序无需管理大量的套接字描述符,单个套接字描述符可以代表多个关联,类似于一个UDP套接字能从多个客户接收消息那样。
一到多套接字的核心优势:
💡 选择建议:对大多数使用SCTP开发的新应用而言,推荐使用一到多接口。一到多接口允许使用SCTP的所有特性,而简单将socket第三个参数改为IPPROTO_SCTP的方式无法发挥SCTP除多宿外的特性。
3.4 两种编程模式的调用流程对比图
┌─────────────────────────────────────────────────────────────────────────────────┐
│ SCTP两种编程模式调用流程对比 │
├─────────────────────────────────────────────────────────────────────────────────┤
│ │
│ 一到一模式(类似TCP) 一到多模式(类似UDP) │
│ │
│ 服务器 服务器 │
│ ┌──────────┐ ┌──────────┐ │
│ │ socket() │ │ socket() │ │
│ │SOCK_STREAM│ │SOCK_SEQPACKET│ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ bind() │ │ bind() │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ ┌──────────┐ │
│ │ listen() │ │ listen() │ │
│ └────┬─────┘ └────┬─────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────┐ 关联建立 ┌──────────┐ sctp_recvmsg() 等待多个关联的消息 │
│ │ accept() │←───────────────→ │ connect()│ (自动创建关联) │
│ └────┬─────┘ 四路握手 └──────────┘ │ │
│ │ 客户端 │ │
│ ▼ ▼ │
│ read/write 与单个客户通信 sctp_recvmsg/sctp_sendmsg │
│ │ │ │
│ ▼ ▼ │
│ close() close() │
│ │
│ 特点:每个关联需要独立套接字 特点:单套接字管理所有关联 │
│ │
└─────────────────────────────────────────────────────────────────────────────────┘
四、核心API详解
4.1 套接字创建:socket函数
#include <sys/socket.h>
// 一到一套接字(TCP风格)
int sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP);
// 一到多套接字(UDP风格)
int sockfd = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
💡 关键区别:type参数决定编程范式。SOCK_STREAM用于一到一模式,SOCK_SEQPACKET用于一到多模式。
4.2 核心收发函数:sctp_recvmsg和sctp_sendmsg
SCTP提供了专用的包装函数来支持其高级特性,这些是SCTP编程的核心函数。
sctp_recvmsg:
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/sctp.h>
ssize_t sctp_recvmsg(int sd, void *msg, size_t len,
struct sockaddr *from, socklen_t *fromlen,
struct sctp_sndrcvinfo *sinfo, int *msg_flags);
// 返回值:成功返回接收的字节数,出错返回-1
sctp_sendmsg:
ssize_t sctp_sendmsg(int sd, 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);
// 返回值:成功返回发送的字节数,出错返回-1
关键参数说明:
| sinfo | 指向sctp_sndrcvinfo结构,接收端可获知消息到达的流号等信息 |
| msg_flags | 返回消息标志,MSG_NOTIFICATION表示是通知而非用户数据 |
| ppid | 应用协议ID,用于标识消息类型 |
| stream_no | 指定发送消息所使用的流号 |
| flags | 控制发送行为,如MSG_EOF、MSG_ABORT等 |
💡 为什么需要这两个专用函数?SCTP的标准收发函数(read/write/recvfrom/sendto)无法访问SCTP特有的信息(如流号、多宿地址等)。sctp_recvmsg和sctp_sendmsg通过sctp_sndrcvinfo结构暴露了这些特性。
4.3 sctp_sndrcvinfo结构
struct sctp_sndrcvinfo {
uint16_t sinfo_stream; // 流号(0-65535)
uint16_t sinfo_ssn; // 流序列号
uint16_t sinfo_flags; // 标志(如MSG_EOF、MSG_UNORDERED等)
uint32_t sinfo_ppid; // 应用协议ID
uint32_t sinfo_context; // 上下文信息
uint32_t sinfo_timetolive; // 生存时间(毫秒)
uint32_t sinfo_tsn; // 传输序列号
uint32_t sinfo_cumtsn; // 累计TSN
sctp_assoc_t sinfo_assoc_id;// 关联ID
};
五、SCTP关联的建立与终止
5.1 四路握手(Four-way Handshake)
SCTP的关联建立需要通过四路握手,相比TCP的三次握手,SCTP的设计可以抵御SYN Flood类型的DoS攻击。
客户端 服务器
| |
| 1. INIT(带验证标签、初始序号) |
| 2. INIT ACK(含状态Cookie) |
|<———————————|
| |
| 3. COOKIE ECHO(回显Cookie) |
|———————————>|
| |
| 4. COOKIE ACK |
|<———————————|
| |
| 关联建立完成 |
▼ ▼
为什么四路握手更安全?
当服务器收到INIT消息时,它并不立即分配资源,而是构造一个包含所有连接状态信息的Cookie,随INIT ACK发回客户端。只有在收到客户端回显的COOKIE ECHO并验证通过后,服务器才分配资源建立关联。这一机制可以有效抵御SYN Flood攻击——攻击者发送大量INIT不会导致服务器资源耗尽。
💡 四路握手的安全性本质:TCP在收到SYN后会立即分配资源(建立连接请求队列),而SCTP收到INIT后不分配资源,只生成并存储一个加密Cookie。只有当后续收到正确的Cookie后才分配资源,这使得SCTP天然免疫SYN Flood攻击。
5.2 关联终止(Shutdown)
SCTP不像TCP那样使用FIN来终止,而是使用SHUTDOWN机制:
主动关闭方 被动关闭方
| |
| SHUTDOWN(包含累计序号) |
|———————————>|
| |
| SHUTDOWN ACK |
|<———————————|
| |
| SHUTDOWN COMPLETE |
|———————————>|
▼ ▼
与TCP的差异:SCTP端点对shutdown的反应不同于TCP。当两个SCTP端点中任何一个发起关联终止序列时,这两个端点都得把已排队的任何数据发送掉,然后关闭关联。同一个关联可用于连接到一个新的对端端点。
六、完整源代码分析
6.1 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));
bzero(&events, sizeof(events));
// 预订sctp_data_io_event,允许服务器查看sctp_sndrcvinfo结构
// 服务器可从该结构确定消息到达所在的流号
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);
// 如果需要增长流号
if (stream_increment) {
sri.sinfo_stream++;
// 如果增长到最大流号,重置流号为0
// sctp_get_no_strms函数通过SCTP_STATUS套接字选项找出商定的流数目
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 | 一到多式套接字类型 |
| SCTP_EVENTS选项 | 预订sctp_data_io_event事件,使服务器能获取sctp_sndrcvinfo结构 |
| sctp_recvmsg | 接收消息,同时获得源地址和流信息(填充sri结构) |
| sri.sinfo_stream | 消息到达的流号,服务器将其加1后发回 |
| sctp_get_no_strms | 获取关联中协商的最大流数 |
6.2 SCTP回射客户端(一到多式)
#include "unp.h"
int main(int argc, char **argv)
{
int sock_fd;
struct sockaddr_in servaddr;
struct sctp_event_subscribe events;
int rd_sz;
char sendline[MAXLINE], recvline[MAXLINE];
struct sctp_sndrcvinfo sri;
if (argc != 2)
err_quit("usage: sctpcli <IPaddress>");
sock_fd = Socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP);
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
// 预订数据I/O事件以接收流信息
bzero(&events, sizeof(events));
events.sctp_data_io_event = 1;
Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
// 使用sendto触发隐式关联建立(四路握手)
// 第一个sendto触发关联建立,后续可发送更多数据
while (Fgets(sendline, MAXLINE, stdin) != NULL) {
// 发送数据
Sctp_sendmsg(sock_fd, sendline, strlen(sendline),
(SA *)&servaddr, sizeof(servaddr), 0, 0, 0, 0, 0);
// 接收回射
rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),
NULL, NULL, &sri, NULL);
recvline[rd_sz] = 0;
printf("from server: %s", recvline);
}
Close(sock_fd);
return 0;
}
隐式关联建立:sendto触发隐式关联建立,调用sctp_sendmsg时如果没有现有关联,SCTP栈会自动启动四路握手建立关联。
七、SCTP事件通知机制
7.1 什么是事件通知?
SCTP中的通知使得一个应用程序能够知晓除用户数据到达外的重要协议事件。用户数据和通知将在套接字缓冲区中交错出现,为了区分数据和通知,应该使用recvmsg或sctp_recvmsg函数。如果所返回的数据是一个事件通知,那么这两个函数返回的msg_flags参数将含有MSG_NOTIFICATION标志。
7.2 通知的格式
所有通知都采用TLV格式,前8个字节给出通知的类型和总长度:
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;
};
struct sctp_tlv {
u_int16_t sn_type; // 通知类型
u_int16_t sn_flags; // 标志
u_int32_t sn_length; // 总长度
};
7.3 可预订的8种事件
使用SCTP_EVENTS套接字选项可以预订8个事件,其中7个产生称为通知的额外数据:
| 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 | 适配层事件 |
7.4 预订事件的代码示例
struct sctp_event_subscribe events;
bzero(&events, sizeof(events));
// 预订数据I/O事件
events.sctp_data_io_event = 1;
// 预订关联状态变化事件
events.sctp_association_event = 1;
Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
⚠️ 注意:sctp_data_io_event是唯一默认开启的事件,其他所有事件默认都是禁止的。
八、多宿技术的深入理解
8.1 多宿的工作原理
多宿是SCTP最具特色的特性之一。当一个端点有多个IP地址时(例如同时连接Wi-Fi和以太网,或同时连接4G和有线网络),SCTP可以在关联建立时向对端通告所有可用的IP地址。这样,即使主路径发生故障,SCTP也能自动切换到备用地址继续传输。
8.2 多宿的实现机制
1. 获取对端地址列表:sctp_getpaddrs
int sctp_getpaddrs(int sd, sctp_assoc_t id, struct sockaddr **addrs);
// 返回值:成功返回地址数目,出错返回-1
2. 获取本端地址列表:sctp_getladdrs
int sctp_getladdrs(int sd, sctp_assoc_t id, struct sockaddr **addrs);
// 返回值:成功返回地址数目,出错返回-1
3. 释放地址列表内存
void sctp_freepaddrs(struct sockaddr *addrs);
void sctp_freeladdrs(struct sockaddr *addrs);
8.3 多宿的配置:sctp_bindx
默认情况下,SCTP套接字绑定通配地址时会绑定所有可用IP地址。如果需要只绑定特定地址子集,可以使用sctp_bindx函数:
int sctp_bindx(int sd, struct sockaddr *addrs, int addrcnt, int flags);
// flags: SCTP_BINDX_ADD_ADDR 或 SCTP_BINDX_REM_ADDR
九、sctp_peeloff:将一到多套接字剥离为一到一
sctp_peeloff函数用于从一个一到多套接字剥离出某个特定的关联,独自构成一个一到一套接字。剥离出的关联所在的一到一套接字随后可以遣送到它自己的线程或进程。
int sctp_peeloff(int sd, sctp_assoc_t assoc_id);
// 返回值:成功返回新的套接字描述符,出错返回-1
使用场景:整体上接受短期请求、偶尔需要长期会话的应用系统可以利用sctp_peeloff。例如,一个服务器使用一到多套接字处理大量短期请求,当某个客户发起一个需要长期保持连接的会话时,可以将该关联剥离出来,交给一个专门的线程或进程处理,主线程继续处理其他短期请求。
流程图:
┌─────────────────────────────────────────────────────────────────────────────┐
│ sctp_peeloff剥离示意图 │
├─────────────────────────────────────────────────────────────────────────────┤
│ │
│ 剥离前 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 一到多套接字 (sock_fd) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ 关联 A │ │ 关联 B │ │ 关联 C │ │ 关联 D │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │ │
│ └─────────────────────────────────────────────────────────────────────┘ │
│ │
│ │ │
│ ▼ sctp_peeloff(sock_fd, assoc_id_B) │
│ │
│ 剥离后 │
│ ┌─────────────────────────────────────────────────────────────────────┐ │
│ │ 一到多套接字 (sock_fd) │ 一到一套接字 (new_fd) │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ ┌──────────┐ │ │
│ │ │ 关联 A │ │ 关联 C │ │ 关联 D │ │ │ 关联 B │ │ │
│ │ └──────────┘ └──────────┘ └──────────┘ │ └──────────┘ │ │
│ └──────────────────────────────────────────┴──────────────────────────┘ │
│ │
│ 💡 设计模式:主线程处理大多数短期请求,将需要长期处理的关联剥离后交给专门线程 │
│ │
└─────────────────────────────────────────────────────────────────────────────┘
十、常见问题与注意事项
10.1 常见编译错误及解决
| redefinition of 'struct in_pktinfo' | 标准头文件与unp.h重复定义 | 将unpv13e/sctp/unp.h中的结构体声明注释掉 |
| undefined reference to 'sctp_recvmsg' | 未链接sctp库 | 编译时添加-lsctp参数 |
| SCTP_EVENTS未定义 | 缺少头文件或系统不支持SCTP | 确保包含<netinet/sctp.h>,且内核支持SCTP |
10.2 SCTP编程注意事项
| 内核支持 | 确保系统内核支持SCTP协议(modprobe sctp) |
| 消息边界 | 一到多式套接字使用SOCK_SEQPACKET,保留消息边界 |
| 关联ID | 在一到多套接字中,用关联ID区分不同的关联 |
| 通知处理 | 如果不希望收到通知,必须显式禁用相应事件 |
| 多宿前提 | 多宿需要主机配置多个IP地址才能发挥实际作用 |
| 可移植性 | SCTP并非所有Unix系统都支持,需考虑可移植性 |
十一、本章核心知识点总结
11.1 核心概念速查表
| SCTP | 流控制传输协议,2000年标准化 |
| 关联 | SCTP中“连接”的术语,因多宿可能涉及多个IP地址 |
| 多流 | 一个关联内提供多个独立流,避免队头阻塞 |
| 多宿 | 单个端点支持多个IP地址,提供路径冗余 |
| 一到一套接字 | 一个套接字对应一个关联,适合移植TCP应用 |
| 一到多套接字 | 一个套接字对应多个关联,适合新开发应用 |
| 四路握手 | SCTP的关联建立过程,包含Cookie机制防御DoS |
| 通知 | SCTP的事件通知机制,8种可预订事件 |
11.2 本章思维导图
第九章 基本SCTP套接字编程
├── SCTP协议特性
│ ├── 消息边界(面向消息,保留记录边界)
│ ├── 多流(多个独立流,无队头阻塞)
│ └── 多宿(多IP地址,路径冗余)
├── 编程范式
│ ├── 一到一(SOCK_STREAM,移植TCP应用)
│ │ ├── 一个套接字对应一个关联
│ │ └── 需要fork/线程处理并发
│ └── 一到多(SOCK_SEQPACKET,推荐新应用)
│ ├── 一个套接字对应多个关联
│ ├── 使用关联ID区分不同关联
│ └── 支持四路握手捎带数据
├── 核心函数
│ ├── sctp_recvmsg(接收消息+获取流信息)
│ ├── sctp_sendmsg(发送消息+指定流号)
│ ├── sctp_bindx(绑定地址子集)
│ ├── sctp_getpaddrs/sctp_getladdrs(获取地址列表)
│ └── sctp_peeloff(剥离关联为一到一套接字)
├── 事件通知
│ ├── 8种可预订事件(SCTP_EVENTS选项)
│ ├── sctp_data_io_event(唯一默认开启)
│ └── MSG_NOTIFICATION标志区分通知与数据
└── 与TCP/UDP对比
├── 优于TCP:消息边界、多流、多宿、防DoS
├── 优于UDP:可靠性、流量控制、拥塞控制
└── 劣势:实现复杂、系统支持不普遍
十二、实用工具
12.1 SCTP测试工具
# 加载SCTP内核模块
sudo modprobe sctp
# 查看SCTP关联状态
cat /proc/net/sctp/assocs
# 使用sctp_darn测试回射服务
sctp_darn -H 0 -P 2500 -l # 服务器端
sctp_darn -H 127.0.0.1 -P 2500 -h 127.0.0.1 -p 2500 -s # 客户端
# 检查SCTP是否可用
ss -s # 查看SCTP统计信息
12.2 tcpdump抓取SCTP数据包
# 抓取SCTP协议数据包
sudo tcpdump -i eth0 sctp
# 抓取指定端口的SCTP数据包
sudo tcpdump -i eth0 sctp port 9877
# 详细显示SCTP数据包内容
sudo tcpdump -i eth0 sctp -vv
十三、下一章预告
📌 下一篇:《UNIX网络编程》读书笔记(十):第十章 SCTP客户/服务器程序例子
第十章将深入探讨:
学习目标:学完第十章后,你将能够——
- 编写功能完整的SCTP一到多回射客户/服务器程序
- 正确处理SCTP_ASSOC_CHANGE等事件通知
- 理解SCTP中流号、流序列号和传输序列号的区别与作用
- 使用SCTP_STATUS选项获取关联状态信息
敬请期待!
参考资料
本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!





