SCTP协议详解与实例

1.SCTP是什么?

只要是接触过编程的人,当你问他传输层都有哪些协议?我想几乎很多人会说TCP,IP协议而很少有人知道SCTP(流控制传输协议)这个和上述俩个协议具有相同地位的协议。

SCTP提供的服务与TCP,UDP类似,或者甚至可以理解为其是TCP与UDP协议各自优点的组合后的产物。

2.SCTP的特点

(1)SCTP连接的建立

SCTP协议建立连接可调用

int sctp_connectx(int sd, struct sockaddr *addrs, int addrcnt);
//或者直接发送消息就可建立连接
int sctp_sendmsg(int s, 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);

介绍完建立连接的接口,那么就来谈谈其连接建立的具体过程,废话不多说,先给出其建立连接过程的示意图

SCTP连接建立的过程如上图所示:

(1)客户端给服务器发送一个INIT初始化消息,消息中包含有客户端要告诉服务器自己的IP地址的清单,初始化列号(可以和TCP初始化序列一样来理解),用于表示本关联中的所有分组的其实标志,客户请求以及能够支持流的数目等

(2)服务器给客户端回一个INIT ACK来确认刚收到的消息,并且还有和刚才客户端给服务器发的所有种类的服务器自己的信息,除此之外还会还有一个状态cookie

(3)客户端收到状态cookie之后,给服务器回一个COOKIE ECHO 并且此时还可以在此数据包中包含用户数据

(4)服务器收到客户端给其发的COOKIE ECHO之后在给其发个COOKIE ACK 此时同样也可以携带数据

至此一个SCTP关联就建立成功

(2)SCTP断开连接

SCTP断开连接和TCP的接口相同都是调用shutdown(),但是语义却不同其how参数对SCTP来说都是读写都禁掉

其具体流程如下图

SCTP不像TCP那样允许”半关闭的关联”。当一端关闭某个关联时,另一段必须停止发送数据。

(3)多宿主

TCP为客户与服务器之间提供连接,从而可以使双方安全的传送数据,而SCTP将连接改为了”关联”。

要解释清楚为什么SCTP将TCP的连接改为”关联”我打算从他们的bind(绑定接口)来说明原因,先以服务器调用bind为例,

TCP的bind接口如下

int bind(int sockfd,const struct sockaddr *my_addr,socklen_t addrlen);

SCTP的bind接口如下

int sctp_bindx(int sockfd, struct sockaddr *my_addrs, int addrcnt, int flags);

上面给出了TCP和STCP对应的绑定套接字的接口,我们逐一的对比其参数

(1)首先二者的第一个参数sockfd含义相同,都是指待绑定的套接字

(2)bind中的参数my_addr指的是要把哪个插座(IP地址和端口号)绑定到sockfd上,这相当与给了sockfd一个身份,之后网络中就可以唯一的标识该sockfd了,而sctp_bindx中对应位置的参数名变为my_addrs,细心的读者会发现这个参数变为了复数了。没错该参数并不是单一的某个sockaddr结构体的地址,而是sockaddr结构体数组的首地址,参数addrcnt表示该数组中元素的个数。既然相比TCP的bind接口,此处为多个地址,我们也不难理解sctp_bindx所做的工作是将这些地址都绑定到sockfd上。这样其和TCP的第一个不同点就出来了。即TCP的bind之后的socket只有一个身份(只绑定了一个地址),而SCTP bind之后socket有多个身份(绑定了多个地址)。关于flags参数我们之后在补充

上述中我们的俩种协议的服务器调用bind接口有所区别,那么SCTP其对应的客户端建立连接时调用的connect接口也类似稍有不同

sctpconnect接口

int sctp_connectx(int sd, struct sockaddr *addrs, int addrcnt);

可以看出SCTP在建立连接时addrs也为一个sockaddr结构体数组

那么SCTP相较TCP的绑定端口和连接时使用了地址数组有什么用意呢?没错这就是我们这里最终想要说明的其多宿主的特性。服务器调用多个地址来标识自己,客户端可以和每一个地址建立一个路径。这样当客户端的哪条路劲失效时,可以自动切换到另一条路径上,应用程序甚至都不必知道发生了故障,这样是不是比起TCP的单一路径安全保障性更好一点呢?

(4)多流解决头端阻塞

还是与TCP做比较,TCP虽然是个全双工协议,但是其读写方向各自只有一个流。一个流很多情况下会发生头端阻塞,其大概意思如下图

如上图所示,因为TCP每一个方向上只有一个流,那么当该流中的某个部分的报文丢失之后,其后的所有报文都只能等待丢失部分重传之后,并在重新排序之后方可被送入应用的接受缓冲区。这种协议设定对发送数据有严格顺序要求时还可以接受,但如果对发送数据没有这么强的顺序限制时就显的不那么友好了。

一个具体的场景

当我们的一个web页面要展示好多张图片时,就像上图所示。在这种情况下其实图片1到4之间到达的先后顺序是完全不影响的。此时如果你用TCP这种单项流协议,那么图片一有数据丢失,导致你因此也看不了其实已经发来的图二到图4。如果你改用SCTP这种支持多流的协议,那么你的每张图片发送都使用不同的流号,到时候,当图一对应流号的数据有丢失,那么只有这一个流会等待重传,其他几张图片都不会被其影响,这样我们就可以先看到图2到图4了,完全不受图1丢失的影响。不知道读者此时会不会感觉出来点多流在此种场景下的巨大优势

(5) 面向消息

SCTP是面向消息的协议,不像TCP那样没有包的概念,也意味着消息没有边界,边界只能由应用层来设计和划分,而SCTP一个包就是一个消息,这就大大降低了编程者的难度。其次它的发送接受接口还可以传递消息类型,接口具体如下

int sctp_sendmsg(int s, 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);

上述的发送接口中flags参数就是用来标识消息类型的参数。通过这样直接获取消息类型由可以减少我们不必要的拆包操作,真的是方便之极

(6)一到多特性

SCTP的一到多特性其实和UDP的套接字一样,都能通过一个套接字接受多个消息,这意味着程序员可以不用向TCP那样管理大量的套接字了

3.SCTP常用接口

一下接口我们都是针对一到多形式的SCTP

(1)sctp_bindx端口绑定

该接口用来命名一个套接字

int sctp_bindx(int sd, struct sockaddr *addrs, int addrcnt, int flags);
//成功返回0,出错返回1

该接口用来给sd绑定一组地址,或从addrs地址组中添加,删除某个地址。具体操作由flags来控制,由于之前我们以介绍过其他参数,这里值介绍flags

flags 说明
SCTP_BINDX_ADD_ADDR 往套接字里添加地址
SCTP_BINDX_REM_ADDR 从套接字中删除地址

(2)sctp_connectx函数

该接口用于与服务器建立连接

int sctp_connectx(int sd, struct sockaddr *addrs, int addrcnt);

参数含义上文有介绍这里不在赘余

(3)sctp_sendmsg

如果之前没有调用sctp_connectx那么第一次调用该接口既负责建立连接也负责发送数据

int sctp_sendmsg(int s, 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);

前5个参数与UDP的sendto参数含义相同,我们在此只讨论后4个

ppid参数指定将随数据块传递的净荷协议

flags参数标识消息类型

stream_no参数标识具体的流号

timetolive指定消息的生命期

context保存消息传输过程中可能产生的上下文

(4)sctp_recvmsg

该接口负责接受数据

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

由于前5个参数与UDP的recvfrom相同,在此我们同样也只讨论后几个参数

sinfo保存了消息相关的细节

msg_flags和sctp_sendmsg中的flags相对应

4.SCTP实例代码

下面是用SCTP的一到多实现的一个回显服务器

server.h

#pragma once

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>

#define SERVER_PORT 6666
#define BUFFER_SIZE 1024
#define LISTEN_QUEUE 100

class SctpServer
{
    public:
        SctpServer();
        void start(void);
    private:
        //开启监听socket
        void listenSocket(void);
        //循环处理请求
        void loop(void);

        int sockFd_;                            //用来接受的套接字
        int messageFlags_;                      //消息类型
        char readBuf_[BUFFER_SIZE];             //接受缓冲区
        struct sockaddr_in clientAddr_;         //用来保存客户端地址
        struct sockaddr_in serverAddr_;         //用来保存服务端地址
        struct sctp_sndrcvinfo sri_;            //消息相关细节信息
        struct sctp_event_subscribe events_;    //事件集
        int streamIncrement_;                   //流号
        socklen_t len_;                         //地址长度
        size_t readSize_;                       //读到的大小
};

server.cpp

#include "server.h"
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <arpa/inet.h>

SctpServer::SctpServer()
    :streamIncrement_(1)
{

}

void SctpServer::listenSocket(void)
{
    //创建SCTP套接字
    sockFd_ = socket(AF_INET,SOCK_SEQPACKET,IPPROTO_SCTP);
    bzero(&serverAddr_,sizeof(serverAddr_));
    serverAddr_.sin_family = AF_INET;
    serverAddr_.sin_addr.s_addr = htonl(INADDR_ANY);
    serverAddr_.sin_port = htons(SERVER_PORT);
    inet_pton(AF_INET,"127.0.0.1",&serverAddr_.sin_addr);   

    //地址绑定
    bind(sockFd_,(struct sockaddr *)&serverAddr_,sizeof(serverAddr_));

    //设置SCTP通知事件(此处只设置了I/O通知事件)
    bzero(&events_,sizeof(events_));
    events_.sctp_data_io_event = 1;
    setsockopt(sockFd_,IPPROTO_SCTP,SCTP_EVENTS,&events_,sizeof(events_));

    //开始监听
    listen(sockFd_,LISTEN_QUEUE);
}

void SctpServer::loop(void)
{
    while(true)
    {
        len_ = sizeof(struct sockaddr_in);
        //从socket读取内容
        readSize_ = sctp_recvmsg(sockFd_,readBuf_,BUFFER_SIZE,
                                 (struct sockaddr *)&clientAddr_,&len_,&sri_,&messageFlags_);
        //增长消息流号
        if(streamIncrement_)
        {
            sri_.sinfo_stream++;
        }
        sctp_sendmsg(sockFd_,readBuf_,readSize_,
                     (struct sockaddr *)&clientAddr_,len_,
                      sri_.sinfo_ppid,sri_.sinfo_flags,sri_.sinfo_stream,0,0);
    }
}

void SctpServer::start(void)
{
    listenSocket();
    loop();
}

main.cpp

#include "server.h"

int main(int argc,char **argv)
{
  SctpServer server;
  server.start();
  return 0;
}

client.h

#pragma once

#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#define SERVER_PORT 6666
#define MAXLINE     1024

void sctpstr_cli(FILE *fp,int sock_fd,struct sockaddr *to,socklen_t tolen);

class SctpClient
{
    public:
        SctpClient():echoToAll_(0)
        {

        }
        ~SctpClient()
        {
            close(sockFd_);
        }
        //启动客户端
        void start(void)
        {
            makeSocket();
        }

    private:

        void makeSocket(void)
        {
            sockFd_ = socket(AF_INET,SOCK_SEQPACKET,IPPROTO_SCTP);
            bzero(&serverAddr_,sizeof(serverAddr_));
            serverAddr_.sin_family = AF_INET;
            serverAddr_.sin_addr.s_addr = htonl(INADDR_ANY);
            serverAddr_.sin_port = htons(SERVER_PORT);
            inet_pton(AF_INET,"127.0.0.1",&serverAddr_.sin_addr);

            bzero(&events_,sizeof(events_));
            events_.sctp_data_io_event = 1;
            setsockopt(sockFd_,IPPROTO_SCTP,SCTP_EVENTS,&events_,sizeof(events_));
            if(echoToAll_ == 0)
            {
                sctpstr_cli(stdin,sockFd_,(struct sockaddr *)&serverAddr_,sizeof(serverAddr_));
            }
        }

        int sockFd_;
        struct sockaddr_in serverAddr_;
        struct sctp_event_subscribe events_;
        int echoToAll_;
};

//循环发送并接受消息
void sctpstr_cli(FILE *fp,int sock_fd,struct sockaddr *to,socklen_t tolen)
{
    struct sockaddr_in peeraddr;
    struct sctp_sndrcvinfo sri;
    char sendline[MAXLINE];
    char recvline[MAXLINE];
    socklen_t len;
    int out_sz,rd_sz;
    int msg_flags;

    bzero(&sri,sizeof(sri));
    while(fgets(sendline,MAXLINE,fp) != NULL)
    {
        if(sendline[0] != ‘[‘)
        {
            printf("ERROR\n");
            continue;
        }
        sri.sinfo_stream = sendline[1] - ‘0‘;
        out_sz = strlen(sendline);

        //发送消息
        int count = sctp_sendmsg(sock_fd,sendline,out_sz,to,tolen,0,0,sri.sinfo_stream,0,0);
        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\n",rd_sz,recvline);
    }
}

client.cpp

#include "client.h"

int main(int argc,char **argv)
{
  SctpClient client;
  client.start();
  return 0;
}
时间: 2024-10-11 21:17:54

SCTP协议详解与实例的相关文章

TCP协议详解即实例分析

 TCP协议详解 3.1 TCP服务的特点 TCP协议相对于UDP协议的特点是面向连接.字节流和可靠传输. 使用TCP协议通信的双方必须先建立链接,然后才能开始数据的读写.双方都必须为该链接分配必要的内核资源,以还礼链接状态和连接上数据的传输.TCP链接是全双工的,即双方的数据读写可以通过一个连接进行.完成数据交换之后,通信双方都必须断开连接以释放系统资源. TCP协议的这种连接是一对一的,所以基于广播和多播(目标是多个主机地址)的应用程序不能使用TCP服务.而无连接协议UDP则非常适合于广

HTTP协议详解

HTTP协议详解 转载(http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html) 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了Web应用程序的内部工作. 本文将对HTTP协议进行详细的实例讲解,内容较多,希望大家耐心看.也希望对大家的开发工作或者测试工作有所帮助.使用Fidd

IP协议详解

IP协议详解 前言 本屌今天可算是累坏了,一大早起来本来寻思赶快centOS虚拟机玩玩吧,那天刚装了系统,本来的虚拟机没了,今天想着先把centOS装上,结果给个系统不停的给我扯淡啊,显示虚拟机上不去网,好不容易上去网了,ping不通主机,主机ping不通虚拟机,各种办法都试了,最后我吧VMware8那块网卡禁用了,卧槽!!啥都好了,本屌一直鼓捣到晚上八点,从早晨10点多.服了我自己了. 引入 在前面的学习中,我们简单地IP接力和IP地址后,咱们今天具体的说说IP协议的具体细节和设计哲学. IP

(转)HTTP协议详解

HTTP协议详解 一.概念 协议是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的规定或规则,超文本传输协议(HTTP)是一种通信协议,它允许将超文本标记语言(HTML)文档从Web服务器传送到客户端的浏览器. HTTP协议,即超文本传输协议(Hypertext transfer protocol).是一种详细规定了浏览器和万维网(WWW = World Wide Web)服务器之间互相通信的规则,通过因特网传送万维网文档的数据传送协议. HTTP协议是用于从WWW服务器传输超文本到本地

HTTP协议 (三) HTTP协议详解

HTTP协议详解 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了Web应用程序的内部工作. 本文将对HTTP协议进行详细的实例讲解,内容较多,希望大家耐心看.也希望对大家的开发工作或者测试工作有所帮助.使用Fiddler工具非常方便地捕获HTTP Request和HTTP Response,  关于Fiddler工具的用法,请看我另一篇博客[Fid

入木三分学网络第一篇--VRRP协议详解第一篇(转)

因为keepalived使用了VRRP协议,所有有必要熟悉一下. 虚拟路由冗余协议(Virtual Router Redundancy Protocol,简称VRRP)是解决局域网中配置静态网关时,静态网关出现单点失效现象的路由协议. VRRP广泛应用在边缘网络中,它的设计目标是支持特定情况下IP数据流量失败转移不会引起混乱,允许主机使用单路由器(位于一个虚拟路由器组中, 在该组中,只有一台路由器--master路由器工作,转发数据包,其它路由器是backup路由器,不参与转发数据包),以及在实

[转]HTTP 协议详解

出处:http://www.cnblogs.com/TankXiao/archive/2012/02/13/2342672.html 当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了Web应用程序的内部工作. 本文将对HTTP协议进行详细的实例讲解,内容较多,希望大家耐心看.也希望对大家的开发工作或者测试工作有所帮助.使用Fiddler工具非常方便地

HTTP协议详解(转)

当今web程序的开发技术真是百家争鸣,ASP.NET, PHP, JSP,Perl, AJAX 等等. 无论Web技术在未来如何发展,理解Web程序之间通信的基本协议相当重要, 因为它让我们理解了Web应用程序的内部工作. 本文将对HTTP协议进行详细的实例讲解,内容较多,希望大家耐心看.也希望对大家的开发工作或者测试工作有所帮助.使用Fiddler工具非常方便地捕获HTTP Request和HTTP Response,  关于Fiddler工具的用法,请看我另一篇博客[Fiddler 教程]

ARP协议详解之ARP动态与静态条目的生命周期

ARP协议详解之ARP动态与静态条目的生命周期 ARP动态条目的生命周期 动态条目随时间推移自动添加和删除. q  每个动态ARP缓存条目默认的生命周期是两分钟.当超过两分钟,该条目会被删掉.所以,生命周期也被称为超时值. q  延长规则:当ARP条目已存在,使用该条目后,将会重设超时值为两分钟. [实例1-12]下面将验证动态条目的生命周期是两分钟.具体操作步骤如下所示: (1)查看本机的ARP缓存表.执行命令如下所示: C:\Documents and Settings\Administra