In the previous études, the client and server have been running in the same shell. In this étude, you will make the server available to clients running in other shells.
To make a node available to other nodes, you need to name the node by using
the -name
option when starting erl
. It looks like this:
michele@localhost $ erl -name serverNode
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
([email protected])1>
This is a long name. You can also set up a node with a short name by using
the -sname
option:
michele@localhost $ erl -sname serverNode
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(serverNode@localhost)1>
If you set up a node in this way, any other node can connect
to it and do any shell commands at all. In order to prevent this,
you may use the -setcookie
when starting Cookie
erl
. Then,
only nodes that have the same Cookie (which is an atom) can
connect to your node.
To connect to a node, use the net_adm:ping/1
function, and give it
the name of the server you want to connect to as its argument. If you
connect succesfully, the function will return the atom pong
; otherwise,
it will return pang
.
Here is an example. First, start a shell with a (very bad) secret cookie:
michele@localhost $ erl -sname serverNode -setcookie chocolateChip
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(serverNode@localhost)1>
Now, open another terminal window, start a shell with a different cookie, and try to connect to the server node. I have purposely used a different user name to show that this works too.
steve@localhost $ erl -sname clientNode -setcookie oatmealRaisin
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(clientNode@localhost)1> net_adm:ping(serverNode@localhost).
pang
The server node will detect this attempt and let you know about it:
=ERROR REPORT==== 28-Feb-2013::22:41:38 ===
** Connection attempt from disallowed node clientNode@localhost **
Quit the client shell, and restart it with a matching cookie, and all will be well.
steve@localhost erltest $ erl -sname clientNode -setcookie chocolateChip
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(clientNode@localhost)1> net_adm:ping(serverNode@localhost).
pong
To make your weather report server available to other nodes, you need to do these things:
start_link/0
convenience method, set the first argument to
gen_server:start_link/4
to {global, ?SERVER}
instead of
{local, ?SERVER}
gen_server:call/2
and gen_server:cast/2
, replace the
module name weather
with {global, weather}
connect/1
function that takes the server node name as its
argument. This function will use net_adm:ping/1
to attempt to contact
the server. It provides appropriate feedback when it succeeds or fails.
Here is what it looks like when one user starts the server in a shell.
michele@localhost $ erl -sname serverNode -setcookie meteorology
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(serverNode@localhost)1> weather:start_link().
{ok,<0.39.0>}
And here’s another user in a different shell, calling upon the server.
steve@localhost $ erl -sname clientNode -setcookie meteorology
Erlang R15B02 (erts-5.9.2) [source] [smp:2:2] [async-threads:0] [hipe] [kernel-poll:false]
Eshell V5.9.2 (abort with ^G)
(clientNode@localhost)1> weather:connect(serverNode@localhost).
Connected to server.
ok
(clientNode@localhost)2> weather:report("KSJC").
{ok,[{location,"San Jose International Airport, CA"},
{observation_time_rfc822,"Thu, 28 Feb 2013 21:53:00 -0800"},
{weather,"Fair"},
{temperature_string,"52.0 F (11.1 C)"}]}
(clientNode@localhost)3> weather:report("KITH").
{ok,[{location,"Ithaca / Tompkins County, NY"},
{observation_time_rfc822,"Fri, 01 Mar 2013 00:56:00 -0500"},
{weather,"Light Snow"},
{temperature_string,"31.0 F (-0.5 C)"}]}
(clientNode@localhost)4> weather:recent().
ok
Whoa! What happened to the output from that last call?
The problem is that the weather:recent/0
call does
an io:format/3
call; that output will go to the server shell, since the
server is running that code, not the client. Bonus points if you fix this
problem by changing weather:recent/0
from using
gen_server:cast/2
to use gen_server:call/2
instead to return
the recently reported weather stations as its reply.
There’s one more question that went through my mind after I implemented
my solution: how did I know that the client was calling the weather
code
running on the server and not the weather
code in its own shell? It was
easy to find out: I stopped the server.
(serverNode@localhost)2>
User switch command
--> q
michele@localhost $
Then I had the client try to get a weather report.
(clientNode@localhost)5> weather:report("KSJC").
** exception exit: {noproc,{gen_server,call,[{global,weather},"KSJC"]}}
in function gen_server:call/2 (gen_server.erl, line 180)
The fact that it failed told me that yes, indeed, the client was getting its information from the server.
3.147.76.89