欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

UNP卷一chapter9/10 基本SCTP套接字编程

程序员文章站 2022-07-14 20:59:29
...

以下知识点来均来自steven先生所著UNP卷一(version3),刚开始学习网络编程,如有不正确之处请大家多多指正。

1、流控制传输协议(SCTP)

sctp在客户和服务器之间提供关联(连接只涉及两个ip地址之间的通信,而关联指代两个系统之间的一次通信,其支持多宿,涉及不止两个地址)。sctp同样提供可靠性、排序、流量控制以及全双工的数据传送。与tcp不同,sctp面向消息;sctp能够在所连接的端点之间提供多个流,每个流各自可靠地按序递送消息。一个流上某个消息的丢失不会阻塞同一关联其他流上消息的投递(缓解tcp头端阻塞问题)。

sctp套接字的接口模型分为一到一、一到多,其分别建立连接过程见下图:

UNP卷一chapter9/10 基本SCTP套接字编程

一到一形式,与tcp建立连接的过程基本上一致,但还是有稍许不同:

a、具有自己sctp的套接字option

b、sctp保存消息边界,因而应用消息层并非必需(这点没怎看明白,具体分析见书P209)

c、sctp可以让应用进程在应用数据流中告知对端该数据传输数据流已经结束(而tcp采用半关闭策略)

d、send函数能够以普通方式使用,使用sendto或sendmsg函数时,指定的任何地址都被认为是对目的地主地址的重写(特别注意)

一到多形式(服务器无需管理大量套接字描述符,单个套接字描述符将代表多个关联,关联的标识是一个类型为sctp_assoc_t的值,通常是一个整数且不透明),具有的特点如下:

a、client关闭关联时,其服务器也将自动关闭同一个关联

b、对一个与它还没有关联存在的ip地址,任何以它为目的地的sendto、sendmsg或sctp_sendmsg将导致对主动打开的尝试,从而建立一个与该地址的新关联(如果成功的话)

c、一到多式sctp,用户必需使用sendto、sendmsg或sctp_sendmsg

d、任何时候为用其中任何一个分组发送函数时,所用的目的地址是由系统在关联建立阶段选定的主目的地址,除非调用者在所提供的sctp_sndrcvinfo结构中设置了MSG_ADDR_OVER标志。为了提供这个结构,调用者必须使用伴随辅助数据的sendmsg函数或sctp_sendmsg函数(没怎么看懂,唉!!!)

e、关联事件可能被启用,但应用进程不希望收到这些事件,使用SCTP_EVENTS套接字选项显式禁止它们。(见书P211)

2、与sctp相关函数

sctp_bindx、sctp_connectx、sctp_getpaddrs、sctp_freepaddrs、sctp_getladdrs、sctp_freeladdrs、sctp_sendmsg、sctp_recvmsg、sctp_opt_info、sctp_peeloff、shutdown等函数

#include<netinet/sctp.h>
ssize_t sctp_sendmsg(int sockfd, const void *msg, size_t msgsz,
	const struct sockaddr* to, socklen_t tolen,
	uint32_t ppid,//指定将随数据块传递的净荷协议标识符
	uint32_t flags, uint16_t stream,//flags参数将传递给sctp栈,用以标识任何sctp选项,stream为指定的流号
	uint32_t timetolive, uint32_t context);//返回:若成功则为所写字节数,若出错则为-1

ssize_t sctp_recvmsg(int sockfd, void* msg, size_t msgsz,
	struct sockaddr* from, socklen_t* fromlen
	struct sctp_sndrcvinfo* sinfo,//如果通知的sctp_data_io_event被启用(默认情形),就会有与消息相关的细节信息来填充sctp_sndrcvinfo结构
	int * msg_flags);//msg_flags用来区分事件产生的通知还是对端发来的数据
//返回:若成功则为所写字节数,若出错则为-1

在客户端和服务器编程过程中,多使用如sctp_sendmsg和sctp_recvmsg等工具函数可以简化这些高级特性的应用。

3、sctp客户/服务器程序代码

UNP卷一chapter9/10 基本SCTP套接字编程

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 evnts;//套接字options
	int stream_increment = 1;
	socklen_t len;
	size_t rd_sz;

	if (argc == 2)
		stream_increment = atoi(argv[1]);//字符到整型的转换
	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);//INADDR_ANY是一个通配地址,其值为0
	servaddr.sin_port = htons(SERV_PORT);

	Bind(sock_fd, (SA *)&servaddr, sizeof(servaddr));

	bzero(&evnts, sizeof(evnts));
	evnts.sctp_data_io_event = 1;//预定感兴趣的通知事件
	Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS,
		&evnts, sizeof(evnts));//其参数分别是(套接字、level、optname、optval、length)

	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++;
			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);
	}
}

sctp流分回射客户端程序如下所示

#include	"unp.h"

void
sctpstr_cli(FILE *fp, int sock_fd, struct sockaddr *to, socklen_t tolen)
{
	struct sockaddr_in peeraddr;
	struct sctp_sndrcvinfo sri;//作为辅助数据信息,解释见书P175
	char sendline[MAXLINE], 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, line must be of the form '[streamnum]text'\n");
			continue;
		}
		sri.sinfo_stream = strtol(&sendline[1], NULL, 0);//写入流号
		/*long int strtol(const char* nptr,char ** endptr,int base);从起始到遇见非数字或‘\0’结束转换,并返回。
		若endptr为NULL,则不返回非法字符串,第三个参数,表明采取的进制数0,10均表示十进制数*/
		out_sz = strlen(sendline);
		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),
			(SA *)&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("%.*s", rd_sz, recvline);
	}
}

#define	SCTP_MAXLINE	800

void
sctpstr_cli_echoall(FILE *fp, int sock_fd, struct sockaddr *to, socklen_t tolen)
{
	struct sockaddr_in peeraddr;
	struct sctp_sndrcvinfo sri;
	char sendline[SCTP_MAXLINE], recvline[SCTP_MAXLINE];
	socklen_t len;
	int rd_sz, i, strsz;
	int msg_flags;

	bzero(sendline, sizeof(sendline));
	bzero(&sri, sizeof(sri));
	while (fgets(sendline, SCTP_MAXLINE - 9, fp) != NULL) {
		strsz = strlen(sendline);
		if (sendline[strsz - 1] == '\n') {//将换行符转为‘\0’
			sendline[strsz - 1] = '\0';
			strsz--;
		}
		for (i = 0; i<SERV_MAX_SCTP_STRM; i++) {
			snprintf(sendline + strsz, sizeof(sendline) - strsz,
				".msg.%d", i);//该函数即将.msg.%d写入到起始地址为sendline+strsz处,最多只能写入sizeof(sendline)-strsz个字符
			Sctp_sendmsg(sock_fd, sendline, sizeof(sendline),
				to, tolen,
				0, 0,
				i,//表示流号
				0, 0);
		}
		for (i = 0; i<SERV_MAX_SCTP_STRM; i++) {
			len = sizeof(peeraddr);
			rd_sz = Sctp_recvmsg(sock_fd, recvline, sizeof(recvline),
				(SA *)&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("%.*s\n", rd_sz, recvline);//没怎么看懂%.*s为什么对应两个输出参数???????
		}
	}
}


int
main(int argc, char **argv)
{
	int sock_fd;
	struct sockaddr_in servaddr;
	struct sctp_event_subscribe evnts;
	int echo_to_all = 0;

	if (argc < 2)
		err_quit("Missing host argument - use '%s host [echo]'\n",
			argv[0]);
	if (argc > 2) {
		printf("Echoing messages to all streams\n");
		echo_to_all = 1;
	}
	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);
	Inet_pton(AF_INET, argv[1], &servaddr.sin_addr);

	bzero(&evnts, sizeof(evnts));
	evnts.sctp_data_io_event = 1;//开启事件通知
	Setsockopt(sock_fd, IPPROTO_SCTP, SCTP_EVENTS,
		&evnts, sizeof(evnts));
	if (echo_to_all == 0)//以示区别调用哪个cli程序
		sctpstr_cli(stdin, sock_fd, (SA *)&servaddr, sizeof(servaddr));
	else
		sctpstr_cli_echoall(stdin, sock_fd, (SA *)&servaddr, sizeof(servaddr));
	Close(sock_fd);
	return(0);
}

4、查看sctp网终连接状态命令

netstat -tua -s...只支持查看tcp、udp网终状态情况,而对于sctp,可以到/proc/net/sctp目录里查看相应连接情况(IP地址及端口)。