erlang网络库
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.
 

129 wiersze
4.1 KiB

-module(nlUdpIns).
-export([
server/4
, count_peers/1
, stop/1
]).
-export([
init/1
, handleMsg/2
, terminate/2
]).
-record(state, {proto, sock, port, peers, mfa}).
-define(ERROR_MSG(Format, Args),
error_logger:error_msg("[~s]: " ++ Format, [?MODULE | Args])).
%%--------------------------------------------------------------------
%% API
%%--------------------------------------------------------------------
-spec(server(atom(), esockd:listen_on(), [gen_udp:option()], mfa())
-> {ok, pid()} | {error, term()}).
server(Proto, Port, Opts, MFA) when is_integer(Port) ->
gen_server:start_link(?MODULE, [Proto, Port, Opts, MFA], []);
server(Proto, {Host, Port}, Opts, MFA) when is_integer(Port) ->
IfAddr = case proplists:get_value(ip, Opts) of
undefined -> proplists:get_value(ifaddr, Opts);
Addr -> Addr
end,
(IfAddr == undefined) orelse (IfAddr = Host),
gen_server:start_link(?MODULE, [Proto, Port, merge_addr(Host, Opts), MFA], []).
merge_addr(Addr, Opts) ->
lists:keystore(ip, 1, Opts, {ip, Addr}).
-spec(count_peers(pid()) -> integer()).
count_peers(Pid) ->
gen_server:call(Pid, count_peers).
-spec(stop(pid()) -> ok).
stop(Pid) ->
gen_server:stop(Pid, normal, infinity).
%%--------------------------------------------------------------------
%% gen_server callbacks
%%--------------------------------------------------------------------
init([Proto, Port, Opts, MFA]) ->
process_flag(trap_exit, true),
case gen_udp:open(Port, esockd_util:merge_opts([binary, {reuseaddr, true}], Opts)) of
{ok, Sock} ->
%% Trigger the udp_passive event
inet:setopts(Sock, [{active, 1}]),
%% error_logger:info_msg("~s opened on udp ~p~n", [Proto, Port]),
{ok, #state{proto = Proto, sock = Sock, port = Port, peers = #{}, mfa = MFA}};
{error, Reason} ->
{stop, Reason}
end.
handleMsg(count_peers, _From, State = #state{peers = Peers}) ->
{reply, maps:size(Peers) div 2, State, hibernate};
handleMsg(Req, _From, State) ->
?ERROR_MSG("unexpected call: ~p", [Req]),
{reply, ignored, State}.
handleMsg({udp, Sock, IP, InPortNo, Packet},
State = #state{sock = Sock, peers = Peers, mfa = {M, F, Args}}) ->
Peer = {IP, InPortNo},
case maps:find(Peer, Peers) of
{ok, Pid} ->
Pid ! {datagram, self(), Packet},
{noreply, State};
error ->
try erlang:apply(M, F, [{udp, self(), Sock}, Peer | Args]) of
{ok, Pid} ->
_Ref = erlang:monitor(process, Pid),
Pid ! {datagram, self(), Packet},
{noreply, store_peer(Peer, Pid, State)};
{error, Reason} ->
?ERROR_MSG("Error returned. udp channel: ~s, reason: ~p",
[esockd_net:format(Peer), Reason]),
{noreply, State}
catch
_Error:Reason ->
?ERROR_MSG("Failed to start udp channel: ~s, reason: ~p",
[esockd_net:format(Peer), Reason]),
{noreply, State}
end
end;
handleMsg({udp_passive, Sock}, State) ->
%% TODO: rate limit here?
inet:setopts(Sock, [{active, 100}]),
{noreply, State, hibernate};
handleMsg({'DOWN', _MRef, process, DownPid, _Reason}, State = #state{peers = Peers}) ->
case maps:find(DownPid, Peers) of
{ok, Peer} ->
{noreply, erase_peer(Peer, DownPid, State)};
error -> {noreply, State}
end;
handleMsg({datagram, Peer = {IP, Port}, Packet}, State = #state{sock = Sock}) ->
case gen_udp:send(Sock, IP, Port, Packet) of
ok -> ok;
{error, Reason} ->
?ERROR_MSG("Dropped packet to: ~s, reason: ~s", [esockd_net:format(Peer), Reason])
end,
{noreply, State};
handleMsg(Info, State) ->
?ERROR_MSG("unexpected info: ~p", [Info]),
{noreply, State}.
terminate(_Reason, #state{sock = Sock}) ->
gen_udp:close(Sock).
store_peer(Peer, Pid, State = #state{peers = Peers}) ->
State#state{peers = maps:put(Pid, Peer, maps:put(Peer, Pid, Peers))}.
erase_peer(Peer, Pid, State = #state{peers = Peers}) ->
State#state{peers = maps:remove(Peer, maps:remove(Pid, Peers))}.