-
Notifications
You must be signed in to change notification settings - Fork 4
Sockerl behaviour
In Erlang, a behavior is a design pattern implemented in a module/library. It provides functionality in a fashion similar to inheritance in object-oriented programming. A number of callback functions must be defined for each behavior to work in a module called callback-module.
When you want to start a server or client, you should specify your module which has some callbacks for Sockerl library.
Sockerl calls functions of your callback module in different states for different things. For example in server or client implementation, when connection process receives a packet from socket, calls YourModule:handle_packet
with some arguments and assumes that your function will return some valid values that it can understands.
sockerl behavior has some optional and some mandatory callbacks which you may want to define in your callback module.
Sockerl Server, client connection pool and stand-alone client can be started using:
%% Server:
sockerl:start_link_server(CallbackModule, InitArgument, Port)
sockerl:start_link_server(Name, Mod, InitArgument, Port)
sockerl:start_link_server(Mod, InitArgument, Port, StartOpts)
sockerl:start_link_server(Name, Mod, InitArgument, Port, StartOpts)
%% Client connection pool:
sockerl:start_link_connector_pool(CallbackModule, InitArgument, Addrs)
sockerl:start_link_connector_pool(Name, CallbackModule, InitArgument, Addrs)
sockerl:start_link_connector_pool(CallbackModule, InitArgument, Addrs, StartOpts)
sockerl:start_link_connector_pool(Name, CallbackModule, InitArgument, Addrs, StartOpts)
%% Stand-alone client
sockerl:start_link_connector(CallbackModule, InitArgument, Host, Port)
sockerl:start_link_connector(Name, CallbackModule, InitArgument, Host, Port)
sockerl:start_link_connector(CallbackModule, InitArgument, Host, Port, Opts)
sockerl:start_link_connector(Name, CallbackModule, InitArgument, Host, Port, Opts)
CallbackModule
is your callback-module that you defined Sockerl callback-functions in it.
One of mandatory arguments is InitArgument
which can be any Erlang term. You will see its usage.
Name
is register name for Server, Client connection pool or stand-alone client connection.
Port
in server implementation is port which server wants to listen on.
Addrs
is a list of {Host, Port}
that client connection pool makes one or some connection to them.
StartOpts
is:
%% ---------------------------------------------------------------------
%% Only for server implementation:
%% Number of connection acceptors.
%% Default is 1.
{acceptor_count, non_neg_integer()}
%% If you don't want to start accepting connections after binding socket,
%% you can set this to 'sleep' and call sockerl:wakeup_acceptors/1 for
%% start accepting.
%% Default is 'accept'
{acceptor_mode, 'accept' | 'sleep'}
%% Acceptor Debug. Can be standard Erlang/OTP 'sys' debug options.
%% Default is [].
{acceptor_debug, [sys:dbg_opt()]}
%% ---------------------------------------------------------------------
%% Only for server and client connection-pool implementation:
%% Plan for connection child-spec.
%% Default is [fun sockerl_utils:default_connection_plan_fun/2].
{connector_childspec_plan, list()}
%% Count of running child-spec plan
%% Default is 1.
{connector_childspec_count, non_neg_integer()}
%% For more info about connector_childspec_count and connector_childspec_plan
%% see https://github.com/Pouriya-Jahanbakhsh/director
%% ---------------------------------------------------------------------
%% Only for client connection-pool implementation:
%% Number of connectors per address:
%% Default is 1.
{connector_per_address, non_neg_integer()}
%% ---------------------------------------------------------------------
%% For server, client connection-pool and stand-alone client connection:
%% Transporter is a module for interacting with socket.
%% Sockerl comes with 'sockerl_tcp_transporter' and
%% 'sockerl_ssl_transporter' but you can write your own module by
%% implementing 'sockerl_transporter' behaviour.
%% Default is 'sockerl_tcp_transporter'.
{transporter, module()}
%% Connector debug. Can be standard Erlang/OTP 'sys' debug options.
%% Default is [].
{connector_debug, [sys:dbg_opt()]}
%% You can specify other options, for example if your transporter module
%% needs its options.
%% Sockerl transporters options:
{socket_options, list()}
%% You can use all 'ssl' and 'gen_tcp' options except {'active', 'once'}
When servers binds its listen socket to given port, Sockerl passes this argument to YourModule:listen_init/2
as first argument. Second argument is listen socket for the server.
listen_init
should return one of:
ok
{ok, State}
{stop, Reason}
ignore
State
is your own data which you want to use, change, increase, decrease, etc in the future.
For execute other callbacks, Sockerl will give them Your State
.
If listen_init/2
returns {'stop', Reason}
, entire server will crash with reason Reason
.
This callback is mandatory only for server implementation.
Every server or client implementation must define this function.
In server side when a server-acceptor accepts new connection, gives connected socket to its pool supervisor for starting:
Pool supervisor gives this socket to new process for controlling its different behaviors, (handling calls, cast, received packets, etc) so every connected socket has its own process.
New process before doing anything calls connector_init/2
once.
It passes returned State
of listen_init/2
as first argument. If listen_init/2
returns just ok
, It passes InitArg
instead. Second argument is Metadata
. I will explain Metadata.
In Client side (can be started using sockerl:start_link_connector_pool/4-5-6
or sockerl:start_link_connector/4-5-6
) first argument always is your InitArg
.
Return value of connector_init/2
can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
ignore
CallbackRetOpts
is:
-type return_options() :: [] | [return_option()].
-type return_option() :: {packet, Pkt :: any()} % When connection process gets
% this from your callback function, Sends packet through
% socket. Type of Pkt depends on transporter module, for
% sockerl_tcp_transporter and sockerl_ssl_transporter it
% is string() or binary().
| {state, State :: any()} % This is your state data, if
% in connector_init you don't return it, your state will
% be atom 'undefined'.
| {timeout, timeout()} % When connection process gets this
% from your callback function, Waits specified milliseconds
% for Erlang message, if did not receive any message,
% process will call function timeout/2.
% Default is 'infinity'.
| {srtimeout, timeout} % When connection process gets this
% from your callback function, Waits specified milliseconds
% for receiving packet from socket, if did not receive any
% packet, process will call function srtimeout/2.
% Default is 'infinity'.
| {reply, From :: {ref(), pid()}, Msg :: any()} % When
% connection process gets this from your callback function,
% Sends Msg to given pid with given reference.
% This is useful in return of handle_call/4 callback function.
| {length, N :: sockerl_types:length()} % Connection process
% will receive N bytes from passive socket in next socket
% receiving. Default is 0 which means: read all available
% data from socket buffer. Negative integer means: don't
% receive from socket.
| {setopts, Opts :: list()} % Sets options Opts to socket.
| {transporter, module()} % Changes transporter module
% dynamically
| {socket, Sock :: any()}. % Changes socket dynamically
If returns close
or {close, CallbackRetOpts}
, process will close socket and crash with reason normal
.
Metadata is an Erlang record containing useful values.
#sockerl_metadata{socket % Socket connection.
,timeout % last defined timeout, default is 'infinity'.
,length % how much byte passive socket should read from socket.
,srtimeout % passive socket receive timeout.
,transporter % transporter module, 'sockerl_tcp_transporter' for TCP
% and 'sockerl_ssl_transporter' for SSL.
,options % your start options.
,last_message % in connector_init/2 it's 'undefined' because process
% did not receive any Erlang message, but in other
% callbacks is the last message that process receives.
,last_callback % in connector_init/2 it's 'undefined' because process
% did not call other callbacks, but after this it will
% be 'connector_init' and after that it's last callback
% which process calls.
,address} % sockerl_types:address()
If you don't want to define metadata record or include it, use API of sockerl_metadata module.
When connection process receives packet from socket (in server implementation or client, for passive or acrtive sockets) calls YourCallbackModule:handle_packet(Pkt, State, Metadata)
.
Pkt
is received packet and State is your last specified state in return of other callbacks or atom 'undefined'.
If you specified some options in previous called callbacks and you don't want to change their values in other callback calls, don't re-specify them, because their value is kept for next calls.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
For example in blow code i will send received packet back to the connection and increase last timeout if last timeout is in milliseconds:
handle_packet(Pkt, _State, #sockerl_metadata{timeout = infinity}) ->
ok;
handle_packet(Pkt, _State ,#sockerl_metadata{timeout = T}) ->
{ok, [{packet, Pkt}, {timeout, T+1}]}.
When socket connection closes by other side, connection process will call YourCallbackModule:handle_disconnect(State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When connection process receives call request (request which made from gen_server:call/2-3
or gen:call/3-4
), Calls YourCallbackModule:handle_call(Request, From, State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When connection process receives a cast message (message which made from gen_server:cast/2
), Calls YourCallbackModule:handle_cast(Msg, State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When connection process receives an event (message in form of {'$gen_event', Event}
), Calls YourCallbackModule:handle_event(Event, State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When connection process receives a message which is not a call request, cast message or event, Calls YourCallbackModule:handle_info(Msg, State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When you specified {timeout, N :: timeout()}
in CallbackRetOpts
, connection process waits N milliseconds for receiving any Erlang message, if did not receive any message, Process calls YourCallbackModule:timeout(State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When you specified {srtimeout, N :: timeout()}
in CallbackRetOpts
, connection process waits N milliseconds for receiving any packet from passive socket, if did not receive any packet, Process calls YourCallbackModule:srtimeout(State, Metadata)
.
Return value can be one of:
ok
{ok, CallbackRetOpts}
{stop, Reason}
{stop, Reason, CallbackRetOpts}
close
{close, CallbackRetOpts}
When connection process terminated (for any reason, for example disconnecting, badmatch
error in your code, etc), Process calls YourCallbackModule:terminate(TerminateReason, State, Metadata)
.
Return value can be anything.
In upgrading code, connection process calls YourCallbackModule:code_change(Term, State, Term)
.
Return value should be {ok, State_or_NewState}
.