流控制传输协议 (SCTP) 关联
SCTP 关联是 TCP 连接的泛化。通常,TCP 连接是服务器上的一个网络接口与客户端上的另一个网络接口之间的一对一连接。 相比之下,SCTP 关联在两个方面都是多对多的
服务器上的多个网络接口可以与客户端上的多个接口关联。 例如,假设服务器和客户端都有一张以太网卡和一张连接到互联网的 Wi-Fi 卡。 然后,数据可以在单个关联中以最多四种可能的方式流动:以太网到以太网、以太网到 Wi-Fi、Wi-Fi 到以太网或 Wi-Fi 到 Wi-Fi。
一个关联还可以承载多个逻辑流。 这些流从零开始编号。 因此,例如,流零可以承载控制指令,而流一可以承载小数据块(例如小文件),流二可以承载较大的数据块(例如 MPEG 电影)。 这三个流在逻辑上是独立的,因此一个流上的延迟不会导致任何其他流上的延迟。
请注意,单个套接字可以有多个关联——也就是说,一个套接字可以用来与多个其他主机通信。 一般来说,这些不同的关联通过关联 ID 来区分。 用于 SCTP 的套接字 API 区分了只能存在一个关联的情况(一对一套接字)或套接字可以管理多个关联的情况(一对多套接字)。 第一种情况对应于我在关于 SCTP 的第一篇文章 [LJ,2007 年 9 月] 中讨论的类似 TCP 的情况。 第二种情况将在下一篇文章中介绍。 在本文中,我只关注单个关联,这适用于一对一和一对多套接字。
TCP 和 UDP 在端点上使用单个网络接口,方法是在 sockaddr 结构中指定其 IP 地址。 如果您指定通配符地址 INADDR_ANY,服务器将监听所有接口,而客户端将只选择一个接口。 在任何情况下,通信都只发生在每个端点上的单个接口之间。 顺便说一句,如果您想知道机器上的所有接口是什么,请使用 ioctl() 调用和参数 SIOCGIFCONF。 WR Stevens 等人的 Unix 网络编程,第 1 卷,第 17.5 节中描述了如何做到这一点。
当有多个接口可用时,仅使用一个接口会降低可靠性。 网线可能连接不良,或者您可能离无线接入点太远而无法获得可靠的信号。 另一方面,使用所有接口可能并不总是可取的。 例如,在澳大利亚,3-G 或 WiMAX 连接的下载费用非常昂贵,因此您只会在没有其他连接可用时才使用该接口。 或者,网桥会将内部和外部接口分别暴露给不同的用户组。
SCTP 允许应用程序选择关联的源端或目标端接口的子集。 一些实现还允许动态添加或删除接口,因此应用程序可以适应不同的网络连接状态。 通过注册关联更改事件(这将在下一篇文章中讨论),一个端点可以跟踪另一端接口的变化。
正常的套接字调用 bind() 只接受一个 sockaddr 参数,将套接字绑定到单个 IP 地址(或通配符地址)。 SCTP 通过引入一个新的调用 sctp_bindx() 来扩展这一点,该调用接受一个 sockaddr 数组,将套接字绑定到所有这些地址。 但是,套接字只绑定到一个端口; sockaddr 数组中的所有端口号必须具有相同的端口号。 并且,如果以后添加或删除地址,它们必须具有相同的绑定端口值。 否则,调用将失败。
关于 IPv4 和 IPv6 地址,sctp_bindx() 还有另一个问题。 可以将套接字传递一组仅 IPv4 sockaddr、一组仅 IPv6 sockaddr 或两者混合的集合。 两种类型的套接字地址结构 sockaddr_in 和 sockaddr_in6 具有不同的大小,因此在同一个数组中混合它们可能会导致对齐问题。 SCTP 将结构打包在一起,它们之间没有浪费空间。 因此,您不能只使用数组的索引,您必须为每个结构复制正确数量的字节,然后按该数量向上移动。
绑定一组地址的调用是
int sctp_bindx(int sd, struct sockaddr *addrs, ↪int addrcount, int flags)
其中 flags 可以是 SCTP_BINDX_ADD_ADDR 或 SCTP_BINDX_REM_ADDR 之一,第二个参数是 IPv4 和 IPv6 套接字地址结构的打包列表。
在服务器上使用它来允许客户端在服务器的任何绑定接口上连接是相对容易的。 但是,如何在客户端上做到这一点呢? bind() 操作在哪里? 就像 TCP 一样,这隐藏在幕后。 在 TCP 中,如果调用 connect() 并且套接字尚未绑定(客户端的通常情况),TCP 协议栈将选择一个接口和一个临时端口。 您可以显式调用 bind(),让您选择接口,但通常端口保留为零,因此仍然会选择临时端口。
您可以使用 SCTP 执行完全相同的操作——不要调用 bind(),将其留给 SCTP 协议栈。 这将像 TCP 一样选择一个临时端口,但是它不会使用单个接口,而是选择一组接口(可能是所有可用的接口)。 因此,在客户端上调用 connect() 而不进行初始 bind() 或 sctp_bindx() 将自动为您提供多宿主。
如果您在客户端的 connect() 之前使用指定的接口调用 bind(),您将只获得该单个客户端接口,从而失去 SCTP 的优势之一! 如果您使用通配符地址 INADDR_ANY 调用 bind(),SCTP 将为您选择一组接口。 因此,SCTP 将尝试为您提供多宿主,除非您使用 bind() 将其固定到单个地址或使用 sctp_bindx() 固定到特定地址集。
对于 SCTP,我期望使用 sctp_bindx() 调用并将所有端口设置为零,为所有地址选择相同的临时端口。 但是,当前的 Linux 实现(高达内核 2.6.21)为第一个地址获取一个临时端口,然后抛出一个错误,因为稍后地址中的端口仍然是零,而不是这个临时值。 解决方法是使用端口零和一个地址调用 bind(),查看系统将端口设置为哪个端口,然后使用这个新端口号在剩余地址上调用 bindx()。 列表 1 (multi_homed_client.c) 显示了这方面的一个示例。 在 SCTP 邮件列表上讨论之后,这种解决方法可能会在下一个 SCTP 规范中变得不必要。
列表 1. multi_homed_client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/sctp.h> int main(int argc, char *argv[]) { int sockfd; int n; struct sockaddr_in addr, *addresses; int addr_size = sizeof(struct sockaddr_in); int addr_count = argc - 2; int port; if (argc < 2) { fprintf(stderr, "Usage %s client-addresses...\n", argv[0]); exit(1); } /* create endpoint */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP); if (sockfd < 0) { perror("socket"); exit(2); } addresses = malloc(addr_size * addr_count); if (addresses == NULL) { perror("malloc"); exit(1); } /* do bind to get ephemeral port first */ addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[1]); addr.sin_port = 0; if (bind(sockfd, (struct sockaddr *) &addr, addr_size) == -1) { perror("bind"); exit(1); } /* this gets sin.sin_port so we can find the ephemeral port */ getsockname(sockfd, (struct sockaddr *) &addr, &addr_size); port = addr.sin_port; printf("Ephemeral port is %d\n", port); for ( n = 2; n < argc; n++) { addr.sin_family = AF_INET; addr.sin_addr.s_addr = inet_addr(argv[n]); addr.sin_port = port; memcpy(addresses + (n-2), &addr, addr_size); } if (sctp_bindx(sockfd, (struct sockaddr *) addresses, addr_count, SCTP_BINDX_ADD_ADDR) == -1) { perror("sctp bindx"); exit(2); } /* get local list */ addr_count = sctp_getladdrs(sockfd, 0, (struct sockaddr**)&addresses); for (n = 0; n < addr_count; n++) { memcpy(&addr, addresses+n, addr_size); printf("addr %s, port %d\n", inet_ntoa(addr.sin_addr.s_addr), addr.sin_port); } /* we don't actually connect to any server in this program */ close(sockfd); exit(0); }
您可以使用 sctp_bindx() 设置要使用的本地接口。 客户端还可以通过使用调用 sctp_connectx() 来指定它想要用来连接到服务器的地址子集,该调用接受一个套接字地址结构列表,就像 sctp_bindx() 一样。 为什么要这样做? 好吧,在完成初始连接时,使用 connect() 和单个地址可能是故障点。 这就是函数 sctp_connectx() 解决的问题。 它允许客户端尝试多个地址以连接到服务器。
sctp_connectx() 中的地址集仅用于建立初始连接。 但是,在建立连接之后,两个端点之间会发生信息交换。 在该交换中,远程对等方告诉本地对等方它实际想要使用的地址,反之亦然。 远程对等方将使用的远程地址集不必与客户端在连接中使用的地址集相同。 但是,您至少可以假设传递给 sctp_connectx() 的地址之一(但您不知道是哪一个)将出现在远程对等方提供的列表中,因为本地客户端必须连接到某个东西!
因此,如果远程对等方选择它使用的地址集,则本地客户端如何找到它们是哪些地址呢? 这是通过另一个函数 sctp_getpaddrs() 完成的,该函数提供远程对等方地址的集合。 还有一个 sctp_getladdrs() 函数,以防本地对等方忘记它正在使用的地址!
一旦在两个端点之间建立关联,就可以在它们之间发送消息。 请注意,SCTP 不关心 QoS(服务质量)问题,例如实时交付,而只关心可靠性问题。 SCTP 使用多宿主功能来尝试尽可能多的路由以传递消息。 因此,在发送端,无法控制使用哪些接口; 实际上,发送方甚至可能在其接口之间使用轮询方案来处理每条消息。 但是,发送应用程序可以向其 SCTP 协议栈指示它希望使用远程对等方的哪个接口,并且它可以告诉远程对等方它希望在哪些接口上接收消息。 这些是通过使用 setsockopt() 调用,选项类型为 SCTP_PRIMARY_ADDR 或 SCTP_SET_PEER_PRIMARY_ADDR 来完成的。 当然,如果这些特定地址不可用,SCTP 将简单地在关联中使用不同的地址。
一旦告知 SCTP 使用哪些接口,它基本上会自行处理事情。 它使用心跳来跟踪哪些接口处于活动状态,并在发生故障时透明地切换接口。 这是为了满足 SCTP 针对提高 TCP 可靠性的设计目标。 应用程序可以向 SCTP 协议栈提供有关使用哪些接口的提示,但协议栈将在发生故障时忽略这些提示。
在 TCP 中,流只是一个字节序列。 在 SCTP 中,它有不同的含义; 流是一个逻辑通道,消息沿着该通道发送,并且单个关联可以有多个流。 流的最初动机来自电话领域,在该领域需要多个可靠的通道,但每个通道上的消息都独立于其他通道上的消息。 在上个月的文章中,我们指出了一些可以从流中受益的 TCP 应用程序,例如 FTP,它使用两个套接字来处理数据和控制消息。 此外,越来越多的应用程序是多线程的,流开启了一个对等方中的线程能够与另一个对等方中的线程通信的可能性,而无需担心被其他线程发送的消息阻塞。
套接字 I/O 调用 read/write/send/recv 不知道 SCTP 流。 默认情况下,write 调用都使用流号零(这可以通过套接字选项更改),但是 read 调用将读取所有流上的消息,并且没有指示使用哪个流。 因此,要有效地使用流,您需要使用一些专门为 SCTP 设计的 I/O 调用。
关联的每个端点将支持一定数量的流。 默认情况下,Linux 端点将期望能够发送到十个流,同时可以在 65,535 个流上接收。 其他 SCTP 协议栈可能具有不同的默认值。 这些值可以通过设置套接字选项 SCTP_INITMSG 来更改,该选项采用结构 sctp_initmsg
struct sctp_initmsg { uint16_t sinit_num_ostreams; uint16_t sinit_max_ostreams; uint16_t sinit_max_attempts; uint16_t sinit_max_init_timeo; }
如果使用此套接字选项设置值,则必须在建立关联之前完成。 参数将在关联初始化期间发送到对等端点。
关联中的每个端点都会知道它将在关联上允许多少输入和输出流,如上一段所述。 在建立关联期间,端点交换这些值。 最终值的协商只是取最小值的问题。 如果一端想要 20 个输出流,而另一端只想要 10 个输入流,则结果是较小的 10,反之亦然,对于相反方向的流数也是如此。
端点将需要知道有多少输出流可用于写入,以避免超过限制。 此值在关联设置期间确定。 设置后,端点可以通过使用 getsockopt() 进行查询来找到此值。 但是,这里有一个小问题:一个套接字可能有很多关联(到不同的端点),并且每个关联可能设置了不同的值。 因此,我们必须进行一个查询,询问特定关联的参数,而不仅仅是套接字的参数。 要询问的参数是 SCTP_STATUS,它采用 sctp_status 类型的结构
struct sctp_status { sctp_assoc_t sstat_assoc_id; int32_t sstat_state; uint32_t sstat_rwnd; uint16_t sstat_unackdata; uint16_t sstat_penddata; uint16_t sstat_instrms; uint16_t sstat_outstrms; uint32_t sstat_fragmentation_point; struct sctp_paddrinfo sstat_primary; };
这具有字段 sstat_instrms 和 sstat_outstrms,其中包含所需的信息。 请参阅列表 2 和 3,了解在每个方向协商流数量的客户端和服务器。
列表 2. streamcount_echo_client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/sctp.h> #define ECHO_PORT 2013 char *usage_msg = "usage: astreamcount_echo_client ip-addr istreams ostreams"; char *msg = "hello"; void usage() { fprintf(stderr, "%s\n", usage_msg); exit(1); } int main(int argc, char *argv[]) { int sockfd; int len; struct sockaddr_in serv_addr; int port = ECHO_PORT; struct sctp_initmsg initmsg; struct sctp_status status; if (argc != 4) usage(); /* create endpoint */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP ); if (sockfd < 0) { perror("socket creation"); exit(2); } /* connect to server */ serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(port); memset(&initmsg, 0, sizeof(initmsg)); initmsg.sinit_max_instreams = atoi(argv[2]); initmsg.sinit_num_ostreams = atoi(argv[3]); printf("Asking for: input streams: %d, output streams: %d\n", initmsg.sinit_max_instreams, initmsg.sinit_num_ostreams); if (setsockopt(sockfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) { perror("set sock opt\n"); } if (connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) { perror("connectx"); exit(3); } len = sizeof(status); memset(&status, 0, len); if (getsockopt(sockfd, IPPROTO_SCTP, SCTP_STATUS, &status, &len) == -1) { perror("get sock opt"); } printf("Got: input streams: %d, output streams: %d\n", status.sstat_instrms, status.sstat_outstrms); /* give the server time to do something */ sleep(2); /* no reads/writes are done */ close(sockfd); exit(0); }
列表 3. streamcount_echo_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/sctp.h> #define ECHO_PORT 2013 char *usage_msg = "usage: streamcount_echo_server istreams ostreams"; void usage() { fprintf(stderr, "%s\n", usage_msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, client_sockfd; int len; struct sockaddr_in serv_addr, client_addr; int port = ECHO_PORT; struct sctp_initmsg initmsg; struct sctp_status status; if (argc != 3) usage(); /* create endpoint */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP ); if (sockfd < 0) { perror("socket"); exit(2); } serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(port); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) == -1) { perror("sctp bind"); exit(2); } memset(&initmsg, 0, sizeof(initmsg)); initmsg.sinit_max_instreams = atoi(argv[1]); initmsg.sinit_num_ostreams = atoi(argv[2]); printf("Asking for: input streams: %d, output streams: %d\n", initmsg.sinit_max_instreams, initmsg.sinit_num_ostreams); if (setsockopt(sockfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) { perror("set sock opt\n"); } /* specify queue */ listen(sockfd, 5); for (;;) { len = sizeof(client_addr); client_sockfd = accept(sockfd, (struct sockaddr *) &client_addr, &len); if (client_sockfd == -1) { perror(NULL); continue; } memset(&status, 0, sizeof(status)); len = sizeof(status); if (getsockopt(client_sockfd, IPPROTO_SCTP, SCTP_STATUS, &status, &len) == -1) { perror("get sock opt"); } printf("Got: input streams: %d, output streams: %d\n", status.sstat_instrms, status.sstat_outstrms); /* give the client time to do something */ sleep(2); close(client_sockfd); } }
对于我们在本系列第一篇文章中讨论的一对一套接字,在任何时候都只能有一个关联。 对于我们将在下一篇文章中介绍的一对多套接字,在任何一个时间都可以有许多关联处于活动状态——一个对等方可以同时连接到许多其他对等方。 这与 TCP 不同,在 TCP 中,一个套接字上只能存在一个连接,也与 UDP 不同,在 UDP 中,不存在连接,消息只是发送到任意对等方。
当可以有多个关联时,您需要能够区分它们。 这是通过称为关联 ID 的不透明数据类型完成的。 您有时需要使用它,但并非每次都需要。 对于一对一套接字,只有一个关联,因此关联 ID 始终被忽略。 对于一对多套接字,当关联“显而易见”时,关联 ID 再次被忽略。 例如,当您写入对等方并提供对等方的套接字地址时,就会发生这种情况; 只能有一个到对等方的关联(但有许多到许多对等方的关联),因此如果对等方是已知的,则关联是已知的,并且不需要 ID。 但是,当 SCTP 协议栈无法自行确定哪个关联是预期关联时,必须使用关联 ID。 发生这种情况的一个地方是在前面描述的 getsockopt() 调用中,以查找一对多套接字上的关联的流数量。 我会将关于如何查找关联 ID 的讨论推迟到下一篇文章,在下一篇文章中,我将介绍一对多套接字。
有几种方法可以写入流并告知读取属于哪个流。 其中一些方法使用 sctp_sndrcvinfo 类型的结构
struct sctp_sndrcvinfo { uint16_t sinfo_stream; uint16_t sinfo_ssn; uint16_t sinfo_flags; uint32_t sinfo_ppid; uint32_t sinfo_context; uint32_t sinfo_timetolive; uint32_t sinfo_tsn; uint32_t sinfo_cumtsn; sctp_assoc_t sinfo_assoc_id; }
此结构中的大多数字段目前对我们不感兴趣。 有趣的是第一个字段 sinfo_stream。 要写入特定流,请将所有字段清零并设置此字段; 要读取,请再次将所有字段清零,执行读取,然后检查此字段。 (顺便说一句,如果 SCTP 协议栈无法确定哪个关联是预期关联,则必须设置最后一个字段 sinfo_assoc_id。)
写入消息的函数调用是
int sctp_send(int sd, const void *msg, size_t len, const struct sctp_sndrcvinfo *sinfo, int flags);
其中已设置了 sinfo 的字段 sinfo_stream。
相反,读取的调用是
ssize_t sctp_recvmsg(int sd, void *msg, size_t len, struct sockaddr *from, socklen_t *fromlen struct sctp_sndrcvinfo *sinfo int *msg_flags)
流号在 sinfo.sinfo_stream 中可用。
SCTP 协议栈保留有关在对等方之间传递的每条消息的大量信息。 它还保留有关每个关联状态的信息。 为了避免应用程序过载,大多数信息被抑制并且不传递给应用程序。 特别是,默认情况下,sctp_sndrcvinfo 结构未填充,因此读取器无法知道读取发生在哪个流上! 要启用填充,必须首先调用套接字选项,如下所示
struct sctp_event_subscribe events; bzero(&events, sizeof(events)); events.sctp_data_io_event = 1; setsockopt(sockfd, IPPROTO_SCTP, SCTP_EVENTS, &events, sizeof(events));
(有关 SCTP 事件的更多详细信息将在下一篇文章中给出。) 有关客户端和服务器使用特定流进行通信的示例,请参阅列表 4 (streamsend_echo_client.c) 和列表 5 (streamsend_echo_server.c)。 。
列表 4. streamsend_echo_client.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/sctp.h> #define SIZE 1024 char buf[SIZE]; #define ECHO_PORT 2013 char *usage_msg = "usage: streamsend_echo_client ip-addr istreams ostreams stream"; void usage() { fprintf(stderr, "%s\n", usage_msg); exit(1); } int main(int argc, char *argv[]) { int sockfd; int len; struct sockaddr_in serv_addr; struct sockaddr_in *addresses; int addr_size = sizeof(struct sockaddr_in); int addr_count = argc - 1; int port = ECHO_PORT; char *message = "hello\n"; struct sctp_initmsg initmsg; struct sctp_status status; struct sctp_sndrcvinfo sinfo; int ochannel; if (argc != 5) usage(); /* create endpoint */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP ); if (sockfd < 0) { perror(NULL); exit(2); } /* connect to server */ addresses = malloc(addr_size); if (addresses == NULL) { exit(1); } serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(port); memcpy(addresses, &serv_addr, addr_size); memset(&initmsg, 0, sizeof(initmsg)); initmsg.sinit_max_instreams = atoi(argv[2]); initmsg.sinit_num_ostreams = atoi(argv[3]); printf("Asking for: input streams: %d, output streams: %d\n", initmsg.sinit_max_instreams, initmsg.sinit_num_ostreams); if (setsockopt(sockfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) { perror("set sock opt\n"); } if (sctp_connectx(sockfd, (struct sockaddr *) addresses, 1) < 0) { perror("connectx"); exit(3); } memset(&status, 0, sizeof(status)); len = sizeof(status); status.sstat_assoc_id = 1; if (getsockopt(sockfd, IPPROTO_SCTP, SCTP_STATUS, &status, &len) == -1) { perror("get sock opt\n"); } printf("Got: input streams: %d, output streams: %d\n", status.sstat_instrms, status.sstat_outstrms); /* sanity check channel */ ochannel = atoi(argv[4]); if (ochannel >= status.sstat_outstrms) printf("Writing on illegal channel %d\n", ochannel); /* transfer data */ bzero(&sinfo, sizeof(sinfo)); sinfo.sinfo_stream = ochannel; sctp_send(sockfd, message, strlen(message), &sinfo, 0); sinfo.sinfo_flags = SCTP_EOF; sctp_send(sockfd, NULL, 0, &sinfo, 0); close(sockfd); exit(0); }
列表 5. streamsend_echo_server.c
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netinet/sctp.h> #define SIZE 1024 char buf[SIZE]; #define TIME_PORT 2013 char *usage_msg = "usage: app ip-addr istreams ostreams"; void usage() { fprintf(stderr, "%s\n", usage_msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, client_sockfd; int nread, len; struct sockaddr_in serv_addr, client_addr; time_t t; struct sockaddr_in *addresses; int addr_size = sizeof(struct sockaddr_in); int addr_count = 1; int port = TIME_PORT; int n; struct sctp_initmsg initmsg; struct sctp_status status; sctp_assoc_t associd; struct sctp_sndrcvinfo sinfo; struct sctp_event_subscribe events; int flags; if (argc != 4) usage(); /* create endpoint */ sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_SCTP ); if (sockfd < 0) { perror(NULL); exit(2); } serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr(argv[1]); serv_addr.sin_port = htons(port); if (sctp_bindx(sockfd, (struct sockaddr *) &serv_addr, addr_count, SCTP_BINDX_ADD_ADDR) == -1) { perror("sctp bindx"); exit(2); } memset(&initmsg, 0, sizeof(initmsg)); initmsg.sinit_max_instreams = atoi(argv[2]); initmsg.sinit_num_ostreams = atoi(argv[3]); printf("Asking for: input streams: %d, output streams: %d\n", initmsg.sinit_max_instreams, initmsg.sinit_num_ostreams); if (setsockopt(sockfd, IPPROTO_SCTP, SCTP_INITMSG, &initmsg, sizeof(initmsg))) { perror("set sock opt\n"); } /* specify queue */ listen(sockfd, 5); for (;;) { len = sizeof(client_addr); client_sockfd = accept(sockfd, (struct sockaddr *) &client_addr, &len); if (client_sockfd == -1) { perror(NULL); continue; } memset(&status, 0, sizeof(status)); len = sizeof(status); status.sstat_assoc_id = 0; if (getsockopt(client_sockfd, IPPROTO_SCTP, SCTP_STATUS, &status, &len) == -1) { perror("get sock opt\n"); } printf("Got: input streams: %d, output streams: %d\n", status.sstat_instrms, status.sstat_outstrms); for(;;) { /* transfer data */ len = sizeof(client_addr); bzero(&sinfo, sizeof(sinfo)); nread = sctp_recvmsg(client_sockfd, buf, SIZE, (struct sockaddr *) &client_addr, &len, &sinfo, &flags); if (nread == 0) { break; } printf("read %d bytes on channel %hd\n", nread, sinfo.sinfo_stream); printf("sinfo flags: %d\n", sinfo.sinfo_flags); write(1, buf, nread); } close(client_sockfd); } }
没有办法指定从哪个流读取。 这是故意的; 目的是当任何流上有数据准备就绪时,您就读取它。 否则,数据可能会在一个没有读取器的流上被阻塞,最终可能会填满系统缓冲区。 因此,您不能限制读取到任何特定流。 但是,一旦完成读取,您就可以使用上述机制告知它来自哪个流。
通常,读取和处理消息的服务器将具有如下所示的(伪代码)
while (true) { nread = sctp_recvmsg(..., msg, ..., &sinfo, ...) if (nread <= 0) break; assoc_id = sinfo.sinfo_assoc_id; stream = sinfo.sinfo_stream; handle_mesg(assoc_id, stream, msg, nread); }
这是一个单线程读取循环。 它确保读取信息,无论它是在哪个关联或流上发送的。 应用程序函数 handle_mesg() 当然可以将消息分派到不同的线程,如果它想要这样做的话。 另一方面,写入可以从多个线程发送,如果需要的话。
Jan Newmarch 是莫纳什大学的荣誉高级研究员。 他自内核 0.98 以来一直使用 Linux。 他撰写了四本书和许多论文,并讲授了许多技术主题的课程,最近六年专注于网络编程。 他的网站是 jan.newmarch.name。