Reputation: 1389
I have one simple udp server written in gen_server behaviour. When I run it, and try to send message by using gen_udp:send, the server replies nothing, seems like the udp server didn't accept packet successfully. Here is my code
gen_udp_server.erl:
-module(gen_udp_server).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2, terminate/2, code_change/3]).
-export([start_link/1]).
-define(SERVER, ?MODULE).
-record(state, {socket,
port,
local_ip,
broad_ip}).
start_link(Port) ->
{ok, Socket} = gen_udp:open(Port, [binary,
{active, false},
{reuseaddr, true}]),
gen_server:start_link(?MODULE, [Socket, Port], []).
init([Socket, Port]) ->
{ok, #state{socket = Socket, port = Port}}.
handle_cast(_Request, State) ->
{noreply, State}.
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_info({udp, _Socket, _Addr, _Port, Data}, #state{socket = Socket} = State) ->
inet:setopts(Socket, [{active, once}]),
io:format("Server received data ~p from socket ~p~n", [Data, Socket]),
{ok, State}.
terminate(_Reason, {socket = LSocket}) ->
gen_udp:close(LSocket).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
Starting server on server 192.168.146.129: gen_udp_server:start_link(10000).
Sending message from 192.168.146.128:
{ok, Socket} = gen_udp:open(4399, [binary, {active, false}]).
gen_udp:send(Socket, {192,168,146,129}, 10000, "hello").
The udp server should print some message when it receives packets, but my one failed. Can anyone help me?
Upvotes: 3
Views: 719
Reputation: 48659
handle_info()
deals with unknown messages (i.e. unhandled messages) that arrive in the gen_server
's mailbox. But when a process opens a socket in passive mode: {active, false}
, messages sent to the socket do not land in the process's mailbox. Instead, the process has to manually read messages from the socket by calling gen_udp:recv(Socket, Length)
. After all, the whole point of creating a passive socket is to keep messages from flooding the process's mailbox. As a result, handle_info()
doesn't get called when a client sends a message to a passive socket.
Furthermore, because gen_server
is event driven you need to call gen_udp:recv(Socket, Length)
in response to some event. For instance, you could define the server functions:
process_message() ->
gen_server:cast(?MODULE, process_msg).
handle_cast(process_msg, #state{socket=Socket} = State) ->
Data = gen_udp:recv(Socket, 0),
io:format("Server received data ~p from socket ~p~n", [Data, Socket]),
{noreply, State}.
Then you need someone to periodically call process_message()
. The following seems to work:
start() ->
io:format("start~n"),
{ok, Server} = gen_server:start_link({local,?MODULE}, ?MODULE, [], []),
Poller = spawn(?MODULE, poll, []), %%<***** HERE *****
io:format("Server: ~w~nPoller: ~w~n", [Server,Poller]).
...
...
handle_cast(process_msg, #state{socket=Socket} = State) ->
case gen_udp:recv(Socket, 10000, 500) of
{error, timeout} -> %%Timeout message.
ok;
{error, Error} ->
io:format("Error: ~p~n", [Error]);
Data ->
io:format("Server received data ~p from socket ~p~n", [Data, Socket])
end,
{noreply, State}.
poll() ->
timer:sleep(1000),
process_message(),
poll().
As for the Length
in the recv()
, I'm not sure what you're supposed to specify: I tried 0, 2, and 10,000, and I couldn't discern a difference.
Here's my client:
client() ->
Port = 15000,
{ok, Socket} = gen_udp:open(0, [binary, {active, false}]),
gen_udp:send(Socket, "localhost", Port, "hello").
Note that open(0, ....)
instructs erlang to open any free port (the client and the server cannot open the same port if running on the same computer--contrary to what you need with a gen_tcp
socket). However, gen_udp:send()
must specify the same port that the server opened. Also, the atom localhost
and the list "localhost"
both work for me.
Complete server code:
-module(s2).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2, code_change/3]).
-export([start/0, process_message/0, poll/0]).
-record(state, {socket,
port,
local_ip,
broad_ip}).
%%======== PASSIVE SOCKET: {active,false} ===========
%% External interface:
start() ->
io:format("start~n"),
{ok, Server} = gen_server:start_link({local,?MODULE}, ?MODULE, [], []),
Poller = spawn(?MODULE, poll, []),
io:format("Server: ~w~nPoller: ~w~n", [Server,Poller]).
process_message() ->
gen_server:cast(?MODULE, process_msg).
poll() ->
timer:sleep(1000),
process_message(),
poll().
%%Internal server methods:
init([]) ->
Port = 15000,
{ok, Socket} = gen_udp:open(Port, [binary,
{active, false},
{reuseaddr, true}]),
{ok, #state{socket = Socket, port = Port}}.
handle_cast(process_msg, #state{socket=Socket} = State) ->
case gen_udp:recv(Socket, 10000, 500) of
{error, timeout} -> %%Timeout message.
ok;
{error, Error} ->
io:format("Error: ~p~n", [Error]);
Data ->
io:format("Server received data ~p from socket ~p~n", [Data, Socket])
end,
{noreply, State}.
handle_call(_Request, _From, State) ->
{noreply, State}.
handle_info(Msg, State) ->
io:format("Msg: ~w, State:~w~n", [Msg, State]),
{noreply, State}.
terminate(_Reason, #state{socket = LSocket}) ->
gen_udp:close(LSocket).
code_change(_OldVsn, State, _Extra) ->
{ok, State}.
In the shell:
shell #1---
$ erl
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V8.2 (abort with ^G)
1> c(s2).
{ok,s2}
2> s2:start().
start
Server: <0.64.0>
Poller: <0.65.0>
ok
shell #2--
$ erl
Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] [async-threads:10] [hipe] [kernel-poll:false]
Eshell V8.2 (abort with ^G)
1> c(c2).
{ok,c2}
2> c2:client().
ok
shell #1--
Server received data {ok,{{127,0,0,1},61841,<<"hello">>}} from socket #Port<0.2110>
3>
shell #2--
3> c2:client().
ok
4>
shell #1--
Server received data {ok,{{127,0,0,1},63983,<<"hello">>}} from socket #Port<0.2110>
3>
Upvotes: 5