在 erlang的OTP中,gen_server作为通用服务器,是使用频率很高也很好用的一个行为模式,而elixir的底层就是erlang,自然逃不过OTP,包括其中的sup,gen—server等行为模式。
下面就看下在elixir中,它的语法和erlang的gen_server 有什么不同,看elixir的gen_server 是如何写的
一个GenServer实现分为两个部分:客户端API和服务端回调函数。
这两部分可以写在同一个模块里,也可以分开写到两个模块中。
客户端和服务端运行于不同进程,依靠调用客户端函数来与服务端来回传递消息。
方便起见,这里我们将这两部分写在一个模块中。
创建文件registry.ex
,包含以下内容:
defmodule GenServerTest do use GenServer ## Client API @doc """ Starts the registry. """ def start_link() do GenServer.start_link(__MODULE__, :ok, []) end @doc """ Looks up the bucket pid for `name` stored in `server`. Returns `{:ok, pid}` if the bucket exists, `:error` otherwise. """ def lookup(server, name) do GenServer.call(server, {:lookup, name}) end @doc """ Ensures there is a bucket associated to the given `name` in `server`. """ def create(server, name) do GenServer.cast(server, {:create, name}) end ## Server Callbacks def init(:ok) do {:ok, %{}} end def handle_call({:lookup, name}, _from, names) do {:reply, Map.fetch(names, name), names} end def handle_cast({:create, name}, names) do if Map.has_key?(names, name) do {:noreply, names} else {:ok, bucket} = KV.Bucket.start_link() {:noreply, Map.put(names, name, bucket)} end end end
第一个函数是start_link/0
,它传递三个参数启动了一个新的GenServer:
- 实现了服务器回调函数的模块名称。这里的
__MODULE__
指的是当前模块 - 初始参数,这里是
:ok
- 一组选项列表,比如可以存放服务器的名字。这里用个空列表
你可以向一个GenServer发送两种请求:call
和cast
。Call 是同步的,
服务器 必须 发送回复给该类请求。Cast 是异步的,服务器 不会 发送回复消息。
再往下的两个方法,lookup/2
和create/2
,它们用来发送这些请求给服务器。
这两种请求,会被第一个参数所指认的服务器中的handle_call/3
和handle_cast/2
函数处理(因此你的服务器回调函数必须包含这两个函数)。GenServer.call/2
和GenServer.cast/2
除了指认服务器之外,还告诉服务器它们要发送的请求。
这个请求存储在元组里,这里即{:lookup, name}
和{:create, name}
,
在下面写相应的回调处理函数时会用到。
这个消息元组第一个元素一般是要服务器做的事儿,后面的元素就是该动作的参数。
在服务器这边,我们要实现一系列服务器回调函数来实现服务器的启动、停止以及处理请求等。
回调函数是可选的,我们在这里只实现所关心的那几个。
第一个是init/1
回调函数,它接受一个状态参数(你在用户API中调用GenServer.start_link/3
中使用的那个),
返回{:ok, state}
。这里state
是一个新建的map。
我们现在已经可以观察到,GenServer的API中,客户端和服务器之间的界限十分明显。start_link/3
在客户端发生。
而其对应的init/1
在服务器端运行。
对于call
请求,我们在服务器端必须实现handle_call/3
回调函数。
参数:接收某请求(那个元组)、请求来源(_from
)以及当前服务器状态(names
)。handle_call/3
函数返回一个{:reply, reply, new_state}
元组。
其中,reply
是你要回复给客户端的东西,而new_statue
是新的服务器状态。
cast
请求,我们必须实现一个handle_cast/2
回调函数,接受参数:request
以及当前服务器状态(names
)。
这个函数返回{:noreply, new_state}
形式的元组。
这两个回调函数,handle_call/3
和handle_cast/2
还可以返回其它几种形式的元组。
还有另外几种回调函数,如terminate/2
和code_change/3
等。
可以参考完整的GenServer文档来学习相关知识。