This repository has been archived by the owner on Oct 7, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 4
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.