• 防范错误数据

    防范错误数据

    回想一下在第??章(程序??.5)中描述的那个用来分析电话号码的服务程序。它的主循环包含了以下代码:

    1. server(AnalTable) ->
    2. receive
    3. {From, {analyse,Seq}} ->
    4. Result = lookup(Seq, AnalTable),
    5. From ! {number_analyser, Result},
    6. server(AnalTable);
    7. {From, {add_number, Seq, Key}} ->
    8. From ! {number_analyser, ack},
    9. server(insert(Seq, Key, AnalTable))
    10. end.

    以上的Seq是一个表示电话号码的数字序列,如[5,2,4,8,9]。在编写lookup/2insert/3这两个函数时,我们应检查Seq是否是一个电话拨号按键字符[1]的列表。若不做这个检查,假设Seq是一个原子项hello,就会导致运行时错误。一个简单些的做法是将lookup/2insert/3放在一个catch语句的作用域中求值:

    1. server(AnalTable) ->
    2. receive
    3. {From, {analyse,Seq}} ->
    4. case catch lookup(Seq, AnalTable) of
    5. {'EXIT', _} ->
    6. From ! {number_analyser, error};
    7. Result ->
    8. From ! {number_analyser, Result}
    9. end,
    10. server(AnalTable);
    11. {From, {add_number, Seq, Key}} ->
    12. From ! {number_analyser, ack},
    13. case catch insert(Seq, Key, AnalTable) of
    14. {'EXIT', _} ->
    15. From ! {number_analyser, error},
    16. server(AnalTable); % Table not changed
    17. NewTable ->
    18. server(NewTable)
    19. end
    20. end.

    注意,借助catch我们的号码分析函数可以只处理正常情况,而让Erlang的错误处理机制去处理badmatchbadargfunction_clause等错误。

    一般来说,设计服务器时应注意即使面对错误的输入数据,服务器也不会“崩溃”。很多情况下发送给服务器的数据都来自服务器的访问函数。在上面的例子中,号码分析服务器获悉的客户端进程标识From是从访问函数获得的,例如:

    1. lookup(Seq) ->
    2. number_analyser ! {self(), {analyse,Seq}},
    3. receive
    4. {number_analyser, Result} ->
    5. Result
    6. end.

    服务器不需要检查From是否是一个进程标识。在这个案例中,我们(借助访问函数)来防范意外的错误情况。然而恶意程序仍然可以绕过访问函数,向服务器发送恶意数据致使服务器崩溃:

    1. number_analyser ! {55, [1,2,3]}

    这样一来号码分析器将试图向进程55发送分析结果,继而崩溃。