receive specific amount of data from gen_tcp socket - erlang

I have the following code using a gen_tcp socket, which should receive using {active, true} some binary commands that are always of size 5 (e.g. Command = <<"hello">>)
{ok, ListenSocket} = gen_tcp:listen(5555, [{active, true}, {mode, binary},
{packet, 0}]),
{ok, Socket} = gen_tcp:accept(ListenSocket),
loop().
receive() ->
{tcp, _Socket, Message:4/binary} ->
io:format("Success~n", []),
loop();
{tcp, _Socket, Other} ->
io:format("Received: ~p~n", [Other]),
loop()
end.
But if I try to connect as a client like this:
1> {ok, S} = gen_tcp:connect("localhost", 5555, []).
{ok,#Port<0.582>}
2> gen_tcp:send(S, <<"hello">>).
ok
I have in the receiving part:
Received: {tcp,#Port<0.585>,<<"hello">>}
so I suppose my error is in the pattern matching...but where?

Your receive clause doesn't look right, it should be:
{tcp, _Socket, <<Message:5/binary>>} ->
....

As far as I understood you need gen_tcp:recv function. From documentation:
recv(Socket, Length) -> {ok, Packet} | {error, Reason}
In your code it will be something like:
...
{ok, Socket} = gen_tcp:accept(ListenSocket),
loop(Socket).
loop(Socket) ->
case gen_tcp:recv(Socket, 5) of
{ok, Data} ->
io:format("Success~n", []),
loop(Socket);
{error, closed} ->
ok
end.

Related

Testing a gen_server module using Common Test

I have this (very simple) gen_server implementation:
-module(rand_gen).
-behaviour(gen_server).
-define(BASE, 1000).
%% Module Functionality
-export([start/0]).
-export([stop/1]).
-export([uniform/1, uniform/2]).
%% Callback Functions
-export([code_change/3]).
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
%%%=======================================================================
%%% Export
%%%=======================================================================
-spec start() -> {ok, pid()} | {error, Reason :: term()}.
start() ->
gen_server:start(?MODULE, [], []).
-spec stop(Pid) -> ok when Pid :: pid().
stop(Pid) ->
gen_server:stop(Pid).
-spec uniform(Pid, Max) -> number() when Pid :: pid(), Max :: non_neg_integer().
uniform(Pid, Max) ->
gen_server:call(Pid, {uniform, #{max => Max}}).
-spec uniform(Pid) -> number() when Pid :: pid().
uniform(Pid) ->
gen_server:call(Pid, uniform).
%%%=======================================================================
%%% Callback Functions
%%%=======================================================================
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
handle_call({uniform, Input = #{max := Max}}, _From, _State) ->
{reply, rand:uniform(Max), Input};
handle_call(uniform, _From, State = #{max := Max}) ->
{reply, rand:uniform(Max), State};
handle_call(Msg, From, State) ->
io:format("Unexpected call [~p] from [~p]~n", [Msg, From]),
{reply, {error, no_call, Msg}, State}.
handle_cast(Msg, State) ->
io:format("Unexpected cast [~p]~n", [Msg]),
{noreply, State}.
handle_info(Msg, State) ->
io:format("Unexpected message ~p~n", [Msg]),
{noreply, State}.
init([]) ->
io:format("Crypto strong seed initialized! Using [~p] as base value.~n", [?BASE]),
<<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer>> = crypto:strong_rand_bytes(?BASE),
_ = rand:seed(exsplus, {I1, I2, I3}),
{ok, #{max => ?BASE}}.
terminate(_Reason, _StateData) ->
ok.
%%%=======================================================================
%%% Internal
%%%=======================================================================
Now I want to write a Common Test suite for testing it ‒ I just started learning this.
-module(rand_gen_SUITE).
-include_lib("common_test/include/ct.hrl").
-compile(export_all).
%%%=======================================================================
%%% Setup
%%%=======================================================================
all() ->
[
{group, generate}
].
groups() ->
[
{generate,
%%[parallel, {repeat, 3}],
[uniform]
}
].
init_per_suite(Config) ->
{ok, Pid} = rand_gen:start(),
[{pid, Pid} | Config].
end_per_suite(_Config) ->
ok.
%%%=======================================================================
%%% Test Cases
%%%=======================================================================
uniform(Config) ->
Pid = ?config(pid, Config),
true = is_number(rand_gen:uniform(Pid)).
...but every time I run the (super-small) suite/file I get back a weird message saying:
===> Running Common Test suites...
%%% rand_gen_SUITE ==> init_per_suite: FAILED
%%% rand_gen_SUITE ==> {{badmatch,<<3,217,51,126,122,17,213,209,222,82,29,106,213,128,218,98,129,12,
123,22,27,194,123,120,74,110,1,254,132,243,147,87,181,74,43,159,
163,94,58,224,18,40,46,190,134,198,43,225,57,81,109,216,64,245,
103,131,46,47,234,165,210,2,78,97,52,44,131,49,0,94,168,207,176,
214,38,69,235,105,116,240,92,37,112,17,16,47,86,0,179,4,167,61,
61,167,88,84,227,247,74,132,86,64,33,92,175,51,130,242,155,174,
167,126,142,100,123,35,77,159,198,113,105,182,43,61,130,173,169,
155,135,0,6,19,90,65,139,70,236,217,253,76,223,86,228,175,145,
198,89,40,251,158,6,193,177,66,202,199,110,39,130,232,28,37,176,
215,45,251,65,220,229,124,204,99,126,132,119,41,83,226,235,117,
152,69,179,211,10,97,194,128,147,134,26,240,175,218,188,204,82,
55,34,35,49,195,183,78,150,125,137,141,76,92,253,1,46,203,37,142,
224,236,8,252,94,129,89,108,84,133,88,213,56,127,255,48,172,26,
246,51,29,79,56,33,59,122,135,80,137,63,37,124,168,90,76,46,12,
155,241,182,16,218,147,227,16,110,9,51,213,74,216,18,51,219,12,
111,229,159,231,33,12,87,114,134,113,79,147,35,122,227,114,154,
52,164,223,95,66,162,136,231,174,47,93,10,66,62,63,76,196,232,
100,240,49,16,122,81,200,222,66,180,212,16,185,117,31,111,152,
216,10,125,212,138,203,219,31,67,94,170,181,160,225,189,182,10,
110,207,197,177,198,199,83,227,121,235,12,186,90,212,160,102,187,
99,61,127,123,255,76,36,63,190,197,175,167,175,132,230,187,162,
68,183,220,9,198,29,18,191,199,182,10,227,139,38,25,113,153,199,
80,244,13,55,62,153,250,165,218,137,211,210,129,123,35,67,226,20,
153,85,25,193,46,53,40,49,134,67,92,198,106,151,195,242,46,153,
223,187,163,100,205,108,253,191,192,220,65,101,15,32,161,41,25,
148,203,153,134,55,33,88,107,222,49,120,44,173,167,82,238,185,
213,107,164,45,64,28,180,253,34,131,92,49,112,202,188,1,36,59,81,
223,0,200,93,34,253,3,214,240,211,51,135,43,70,105,98,125,186,
131,129,85,246,147,167,238,32,176,54,92,250,200,73,63,246,72,159,
64,105,119,246,58,5,153,54,126,4,33,82,153,245,79,163,187,61,45,
172,173,30,45,148,252,230,74,80,213,132,45,52,202,101,234,98,30,
68,18,117,175,231,41,153,243,243,132,184,98,234,72,134,140,229,
239,205,136,228,223,52,189,118,250,195,178,126,33,56,142,247,242,
235,160,129,75,221,73,21,148,139,110,194,87,127,147,9,71,25,111,
218,6,206,38,3,208,154,128,236,127,96,155,223,25,164,139,80,170,
82,230,130,118,88,198,254,109,249,252,146,143,6,108,134,49,29,
152,215,67,74,96,240,109,6,97,36,145,250,234,48,145,17,144,217,
198,236,60,68,213,30,7,120,228,117,41,231,72,208,245,73,153,251,
64,195,16,201,250,12,143,23,138,23,215,74,5,180,228,133,236,61,
227,123,188,100,81,77,254,187,69,213,127,22,38,143,84,184,4,47,
47,124,245,230,221,105,105,36,220,28,52,200,195,104,7,78,155,250,
36,8,42,228,166,212,106,147,247,101,21,230,78,236,91,217,115,84,
76,125,193,18,136,103,85,104,33,102,7,235,250,197,139,238,187,
154,12,84,152,174,133,92,165,72,76,45,70,90,115,32,161,156,251,
197,103,70,141,168,220,223,251,252,14,249,172,40,122,53,69,137,
45,29,129,144,146,238,181,104,28,117,166,19,238,44,162,174,117,
250,21,62,155,39,149,221,207,48,168,19,35,209,53,188,31,122,99,
83,74,138,164,103,72,106,27,175,239,240,3,100,118,152,180,225,82,
227,60,108,66,223,165,173,183,46,166,23,85,236,141,238,166,160,
114,31,7,14,23,220,90,226,79,217,203,240,126,128,241,41,149,224,
148,130,86,130,244,12,27,237,123,234,0,68,164,165,250,235,87,243,
217,253,215,249,13,206,155,197,138,192,249,154,104,131,109,229,
126,77,216,127,254,55,170,192,100,90,49,30,78,209,249,140,184,81,
121,164,178,50,204,131,144,214,8,12,130,32,164,149,92,200,235,
145,48,162,244,238,114,47,251,31,79,242,186,111,117,207,16,178,
212,234,209,161,143,21,241,111,218,32,255,191,70,177,203,5,117,
182,148,199,231,93,118,76,72>>},
[{rand_gen,init,1,
[{file,"/home/x80486/Workshop/erlang/erlang_basics/_build/test/lib/erlang_basics/src/rand_gen.erl"},
{line,74}]},
{rand_gen_SUITE,init_per_suite,1,
[{file,"/home/x80486/Workshop/erlang/erlang_basics/_build/test/lib/erlang_basics/test/rand_gen_SUITE.erl"},
{line,34}]},
{test_server,ts_tc,3,[{file,"test_server.erl"},{line,1546}]},
{test_server,run_test_case_eval1,6,[{file,"test_server.erl"},{line,1147}]},
{test_server,run_test_case_eval,9,[{file,"test_server.erl"},{line,994}]}]}
Any clues? Thanks in advance!
That’s because the result of crypto:strong_rand_bytes(?BASE) doesn’t match <<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer>> since it’s (as you can see in the error) longer than that.
You can try changing that line in your code to something like:
<<I1:32/unsigned-integer, I2:32/unsigned-integer, I3:32/unsigned-integer, _/bitstring>> = crypto:strong_rand_bytes(?BASE),

RabbitMQ work queue is blocking consumers

I'm following the example here
https://www.rabbitmq.com/tutorials/tutorial-two-python.html
only in Erlang with amqp_client
The full code is here
https://github.com/jhw/rabbit_worker
As I understand it, to get worker- queue style behaviour you need to define your RabbitMQ topology as follows -
'direct' exchange
single queue to which multiple consumers (workers) bind
routing key equal to queue name
workers to ack' each message
pre- fetch count of 1
Now I have this working fine, and can see the exchange passing messages round- robin fashion to the workers
The only problem is that there's no parallelism; only one worker is called at a time; it's as if a request to one worker is blocking the exchange from sending messages to any of the others
So I think I probably have my RabbitMQ topology set up incorrectly; but the problem is I don't know where.
Any thoughts ?
TIA.
Core code below -
-module(pool_router).
-behaviour(gen_server).
-include_lib("amqp_client/include/amqp_client.hrl").
%% API.
-export([start_link/0]).
-export([subscribe/1]).
-export([unsubscribe/1]).
-export([publish/2]).
-export([acknowledge/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {rabbit_conn, rabbit_chan, queues}).
-define(RABBIT_USERNAME, <<"guest">>).
-define(RABBIT_PASSWORD, <<"Hufton123">>).
-define(EXCHANGE_NAME, <<"worker_exchange">>).
-define(EXCHANGE_TYPE, <<"direct">>).
-define(QUEUE_NAMES, [<<"worker_queue">>]).
%% API.
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% [stak_worker_sup:spawn_worker() || _ <- lists:seq(1, 3)].
%% [pool_router:publish(<<"worker_queue">>, {<<"fibonacci">>, <<"fibonacci">>, [40]}) || _ <- lists:seq(1, 9)].
publish(QueueName, MFA) ->
gen_server:call(?MODULE, {publish, {QueueName, MFA}}).
acknowledge(Tag) ->
gen_server:call(?MODULE, {acknowledge, Tag}).
subscribe(QueueName) ->
gen_server:call(?MODULE, {subscribe, QueueName}).
unsubscribe(Tag) ->
gen_server:call(?MODULE, {unsubscribe, Tag}).
%% gen_server.
init([]) ->
RabbitParams=#amqp_params_network{username=?RABBIT_USERNAME,
password=?RABBIT_PASSWORD},
{ok, RabbitConn}=amqp_connection:start(RabbitParams),
{amqp_channel, {ok, RabbitChan}}={amqp_channel,
amqp_connection:open_channel(RabbitConn)},
Exchange=#'exchange.declare'{exchange=?EXCHANGE_NAME,
type=?EXCHANGE_TYPE,
auto_delete=false},
#'exchange.declare_ok'{}=amqp_channel:call(RabbitChan, Exchange),
InitQueueFn=fun(QueueName) ->
Queue=#'queue.declare'{queue=QueueName},
#'queue.declare_ok'{}=amqp_channel:call(RabbitChan, Queue),
Binding=#'queue.bind'{queue=Queue#'queue.declare'.queue,
exchange=?EXCHANGE_NAME,
routing_key=QueueName},
#'queue.bind_ok'{}=amqp_channel:call(RabbitChan, Binding),
Queue
end,
Queues=[{QueueName, InitQueueFn(QueueName)} || QueueName <- ?QUEUE_NAMES],
#'basic.qos_ok'{}=amqp_channel:call(RabbitChan, #'basic.qos'{prefetch_count=1}),
io:format("router started~n"),
{ok, #state{rabbit_conn=RabbitConn,
rabbit_chan=RabbitChan,
queues=Queues}}.
handle_call({publish, {QueueName, {Mod, Fn, Args}=MFA}}, {_From, _}, #state{rabbit_chan=RabbitChan}=State) ->
io:format("Publishing ~p~n", [MFA]),
Payload=jsx:encode([{<<"M">>, Mod},
{<<"F">>, Fn},
{<<"A">>, Args}]),
Publish=#'basic.publish'{exchange=?EXCHANGE_NAME,
routing_key=QueueName},
ok=amqp_channel:cast(RabbitChan, Publish, #amqp_msg{payload=Payload}),
{reply, ok, State};
handle_call({acknowledge, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
ok=amqp_channel:cast(RabbitChan, #'basic.ack'{delivery_tag=Tag}),
io:format("~p [~p] acknowledged~n", [From, Tag]),
{reply, ok, State};
handle_call({subscribe, QueueName}, {From, _}, #state{queues=Queues, rabbit_chan=RabbitChan}=State) ->
io:format("~p subscribed~n", [From]),
{_, Queue}=proplists:lookup(QueueName, Queues), %% NB no error checking
#'basic.consume_ok'{consumer_tag=Tag}=amqp_channel:subscribe(RabbitChan, #'basic.consume'{queue=Queue#'queue.declare'.queue}, From),
{reply, {ok, Tag}, State};
handle_call({unsubscribe, Tag}, {From, _}, #state{rabbit_chan=RabbitChan}=State) ->
io:format("~p [~p] unsubscribed~n", [From, Tag]),
#'basic.cancel_ok'{}=amqp_channel:call(RabbitChan, #'basic.cancel'{consumer_tag=Tag}),
{reply, ok, State};
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
ok=amqp_channel:close(State#state.rabbit_chan),
ok=amqp_connection:close(State#state.rabbit_conn),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
%% internal functions
-module(stak_worker).
-behaviour(gen_server).
-include_lib("amqp_client/include/amqp_client.hrl").
%% API.
-export([start_link/0]).
-export([stop/1]).
%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).
-record(state, {rabbit_tag}).
-define(QUEUE_NAME, <<"worker_queue">>).
%% API.
%% {ok, Pid}=stak_worker:start_link().
%% stak_worker:stop(Pid).
start_link() ->
gen_server:start_link(?MODULE, [], []).
stop(Pid) ->
gen_server:cast(Pid, stop).
%% gen_server.
%% don't run request automatically
%% workers should subscribe to router on startup and unsubscribe on termination
%% router then routes messages to workers
init([]) ->
io:format("starting worker ~p~n", [self()]),
{ok, #state{}, 0}.
handle_call(_Request, _From, State) ->
{reply, ignored, State}.
handle_cast(stop, State) ->
{stop, normal, State};
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info({#'basic.deliver'{delivery_tag=Tag}, #amqp_msg{payload=Payload}=_Content}, State) ->
%% io:format("~p received ~p~n", [self(), Payload]),
Struct=jsx:decode(Payload),
{_, ModBin}=proplists:lookup(<<"M">>, Struct),
{_, FnBin}=proplists:lookup(<<"F">>, Struct),
{_, Args}=proplists:lookup(<<"A">>, Struct),
Mod=list_to_atom(binary_to_list(ModBin)),
Fn=list_to_atom(binary_to_list(FnBin)),
Mod:Fn(Args),
ok=pool_router:acknowledge(Tag),
{noreply, State};
handle_info(timeout, State) ->
%% io:format("~p subscribing~n", [self()]),
{ok, RabbitTag}=pool_router:subscribe(?QUEUE_NAME),
{noreply, State#state{rabbit_tag=RabbitTag}};
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, State) ->
io:format("~p shutting down~n", [self()]),
ok=pool_router:unsubscribe(State#state.rabbit_tag),
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Avoid use of prefetch_count; it limits the number of un- ack'ed messages on a single channel to one; so if have a worker pool, thus will cause it to operate sequentially rather than in parallel (because every request sent to a worker is only ack'ed once the worker has completed; ie you have multiple un- ack'ed messages at a given point in time)

Erlang: OTP - properly passing children arguments; workers exiting with exceptions

I have a supervisor / worker tree organized as follows:
game_super -> {keyserver: worker, loginserver: super}
loginserver -> loginlistener: worker (socket pool)
The key server works okay. It just returns the string of random bytes it uses to construct login tokens. It is periodically replaced.
My login server use to work under R16. Then I upgraded to R18 because I wanted to use maps. IDK if that is the issue... I doubt it though. The other change I made is the reason I need maps was for the convenience of setting up children.
I have multiple redis servers, each with their own purpose. One is rate limiting. That's the only one operating right now. I call game_core:redis_init() in game_supervisor:init([]). It returns {ok,{#MapPids, #MapScriptKeys}. Where #MapScriptKeys values are tuples containing all Sha hashes of LUA scripts.
I'm getting exception exit: {shutdown,{failed_to_start_child,login,<0.178.0>}} Pids obviously change but that was the last result after calling game_supervisor:start_inshell().
I've inserted an io:format("~p\n",[State]), just before the {ok, State} in login_listener:init(State) and it prints out everything... once. Considering that's right before returning.... how does it fail?
The code is based on: http://learnyousomeerlang.com/buckets-of-sockets
Is this similar to: Erlang supervisor exception on starting worker , where the same name was reused for children?
Game Supervisor
-module(game_supervisor).
-behavior(supervisor).
-include("constants.hrl").
-export([start/0, start/1, start_inshell/0, init/1]).
start(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
start_inshell() ->
{ok, Pid} = supervisor:start_link({local, ?MODULE}, ?MODULE, []),
unlink(Pid).
start() -> start([]).
init([]) ->
{ok, RedisState} = game_core:redis_init(),
{ok, {
{one_for_one, 2, 10}, [ %more than 2 restarts w/i 10 seconds all killed
{key,
{key_server, start_link, []},
permanent,
10000,
worker,
[key_server]},
{login,
{login_server, start_link, [RedisState]},
permanent,
10000,
supervisor,
[login_server]}%,
% {game,
% {game_server, start_link, [RedisState]},
% permanent,
% 10000,
% supervisor,
% [game_server]}
]}}.
Login Server
-module(login_server).
-behavior(supervisor).
-include("constants.hrl").
-export([start_link/0, start_link/1, init/1, start_socket/0]).
start_link(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
start_link() -> start_link([]).
init({Pid,Scripts}) ->
process_flag(trap_exit, true),
{ok, Socket} = gen_tcp:listen(?PORT_LISTEN_LOGIN, [{active,once}, {reuseaddr, true}]),
%Construct socket pool
spawn_link(fun start_pool/0),
%Put all redis information for login clients here
RedisState = {maps:get(limiter,Pid),maps:get(limiter,Scripts)},
%Child specification
{ok, {
{simple_one_for_one, 2, 10}, [ %more than 2 restarts w/i 10 seconds all killed
{listener,
{login_listener, start_link, [{Socket, RedisState}]},
temporary,
1000,
worker,
[login_listener]}
]}}.
start_pool() ->
[start_socket() || _ <- lists:seq(1,32)],
ok.
start_socket() -> supervisor:start_child(?MODULE, []).
Login Handler
-module(login_listener).
-behavior(gen_server).
-include("constants.hrl").
-export([start_link/1]).
%required callbacks
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
start_link(State) ->
gen_server:start_link(?MODULE, State,[]).
init(State) ->
process_flag(trap_exit, true),
gen_server:cast(self(), accept),
{ok, State}.
handle_cast(accept, State) -> %Param(s): Msg, State
{Socket, RedisState} = State,
{ok, AcceptSocket} = gen_tcp:accept(Socket),
login_server:start_socket(),
{noreply, {AcceptSocket, RedisState}}.
handle_info({tcp, Socket, Str}, State) -> %Param(s): Info
%%Login
{_, {PidLimiter,{ShaLimiter}}} = State,
{ok, {ClientIP, _ }} = inet:peername(Socket),
{ok, Result} = game_core:rate_limit(PidLimiter, ShaLimiter, ClientIP),
ok = inet:setopts(Socket, [{active, once}]),
{noreply, State}.
%%Surpress warnings
handle_call(_, _, State) -> {noreply, State}. %Param(s): Request, From
terminate(_, _) -> ok. %Param(s): Reason, State
code_change(_, State,_) -> {ok, State}. %Param(s): OldVsn, Extra
Why are your supervisor start functions calling spawn/1? For game_supervisor, change this:
start(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
to this:
start(Args) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).
And for login_server, change this:
start_link(Args) ->
spawn(fun() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args)
end).
to this:
start_link(Args) ->
supervisor:start_link({local, ?MODULE}, ?MODULE, Args).

Erlang gen_server communication

If I have several instances of server, how can I pass info from one to another, for example:
I have this:
...
-record(id,{name,hash}).
-record(state, {id ,m, succ, pred}).
start(Name, M) ->
gen_server:start_link({local, Name}, ?MODULE, {new_ring, Name, M}, []).
join_ring(Name, Other) ->
gen_server:start_link({local, Name}, ?MODULE, {join_ring, Name, Other}, []).
...
init({new_ring, Name, M}) ->
Me=#id{name=Name,hash=M}
{ok, #state{
id = Me,
m = M,
succ = Me,
pred = nil,
}};
init({join_ring, Name, Other}) ->
Me=#id{name=Name,hash=M}
{ok, #state{
id = Me,
m = Other,
succ = Me,
pred = nil,
}}.
Let say that I have two servers, one and two. How can I access from the server one info from the state of server two?
-module(wy).
-compile(export_all).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-behaviour(gen_server).
-record(state, {id ,m, succ, pred}).
start(Name, M) ->
gen_server:start_link({local, Name}, ?MODULE, [Name, M], []).
init([Name, M]) ->
{ok, #state{id = Name, m = M}}.
handle_call({get_server_info}, _Frome, State) ->
{reply, State, State};
handle_call(_Request, _From, State) ->
Reply = ok,
{reply, Reply, State}.
handle_cast(_Msg, State) ->
{noreply, State}.
handle_info(_Info, State) ->
{noreply, State}.
terminate(_Reason, _State) ->
ok.
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
get_server_info(ServerName) ->
gen_server:call(ServerName, {get_server_info}).
I think you can refer this code sampe.
You can give the server a name, for example server1 & server2.
Then you use gen_server:call/2 to send the message to the server you wanted.
just start two gen_serber:
4> wy:start(server1, server1).
{ok,<0.50.0>}
5> wy:start(server2, server2).
{ok,<0.52.0>}
To get the state info from the server:
24> wy:get_server_info(server2).
{state,server2,server2,undefined,undefined}
25> wy:get_server_info(server1).
{state,server1,server1,undefined,undefined}

recv message and write to file

I have made a server here which receives messages from the terminal. What I want to do is when the server receives the messages from the terminal it should write it into a file. But I dont know how I should do this. I thought of writing the file methods file:open, file:write in the receive functions but it didnt work out as I thought.
-module(tcp).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3, start/0, stop/0, connect/0, send/1, recv/0]).
-export([start_link/0]).
%% =============================================================================
%% EXPORTED GEN_SERVER CALLBACKS
%% =============================================================================
init([]) -> {ok, {}}.
handle_call({send, Packet}, _From, State) ->
gen_tcp:send(State, Packet),
io:format("Send Working\n"),
{reply, ok, State};
handle_call(recv, _From, State) ->
Message = gen_tcp:recv(State, 0),
io:format("~w~n", [Message]),
{reply, Message, State}.
handle_cast(connect, _) ->
case gen_tcp:listen(6888, [binary]) of
{ok, LSocket}->
io:format("~w~n", [LSocket]),
case gen_tcp:accept(LSocket) of
{ok, Socket} ->
inet:setopts(Socket, [{active, false}]),
io:format("accepted\n"),
{noreply, Socket};
Other ->
error_logger:error_report(["An error occurred which is",Other,"in line",?LINE,"of module",?MODULE])
end;
{error, Reason} ->
error_logger:error_report("An error occurred", Reason, [?LINE,?MODULE])
end;
handle_cast(stop, State) -> {stop, normal, State}.
handle_info(_Info, State) -> {noreply, State}.
terminate(_Reason, _State) -> ok.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% =============================================================================
%% EXPORTED CONVENIENCE FUNCTIONS TO START AND STOP THE SERVER
%% =============================================================================
start() ->
case gen_server:start({local, ?MODULE}, ?MODULE, [], []) of
{ok, Pid} ->
Pid;
Reason ->
error_logger:error_report("An error occurred", Reason, [?LINE,?MODULE])
end.
stop() ->
case gen_server:cast(?MODULE, stop) of
ok ->
ok;
_ ->
{error, stop_error}
end.
%% =============================================================================
%% PUBLIC API FUNCTIONS
%% =============================================================================
connect() -> gen_server:cast(?MODULE, connect).
send(Packet) -> gen_server:call(?MODULE, {send, Packet}).
recv() -> gen_server:call(?MODULE, recv).
write_file(Filename) ->
{ok, IoDevice} = file:open(test.txt, [write, append]),
ok.
start_link() -> gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% =============================================================================
%% LOCAL FUNCTIONS
%% =============================================================================
I did as you suggested and implemented it as a handle_call but when i run i get an error,
function handle_call/3 already defined error
I quite dont understand why I get this error since i have 3 arguments for the handle_call and it should work.
handle_call(write_file, Filename, S) ->
{ok, File} = file:open(Filename, [append]),
ok = file:write(File, S),
ok = file:close(File).
My API Function
write_file() -> gen_server:call(?MODULE, write_file).
Your handle call doesn't work because you have two definitions of it. You have written:
handle_call(...) ->
do_something.
handle_call(M, F, S) ->
do_some_other_thing.
You should make them into the same function, by changing:
do_something.
into:
do_something;
handle_call(M, F, S) ->
do_some_other_thing.
write_file should do something like:
write_file(Fname, S) ->
ok = file:write_file(Fname, S, [append]).
or
write_file(Fname, S) ->
{ok, File} = file:open(Fname, [append]),
ok = file:write(File, S),
ok = file:close(File).

Resources