Skip to content
This repository has been archived by the owner on Oct 7, 2023. It is now read-only.

Blocking server example

Pouriya Jahanbakhsh edited this page Jul 9, 2017 · 6 revisions

reverse_echo_server.erl

-module(reverse_echo_server).
-behaviour(sockerl).

%% API:
-export([start/1
        ,print_buffer/1
        ,clean/1]).


%% sockerl callbacks:
-export([listen_init/2
        ,connector_init/2
        ,handle_packet/3
        ,handle_disconnect/2
        ,handle_call/4
        ,handle_cast/3
        ,terminate/3
        ,code_change/3
        ,timeout/2
        ,srtimeout/2]).





start(Port) ->
    RegisterName = {local, ?MODULE}, %% Optional
    CallbackMod = ?MODULE,
    InitArg = null,

    StartOpts = [{acceptor_count, 3}
                ,{acceptor_mode, sleep}
                ,{acceptor_debug, [trace]}

                ,{connector_debug, [trace]}

                ,{socket_options, [{active, false} % makes blocking server
                                  ,binary]}],

    sockerl:start_link_server(RegisterName
                             ,CallbackMod
                             ,InitArg
                             ,Port
                             ,StartOpts).





print_buffer(Con) ->
    gen_server:call(Con, print_buffer).


clean(Con) ->
    gen_server:cast(Con, clean).





listen_init(null, _LSock) ->
    ok.



%% In passive mode: 
%% When process is waiting for socket packet, it can't receive Erlang messages.
%% When process is waiting for Erlang message, it can't receive socket packets.
%% So i need to define timeout for Erlang receive and socket receive.
%% Use {'timeout', timeout()} for Erlang receive, after timeout if
%%  process did not get message, callback-function 'timeout/2' will be called.
%% Use {'srtimeout', timeout()} (SRTimeout: Socket Receive Timeout) for
%%  socket receive, after timeout if process did not get packet,
%%  callback-function 'srtimeout/2' will be called.
%% In passive mode if you want to read for example 4 bytes from socket,
%%  you can specify {'length', 4} in return of every callback.
%% If you want to use your previous length, timeout or srtimeout value
%%  for increasing or decrasing, etc, dont put it in your state, Metadata has them.
%% #sockerl_metadata{socket = Sock
%%                  ,timeout = Timeout
%%                  ,srtimeout = SRTimeout
%%                  ,length = Len
%%                  ,transporter = TrMod
%%                  ,options = Opts}
%% Just use sockerl_metadata API..
connector_init(null, _SMD) ->
    {ok, [{state, <<"">>}, {timeout, 1}, {srtimeout, 1}]}.





handle_packet(Pkt, Buff, _SMD) ->
    {ok, [{packet, bin_rev(Pkt)}, {state, <<Buff/binary, Pkt/binary>>}]}.





handle_call(print_buffer, From, Buff, _SMD) ->
    io:format("Buffer: ~p~n", [Buff]),
    {ok, [{reply, From, ok}]}.




handle_cast(clean, _Buff, _SMD) ->
    {ok, [{state, <<"">>}]}.





handle_disconnect(_Buff, _SMD) ->
    close.





terminate(_Reason, _State, _SMD) ->
    ok.





code_change(_, State, _) ->
    {ok, State}.




timeout(_Buff, _Metadata) ->
    ok.




srtimeout(_Buff, _Metadata) ->
    ok.





%% Reverses binary
bin_rev(Bin) ->
    Size = erlang:size(Bin)*8,
    <<X:Size/integer-little>> = Bin,
    <<X:Size/integer-big>>.

Compile and run:

1> reverse_echo_server:start(1995).
*DBG* Sockerl acceptor "<0.110.0>" started for listen socket "#Port<0.7500>" with options:
        transporter module: 'sockerl_tcp_transporter'
        pool pid: <0.109.0>
        mode: 'sleep'
*DBG* Sockerl acceptor "<0.111.0>" started for listen socket "#Port<0.7500>" with options:
        transporter module: 'sockerl_tcp_transporter'
        pool pid: <0.108.0>
        mode: 'sleep'
*DBG* Sockerl acceptor "<0.112.0>" started for listen socket "#Port<0.7500>" with options:
        transporter module: 'sockerl_tcp_transporter'
        pool pid: <0.107.0>
        mode: 'sleep'
{ok,<0.104.0>}

%% Turn acceptors to accept mode
2> sockerl:wakeup_acceptors(reverse_echo_server).
*DBG* Sockerl acceptor "<0.110.0>" got request "accept" from "<0.106.0>"
*DBG* Sockerl acceptor "<0.110.0>" changed mod to "accept"
*DBG* Sockerl acceptor "<0.110.0>" sent message "ok" to "<0.106.0>" 
*DBG* Sockerl acceptor "<0.112.0>" got request "accept" from "<0.106.0>"
*DBG* Sockerl acceptor "<0.112.0>" changed mod to "accept"
*DBG* Sockerl acceptor "<0.112.0>" sent message "ok" to "<0.106.0>" 
*DBG* Sockerl acceptor "<0.111.0>" got request "accept" from "<0.106.0>"
*DBG* Sockerl acceptor "<0.111.0>" changed mod to "accept"
*DBG* Sockerl acceptor "<0.111.0>" sent message "ok" to "<0.106.0>" 
ok

Make telnet connection:

$ telnet 127.0.0.1 1995
Trying 127.0.0.1...
Connected to 127.0.0.1.
Escape character is '^]'.

See debug output of Sockerl:

*DBG* Sockerl acceptor "<0.110.0>" accepted socket "#Port<0.7536>"
*DBG* Sockerl acceptor "<0.110.0>" gave socket "#Port<0.7536>" to connection handler "<0.123.0>"

3> [{_, C}] = sockerl:get_server_connections(reverse_echo_server).
[{#Port<0.7536>,<0.123.0>}]

Send 123 and then 456 in telnet and see Sockerl debug output

*DBG* Sockerl connector "<0.118.0>" got packet <<"123\r\n">>
*DBG* Sockerl connector "<0.118.0>" sent packet <<"\n\r321">>
*DBG* Sockerl connector "<0.118.0>" got packet <<"456\r\n">>
*DBG* Sockerl connector "<0.118.0>" sent packet <<"\n\r654">>

Test our API:

4> reverse_echo_server:print_buffer(C).  
*DBG* Sockerl connector "<0.123.0>" got call "print_buffer" from "<0.106.0>" with tag "#Ref<0.0.1.17>"
Buffer: <<"123\r\n456\r\n">>
*DBG* Sockerl connector "<0.123.0>" sent "ok" to "<0.106.0>" with tag "#Ref<0.0.1.17>"
ok

5> reverse_echo_server:clean(C).         
ok
*DBG* Sockerl connector "<0.123.0>" got cast "clean"

6> reverse_echo_server:print_buffer(C).
*DBG* Sockerl connector "<0.123.0>" got call "print_buffer" from "<0.106.0>" with tag "#Ref<0.0.1.31>"
Buffer: <<>>
*DBG* Sockerl connector "<0.123.0>" sent "ok" to "<0.106.0>" with tag "#Ref<0.0.1.31>"
ok

See debug output of send API:

7> sockerl:send_sync(C, "foo").
*DBG* Sockerl connector "<0.123.0>" got synchronous request for sending packet "foo" from "<0.106.0>" with tag "#Ref<0.0.1.43>"
*DBG* Sockerl connector "<0.123.0>" sent packet "foo"
*DBG* Sockerl connector "<0.123.0>" sent "ok" to "<0.106.0>" with tag "#Ref<0.0.1.43>"
ok

8> sockerl:send_async(C, <<"bar">>).
*DBG* Sockerl connector "<0.123.0>" got asynchronous request for sending packet <<"bar">>
ok
*DBG* Sockerl connector "<0.123.0>" sent packet <<"bar">>

Close connection from server:

9> sockerl:stop_connector(C).
ok 
=ERROR REPORT==== 1-Jul-2017::05:30:32 ===
** Sockerl connector "<0.123.0>" terminating 
** Reason for termination == "normal"
** State == "<<>>"

10> sockerl:get_server_connections(reverse_echo_server).
[]

Now stop entire server without crashing yourself:

11> Me = self().
<0.106.0>

12> sockerl:stop_server(reverse_echo_server).
%% Some debug outputs about server termination
ok

13> Me == self().
true

If you want to send some data to remote connection before terminating, the good idea is to getting socket from Metadata in terminate/3 callback and send packet through socket:

terminate(something_goes_wrong=_Reason, _State, SMD) ->
    Sock = sockerl_metadata:get_socket(SMD),
    TrMod = sockerl_metadata:get_transporter(SMD),
    Opts = sockerl_metadata:get_options(SMD),
    sockerl_socket:send(TrMod, Sock, <<"bye">>, Opts),
    ok.
Clone this wiki locally