erlang udp服务器无法接收接受数据包

Posted

技术标签:

【中文标题】erlang udp服务器无法接收接受数据包【英文标题】:erlang udp server can't receive accept packets 【发布时间】:2017-10-13 20:07:29 【问题描述】:

我有一个用 gen_server 行为编写的简单 udp 服务器。当我运行它并尝试使用 gen_udp:send 发送消息时,服务器什么也没回复,好像 udp 服务器没有成功接受数据包。这是我的代码

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, #statesocket = Socket, port = Port.

handle_cast(_Request, State) ->
    noreply, State.

handle_call(_Request, _From, State) ->
    noreply, State.

handle_info(udp, _Socket, _Addr, _Port, Data, #statesocket = 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.

在服务器 192.168.146.129 上启动服务器:gen_udp_server:start_link(10000).

从 192.168.146.128 发送消息:

ok, Socket = gen_udp:open(4399, [binary, active, false]).
gen_udp:send(Socket, 192,168,146,129, 10000, "hello").

udp 服务器在收到数据包时应该打印一些消息,但我的失败了。谁能帮帮我?

【问题讨论】:

您是否厌倦了flush 消息并查看它是否到达了您拥有 gen_server 的节点?您收到的错误信息是什么? @matov 我试过冲洗,但我得到的只是“好的”。 【参考方案1】:

handle_info() 处理到达gen_server 邮箱的未知消息(即未处理的消息)。但是当进程以被动模式打开套接字时:active, false,发送到套接字的消息不会到达进程的邮箱。相反,该进程必须通过调用gen_udp:recv(Socket, Length) 从套接字手动读取消息。毕竟,创建被动套接字的全部目的是防止消息淹没进程的邮箱。因此,当客户端向被动套接字发送消息时,handle_info() 不会被调用。

此外,由于gen_server 是事件驱动的,您需要调用gen_udp:recv(Socket, Length) 来响应某些事件。例如,您可以定义服务器函数:

process_message() ->
    gen_server:cast(?MODULE, process_msg).

handle_cast(process_msg, #statesocket=Socket = State) ->
    Data = gen_udp:recv(Socket, 0),
    io:format("Server received data ~p from socket ~p~n", [Data, Socket]),
    noreply, State.

那么你需要有人定期打电话给process_message()。以下似乎有效:

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, #statesocket=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().

至于recv() 中的Length,我不确定您应该指定什么:我尝试了 0、2 和 10,000,但我看不出有什么区别。

这是我的客户:

client() ->
    Port = 15000,
    ok, Socket = gen_udp:open(0, [binary, active, false]),
    gen_udp:send(Socket, "localhost", Port, "hello").

请注意,open(0, ....) 指示 erlang 打开任何空闲端口(如果在同一台计算机上运行,​​客户端和服务器无法打开相同的端口——这与您需要 gen_tcp 套接字相反)。但是,gen_udp:send() 必须指定服务器打开的相同端口。此外,原子localhost 和列表"localhost" 都对我有用。

完整的服务器代码:

-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, #statesocket = Socket, port = Port.

handle_cast(process_msg, #statesocket=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, #statesocket = LSocket) ->
    gen_udp:close(LSocket).

code_change(_OldVsn, State, _Extra) ->
    ok, State.

在外壳中:

外壳#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

壳#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

外壳#1--

Server received data ok,127,0,0,1,61841,<<"hello">> from socket #Port<0.2110>
3> 

壳#2--

3> c2:client().
ok
4> 

外壳#1--

Server received data ok,127,0,0,1,63983,<<"hello">> from socket #Port<0.2110>
3> 

【讨论】:

以上是关于erlang udp服务器无法接收接受数据包的主要内容,如果未能解决你的问题,请参考以下文章

Erlang Udp 服务器丢弃大量数据包

python3套接字udp设置接受数据超时

Java UDP 客户端不接收数据包

UDP打孔:无法从服务器发送到客户端

无法在 Python 中接收 UDP 数据包

当我们从 UDP 服务器接收数据包时,为啥我们必须在单独的线程中接收它们?