我一直在用C开发这个简单的客户端-服务器应用程序,其中客户端只向服务器发送随机数据,服务器只监听客户端发送的内容。我使用的协议是SCTP,我对如何实现它的多宿功能很感兴趣
我一直在互联网上搜索关于SCTP和多址的信息,但没有找到任何关于如何指示SCTP使用多个地址进行通信的例子。我只找到了在尝试使用多主页设置SCTP时应该使用的命令,它应该非常简单。
我创建了一个客户端和一个服务器,它们都使用我的计算机两个WLAN接口作为连接点。两个适配器都连接到同一个AP。服务器正在从这些接口侦听来自客户端的数据,客户端通过这些接口发送数据。问题是,当我断开客户端发送数据的主WLAN适配器时,当传输应该回退到辅助连接时,传输就会停止。我已经用Wireshark跟踪了数据包,第一个INIT和INIT_ACK数据包报告客户端和服务器都使用WLAN适配器作为通信链路。
当我重新连接主WLAN连接时,传输会在一段时间后继续,并向服务器发送大量数据包,这是不对的。数据包本应通过辅助连接进行传输。在许多网站上,据说SCTP会自动在连接之间切换,但在我的情况下,这并没有发生。那么,你们有什么线索吗?为什么当主链路断开时,即使客户端和服务器知道彼此的地址(包括辅助地址),传输也不会回退到辅助连接?
关于服务器:
服务器创建一个SOCK_SEQPACKET类型的套接字,并绑定使用INADDR_ANY找到的所有接口。getladdrs报告服务器被绑定到3个地址(包括127.0.0.1)。之后服务器监听套接字并等待客户端发送数据。服务器通过sctp_recvmsg调用读取数据。
关于客户:
客户端还会创建一个SEQPACKET套接字,并连接到由命令行参数指定的IP地址。在这种情况下,getladdrs还返回3个地址,类似于服务器的情况。之后,客户端开始向服务器发送数据,并延迟一秒钟发送到服务器,直到用户用Ctrl-C中断发送。
以下是一些源代码:
服务器:
#define BUFFER_SIZE (1 << 16)
#define PORT 10000
int sock, ret, flags;
int i;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;
struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;
struct sctp_prim prim_addr;
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;
void handle_signal(int signum);
int main(void)
{
if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
perror("socket");
memset(&addr, 0, sizeof(struct sockaddr_in));
memset((void*)&event, 1, sizeof(struct sctp_event_subscribe));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
from_len = (socklen_t)sizeof(struct sockaddr_in);
sig_handler.sa_handler = handle_signal;
sig_handler.sa_flags = 0;
if(sigaction(SIGINT, &sig_handler, NULL) == -1)
perror("sigaction");
if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 0)
perror("setsockopt");
if(setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int))< 0)
perror("setsockopt");
if(bind(sock, (struct sockaddr*)&addr, sizeof(struct sockaddr)) < 0)
perror("bind");
if(listen(sock, 2) < 0)
perror("listen");
addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
printf("Addresses binded: %dn", addr_count);
for(i = 0; i < addr_count; i++)
printf("Address %d: %s:%dn", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);
sctp_freeladdrs((struct sockaddr*)*laddr);
while(1)
{
flags = 0;
ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, (struct sockaddr*)&addr, &from_len, NULL, &flags);
if(flags & MSG_NOTIFICATION)
printf("Notification received from %s:%un", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
printf("%d bytes received from %s:%un", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
}
if(close(sock) < 0)
perror("close");
}
void handle_signal(int signum)
{
switch(signum)
{
case SIGINT:
if(close(sock) != 0)
perror("close");
exit(0);
break;
default: exit(0);
break;
}
}
客户:
#define PORT 10000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000
#define PPID 1234
int sock;
struct sockaddr_in *paddrs[10];
struct sockaddr_in *laddrs[10];
void handle_signal(int signum);
int main(int argc, char **argv)
{
int i;
int counter = 1;
int ret;
int addr_count;
char address[16];
char buffer[MSG_SIZE];
sctp_assoc_t id;
struct sockaddr_in addr;
struct sctp_status status;
struct sctp_initmsg initmsg;
struct sctp_event_subscribe events;
struct sigaction sig_handler;
memset((void*)&buffer, 'j', MSG_SIZE);
memset((void*)&initmsg, 0, sizeof(initmsg));
memset((void*)&addr, 0, sizeof(struct sockaddr_in));
memset((void*)&events, 1, sizeof(struct sctp_event_subscribe));
if(argc != 2 || (inet_addr(argv[1]) == -1))
{
puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");
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 = 5;
sig_handler.sa_handler = handle_signal;
sig_handler.sa_flags = 0;
if(sigaction(SIGINT, &sig_handler, NULL) == -1)
perror("sigaction");
if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
perror("socket");
if((setsockopt(sock, SOL_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) != 0)
perror("setsockopt");
if((setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (const void *)&events, sizeof(events))) != 0)
perror("setsockopt");
if(sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr)) == -1)
perror("sendto");
addr_count = sctp_getpaddrs(sock, 0, (struct sockaddr**)paddrs);
printf("nPeer addresses: %dn", addr_count);
for(i = 0; i < addr_count; i++)
printf("Address %d: %s:%dn", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);
sctp_freepaddrs((struct sockaddr*)*paddrs);
addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddrs);
printf("nLocal addresses: %dn", addr_count);
for(i = 0; i < addr_count; i++)
printf("Address %d: %s:%dn", i +1, inet_ntoa((*laddrs)[i].sin_addr), (*laddrs)[i].sin_port);
sctp_freeladdrs((struct sockaddr*)*laddrs);
i = sizeof(status);
if((ret = getsockopt(sock, SOL_SCTP, SCTP_STATUS, &status, (socklen_t *)&i)) != 0)
perror("getsockopt");
printf("nSCTP Status:n--------n");
printf("assoc id = %dn", status.sstat_assoc_id);
printf("state = %dn", status.sstat_state);
printf("instrms = %dn", status.sstat_instrms);
printf("outstrms = %dn--------nn", status.sstat_outstrms);
for(i = 0; i < NUMBER_OF_MESSAGES; i++)
{
counter++;
printf("Sending data chunk #%d...", counter);
if((ret = sendto(sock, buffer, MSG_SIZE, 0, (struct sockaddr*)&addr, sizeof(struct sockaddr))) == -1)
perror("sendto");
printf("Sent %d bytes to peern",ret);
sleep(1);
}
if(close(sock) != 0)
perror("close");
}
void handle_signal(int signum)
{
switch(signum)
{
case SIGINT:
if(close(sock) != 0)
perror("close");
exit(0);
break;
default: exit(0);
break;
}
}
你们知道我做错了什么吗?
好的,我终于解决了多重归位问题。以下是我所做的。
我使用sctp_paddrparams结构将心跳值调整为5000毫秒。位于结构中的flags变量必须处于SPP_HB_ENABLE模式,否则SCTP在尝试使用setsockopt()设置值时会忽略检测信号值。
这就是为什么SCTP没有像我想要的那样经常发送心跳的原因。我没有注意到flag变量的原因是我正在阅读的SCTP的过时引用指南,该指南指出结构中不存在flags变量!最新的参考资料显示确实存在。所以心跳问题解决了!
另一件事是将rto_max值修改为,例如,2000 ms左右。降低该值告诉SCTP更快地更改路径。默认值为60000毫秒,这太高了(在开始更改路径前1分钟)。rto_max值可以使用sctp_rtoinfo结构体进行调整。
有了这两个修改,多重归航开始工作。哦,还有另外一件事。当服务器处于SEQPACKET模式时,客户端必须处于STREAM模式。客户端使用普通的send()命令将数据发送到服务器,服务器使用sctp_recvmsg()读取数据,其中addr struct设置为NULL。
我希望这些信息能帮助其他在SCTP的多重归位中挣扎的人。为你们的意见干杯,这对我有很大帮助!这里有一些代码示例,所以如果你问我的话,这可能是网络中第一个多宿简单示例(除了多宿示例之外,没有找到任何示例)
服务器:
#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 <unistd.h>
#include <stdio.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <stdlib.h>
#include <pthread.h>
#define BUFFER_SIZE (1 << 16)
#define PORT 10000
int sock, ret, flags;
int i, reuse = 1;
int addr_count = 0;
char buffer[BUFFER_SIZE];
socklen_t from_len;
struct sockaddr_in addr;
struct sockaddr_in *laddr[10];
struct sockaddr_in *paddrs[10];
struct sctp_sndrcvinfo sinfo;
struct sctp_event_subscribe event;
struct sctp_prim prim_addr;
struct sctp_paddrparams heartbeat;
struct sigaction sig_handler;
struct sctp_rtoinfo rtoinfo;
void handle_signal(int signum);
int main(void)
{
if((sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)) < 0)
perror("socket");
memset(&addr, 0, sizeof(struct sockaddr_in));
memset(&event, 1, sizeof(struct sctp_event_subscribe));
memset(&heartbeat, 0, sizeof(struct sctp_paddrparams));
memset(&rtoinfo, 0, sizeof(struct sctp_rtoinfo));
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = htonl(INADDR_ANY);
addr.sin_port = htons(PORT);
from_len = (socklen_t)sizeof(struct sockaddr_in);
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 = 2000;
/*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 Signal Handler*/
if(sigaction(SIGINT, &sig_handler, NULL) == -1)
perror("sigaction");
/*Set Events */
if(setsockopt(sock, IPPROTO_SCTP, SCTP_EVENTS, &event, sizeof(struct sctp_event_subscribe)) < 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*/
i = (sizeof heartbeat);
getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);
printf("Heartbeat interval %dn", heartbeat.spp_hbinterval);
/*Print Locally Binded Addresses*/
addr_count = sctp_getladdrs(sock, 0, (struct sockaddr**)laddr);
printf("Addresses binded: %dn", addr_count);
for(i = 0; i < addr_count; i++)
printf("Address %d: %s:%dn", i +1, inet_ntoa((*laddr)[i].sin_addr), (*laddr)[i].sin_port);
sctp_freeladdrs((struct sockaddr*)*laddr);
while(1)
{
flags = 0;
ret = sctp_recvmsg(sock, buffer, BUFFER_SIZE, NULL, 0, NULL, &flags);
if(flags & MSG_NOTIFICATION)
printf("Notification received from %s:%un", inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
printf("%d bytes received from %s:%un", ret, inet_ntoa(addr.sin_addr), ntohs(addr.sin_port));
}
if(close(sock) < 0)
perror("close");
}
void handle_signal(int signum)
{
switch(signum)
{
case SIGINT:
if(close(sock) != 0)
perror("close");
exit(0);
break;
default: exit(0);
break;
}
}
客户:
#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 <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#define PORT 11000
#define MSG_SIZE 1000
#define NUMBER_OF_MESSAGES 1000
int sock;
struct sockaddr_in *paddrs[5];
struct sockaddr_in *laddrs[5];
void handle_signal(int signum);
int main(int argc, char **argv)
{
int i;
int counter = 0;
int asconf = 1;
int ret;
int addr_count;
char address[16];
char buffer[MSG_SIZE];
sctp_assoc_t id;
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;
memset(&buffer, 'j', MSG_SIZE);
memset(&initmsg, 0, sizeof(struct sctp_initmsg));
memset(&addr, 0, sizeof(struct sockaddr_in));
memset(&events, 1, 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))
if(argc != 2 || (inet_addr(argv[1]) == -1))
{
puts("Usage: client [IP ADDRESS in form xxx.xxx.xxx.xxx] ");
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*/
if((ret = setsockopt(sock, SOL_SCTP, SCTP_EVENTS, (void *)&events, sizeof(events))) != 0)
perror("setsockopt");
/*Get And Print Heartbeat Interval*/
i = (sizeof heartbeat);
getsockopt(sock, SOL_SCTP, SCTP_PEER_ADDR_PARAMS, &heartbeat, (socklen_t*)&i);
printf("Heartbeat interval %dn", 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("nPeer addresses: %dn", addr_count);
/*Print Out Addresses*/
for(i = 0; i < addr_count; i++)
printf("Address %d: %s:%dn", i +1, inet_ntoa((*paddrs)[i].sin_addr), (*paddrs)[i].sin_port);
sctp_freepaddrs((struct sockaddr*)*paddrs);
/*Start to Send Data*/
for(i = 0; i < NUMBER_OF_MESSAGES; i++)
{
counter++;
printf("Sending data chunk #%d...", counter);
if((ret = send(sock, buffer, MSG_SIZE, 0)) == -1)
perror("write");
printf("Sent %d bytes to peern",ret);
sleep(1);
}
if(close(sock) != 0)
perror("close");
}
void handle_signal(int signum)
{
switch(signum)
{
case SIGINT:
if(close(sock) != 0)
perror("close");
exit(0);
break;
default: exit(0);
break;
}
}
客户端使用sendto()调用打开关联。还好。但在那之后,就不应该再使用sendto()了。因为sendto()将强制SCTP使用给定IP的接口。因此,使用write()或send()代替sendto():
counter++;
printf("Sending data chunk #%d...", counter);
if((ret = write(sock, buffer, MSG_SIZE) == -1)
perror("write");
我从未尝试过SCTP,但据本网站介绍,它支持两种型号。
一对一风格和一对多风格。
据我所知,多主页适用于一对一的风格。
为了打开一个一对一风格的套接字,你需要一个类似的调用:
connSock = socket( AF_INET, SOCK_STREAM, IPPROTO_SCTP );
请注意,SOCK_STREAM
与您的SOCK_SEQPACKET
不同
当你做
sock = socket(AF_INET, SOCK_SEQPACKET, IPPROTO_SCTP)
它似乎打开了一对多样式的套接字,我无法理解它是否支持多宿。
因此,请尝试使用SOCK_STREAM
参数编写代码。
这里还有一个使用SOCK_STREAM
的示例。