1. 一对多的通信场景
一个SCTP服务端socket,多个SCTP客户端可以连接到这个socket上,不需要有显示的建立连接的过程;每个客户端接入进来时,都会建立一个对应的association
2. Server端代码
2.1 Socket建立
建立socket的时候设置通信语义:SOCK_SEQPACKET;
socket( AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP );
和一对一通信场景最大的区别是,没有accept()
等待连接请求的过程,
2.2 完整的Server端程序
//SCTP Server: SOCK_SEQPACKET
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netinet/sctp.h>
#include "common.h"
#include <errno.h>
#include <string.h>
int Socket()
{
/* Create SCTP TCP-Style Socket */
int sock_fd = socket( AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP );
if (-1 == sock_fd){
printf("create socket failed: %s \n", strerror(errno));
}
return sock_fd;
}
void Bind(int sock_fd)
{
struct sockaddr_in servaddr;
int addr_count = 1;
/* 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);
// replace bind with sctp_bindx
if(-1 == sctp_bindx( sock_fd, (struct sockaddr *)&servaddr, addr_count, SCTP_BINDX_ADD_ADDR))
{
printf("bind socket failed: %s \n", strerror(errno));
return;
}
}
int SetSocketOpt(int sock_fd)
{
struct sctp_event_subscribe evnts;
bzero(&evnts, sizeof(evnts));
evnts.sctp_data_io_event = 1;
if(0 != setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS, &evnts, sizeof(evnts)))
{
printf("Error setsockopt(IPPROTO_SCTP): %s\n", strerror(errno));
return 0;
}
return 1;
}
void Listen(int sock_fd)
{
int listenQueLen = 5;
listen( sock_fd, listenQueLen);
}
void SctpRecevMsg(int sock_fd)
{
int msg_flags;
size_t rd_sz;
char readbuf[MAX_BUFFER+1];
struct sockaddr_in cliaddr;
struct sctp_sndrcvinfo sri;
socklen_t len;
len = sizeof(struct sockaddr_in);
while(1)
{
printf("[Server]: wait for message!\n");
int rz = sctp_recvmsg(sock_fd, readbuf, sizeof(readbuf),
&cliaddr, &len, &sri, &msg_flags);
if (rz == 0)
{
printf("[Server]: EOF received!\n");
return;
}
else if(rz < 0)
{
printf("[Server]: Error sctp_recvmsg: %s\n", strerror(errno));
return;
}
printf("[Server]: Message(size = %d): '%s' received, assoid = %x \n", rz , readbuf, sri.sinfo_assoc_id);
}
}
void Close(int sock_fd)
{
close(sock_fd);
printf("[Server]: Close connect socket!\n");
}
int main()
{
int list_sockfd, conn_sockfd;
list_sockfd = Socket();
Bind(list_sockfd);
SetSocketOpt(list_sockfd);
Listen(list_sockfd);
/* Server loop... */
while( 1 ) {
SctpRecevMsg(list_sockfd);
Close(list_sockfd);
}
return 0;
}
3. Client端代码
3.1 发送消息
这里发送消息使用的是另一个函数sctp_sendmsg()
,在这个函数里会填写目的地址,发送消息payload之前会先进行四次握手建立连接;
struct sockaddr_in servaddr;
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" );
sctp_sendmsg(sock_fd, buf, strlen(buf),
&servaddr, sizeof(servaddr), 0, 0, stream, 0, 0);
3.2 完整的client端代码
//SCTP client: SOCK_SEQPACKET
#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>
#include "common.h"
#include <errno.h>
int Socket()
{
return socket( AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP );
}
void SetSocketOpt(int sock_fd)
{
struct sctp_initmsg initmsg;
struct sctp_event_subscribe events;
int opt = 1;
int len = sizeof(opt);
/* 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;
if(0 != setsockopt( sock_fd, IPPROTO_SCTP, SCTP_INITMSG,
&initmsg, sizeof(initmsg) ))
{
printf("[Client]: error setsocketopt IPPROTO_SCTP, %s\n", strerror(errno));
return;
}
/* Enable receipt of SCTP Snd/Rcv Data via sctp_recvmsg */
memset( (void *)&events, 0, sizeof(events) );
events.sctp_data_io_event = 1;
events.sctp_association_event = 1;
if(0 != setsockopt( sock_fd, SOL_SCTP, SCTP_EVENTS,
(const void *)&events, sizeof(events) ))
{
printf("[Client]: error setsocketopt SOL_SCTP, %s\n", strerror(errno));
return;
}
}
void SendMsg(int sock_fd)
{
char buf[20];
struct sctp_sndrcvinfo sinfo;
struct sockaddr_in servaddr;
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" );
printf("[Client]: Input message to send to server!\n");
if(NULL != gets(buf))
{
int stream = 1;
sctp_sendmsg(sock_fd, buf, strlen(buf),
&servaddr, sizeof(servaddr), 0, 0, stream, 0, 0);
}
}
void Close(int sock_fd)
{
char buf[20];
printf("[Client]: Enter to close socket!\n");
if(NULL != gets(buf))
{
/* Close our socket and exit */
close(sock_fd);
printf("[Client]: Close socket!\n");
return;
}
}
int main()
{
int sock_fd;
/* Create an SCTP TCP-Style Socket */
sock_fd = Socket();
SetSocketOpt(sock_fd);
SendMsg(sock_fd);
Close(sock_fd);
return 0;
}
4. 程序的运行过程
- 服务端启动
建立好socket后,会阻塞等待消息接收,
[Server]: wait for message!
- 客户端启动
往server端发送一条消息
[Client]: Input message to send to server!
hello
- 服务端收到消息
[Server]: Message(size = 5): 'hell' received, assoid = 39
[Server]: wait for message!
打印出association ID后继续回到等待下一条消息的状态
客户端关闭socket
客户端关闭socket后,不会像一对一通信的那样发送一条EOF消息给服务端,所以服务端并没有任何对应的处理;但是我们知道现在SCTP的连接已经不可用了,那么服务端怎么去处理这条association?重新启动一个新的客户端
同样发送一条新的消息给server,可以看到建立了一个新的association,
[Server]: Message(size = 7): 'hello 2' received, assoid = 3b
可以通过这个association ID来区分连到这个server端socket的不同的链路。