防范错误数据
回想一下在第??章(程序??.5)中描述的那个用来分析电话号码的服务程序。它的主循环包含了以下代码:
- server(AnalTable) ->
- receive
- {From, {analyse,Seq}} ->
- Result = lookup(Seq, AnalTable),
- From ! {number_analyser, Result},
- server(AnalTable);
- {From, {add_number, Seq, Key}} ->
- From ! {number_analyser, ack},
- server(insert(Seq, Key, AnalTable))
- end.
以上的Seq是一个表示电话号码的数字序列,如[5,2,4,8,9]。在编写lookup/2和insert/3这两个函数时,我们应检查Seq是否是一个电话拨号按键字符[1]的列表。若不做这个检查,假设Seq是一个原子项hello,就会导致运行时错误。一个简单些的做法是将lookup/2和insert/3放在一个catch语句的作用域中求值:
- server(AnalTable) ->
- receive
- {From, {analyse,Seq}} ->
- case catch lookup(Seq, AnalTable) of
- {'EXIT', _} ->
- From ! {number_analyser, error};
- Result ->
- From ! {number_analyser, Result}
- end,
- server(AnalTable);
- {From, {add_number, Seq, Key}} ->
- From ! {number_analyser, ack},
- case catch insert(Seq, Key, AnalTable) of
- {'EXIT', _} ->
- From ! {number_analyser, error},
- server(AnalTable); % Table not changed
- NewTable ->
- server(NewTable)
- end
- end.
注意,借助catch我们的号码分析函数可以只处理正常情况,而让Erlang的错误处理机制去处理badmatch、badarg、function_clause等错误。
一般来说,设计服务器时应注意即使面对错误的输入数据,服务器也不会“崩溃”。很多情况下发送给服务器的数据都来自服务器的访问函数。在上面的例子中,号码分析服务器获悉的客户端进程标识From是从访问函数获得的,例如:
- lookup(Seq) ->
- number_analyser ! {self(), {analyse,Seq}},
- receive
- {number_analyser, Result} ->
- Result
- end.
服务器不需要检查From是否是一个进程标识。在这个案例中,我们(借助访问函数)来防范意外的错误情况。然而恶意程序仍然可以绕过访问函数,向服务器发送恶意数据致使服务器崩溃:
- number_analyser ! {55, [1,2,3]}
这样一来号码分析器将试图向进程55发送分析结果,继而崩溃。