billcyz
billcyz

Reputation: 1389

erlang udp server can't receive accept packets

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

Answers (1)

7stud
7stud

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

Related Questions