首页 > Erlang并发教程 > 6.8 Erlang并发编程-“客户端-服务端”模型
2013
11-14

6.8 Erlang并发编程-“客户端-服务端”模型

“客户端-服务端”模型

注册进程的一个主要用途就是用于支持“客户端-服务端”模型编程。在这个模型中有一个服务端管理着一些资源,一些客户端通过向服务端发送请求来访问这些资源,如图5.4所示。要实现这个模型,我们需要三个基本组件——一个服务端,一个协议和一个访问库。我们将通过几个例子来阐明基本原则。

在先前的程序5.2中展示的counter模块里,每一根计数器都是一个服务端。客户端通过调用模块所定义的函数来访问服务端。

../_images/5.4.png图5.4

程序5.5中展示的例子是一个可以用于电话交换机系统里分析用户所拨打的号码的服务端。start()会调用spawn并将新建的进程注册为number_analyser,这就完成了号码分析服务端的创建。之后服务端进程会在server函数中不断循环并等待服务请求。如果收到了一个形如{add_number,Seq,Dest}的请求,该号码序列(Seq)以及对应的目标进程(Dest),以及分析出结果之后将会发送的目的地,会被添加到查找表中。这是由函数insert完成的。之后消息ack将会被发送到请求的进程。如果服务端收到了形如{analyse,Seq}的消息,那么它将通过调用lookup完成号码序列Seq的分析,并将包含分析结果的消息发回发送请求的进程。我们在这里没有给出函数insertlookup的具体定义,因为那对于我们目前讨论的问题而言并不重要。

客户端发送到服务端的请求消息包含了自己的进程标识符。这让服务端可以向客户端发送回复。发回的回复消息中也包含了一个“发送者”的标识,在这里就是服务端的注册名字,这使得客户端可以选择性地接收回复消息。这比简单地等待第一个消息到达要更加安全一些——因为客户端的邮箱中也许已经有了一些消息,或者其他进程也许会在服务端回复之前给客户端发送一些消息。

程序 5.5

-module(number_analyser).
-export([start/0,server/1]).
-export([add_number/2,analyse/1]).

start() ->
    register(number_analyser,
             spawn(number_analyser, server, [nil])).

%% The interface functions.
add_number(Seq, Dest) ->
    request({add_number,Seq,Dest}).

analyse(Seq) ->
    request({analyse,Seq}).

request(Req) ->
    number_analyser ! {self(), Req},
    receive
        {number_analyser, Reply} ->
            Reply
    end.

%% The server.
server(AnalTable) ->
    receive
        {From, {analyse,Seq}} ->
            Result = lookup(Seq, AnalTable),
            From ! {number_analyser, Result},
            server(AnalTable);
        {From, {add_number, Seq, Dest}} ->
            From ! {number_analyser, ack},
            server(insert(Seq, Dest, AnalTable))
    end.

现在我们已经实现了服务端并定义了协议。我们在这里使用了一个异步协议,每个发送到服务端的请求都会有一个回复。在服务端的回复中我们使用number_analyser(亦即服务端的注册名字)作为发送者标识,这样做是因为我们不希望暴露服务端的Pid

接下来我们定义一些接口函数用于以一种标准的方式访问服务端。函数add_numberanalyse按照上面描述的方式实现了客户端的协议。它们都使用了局部函数request来发送请求并接收回复。

程序5.6

-module(allocator).
-export([start/1,server/2,allocate/0,free/1]).

start(Resources) ->
    Pid = spawn(allocator, server, [Resources,[]]),
    register(resource_alloc, Pid).

% The interface functions.
allocate() ->
    request(alloc).

free(Resource) ->
    request({free,Resource}).

request(Request) ->
    resource_alloc ! {self(),Request},
    receive
        {resource_alloc,Reply} ->
            Reply
    end.

% The server.
server(Free, Allocated) ->
    receive
        {From,alloc} ->
            allocate(Free, Allocated, From);
        {From,{Free,R}} ->
            free(Free, Allocated, From, R)
    end.

allocate([R|Free], Allocated, From) ->
    From ! {resource_alloc,{yes,R}},
    server(Free, [{R,From}|Allocated]);
allocate([], Allocated, From) ->
    From ! {resource_alloc,no},
    server([], Allocated).

free(Free, Allocated, From, R) ->
    case lists:member({R,From}, Allocated) of
        true ->
            From ! {resource_alloc,ok},
            server([R|Free], lists:delete({R,From}, Allocated));
        false ->
            From ! {resource_alloc,error},
            server(Free, Allocated)
    end.

下一个例子是如程序5.6中所示的一个简单的资源分配器。服务端通过一个需要管理的初始的资源列表来启动。其他进程可以向服务端请求分配一个资源或者将不再使用的资源释放掉。

服务端进程维护两个列表,一个是未分配的资源列表,另一个是已分配的资源列表。通过将资源在两个列表之间移动,服务端可以追踪每个资源的分配情况。

当服务端收到一个请求分配资源的消息时,函数allocate/3会被调用,它会检查是否有未分配的资源存在,如果是则将资源放在回复给客户端的yes消息中发送回去,否则直接发回no消息。未分配资源列表是一个包含所有未分配资源的列表,而已分配资源列表是一个二元组{Resource,AllocPid}的列表。在一个资源被释放之前,亦即从已分配列表中删除并添加到未分配列表中去之前,我们首先会检查它是不是一个已知的资源,如果不是的话,就返回error


6.8 Erlang并发编程-“客户端-服务端”模型》有 4 条评论

  1. lovefang 说:

    今天看到这,希望自己继续坚持。。。

  2. 程序5.6 line27 应该是:{From,{free,R}} ->

  3. 快更新,快更新快更新

  4. 快更新,快更新快更新

留下一个回复

你的email不会被公开。