查看源代码 gen_sctp (内核 v10.2)
SCTP 套接字接口。
此模块提供通过 SCTP 套接字进行通信的功能。该实现假设操作系统内核通过用户级 套接字 API 扩展支持 SCTP (RFC 2960)。
在开发过程中,此实现在以下系统上进行了测试:
- Linux Fedora Core 5.0(需要内核 2.6.15-2054 或更高版本)
- Solaris 10, 11
在 OTP 适配期间,在以下系统上进行了测试:
- SUSE Linux Enterprise Server 10 (x86_64) 内核 2.6.16.27-0.6-smp,使用 lksctp-tools-1.0.6
- Solaris 10 上的简要测试
- SUSE Linux Enterprise Server 10 Service Pack 1 (x86_64) 内核 2.6.16.54-0.2.3-smp,使用 lksctp-tools-1.0.7
- FreeBSD 8.2
此模块是为一对多样式套接字(类型 seqpacket
)编写的。通过添加 peeloff/2
,引入了一对一样式套接字(类型 stream
)。
可以使用以下命令找到此模块的记录定义:
-include_lib("kernel/include/inet_sctp.hrl").
这些记录定义使用“新”的拼写“adaptation”,而不是已弃用的“adaption”,无论底层 C API 使用哪个拼写。
SCTP 套接字选项
允许的 SCTP 套接字选项集在构造上与 TCP、UDP 和通用 inet
选项集正交。此处仅列出 SCTP 套接字允许的选项。
选项可以在调用 open/1,2
时在套接字上设置,并在调用 connect/4,5
或通过调用 inet:setopts/2
时更改。可以使用 inet:getopts/2
检索它们。
{mode, list|binary} | list | binary
- 确定从recv/1,2
返回的数据类型或主动模式数据消息中的数据类型。如果
false
(被动模式,默认值),则调用者必须执行显式的recv
调用才能从套接字检索可用数据。如果
true|once|N
(主动模式),则接收到的数据或事件将发送到所有者进程。有关消息格式,请参阅open/0..2
。如果
true
(完全主动模式),则没有流量控制。注意
请注意,这可能会导致消息队列溢出,例如导致虚拟机内存耗尽并崩溃。
如果
once
,则只有一个消息会自动放入消息队列,并且模式重置为被动。这提供了流量控制,并使接收器可以侦听与其他进程间消息交错的传入 SCTP 数据。如果
active
指定为 -32768 到 32767(包括)范围内的整数N
,则该数字将添加到套接字的数据消息计数器。如果加法的结果为负数,则计数器设置为0
。一旦计数器达到0
,无论是通过传递消息还是通过使用inet:setopts/2
显式设置,套接字模式都会重置为被动({active, false}
)。当{active, N}
模式下的套接字转换为被动模式时,消息{sctp_passive, Socket}
将发送到控制进程,以通知它,如果要从套接字接收更多数据消息,则必须调用inet:setopts/2
将套接字设置回主动模式。
{tos, integer()}
- 将发送的 IP 数据报的服务类型字段设置为指定值。这有效地确定了出站数据包的优先级策略。可接受的值取决于系统。{priority, integer()}
- 上面tos
的协议无关的等效项。设置优先级意味着也设置tos
。{dontroute, true|false}
- 默认为false
。如果true
,则内核不会通过任何网关发送数据包,而只会将数据包发送到直接连接的主机。{reuseaddr, true|false}
- 默认为false
。如果true
,则可以立即重用套接字的本地绑定地址{IP,Port}
。不会执行CLOSE_WAIT
状态的等待(某些类型的服务器可能需要)。{sndbuf, integer()}
- 此套接字的操作系统内核发送缓冲区的大小(以字节为单位)。对于大于val(sndbuf)
的数据报,将发生发送错误。设置此选项还会调整驱动程序缓冲区的大小(请参见上面的buffer
)。{recbuf, integer()}
- 此套接字的操作系统内核接收缓冲区的大小(以字节为单位)。对于大于val(recbuf)
的数据报,将发生发送错误。设置此选项还会调整驱动程序缓冲区的大小(请参见上面的buffer
)。
{non_block_send, boolean()}
- 如果此选项已设置为true
,则原本会阻塞(挂起)的发送调用将立即返回,并显示例如{error, eagain}
。默认为false
。{sctp_module, module()}
- 覆盖使用的回调模块。IPv4 的默认为inet_sctp
,IPv6 的默认为inet6_sctp
。{sctp_rtoinfo, #sctp_rtoinfo{}}
#sctp_rtoinfo{ assoc_id = assoc_id(), initial = integer(), max = integer(), min = integer() }
确定由
assoc_id
指定的关联的重传超时参数(以毫秒为单位)。assoc_id = 0
(默认)指示整个端点。有关字段值的确切语义,请参见 RFC 2960 和 SCTP 套接字 API 扩展。{sctp_associnfo, #sctp_assocparams{}}
#sctp_assocparams{ assoc_id = assoc_id(), asocmaxrxt = integer(), number_peer_destinations = integer(), peer_rwnd = integer(), local_rwnd = integer(), cookie_life = integer() }
确定由
assoc_id
指定的关联的关联参数。assoc_id = 0
(默认)指示整个端点。有关其语义的讨论,请参见 SCTP 套接字 API 扩展。很少使用。{sctp_initmsg, #sctp_initmsg{}}
#sctp_initmsg{ num_ostreams = integer(), max_instreams = integer(), max_attempts = integer(), max_init_timeo = integer() }
确定此套接字在与其对等方建立关联时尝试协商的默认参数。必须在
open/*
之后但在第一次connect/*
之前设置。#sctp_initmsg{}
也可以用作对新对等方的第一次send/*
调用(创建新关联时)的辅助数据。num_ostreams
- 出站流的数量max_instreams
- 入站流的最大数量max_attempts
- 建立关联时的最大重传次数max_init_timeo
- 建立关联的超时时间(以毫秒为单位)
{sctp_autoclose, integer() >= 0}
- 确定空闲关联自动关闭的时间(以秒为单位)。0
表示关联永远不会自动关闭。{sctp_nodelay, true|false}
- 启用|禁用 Nagle 算法,用于将小数据包合并为较大的数据包。这会以延迟为代价来提高吞吐量。{sctp_disable_fragments, true|false}
- 如果true
,则在尝试发送大于当前 PMTU 大小的消息(这将需要分片/重组)时会引发错误。请注意,消息分片不会影响其传递的逻辑原子性;提供此选项仅出于性能原因。{sctp_i_want_mapped_v4_addr, true|false}
- 启用|禁用将 IPv4 地址自动映射到 IPv6 地址(如果套接字地址族为AF_INET6
)。{sctp_maxseg, integer()}
- 确定使用消息分片时的最大块大小。如果0
,则块大小仅受路径 MTU 的限制。{sctp_primary_addr, #sctp_prim{}}
#sctp_prim{ assoc_id = assoc_id(), addr = {IP, Port} } IP = ip_address() Port = port_number()
对于由
assoc_id
指定的关联,{IP,Port}
必须是对等地址之一。此选项确定指定的地址被本地 SCTP 堆栈视为对等方的主地址。{sctp_set_peer_primary_addr, #sctp_setpeerprim{}}
#sctp_setpeerprim{ assoc_id = assoc_id(), addr = {IP, Port} } IP = ip_address() Port = port_number()
设置后,通知对等方使用
{IP, Port}
作为由assoc_id
指定的关联的本地端点的主地址。{sctp_adaptation_layer, #sctp_setadaptation{}}
#sctp_setadaptation{ adaptation_ind = integer() }
设置后,请求本地端点使用由
adaptation_ind
指定的值作为建立新关联的适应指示参数。有关详细信息,请参见 RFC 2960 和 SCTP 套接字 API 扩展。{sctp_peer_addr_params, #sctp_paddrparams{}}
#sctp_paddrparams{ assoc_id = assoc_id(), address = {IP, Port}, hbinterval = integer(), pathmaxrxt = integer(), pathmtu = integer(), sackdelay = integer(), flags = list() } IP = ip_address() Port = port_number()
确定由
assoc_id
和对等地址address
指定的关联的各种每个地址参数(SCTP 协议支持多宿主,因此多个地址可以对应于指定的关联)。hbinterval
- 心跳间隔,以毫秒为单位pathmaxrxt
- 在此地址被认为无法访问(并选择备用地址)之前的最大重传次数pathmtu
- 固定路径 MTU,如果禁用自动发现(请参见下面的flags
)sackdelay
- SAC 消息的延迟(以毫秒为单位)(如果启用延迟,请参见下面的flags
)flags
- 提供以下标志hb_enable
- 启用心跳hb_disable
- 禁用心跳hb_demand
- 立即启动心跳pmtud_enable
- 启用自动路径 MTU 发现pmtud_disable
- 禁用自动路径 MTU 发现sackdelay_enable
- 启用 SAC 延迟sackdelay_disable
- 禁用 SAC 延迟
{sctp_default_send_param, #sctp_sndrcvinfo{}}
#sctp_sndrcvinfo{ stream = integer(), ssn = integer(), flags = list(), ppid = integer(), context = integer(), timetolive = integer(), tsn = integer(), cumtsn = integer(), assoc_id = assoc_id() }
#sctp_sndrcvinfo{}
既用于此套接字选项,也用作发送或接收 SCTP 消息时的辅助数据。当设置为选项时,它为后续在由assoc_id
指定的关联上进行的send
调用提供默认值。assoc_id = 0
(默认)表示整个端点。以下字段通常必须由发送方指定
sinfo_stream
- 在关联中用于发送消息的流编号(从 0 开始);sinfo_flags
- 可识别以下标志unordered
- 消息将以无序方式发送addr_over
-send
中指定的地址将覆盖主对等地址abort
- 中止当前关联,而不刷新任何未发送的数据eof
- 正常关闭当前关联,并刷新未发送的数据
其他字段很少使用。有关完整信息,请参阅 RFC 2960 和 SCTP 的套接字 API 扩展。
{sctp_events, #sctp_event_subscribe{}}
#sctp_event_subscribe{ data_io_event = true | false, association_event = true | false, address_event = true | false, send_failure_event = true | false, peer_error_event = true | false, shutdown_event = true | false, partial_delivery_event = true | false, adaptation_layer_event = true | false }
此选项确定要接收的 SCTP 事件(通过
recv/*
)以及数据。唯一的例外是data_io_event
,它启用或禁用接收#sctp_sndrcvinfo{}
辅助数据,而不是事件。默认情况下,除了adaptation_layer_event
之外的所有标志都启用,尽管驱动程序本身使用sctp_data_io_event
和association_event
,并且不将其导出到用户级别。{sctp_delayed_ack_time, #sctp_assoc_value{}}
#sctp_assoc_value{ assoc_id = assoc_id(), assoc_value = integer() }
很少使用。确定指定关联或整个端点的 ACK 时间(由
assoc_value
指定,以毫秒为单位),如果assoc_value = 0
(默认)。{sctp_status, #sctp_status{}}
#sctp_status{ assoc_id = assoc_id(), state = atom(), rwnd = integer(), unackdata = integer(), penddata = integer(), instrms = integer(), outstrms = integer(), fragmentation_point = integer(), primary = #sctp_paddrinfo{} }
此选项为只读。它确定由
assoc_id
指定的 SCTP 关联的状态。以下是state
的可能值(状态名称大多是不言自明的)sctp_state_empty
- 默认。表示没有其他状态处于活动状态。sctp_state_closed
sctp_state_cookie_wait
sctp_state_cookie_echoed
sctp_state_established
sctp_state_shutdown_pending
sctp_state_shutdown_sent
sctp_state_shutdown_received
sctp_state_shutdown_ack_sent
其他字段的语义
sstat_rwnd
- 关联的当前接收窗口大小sstat_unackdata
- 未确认的数据块数sstat_penddata
- 待接收的数据块数sstat_instrms
- 入站流的数量sstat_outstrms
- 出站流的数量sstat_fragmentation_point
- 发生 SCTP 分片的消息大小sstat_primary
- 有关当前主对等地址的信息(有关#sctp_paddrinfo{}
的格式,请参见下文)
{sctp_get_peer_addr_info, #sctp_paddrinfo{}}
#sctp_paddrinfo{ assoc_id = assoc_id(), address = {IP, Port}, state = inactive | active | unconfirmed, cwnd = integer(), srtt = integer(), rto = integer(), mtu = integer() } IP = ip_address() Port = port_number()
此选项为只读。它确定由
assoc_id
指定的关联中由address
指定的对等地址的特定参数。字段address
必须由调用者设置;所有其他字段在返回时填充。如果assoc_id = 0
(默认),则address
将自动转换为相应的关联 ID。此选项很少使用。有关所有字段的语义,请参阅 RFC 2960 和 SCTP 的套接字 API 扩展。
SCTP 示例
Erlang SCTP 服务器的示例,该服务器接收 SCTP 消息并在标准输出上打印它们
-module(sctp_server).
-export([server/0,server/1,server/2]).
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
server() ->
server(any, 2006).
server([Host,Port]) when is_list(Host), is_list(Port) ->
{ok, #hostent{h_addr_list = [IP|_]}} = inet:gethostbyname(Host),
io:format("~w -> ~w~n", [Host, IP]),
server([IP, list_to_integer(Port)]).
server(IP, Port) when is_tuple(IP) orelse IP == any orelse IP == loopback,
is_integer(Port) ->
{ok,S} = gen_sctp:open(Port, [{recbuf,65536}, {ip,IP}]),
io:format("Listening on ~w:~w. ~w~n", [IP,Port,S]),
ok = gen_sctp:listen(S, true),
server_loop(S).
server_loop(S) ->
case gen_sctp:recv(S) of
{error, Error} ->
io:format("SCTP RECV ERROR: ~p~n", [Error]);
Data ->
io:format("Received: ~p~n", [Data])
end,
server_loop(S).
Erlang SCTP 客户端与上述服务器交互的示例。请注意,在此示例中,客户端创建与服务器的关联,其中包含 5 个出站流。因此,在流 0 上发送 "Test 0"
成功,但在流 5 上发送 "Test 5"
失败。然后,客户端 abort
该关联,这会导致在服务器端接收到相应的事件。
-module(sctp_client).
-export([client/0, client/1, client/2]).
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
client() ->
client([localhost]).
client([Host]) ->
client(Host, 2006);
client([Host, Port]) when is_list(Host), is_list(Port) ->
client(Host,list_to_integer(Port)),
init:stop().
client(Host, Port) when is_integer(Port) ->
{ok,S} = gen_sctp:open(),
{ok,Assoc} = gen_sctp:connect
(S, Host, Port, [{sctp_initmsg,#sctp_initmsg{num_ostreams=5}}]),
io:format("Connection Successful, Assoc=~p~n", [Assoc]),
io:write(gen_sctp:send(S, Assoc, 0, <<"Test 0">>)),
io:nl(),
timer:sleep(10000),
io:write(gen_sctp:send(S, Assoc, 5, <<"Test 5">>)),
io:nl(),
timer:sleep(10000),
io:write(gen_sctp:abort(S, Assoc)),
io:nl(),
timer:sleep(1000),
gen_sctp:close(S).
一个简单的 Erlang SCTP 客户端,它使用 connect_init
API
-module(ex3).
-export([client/4]).
-include_lib("kernel/include/inet.hrl").
-include_lib("kernel/include/inet_sctp.hrl").
client(Peer1, Port1, Peer2, Port2)
when is_tuple(Peer1), is_integer(Port1), is_tuple(Peer2), is_integer(Port2) ->
{ok,S} = gen_sctp:open(),
SctpInitMsgOpt = {sctp_initmsg,#sctp_initmsg{num_ostreams=5}},
ActiveOpt = {active, true},
Opts = [SctpInitMsgOpt, ActiveOpt],
ok = gen_sctp:connect(S, Peer1, Port1, Opts),
ok = gen_sctp:connect(S, Peer2, Port2, Opts),
io:format("Connections initiated~n", []),
client_loop(S, Peer1, Port1, undefined, Peer2, Port2, undefined).
client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2, AssocId2) ->
receive
{sctp, S, Peer1, Port1, {_Anc, SAC}}
when is_record(SAC, sctp_assoc_change), AssocId1 == undefined ->
io:format("Association 1 connect result: ~p. AssocId: ~p~n",
[SAC#sctp_assoc_change.state,
SAC#sctp_assoc_change.assoc_id]),
client_loop(S, Peer1, Port1, SAC#sctp_assoc_change.assoc_id,
Peer2, Port2, AssocId2);
{sctp, S, Peer2, Port2, {_Anc, SAC}}
when is_record(SAC, sctp_assoc_change), AssocId2 == undefined ->
io:format("Association 2 connect result: ~p. AssocId: ~p~n",
[SAC#sctp_assoc_change.state, SAC#sctp_assoc_change.assoc_id]),
client_loop(S, Peer1, Port1, AssocId1, Peer2, Port2,
SAC#sctp_assoc_change.assoc_id);
{sctp, S, Peer1, Port1, Data} ->
io:format("Association 1: received ~p~n", [Data]),
client_loop(S, Peer1, Port1, AssocId1,
Peer2, Port2, AssocId2);
{sctp, S, Peer2, Port2, Data} ->
io:format("Association 2: received ~p~n", [Data]),
client_loop(S, Peer1, Port1, AssocId1,
Peer2, Port2, AssocId2);
Other ->
io:format("Other ~p~n", [Other]),
client_loop(S, Peer1, Port1, AssocId1,
Peer2, Port2, AssocId2)
after 5000 ->
ok
end.
另请参阅
gen_tcp
、gen_udp
、inet
、RFC 2960(流控制传输协议)、SCTP 的套接字 API 扩展
摘要
类型:导出的数据类型
关联 ID。
SCTP 套接字选项名称和值,用于设置。
SCTP 套接字选项名称,用于获取。
SCTP 套接字选项名称和值,您获取的内容。
从 open/*
返回的套接字标识符。
函数
中止关联。
关闭 SCTP 套接字。
与对等方建立关联。
开始与对等方建立关联。
开始与对等方建立关联。
开始与对等方建立关联(多个地址)。
开始与对等方建立关联(多个地址)。
更改套接字的控制进程(所有者)。
正常终止关联。
将错误号转换为字符串或原子。
使 SCTP 套接字侦听传入的关联。
创建 SCTP 套接字。
将关联分支到类型为 stream
的新套接字中。
接收 Data
消息。
发送一个功能齐全的 Data
消息。
类型:导出的数据类型
-type assoc_id() :: term().
关联 ID。
在例如 #sctp_paddr_change{}
中返回的不透明项,它标识 SCTP 套接字的关联。该项是不透明的,除了特殊值 0
,它具有诸如“整个端点”或“所有未来关联”之类的含义。
-type option() :: elementary_option() | record_option().
SCTP 套接字选项名称和值,用于设置。
-type option_name() :: elementary_option_name() | record_option() | ro_option().
SCTP 套接字选项名称,用于获取。
-type option_value() :: elementary_option() | record_option() | ro_option().
SCTP 套接字选项名称和值,您获取的内容。
-type sctp_socket() :: port().
从 open/*
返回的套接字标识符。
类型:内部数据类型
-type elementary_option() :: {active, true | false | once | -32768..32767} | {buffer, non_neg_integer()} | {non_block_send, boolean()} | {debug, boolean()} | {dontroute, boolean()} | {exclusiveaddruse, boolean()} | {high_msgq_watermark, pos_integer()} | {linger, {boolean(), non_neg_integer()}} | {low_msgq_watermark, pos_integer()} | {mode, list | binary} | list | binary | {priority, non_neg_integer()} | {recbuf, non_neg_integer()} | {reuseaddr, boolean()} | {reuseport, boolean()} | {reuseport_lb, boolean()} | {ipv6_v6only, boolean()} | {sndbuf, non_neg_integer()} | {sctp_autoclose, non_neg_integer()} | {sctp_disable_fragments, boolean()} | {sctp_i_want_mapped_v4_addr, boolean()} | {sctp_maxseg, non_neg_integer()} | {sctp_nodelay, boolean()} | {tos, non_neg_integer()} | {tclass, non_neg_integer()} | {ttl, non_neg_integer()} | {recvtos, boolean()} | {recvtclass, boolean()} | {recvttl, boolean()}.
-type elementary_option_name() ::
active | buffer | non_block_send | debug | dontroute | exclusiveaddruse |
high_msgq_watermark | linger | low_msgq_watermark | mode | priority | recbuf | reuseaddr |
reuseport | reuseport_lb | ipv6_v6only | sctp_autoclose | sctp_disable_fragments |
sctp_i_want_mapped_v4_addr | sctp_maxseg | sctp_nodelay | sndbuf | tos | tclass | ttl |
recvtos | recvtclass | recvttl.
-type record_option() :: {sctp_adaptation_layer, #sctp_setadaptation{adaptation_ind :: term()}} | {sctp_associnfo, #sctp_assocparams{assoc_id :: term(), asocmaxrxt :: term(), number_peer_destinations :: term(), peer_rwnd :: term(), local_rwnd :: term(), cookie_life :: term()}} | {sctp_default_send_param, #sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()}} | {sctp_delayed_ack_time, #sctp_assoc_value{assoc_id :: term(), assoc_value :: term()}} | {sctp_events, #sctp_event_subscribe{data_io_event :: term(), association_event :: term(), address_event :: term(), send_failure_event :: term(), peer_error_event :: term(), shutdown_event :: term(), partial_delivery_event :: term(), adaptation_layer_event :: term(), authentication_event :: term()}} | {sctp_initmsg, #sctp_initmsg{num_ostreams :: term(), max_instreams :: term(), max_attempts :: term(), max_init_timeo :: term()}} | {sctp_peer_addr_params, #sctp_paddrparams{assoc_id :: term(), address :: term(), hbinterval :: term(), pathmaxrxt :: term(), pathmtu :: term(), sackdelay :: term(), flags :: term()}} | {sctp_primary_addr, #sctp_prim{assoc_id :: term(), addr :: term()}} | {sctp_rtoinfo, #sctp_rtoinfo{assoc_id :: term(), initial :: term(), max :: term(), min :: term()}} | {sctp_set_peer_primary_addr, #sctp_setpeerprim{assoc_id :: term(), addr :: term()}}.
-type ro_option() :: {sctp_get_peer_addr_info, #sctp_paddrinfo{assoc_id :: term(), address :: term(), state :: term(), cwnd :: term(), srtt :: term(), rto :: term(), mtu :: term()}} | {sctp_status, #sctp_status{assoc_id :: term(), state :: term(), rwnd :: term(), unackdata :: term(), penddata :: term(), instrms :: term(), outstrms :: term(), fragmentation_point :: term(), primary :: term()}}.
函数
-spec abort(Socket, Assoc) -> ok | {error, inet:posix()} when Socket :: sctp_socket(), Assoc :: #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}.
中止关联。
异常终止由 Assoc
指定的关联,而不刷新未发送的数据。套接字本身保持打开状态。在此套接字上打开的其他关联仍然有效,并且该套接字可以在新的关联中使用。
-spec close(Socket) -> ok | {error, inet:posix()} when Socket :: sctp_socket().
关闭 SCTP 套接字。
关闭套接字及其上的所有关联。未发送的数据会像 eof/2
一样刷新。close/1
调用是阻塞的,具体取决于 linger
套接字[选项]的值。如果它是 false
或延迟超时时间到期,则调用返回,并且未发送的数据在后台刷新。
-spec connect(Socket, SockAddr, Opts) -> {ok, #sctp_assoc_change{state :: comm_up, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, #sctp_assoc_change{state :: cant_assoc, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, inet:posix()} when Socket :: sctp_socket(), SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(), Opts :: [Opt :: option()].
-spec connect(Socket, SockAddr, Opts, Timeout) -> {ok, #sctp_assoc_change{state :: comm_up, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, #sctp_assoc_change{state :: cant_assoc, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, inet:posix()} when Socket :: sctp_socket(), SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(), Opts :: [Opt :: option()], Timeout :: timeout(); (Socket, Addr, Port, Opts) -> {ok, #sctp_assoc_change{state :: comm_up, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, #sctp_assoc_change{state :: cant_assoc, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, inet:posix()} when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), Opts :: [Opt :: option()].
与对等方建立关联。
使用参数 Addr
和 Port
,等效于 connect(Socket, Addr, Port, Opts, infinity)
。
使用参数 SockAddr
和 Opts
(自 OTP 24.3 起),等效于 connect(Socket, Addr, Port, Opts, Timeout)
,其中 Addr
和 Port
从 SockAddr
中提取。
-spec connect(Socket, Addr, Port, Opts, Timeout) -> {ok, #sctp_assoc_change{state :: comm_up, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, #sctp_assoc_change{state :: cant_assoc, error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}} | {error, inet:posix()} when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), Opts :: [Opt :: option()], Timeout :: timeout().
与对等方建立关联。
为套接字 Socket
建立与 Addr
和 Port
指定的对等方(SCTP 服务器套接字)的新关联。Timeout
以毫秒为单位表示。一个套接字可以与多个对等方关联。该套接字必须是 seqpacket
类型。
警告
使用小于操作系统建立关联所需的最大时间(如果使用 RFC 4960 中的默认值,大约为 4.5 分钟)的
Timeout
值,可能会导致不一致或不正确的返回值。当关联共享相同的Socket
(即源地址和端口)时,这一点尤其重要,因为控制进程会阻塞,直到connect/*
返回。connect_init/*
提供了一种没有此限制的替代方案。
#sctp_assoc_change{}
connect/*
的结果是一个 #sctp_assoc_change{}
事件,其中特别包含新的 关联 ID:l
#sctp_assoc_change{
state = atom(),
error = integer(),
outbound_streams = integer(),
inbound_streams = integer(),
assoc_id = assoc_id()
}
可以通过为 connect
提供一个 sctp_initmsg
选项来设置关联的传出和传入流的数量,如下所示:
connect(Socket, Ip, Port>,
[{sctp_initmsg,#sctp_initmsg{num_ostreams=OutStreams,
max_instreams=MaxInStreams}}])
在尝试关联之前,会在套接字上设置所有选项 Opt
。如果选项记录具有未定义的字段值,则首先从套接字读取这些值的选项记录。实际上,Opt
选项记录只需要定义在连接之前要更改的字段值即可。
返回的 outbound_streams
和 inbound_streams
是套接字上的流数量。如果对等方要求较低的值,则这些值可能与请求的值(分别为 OutStreams
和 MaxInStreams
)不同。
state
可以具有以下值:
comm_up
- 关联已成功建立。这表示connect
已成功完成。cant_assoc
- 无法建立关联(connect/*
失败)。
其他状态通常不会出现在 connect/*
的输出中。相反,它们可能会出现在 #sctp_assoc_change{}
事件中,这些事件不是从 recv/*
调用或套接字消息接收到的数据。所有这些都表示由于各种错误情况而丢失了关联,并且为了完整性在此处列出:
comm_lost
restart
shutdown_comp
字段 error
可以提供更详细的诊断信息。可以使用 error_string/1
将其值转换为字符串。
-spec connect_init(Socket, SockAddr, Opts) -> ok | {error, inet:posix()} when Socket :: sctp_socket(), SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(), Opts :: [option()].
-spec connect_init(Socket, SockAddr, Opts, Timeout) -> ok | {error, inet:posix()} when Socket :: sctp_socket(), SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(), Opts :: [option()], Timeout :: timeout(); (Socket, Addr, Port, Opts) -> ok | {error, inet:posix()} when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), Opts :: [option()].
开始与对等方建立关联。
使用参数 Addr
和 Port
,等效于 connect_init(Socket, Addr, Port, Opts, infinity)
。
使用参数 SockAddr
和 Opts
(自 OTP 24.3 起),等效于 connect_init(Socket, Addr, Port, Opts, Timeout)
,其中 Addr
和 Port
是从 SockAddr
中提取的。
-spec connect_init(Socket, Addr, Port, Opts, Timeout) -> ok | {error, inet:posix()} when Socket :: sctp_socket(), Addr :: inet:ip_address() | inet:hostname(), Port :: inet:port_number(), Opts :: [option()], Timeout :: timeout().
开始与对等方建立关联。
为套接字 Socket
发起一个新的关联,对等方(SCTP 服务器套接字)由 Addr
和 Port
指定。
此 API 和 connect/*
的根本区别在于返回值是底层操作系统 connect(2)
系统调用的返回值。如果返回 ok
,则表示操作已成功发起,并且关联建立的最终结果将作为 #sctp_assoc_change{}
事件发送到套接字所有者(控制进程)。套接字所有者必须准备好接收此事件,recv/*
调用必须轮询,具体取决于 active 选项的值。
这些参数与 connect/*
的描述相同,除了 Timeout
值,因为对于此函数,超时仅适用于 Addr
为 inet:hostname/0
时的名称解析。
-spec connectx_init(Socket, SockAddrs, Opts) -> {ok, assoc_id()} | {error, inet:posix()} when Socket :: sctp_socket(), SockAddrs :: [{inet:ip_address(), inet:port_number()} | inet:family_address() | socket:sockaddr_in() | socket:sockaddr_in6()], Opts :: [option()].
开始与对等方建立关联(多个地址)。
与 connectx_init/5
类似,但使用套接字地址,并且没有 Timeout
。由于地址不需要查找,并且连接是非阻塞的,因此此调用会立即返回。
每个套接字地址 port
的值必须相同或为零。至少一个套接字地址必须具有非零的 port
-spec connectx_init(Socket, Addrs, Port, Opts) -> {ok, assoc_id()} | {error, inet:posix()} when Socket :: sctp_socket(), Addrs :: [inet:ip_address() | inet:hostname()], Port :: inet:port_number() | atom(), Opts :: [option()].
-spec connectx_init(Socket, Addrs, Port, Opts, Timeout) -> {ok, assoc_id()} | {error, inet:posix()} when Socket :: sctp_socket(), Addrs :: [inet:ip_address() | inet:hostname()], Port :: inet:port_number() | atom(), Opts :: [option()], Timeout :: timeout().
开始与对等方建立关联(多个地址)。
为套接字 Socket
发起一个新的关联,对等方(SCTP 服务器套接字)由 Addrs
和 Port
指定。
此 API 与 connect_init/*
类似,只是使用底层操作系统 sctp_connectx(3)
系统调用,该调用接受多个目标地址。
如果成功,将返回关联 ID,该 ID 将在后续的 #sctp_assoc_change{}
事件中收到。
这些参数如 connect_init/5
中所述。
注意
此 API 允许操作系统在建立关联时使用所有 Addrs,但不保证它会这样做。因此,如果连接失败,用户可能希望为后续调用轮换地址的顺序。
-spec controlling_process(Socket, Pid) -> ok | {error, Reason} when Socket :: sctp_socket(), Pid :: pid(), Reason :: closed | not_owner | badarg | inet:posix().
更改套接字的控制进程(所有者)。
为 Socket
分配一个新的控制进程 Pid
。请参阅 gen_udp:controlling_process/2
。
-spec eof(Socket, Assoc) -> ok | {error, Reason} when Socket :: sctp_socket(), Assoc :: #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()}, Reason :: term().
正常终止关联。
正常终止由 Assoc
指定的关联,刷新所有未发送的数据。套接字本身保持打开状态。在此套接字上打开的其他关联仍然有效。套接字可以在新的关联中使用。
将错误号转换为字符串或原子。
将 SCTP 错误号(例如,从 #sctp_remote_error{}
或 #sctp_send_failed{}
)转换为解释性字符串,或转换为原子 ok
(表示没有错误)或 unknown_error
(表示无法识别的整数)。
-spec listen(Socket, IsServer) -> ok | {error, Reason} when Socket :: sctp_socket(), IsServer :: boolean(), Reason :: term(); (Socket, Backlog) -> ok | {error, Reason} when Socket :: sctp_socket(), Backlog :: integer(), Reason :: term().
使 SCTP 套接字侦听传入的关联。
套接字将侦听它绑定的 IP 地址和端口号。
对于 seqpacket
类型(套接字(默认))的套接字,参数 IsServer
必须是一个 boolean/0
。与 stream
套接字相反,没有侦听队列长度。如果 IsServer
为 true
,则套接字接受新的关联,即它变为 SCTP 服务器套接字。
对于 stream
类型套接字,参数 Backlog
设置与 TCP 相同的后备队列长度。
-spec open() -> {ok, Socket} | {error, inet:posix()} when Socket :: sctp_socket().
等效于 open([])
。
-spec open(Port) -> {ok, Socket} | {error, inet:posix()} when Port :: inet:port_number(), Socket :: sctp_socket(); (Opts) -> {ok, Socket} | {error, inet:posix()} when Opts :: [Opt], Opt :: {ifaddr, IP | SockAddr} | {ip, IP} | {port, Port} | inet:address_family() | {type, SockType} | {netns, file:filename_all()} | {bind_to_device, binary()} | option(), IP :: inet:ip_address() | any | loopback, SockAddr :: socket:sockaddr_in() | socket:sockaddr_in6(), Port :: inet:port_number(), SockType :: seqpacket | stream, Socket :: sctp_socket().
创建 SCTP 套接字。
使用参数 Port
,等效于 open([{port, Port}]
。
创建一个 SCTP 套接字,并将其绑定到所有 {ip,IP}
(或同义的 {ifaddr,IP}
)选项指定的本地地址(此功能称为 SCTP 多宿主)。默认的 IP
和 Port
是 any
和 0
,表示绑定到任何空闲端口上的所有本地地址。
也可以使用 {ifaddr, SockAddr}
,在这种情况下,它优先于 ip
和 port
选项。但是,这些选项可以用于更新 ifaddr 的地址和端口(如果它们在选项列表中出现在 ifaddr 之后),尽管不建议这样做。
其他选项
inet6
- 为 IPv6 设置套接字。inet
- 为 IPv4 设置套接字。这是默认设置。
使用一组默认的套接字 选项。特别是,套接字以 二进制和 被动模式打开,SockType 为 seqpacket
,并且具有相当大的 内核和驱动程序 缓冲区。
当套接字处于 被动模式时,可以通过 recv/1,2
调用接收数据。
当套接字处于 主动模式时,接收到的数据会作为消息传递到控制进程。
{sctp, Socket, FromIP, FromPort, {AncData, Data}}
有关消息字段的描述,请参阅 recv/1,2
。
注意
-spec peeloff(Socket, Assoc) -> {ok, NewSocket} | {error, Reason} when Socket :: sctp_socket(), Assoc :: #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()} | assoc_id(), NewSocket :: sctp_socket(), Reason :: term().
将关联分支到类型为 stream
的新套接字中。
套接字 Socket
(必须为 seqpacket
类型;一对多样式)中现有的关联 Assoc
分支到类型为 stream
(一对一样式)的新套接字 NewSocket
中。
现有的关联参数 Assoc
可以是 #sctp_assoc_change{}
记录,例如从 recv/*
、connect/*
返回的,或者从主动模式下的侦听套接字返回的。它也可以只是此类记录中的字段 assoc_id
integer/0
。
-spec recv(Socket) -> {ok, {FromIP, FromPort, AncData, Data}} | {error, Reason} when Socket :: sctp_socket(), FromIP :: inet:ip_address(), FromPort :: inet:port_number(), AncData :: [#sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()} | inet:ancillary_data()], Data :: binary() | string() | #sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()} | #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()} | #sctp_paddr_change{addr :: term(), state :: term(), error :: term(), assoc_id :: term()} | #sctp_adaptation_event{adaptation_ind :: term(), assoc_id :: term()}, Reason :: inet:posix() | #sctp_send_failed{flags :: term(), error :: term(), info :: term(), assoc_id :: term(), data :: term()} | #sctp_paddr_change{addr :: term(), state :: term(), error :: term(), assoc_id :: term()} | #sctp_pdapi_event{indication :: term(), assoc_id :: term()} | #sctp_remote_error{error :: term(), assoc_id :: term(), data :: term()} | #sctp_shutdown_event{assoc_id :: term()}.
-spec recv(Socket, Timeout) -> {ok, {FromIP, FromPort, AncData, Data}} | {error, Reason} when Socket :: sctp_socket(), Timeout :: timeout(), FromIP :: inet:ip_address(), FromPort :: inet:port_number(), AncData :: [#sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()} | inet:ancillary_data()], Data :: binary() | string() | #sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()} | #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()} | #sctp_paddr_change{addr :: term(), state :: term(), error :: term(), assoc_id :: term()} | #sctp_adaptation_event{adaptation_ind :: term(), assoc_id :: term()}, Reason :: inet:posix() | #sctp_send_failed{flags :: term(), error :: term(), info :: term(), assoc_id :: term(), data :: term()} | #sctp_paddr_change{addr :: term(), state :: term(), error :: term(), assoc_id :: term()} | #sctp_pdapi_event{indication :: term(), assoc_id :: term()} | #sctp_remote_error{error :: term(), assoc_id :: term(), data :: term()} | #sctp_shutdown_event{assoc_id :: term()}.
接收 Data
消息。
从套接字的任何关联接收 Data
消息。如果接收超时,则返回 {error,timeout}
。默认超时为 infinity
。FromIP
和 FromPort
指示发送方的地址。
AncData
是与主 Data
一起接收的辅助数据项列表。此列表可以为空,或者如果启用了接收辅助数据(请参阅选项 sctp_events
),则可以包含一个 #sctp_sndrcvinfo{}
记录。默认情况下,它是启用的,因为此类辅助数据提供了一种简便的方法来确定接收消息的关联和流。(另一种方法是使用套接字选项 sctp_get_peer_addr_info
从 FromIP
和 FromPort
获取关联 ID,但这仍然不提供流号)。
AncData
也可能包含来自套接字 选项 recvtos
、recvtclass
或 recvttl
的辅助数据,如果平台支持该套接字。
接收到的 Data
可以根据套接字模式,是 binary/0
或字节(范围为 0
到 255
的整数)的 list/0
,或者它可以是 SCTP 事件。
可能的 SCTP 事件
#sctp_paddr_change{ addr = {ip_address(),port()}, state = atom(), error = integer(), assoc_id = assoc_id() }
表示由
addr
指定的对等方的 IP 地址在assoc_id
关联中的状态更改。state
的可能值(大部分不言自明)包括addr_unreachable
addr_available
addr_removed
addr_added
addr_made_prim
addr_confirmed
如果发生错误(例如,
addr_unreachable
),则字段error
提供更多诊断信息。在这种情况下,事件#sctp_paddr_change{}
会自动转换为recv
返回的error
项。可以使用error_string/1
将error
字段值转换为字符串。#sctp_send_failed{ flags = true | false, error = integer(), info = #sctp_sndrcvinfo{}, assoc_id = assoc_id() data = binary() }
如果发送操作失败,发送者可以接收此事件。
flags
- 一个布尔值,指定数据是否已通过网络传输。error
- 提供扩展的诊断信息,请使用error_string/1
。info
- 失败的send/*
中使用的原始#sctp_sndrcvinfo{}
记录。data
- 尝试发送的整个原始数据块。
在当前 Erlang/SCTP 绑定的实现中,此事件在内部转换为
recv/*
返回的error
项。#sctp_adaptation_event{ adaptation_ind = integer(), assoc_id = assoc_id() }
当对等方发送自适应层指示参数(通过选项
sctp_adaptation_layer
配置)时传递。请注意,在当前 Erlang/SCTP 绑定的实现中,此事件默认情况下处于禁用状态。#sctp_pdapi_event{ indication = sctp_partial_delivery_aborted, assoc_id = assoc_id() }
部分传递失败。在当前 Erlang/SCTP 绑定的实现中,此事件在内部转换为
recv/*
返回的error
项。
-spec send(Socket, SndRcvInfo, Data) -> ok | {error, Reason} when Socket :: sctp_socket(), SndRcvInfo :: #sctp_sndrcvinfo{stream :: term(), ssn :: term(), flags :: term(), ppid :: term(), context :: term(), timetolive :: term(), tsn :: term(), cumtsn :: term(), assoc_id :: term()}, Data :: binary() | iolist(), Reason :: term().
发送一个功能齐全的 Data
消息。
使用 #sctp_sndrcvinfo{}
记录中的所有发送参数发送 Data
消息。这样,用户可以指定 PPID(传递到远程端)和上下文(传递到本地 SCTP 层),例如,可用于错误识别。但是,很少需要这种细粒度的用户控制。函数 send/4
对于大多数应用程序来说足够了。
注意
发送通常是阻塞的,但如果套接字选项
non_block_send
设置为 true,则在函数会阻塞的情况下,该函数将返回例如 {error, eagain}。然后,由用户稍后重试。
-spec send(Socket, Assoc, Stream, Data) -> ok | {error, Reason} when Socket :: sctp_socket(), Assoc :: #sctp_assoc_change{state :: term(), error :: term(), outbound_streams :: term(), inbound_streams :: term(), assoc_id :: term()} | assoc_id(), Stream :: integer(), Data :: binary() | iolist(), Reason :: term().
发送数据消息。
在关联 Assoc
和 Stream
上发送 Data
消息。
Assoc
可以使用来自关联建立的 #sctp_assoc_change{}
记录指定,也可以使用 assoc_id/0
integer/0
字段值指定。
注意
发送通常是阻塞的,但如果套接字选项
non_block_send
设置为 true,则在函数会阻塞的情况下,该函数将返回例如 {error, eagain}。然后,由用户稍后重试。