Linux Socket C语言网络编程:SCTP Socket
TCP和SCTP之间有三个重要的相似点:两种协议都需要通信伙伴之间的连接,提供过载控制的机制,并且还很可靠-因此它们都确保数据包到达接收方而不会丢失。由于缺少确认消息,UDP不提供这种保证。但是,作为回报,UDP使用户应用程序不必设置自己的数据记录标记(以标记数据包边界),因为它不是面向字节的,而是面向消息的-SCTP也提供了这一优势。
Table of Contents
SCTP介绍
RFC 4960:https://tools.ietf.org/html/rfc4960
流控制传输协议(SCTP,Stream Control Transmission Protocol)是一种在网络连接两端之间同时传输多个数据流的协议。SCTP提供的服务与UDP和TCP类似。
SCTP在RFC2960中详细说明,并有RFC3309加以更新。RFC 3286给出了SCTP的简要介绍 。SCTP在客户和服务器之间提供关联(association),并像TCP那样给应用提供可靠性、排序、流量控制以及全双工的数据传输。SCTP中使用“关联”一词替代“连接”是为了避免这样的内涵:一个连接只涉及两个IP地址间的通信。一个关联指代两个系统之间的一次通信,它可能因为SCTP支持多宿而涉及不止两个地址。
与TCP不同的是,SCTP是面向消息的(message-oriented)。它提供各个记录的按序递送服务。与UDP一样,由发送端写入的每一条记录的长度随数据一道传递给接收端应用。
SCTP能给在所连接的端点之间提供多个流,每个流各自可靠地按序递送消息。一个流上某个消息的丢失不会阻塞同一关联其他流上消息的投递。这种做法与TCP正好相反,就TCP而言,在单一字节流中任何位置的字节丢失都将在阻塞该连接上其后所有数据的递送,直到该丢失被修复为止。
SCTP还提供多宿特性,使得单个SCTP端点能够支持多个IP地址。该特性可以增强应对网络故障的健壮性。一个端点可能有多个冗余的连接,每个网络又可能有各自接入因特网基础设施的连接。当该端点与另一个端点建立一个关联之后,如果它的某个网络或某个跨域因特网的通路发生故障,SCTP就可以通过切换到使用已与该关联的另一个地址来避免发生的故障 。
SCTP(流控制传输协议)
https://searchnetworking.techtarget.com/definition/SCTP
SCTP(流控制传输协议)是一种协议,用于在网络中已建立连接的两个端点之间同时传输多个数据流。有时称为“下一代TCP ”(传输控制协议)或TCPng,SCTP旨在使支持Internet上的电话连接(特别是支持Internet上电话系统的信令系统7-SS7)更加容易。连接)。电话连接要求将信令信息(控制连接)与语音和其他数据同时发送。SCTP还旨在使通过无线管理连接更容易网络并管理多媒体数据的传输。SCTP是由Internet工程任务组(IETF)开发的标准协议(RFC 2960 )。
与TCP一样,SCTP通过Internet基本无连接的Internet协议(IP)来管理“可靠的传输”(确保通过网络发送的数据单元的完整到达),该协议负责移动数据但不管理是否所有数据到达。与TCP不同,SCTP确保在连接的端点之间完全并发传输数个数据流(以称为消息的单位)。SCTP还支持多宿主,这意味着连接的端点可以具有与其关联的备用IP地址,以绕过网络故障或变化的条件进行路由。
TCP在单个流(有时称为字节流)中传输数据,并保证在端点将数据按顺序传递给应用程序或用户。如果存在数据丢失或排序错误,则必须延迟传递,直到重新传输丢失的数据或接收到乱序消息为止。SCTP的多数据流允许将数据分流到多个独立的流中,因此,如果一个流中有数据丢失,则其他流的传递不会受到影响。对于某些传输,例如文件或记录,必须保留序列。但是,对于某些应用程序,并不一定要保留精确的数据顺序。例如,在信令传输中,序列保留仅对于影响相同资源(例如相同资源)的消息是必需的频道或通话)。因为多流技术允许无错误流中的数据在一个流出现错误时继续传送,所以不会延迟整个传输。
https://www.tutorialandexample.com/stream-control-transmission-protocol/
流控制传输协议:SCTP代表流控制传输协议。SCTP由Internet工程任务组(IETF)开发。它是传输层的可靠的面向消息的协议。它提供了TCP和UDP的最佳功能。它是为特定的应用程序而设计的,例如多媒体。
SCTP服务
- 它提供了进程间通信,例如UDP和TCP。
- 它允许在设备的每个连接中进行多流服务,这称为关联。在设备中,如果一个连接被阻止,另一连接仍然可以传送该数据。
- 它提供了像TCP这样的全双工服务,其中数据可以同时在两个方向上流动。
- 它使用确认(ACK)机制来验证数据传递。
SCTP封包格式
SCTP数据包包含一个常规头和一组称为块的块。SCTP数据包中有两种类型的块:控制块和数据块。分组的第一块保持关联,并且分组的第二块携带用户数据。控制块先于数据块到达数据包。
SCTP的常规头格式如下所示。
在常规标题中,有四个字段。
- 源端口地址:源端口地址的大小为16位。它定义了发送数据包的进程的端口号。
- 目的端口地址:目的端口地址的大小为16位。它定义了正在接收数据包的进程的端口号。
- 验证标签:验证标签的大小为32位。此字段用于检查数据包是否从正确的发送者接收。
- 校验和:校验和的大小为32位。
SCTP,TCP和UDP的比较
服务 | UDP协议 | TCP协议 | SCTP协议 |
序列数据传送 | 没有 | 是 | 是 |
多流 | 没有 | 没有 | 是 |
多宿主 | 没有 | 没有 | 是 |
面向连接 | 没有 | 是 | 是 |
无连接 | 是 | 没有 | 没有 |
允许半封闭连接 | 不适用 | 是 | 没有 |
应用PDU捆绑 | 没有 | 是 | 是 |
拥塞控制 | 没有 | 是 | 是 |
应用PDU分段 | 没有 | 是 | 是 |
保留消息边界 | 是 | 没有 | 是 |
部分可靠的数据传输 | 没有 | 没有 | 可选的 |
选择性ack | 没有 | 可选的 | 是 |
拥塞
- 拥塞: 如果网络负载大于网络容量,则将这种情况称为拥塞。
- 拥塞控制: 它是指用于控制拥塞并使流量保持在网络容量以下的机制。拥塞控制分为两类:开环和闭环,如下图所示。
开环拥塞控制
开环拥塞控制策略用于在拥塞发生之前停止拥塞。拥塞控制由源或目标控制。开环拥塞控制分为几类。
- 重传策略: 这是再次发送数据包的策略。当发送方感知到发送的数据包丢失时,发送方再次发送该数据包。
- 窗口策略: 在窗口策略中,使用选择性重复窗口方法来控制拥塞。
- 丢弃策略:路由器可以丢弃不太敏感的数据包以确保拥塞。
闭环拥塞控制
闭环拥塞控制策略试图减少拥塞发生后的拥塞。闭环拥塞控制分为以下几类:背压,阻塞点,隐式信令和显式信令。
SCTP协议的各个功能如何工作
为了阐明通过流控制传输协议进行数据传输的功能,我们将仔细研究SCTP的最重要功能-从四次握手到分片再到数据包的传输。
SCTP连接设置和删除
像TCP一样,SCTP首先是面向连接的协议,该协议要求客户端和服务器之间存在现有连接,以便它们可以交换数据包。为了建立这样的连接,双方都要进行所谓的四次握手,客户端使用INIT请求进行初始化。服务器用INIT-ACK消息响应此请求,除确认消息外,该消息还包含一个cookie,该cookie唯一地标识提议的连接。该cookie继而在COOKIE-ECHO请求中将客户端发送回服务器,随后,服务器通过COOKIE-ACK消息完成连接。
为了即使经过四次握手,SCTP数据传输也尽可能快,COOKIE ECHO和COOKIE ACK消息必须已经包含用户数据。
建立的SCTP连接可以在传输结束后立即由应用程序或用户关闭,或者由于错误而过早中断。此外,还可以根据要求随时终止连接。无论如何,只要参与者断开连接,数据传输就会完全停止。
流(数据流)中的顺序传输
SCTP标准中的术语流是指在服务器和客户端之间交换的用户数据序列。单个SCTP连接允许任意数量的流,由此用户可以在建立连接时指定确切的数量。尽管严格遵守数据流中的数据顺序,但是在交付不同的流时没有固定的层次结构和依赖性。因此,如果数据流的传输存在问题,那么这对其他流的传输没有影响。此外,还有一种机制可以绕过序列传输并发送优先消息包。
用户数据碎片
SCTP提供了对数据包进行分段的选项,以便随时符合路径最大传输单位(PMTU),即可以在各个连接路径上传输的最大数据包大小。在接收时,各个片段将重新组合并作为完整的消息转发给用户。与IP协议执行的网络级分段相比,这种在传输层的分段具有一些优势:例如,它减轻了负责分段IP数据包的路由器的负担。它还消除了由于网络中丢失单个片段而不得不重新发送整个消息的问题。
包装确认和过载控制
流控制传输协议使用所谓的传输序列号标识所有数据片段或未分段的消息。对于这些序列号中的每一个,发送方都希望接收方发出确认消息。如果在指定时间内没有发生此情况,将重新发送相应的程序包。为了能够可靠且独立于有序传输来确保此传输,即使传输序列中存在间隙,接收器也要确认已接收到传输序列号。为了确保传输不仅可靠而且尽可能快,SCTP使用类似的拥塞控制算法作为TCP。这些规定了运输,以使其不会造成包装的拥挤,从而不会导致主机超载。
块捆绑(在单个SCTP数据包中捆绑多个消息)
SCTP允许您将多个消息捆绑在一个程序包中。以这种方式,可以在公共报头下发送几位控制信息和/或用户数据,在SCTP标准中也称为块。块捆绑机制负责在接收方组装和拆卸完整的包装。
包验证
建立SCTP连接时,两个端点协商一个验证标签,该验证标签必须在整个传输过程中发送的数据包头中指定。如果通信伙伴之一收到没有此指示符的数据包,它将立即丢弃相应的数据包。这样,该协议可防止未经授权的访问,并防止继续接收来自先前连接的数据包。
为了额外保护数据,发送方可以选择在标头中添加CRC32C校验和。协议为此提供了一个可选的32位字段。
路径管理
由于SCTP支持多宿主,因此用户可以指定完整的传输地址集,这些地址可用作发送数据包的潜在目的地。如果列出了多个地址,则协议默认使用主地址路径。如果无法到达主地址路径,则选择备用地址,以便继续传输而不会中断。为了提供此服务,流控制传输协议已实现了使用指定地址指令的路径管理功能。它还通过定期向它们发送所谓的心跳(控制信号)来监视所有已定义地址路径的可用性。
流控制传输协议的优点
作为传输层的协议,SCTP特别与已经提到的协议TCP和UDP竞争。但是,对SCTP的已实现功能和属性的概述表明,它不会替代任何一种协议,而是将它们组合在一起。下表总结了流控制传输协议在哪些方面与TCP协议更相似,在哪些方面与UDP协议更相似:
就传输顺序而言,SCTP与TCP以及UDP都具有相似性,因为它在原则上是存在的,但不一定必须遵守。
TCP和SCTP之间有三个重要的相似点:两种协议都需要通信伙伴之间的连接,提供过载控制的机制,并且还很可靠-因此它们都确保数据包到达接收方而不会丢失。由于缺少确认消息,UDP不提供这种保证。但是,作为回报,UDP使用户应用程序不必设置自己的数据记录标记(以标记数据包边界),因为它不是面向字节的,而是面向消息的-SCTP也提供了这一优势。
除了这种灵活性之外,它使SCTP成为VoIP(IP语音)等语音传输服务的理想解决方案,该协议还通过多流和多归属(容错而不是替代主机)的支持而得分。UDP或TCP提供。此外,具有四次握手(包括身份验证cookie)的流控制传输协议以及发送的每个数据包的报头中的强制性验证标签,确保了所有三种传输协议的最高安全便利性。
SCTP S-C模型示例代码
https://github.com/Rtoax/test/tree/master/ipc/socket/sctpdemo
common.h
#define BUFFER_SIZE 1024
#define PORT 23001
#define CTRL_STREAM 0
#define DATA_STREAM 1
sctps.c
/*
* sctps.c
* Servidor SCTP
* Rafael Damian Fito Izquierdo
*
* Basado en parte en el codigo de Kari Vatjus-Anttila
* http://stackoverflow.com/questions/6342617/sctp-multihoming
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <net/if.h>
#include <stdlib.h>
#include <unistd.h>
#include "common.h"
int sock, conSock;
void handle_signal(int signum);
int main(void) {
int ret, i=0;
int reuse = 1;
int addr_count = 0;
char buffer[BUFFER_SIZE+1];
socklen_t addr_len;
socklen_t hb_len;
struct sockaddr_in addr;
struct sockaddr_in *laddr[9];
struct sctp_event_subscribe events;
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;
struct sctp_rtoinfo rtoinfo;
struct sockaddr_in client;
if((sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP)) < 0)
perror("socket");
memset(&addr, 0, sizeof(struct sockaddr_in));
memset(&events, 0, sizeof(struct sctp_event_subscribe));
memset(&heartbeat, 0, sizeof(struct sctp_paddrparams));
memset(&rtoinfo, 0, sizeof(struct sctp_rtoinfo));
addr_len = (socklen_t)sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
sig_handler.sa_handler = handle_signal;
sig_handler.sa_flags = 0;
heartbeat.spp_flags = SPP_HB_ENABLE;
heartbeat.spp_hbinterval = 5000;
heartbeat.spp_pathmaxrxt = 1;
rtoinfo.srto_max = 3000;
/*Set Signal Handler*/
if(sigaction(SIGINT, &sig_handler, NULL) == -1)
perror("sigaction");
/*Set Heartbeats*/
if(setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS,
&heartbeat, sizeof(heartbeat)) != 0)
perror("setsockopt");
/*Set rto_max*/
if(setsockopt(sock, SOL_SCTP, SCTP_RTOINFO,
&rtoinfo, sizeof(rtoinfo)) < 0)
perror("setsockopt");
/*Set Events */
events.sctp_data_io_event = 1;
events.sctp_association_event = 1;
if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS,
&events, sizeof(events)) < 0)
perror("setsockopt");
/*Set the Reuse of Address*/
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR,
&reuse, sizeof(int)) < 0)
perror("setsockopt");
/*Bind the Addresses*/
if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
perror("bind");
if(listen(sock, 2) < 0)
perror("listen");
/*Get Heartbeat Value*/
hb_len = (socklen_t)(sizeof heartbeat);
getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, &hb_len);
/*printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);*/
/*Print Locally Binded Addresses*/
addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
printf("# IP locales: %d\n", addr_count);
for(i = 0; i < addr_count; i++)
printf(" IP %d: %s\n", i+1, inet_ntoa((*laddr)[i].sin_addr));
sctp_freeladdrs((struct sockaddr*)*laddr);
while(1) {
i=0;
ret=1;
printf("# Esperando conexion\n");
if((conSock = accept(sock,
(struct sockaddr *)&client, &addr_len)) == -1)
perror("accept");
printf(" Cliente: %s\n", inet_ntoa(client.sin_addr));
while(ret > 0) {
snprintf(buffer, BUFFER_SIZE, "%s%02d\n", "control_", i);
ret = sctp_sendmsg(conSock, (void *)buffer, (size_t)strlen(buffer),
(struct sockaddr *)&client, addr_len, 0, 0,
CTRL_STREAM, 0, 0);
snprintf(buffer, BUFFER_SIZE, "%s%02d\n", "data_", i);
ret = sctp_sendmsg(conSock, (void *)buffer, (size_t)strlen(buffer),
(struct sockaddr *)&client, addr_len, 0, 0,
DATA_STREAM, 0, 0);
/*printf(" Enviando a %s\n", inet_ntoa(client.sin_addr));*/
i++;
sleep(2);
}
/*close(conSock);*/
}
if(close(sock) < 0)
perror("close");
return 0;
}
void handle_signal(int signum) {
printf(" Cerrando...\n");
if(close(conSock) < 0)
perror("close");
if(close(sock) < 0)
perror("close");
exit(0);
}
sctpc.c
/*
* sctpc.c
* Cliente SCTP
* Rafael Damian Fito Izquierdo
*
* Basado en parte en el codigo de Kari Vatjus-Anttila
* http://stackoverflow.com/questions/6342617/sctp-multihoming
*/
#include <sys/types.h>
#include <sys/socket.h>
#include <signal.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include "common.h"
int sock;
void handle_signal(int signum);
int main(int argc, char **argv) {
int i=0;
int ret, flags;
int addr_count;
char address[16];
char buffer[BUFFER_SIZE+1];
socklen_t addr_len;
socklen_t hb_len;
struct sockaddr_in addr;
struct sctp_status status;
struct sctp_initmsg initmsg;
struct sctp_event_subscribe events;
struct sigaction sig_handler;
struct sctp_paddrparams heartbeat;
struct sctp_rtoinfo rtoinfo;
struct sctp_sndrcvinfo sndrcvinfo;
struct sockaddr_in *paddrs[5];
struct sockaddr_in server;
/*memset(&buffer, '0', BUFFER_SIZE);*/
memset(&initmsg, 0, sizeof(struct sctp_initmsg));
memset(&addr, 0, sizeof(struct sockaddr_in));
memset(&events, 0, sizeof(struct sctp_event_subscribe));
memset(&status, 0, sizeof(struct sctp_status));
memset(&heartbeat, 0, sizeof(struct sctp_paddrparams));
memset(&rtoinfo, 0, sizeof(struct sctp_rtoinfo));
addr_len = (socklen_t)sizeof(struct sockaddr_in);
if(argc < 2 || (inet_addr(argv[1]) == -1)) {
puts("usage: sctpc [IP address]");
return 0;
}
strncpy(address, argv[1], 15);
address[15] = 0;
addr.sin_family = AF_INET;
inet_aton(address, &(addr.sin_addr));
addr.sin_port = htons(PORT);
initmsg.sinit_num_ostreams = 2;
initmsg.sinit_max_instreams = 2;
initmsg.sinit_max_attempts = 1;
heartbeat.spp_flags = SPP_HB_ENABLE;
heartbeat.spp_hbinterval = 5000;
heartbeat.spp_pathmaxrxt = 1;
rtoinfo.srto_max = 2000;
sig_handler.sa_handler = handle_signal;
sig_handler.sa_flags = 0;
/*Handle SIGINT in handle_signal Function*/
if(sigaction(SIGINT, &sig_handler, NULL) == -1)
perror("sigaction");
/*Create the Socket*/
if((ret = (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP))) < 0)
perror("socket");
/*Configure Heartbeats*/
if((ret = setsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS,
&heartbeat, sizeof(heartbeat))) < 0)
perror("setsockopt");
/*Set rto_max*/
if((ret = setsockopt(sock, SOL_SCTP, SCTP_RTOINFO,
&rtoinfo, sizeof(rtoinfo))) < 0)
perror("setsockopt");
/*Set SCTP Init Message*/
if((ret = setsockopt(sock, SOL_SCTP, SCTP_INITMSG,
&initmsg, sizeof(initmsg))) < 0)
perror("setsockopt");
/*Enable SCTP Events*/
events.sctp_data_io_event = 1;
events.sctp_association_event = 1;
if((ret = setsockopt(sock, SOL_SCTP, SCTP_EVENTS,
(void *)&events, sizeof(events))) < 0)
perror("setsockopt");
/*Get And Print Heartbeat Interval*/
hb_len = (socklen_t)(sizeof heartbeat);
getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, &hb_len);
/*printf("Heartbeat interval %d\n", heartbeat.spp_hbinterval);*/
/*Connect to Host*/
if(((ret = connect(sock, (struct sockaddr*)&addr,
sizeof(struct sockaddr)))) < 0) {
perror("connect");
close(sock);
exit(0);
}
/*Get Peer Addresses*/
addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
printf("# IP asociadas: %d\n", addr_count);
for(i = 0; i < addr_count; i++)
printf(" IP %d: %s\n", i+1, inet_ntoa((*paddrs)[i].sin_addr));
sctp_freepaddrs((struct sockaddr*)*paddrs);
/*Start to recv data*/
while((ret = sctp_recvmsg(sock, (void *)buffer, sizeof(buffer),
(struct sockaddr *)&server, &addr_len, &sndrcvinfo, &flags)) > 0) {
if(!(flags & MSG_NOTIFICATION)) {
buffer[ret] = 0;
if(sndrcvinfo.sinfo_stream == CTRL_STREAM) {
printf("%s CTRL(%d): %s", inet_ntoa(server.sin_addr),
sndrcvinfo.sinfo_stream, buffer);
} else if(sndrcvinfo.sinfo_stream == DATA_STREAM) {
printf("%s DATA(%d): %s", inet_ntoa(server.sin_addr),
sndrcvinfo.sinfo_stream, buffer);
}
}
}
if(close(sock) < 0)
perror("close");
return 0;
}
void handle_signal(int signum) {
printf(" Cerrando...\n");
if(close(sock) < 0)
perror("close");
exit(0);
}
Makefile
# make
CC := gcc
CFLAGS := -Wall
all: sctpc sctps
sctpc: sctpc.o
$(CC) $(CFLAGS) -o $@ sctpc.c -L/usr/local/lib -lsctp
sctps: sctps.o
$(CC) $(CFLAGS) -o $@ sctps.c -L/usr/local/lib -lsctp
clean:
rm -f sctpc sctps *.o
更多推荐
所有评论(0)