diff options
author | Simon Cornish <[email protected]> | 2009-12-21 22:48:42 -0800 |
---|---|---|
committer | Björn Gustavsson <[email protected]> | 2010-02-06 09:44:40 +0100 |
commit | 827bb85bafd96ec3037c849ea42878e4f581d22f (patch) | |
tree | 663df2c2751babc414e02cbc83357e535618dcaf /lib/kernel/doc/src | |
parent | c6bf34b7035a6292650babf2eb8af80c19256fab (diff) | |
download | otp-827bb85bafd96ec3037c849ea42878e4f581d22f.tar.gz otp-827bb85bafd96ec3037c849ea42878e4f581d22f.tar.bz2 otp-827bb85bafd96ec3037c849ea42878e4f581d22f.zip |
Implement a non-blocking SCTP connect
This patch adds a new set of functions - gen_sctp:connect_init/* that initiate
an SCTP connection without blocking for the result. The result is delivered
asynchronously as an sctp_assoc_change event.
The new functions have the same API as documented for gen_sctp:connect/* with
the following exceptions:
* Timeout is only used to supervise resolving Addr (the peer address)
* The possible return values are ok | {error, posix()}
The caller application is responsible for receiving the #sctp_assoc_change{}
event and correctly determining the connect it originated from (for example,
by examining the remote host and/or port). The application should have at
least {active, once} or use gen_sctp:recv to retrieve the connect result.
The implementation of gen_sctp:connect suffers from a number of
shortcomings which the user may avoid by using gen_sctp:connect_init and
adding code to receive the connect result.
First, irrespective of the Timeout value given to gen_sctp:connect, the OS
attempts and retries the SCTP INIT according to various kernel parameters. If
the Timeout value is shorter than the entire attempt then the application will
still receive an sctp_assoc_change event after the {error, timeout} is
returned from the initial call. This could be somewhat confusing (either to
the application or the designer!) especially if the status is
comm_up. Subsequent calls to connect before the OS has finished this process
return {error, ealready} which may also be counter-intuitive.
Second, there is a race-condition (documented in comments in inet_sctp.erl)
that can cause the wrong sctp_assoc_change record to be returned to an
application calling gen_sctp:connect. The race seriously affects connection
attempts when using one-to-many sockets.
Diffstat (limited to 'lib/kernel/doc/src')
-rw-r--r-- | lib/kernel/doc/src/gen_sctp.xml | 113 |
1 files changed, 110 insertions, 3 deletions
diff --git a/lib/kernel/doc/src/gen_sctp.xml b/lib/kernel/doc/src/gen_sctp.xml index de41178a17..40efba2bb7 100644 --- a/lib/kernel/doc/src/gen_sctp.xml +++ b/lib/kernel/doc/src/gen_sctp.xml @@ -170,10 +170,19 @@ <p>Establishes a new association for the socket <c>Socket</c>, with the peer (SCTP server socket) given by <c>Addr</c> and <c>Port</c>. The <c>Timeout</c>, - is expressed in milliseconds.</p> - <p>A socket can be associated with multiple peers. - <marker id="record-sctp_assoc_change"></marker> + is expressed in milliseconds. A socket can be associated with multiple peers.</p> + <p><b>WARNING:</b>Using a value of <c>Timeout</c> less than + the maximum time taken by the OS to establish an association (around 4.5 minutes + if the default values from RFC 4960 are used) can result in + inconsistent or incorrect return values. This is especially + relevant for associations sharing the same <c>Socket</c> + (i.e. source address and port) since the controlling process + blocks until <c>connect/*</c> returns. + <seealso marker="#connect_init/4">connect_init/*</seealso> + provides an alternative not subject to this limitation.</p> + + <p><marker id="record-sctp_assoc_change"></marker> The result of <c>connect/*</c> is an <c>#sctp_assoc_change{}</c> event which contains, in particular, the new <seealso marker="#type-assoc_id">Association ID:</seealso></p> @@ -233,6 +242,45 @@ </desc> </func> <func> + <name>connect_init(Socket, Addr, Port, Opts) -> ok | {error, posix()}</name> + <fsummary>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>.</fsummary> + <desc> + <p>Same as <c>connect_init(Socket, Addr, Port, Opts, infinity)</c>.</p> + </desc> + </func> + <func> + <name>connect_init(Socket, Addr, Port, [Opt], Timeout) -> ok | {error, posix()}</name> + <fsummary>Initiate a new association for the socket <c>Socket</c>, with a peer (SCTP server socket)</fsummary> + <type> + <v>Socket = sctp_socket()</v> + <v>Addr = ip_address() | Host</v> + <v>Port = port_number()</v> + <v>Opt = sctp_option()</v> + <v>Timeout = timeout()</v> + <v>Host = atom() | string()</v> + </type> + <desc> + <p>Initiates a new association for the socket <c>Socket</c>, + with the peer (SCTP server socket) given by + <c>Addr</c> and <c>Port</c>.</p> + <p>The fundamental difference between this API + and <c>connect/*</c> is that the return value is that of the + underlying OS connect(2) system call. If <c>ok</c> is returned + then the result of the association establishement is received + by the calling process as + an <seealso marker="#record-sctp_assoc_change"> + #sctp_assoc_change{}</seealso> + event. The calling process must be prepared to receive this, or + poll for it using <c>recv/*</c> depending on the value of the + active option.</p> + <p>The parameters are as described + in <seealso marker="#connect/5">connect/*</seealso>, with the + exception of the <c>Timeout</c> value.</p> + <p>The timer associated with <c>Timeout</c> only supervises + IP resolution of <c>Addr</c></p> + </desc> + </func> + <func> <name>controlling_process(sctp_socket(), pid()) -> ok</name> <fsummary>Assign a new controlling process pid to the socket</fsummary> <desc> @@ -1058,6 +1106,65 @@ gen_sctp:close(S). </pre> <p></p> </item> + <item> + <p>A very simple Erlang SCTP Client which uses the + connect_init API.</p> + <pre> +-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. +</pre> + <p></p> + </item> </list> </section> |