Exception handling gen_服务器模块内部的异常处理最佳实践
我刚开始学习Erlang,这是我的一个测试项目中的一个模块。我这样做是为了更好地理解监督树的工作原理,练习快速失败代码和一些编程最佳实践Exception handling gen_服务器模块内部的异常处理最佳实践,exception-handling,erlang,Exception Handling,Erlang,我刚开始学习Erlang,这是我的一个测试项目中的一个模块。我这样做是为了更好地理解监督树的工作原理,练习快速失败代码和一些编程最佳实践 udp_侦听器进程侦听udp消息。它的作用是侦听来自网络中其他主机的通信请求,并使用UDP消息中定义的端口号通过TCP与它们联系 每当套接字接收到UDP消息时,就会调用handle\u info(…)函数,它对UDP消息进行解码并将其传递给tcp\u客户端进程 据我所知,我的代码中唯一的失败点是在句柄信息(…)中调用的解码udp\u消息(数据) 当此功能失败时
udp_侦听器
进程侦听udp消息。它的作用是侦听来自网络中其他主机的通信请求,并使用UDP消息中定义的端口号通过TCP与它们联系
每当套接字接收到UDP消息时,就会调用handle\u info(…)
函数,它对UDP消息进行解码并将其传递给tcp\u客户端
进程
据我所知,我的代码中唯一的失败点是在句柄信息(…)
中调用的解码udp\u消息(数据)
当此功能失败时,整个udp\u侦听器
进程是否重新启动?我应该阻止这种事情发生吗
难道不仅仅是handle\u info(…)
函数不应该在不影响udp\u侦听器的情况下悄无声息地消失吗
如何在解码udp\u消息(数据)
上记录异常?我想注册某个地方的主机,它的失败消息
-module(udp_listener).
-behaviour(gen_server).
-export([init/1, handle_call/3, handle_cast/2,
handle_info/2, terminate/2, code_change/3]).
%% ====================================================================
%% API functions
%% ====================================================================
-export([start_link/1]).
start_link(Port) ->
gen_server:start_link({local, ?MODULE}, ?MODULE, Port, []).
%% ====================================================================
%% Behavioural functions
%% ====================================================================
%% init/1
%% ====================================================================
-spec init(Port :: non_neg_integer()) -> Result when
Result :: {ok, Socket :: port()}
| {stop, Reason :: term()}.
%% ====================================================================
init(Port) ->
SocketTuple = gen_udp:open(Port, [binary, {active, true}]),
case SocketTuple of
{ok, Socket} -> {ok, Socket};
{error, eaddrinuse} -> {stop, udp_port_in_use};
{error, Reason} -> {stop, Reason}
end.
% Handles "!" messages from the socket
handle_info({udp, Socket, Host, _Port, Data}, State) -> Socket = State,
handle_ping(Host, Data),
{noreply, Socket}.
terminate(_Reason, State) -> Socket = State,
gen_udp:close(Socket).
handle_cast(_Request, State) -> {noreply, State}.
handle_call(_Request, _From, State) -> {noreply, State}.
code_change(_OldVsn, State, _Extra) -> {ok, State}.
%% ====================================================================
%% Internal functions
%% ====================================================================
handle_ping(Host, Data) ->
PortNumber = decode_udp_message(Data),
contact_host(Host, PortNumber).
decode_udp_message(Data) when is_binary(Data) ->
% First 16 bits == Port number
<<PortNumber:16>> = Data,
PortNumber.
contact_host(Host, PortNumber) ->
tcp_client:connect(Host, PortNumber).
我喜欢现在的方式,通过添加以下代码,我可以在未来处理协议更改,而不会丢失与旧服务器的向后兼容性:
handle_ping(Host, <<PortNumber:16, Foo:8, Bar:32>>) ->
contact_host(Host, PortNumber, Foo, Bar);
handle_ping(Host, <<PortNumber:16>>) ->
...
handle\u ping(主机,)->
联系_主机(主机、端口号、Foo、Bar);
句柄\u ping(主机,)->
...
@塞缪尔·里瓦斯
tcp_客户端
是另一个拥有自己的管理器的gen_服务器,它将处理自己的故障
->Socket=State
现在只出现在终止
功能中gen_udp:close(Socket)。
更容易吸引眼球 您的解码信息
不是唯一的故障点<代码>联系\u主机
很可能也会失败,但您要么忽略错误元组,要么在tcp\u客户端
实现中处理该故障
除此之外,如果您的udp\u侦听器是由具有正确策略的主管启动的,那么您的错误处理方法将起作用。如果数据
不完全是16位,则匹配将失败,进程将崩溃,出现错误匹配
异常。然后主管将开始一个新的
许多在线风格指南都会宣传这种风格。我认为他们错了。即使你想马上失败,但这并不意味着你不能提供一个比失败更好的理由。所以我会在那里写一些更好的错误处理。通常,我会抛出一个信息元组,但对于gen服务器来说,这是一个棘手的问题,因为它们将每个调用包装在一个catch
中,该catch将抛出值转换为有效值。这是很不幸的,但这是一个很长的解释主题,因此出于实际目的,我将在这里抛出错误。第三种选择是只使用错误元组({ok,Blah}{error,Reason}
),但是这会很快变得复杂。使用哪个选项也是一个长期解释/辩论的主题,所以现在我将继续使用我自己的方法
回到您的代码,如果您想要正确且信息丰富的错误管理,我将使用decode\u udp\u message
函数在这几行中做一些事情(保留您当前的语义,请参见本响应的末尾,因为我认为它们不是您想要的):
解码udp\u消息()->
端口号;
解码udp消息(Ohter)->
%%如果你愿意,你可以在这里登录,如果对你来说足够好的话,你也可以带着崩溃消息生活
erlang:错误({invalid_udp_message,{length,byte_size(Other)}})。
正如您所说,这将占用整个UDP连接。如果进程由主管重新启动,则它将重新连接(这可能会导致问题,除非您使用reuseaddr
sockopt)。除非您计划每秒失败很多次,并且打开连接成为一种负担,否则这将很好。如果是这样,你有几个选择
- 假设您可以控制所有的故障点并在那里处理错误而不会崩溃。例如,在此场景中,您可以忽略格式错误的消息。这在这样的简单场景中可能很好,但不安全,因为很容易忽略故障点
- 将要保持容错的关注点分开。在这种情况下,我将有一个进程来保存连接,另一个进程来解码消息。对于后者,您可以使用“解码服务器”,或者根据您的首选项和预期负载为每条消息生成一个
总结:
- 一旦代码发现超出正常行为范围的内容,就立即失败是一个好主意,但请记住使用管理器来恢复功能
- 就我的经验而言,让它崩溃是一种不好的做法,您应该努力寻找明确的错误原因,当您的系统增长时,这将使您的生活更加轻松
- 流程是隔离故障恢复范围的工具,如果您不希望一个系统受到故障/重新启动的影响,只需生成流程来处理要隔离的复杂性
- 有时,性能会受到影响,您需要在适当的位置妥协并处理错误,而不是让流程崩溃,但像往常一样,在这个意义上避免过早优化
关于与错误处理无关的代码的一些注释:
- 您在
decode\u udp\u消息
中的注释似乎暗示您希望解析前16位,但实际上您强制数据
正好为16位
- 在某些调用中,您执行类似于
->Socket=State
的操作,这种缩进可能是不好的样式,而且变量的重命名有些不必要
handle_ping(Host, <<PortNumber:16, Foo:8, Bar:32>>) ->
contact_host(Host, PortNumber, Foo, Bar);
handle_ping(Host, <<PortNumber:16>>) ->
...
decode_udp_message(<<PortNumber:16>>) ->
PortNumber;
decode_udp_message(Ohter) ->
%% You could log here if you want or live with the crash message if that is good enough for you
erlang:error({invalid_udp_message, {length, byte_size(Other)}}).