如何设计一个灵活的Erlang协议栈创建API

8
我对我目前的方法不满意,正在重新设计在Erlang中构建协议栈的方式。按重要性排序的特性如下:
  1. 性能

  2. 灵活性和实现速度,添加新协议变体

  3. 它将帮助开发人员从shell探索协议变体

我的当前模型(已在此问题中描述)除了通过函数调用发送()和通过消息接收()之间的丑陋不对称之外,已经达到了极限。
整个协议引擎的总体图像如下:

底部:

  • 每个堆栈的底部有几个端口,或者可能也有一个gen_tcp(对于独立通道有多个相同的堆栈,因此我们不能太静态地注册进程,必须在各处传递Pids)。

  • 在端口上面是一些由监督器管理的模块(与系统一起启动,在没有错误的情况下保持整个生命周期)。

顶部:

  • 由事件发生触发(在一般意义上而不是在事件处理程序意义上)的是面向连接的协议端点(例如具有connect()close()语义)。

  • 协议栈的顶部端可能只能动态启动,因为堆叠在一起形成堆栈的模块是动态可配置的,并且可能会从连接到连接改变。

  • 当前计划通过传递模块名称列表+可选参数从顶层消耗,当connect()沿着堆栈调用时。

  • 顶级进程将被链接,因此如果出现任何问题,整个连接都会失败。

模块类型和它们之间的通信类型

迄今为止发现了几种类型的模块:

  • 无状态过滤器模块

  • 带有状态的模块,一些适合gen_server,一些适合gen_fsm,但大多数可能是简单的服务器循环,因为选择性接收通常很有用并且可以简化代码。

层之间通信的类型:

  • 独立发送和接收数据包(从外部看独立)

  • 同步调用发送某些内容,阻塞直到有答案,然后将结果作为返回值返回。

  • 多路复用器向多个模块发出请求(这是我在这里的定义,以便讨论)

  • 具有不同附着点(当前由原子命名)与面向上方的模块进行通信的解复用器。

目前我的分离器只在静态底部堆栈中,而不在动态创建的顶部。当前仅在顶部存在复用器。

在处理我之前链接的问题的答案和评论中,我听说API通常只应该由函数组成,而不是消息,并且我同意这一点,除非有说服力的理由。

请原谅我冗长的问题说明,但我认为它仍然对所有类型的协议实现都有普遍用途。

我将在回答中写下我计划做的事情,并且还将解释结果的实现以及我的经验,以便在这里实现某些普遍有用的东西。

1个回答

2

作为回答的一部分,我会提供我目前计划的内容:

  • connect gets passed a List of modules to stack, looking like a proplist in case of params e.g:

    connect([module1, module2, {module3, [params3]}], param0, further_params)
    

    each layer strips off the head and calls the next layers connect.

  • connect() "somehow" passes fun references up and/or down the layers

    • send for async sending down the stack will be returned by the lower level connect
    • recv for async receiving up the stack will be passed as param to the lower level connect
    • call for sync sending and waiting for an reply returned -- not sure how to handle these, probably also returned from the lower level connect
  • Multiplexers routing lists might look like these

    connect([module1, multiplexer, [[m_a_1, m_a_2, {m_a_3, [param_a_3]}], 
                                    [m_b_1, m_b_2],
                                    [{m_c_1, [param_c_1]}, m_c_2]], param0, 
                                                                    further_params]).
    

目前我决定不会为同步调用添加额外的功能,我只是使用send进行操作。

对于一个无状态的模块,这种想法的实现示例如下:encode/1decode/1对数据包进行一些转换,例如将二进制表示解析成记录,然后再将其转回去:

connect(Chan, [Down|Rest], [], Recv_fun) ->
    {Down_module, Param} = case Down of
                               {F, P} -> {F, P};
                               F when is_atom (F) -> {F, []}
                           end,
    Send_fun = Down_module:connect(Chan, Rest, Param,
                                   fun(Packet) -> recv(Packet, Recv_fun) end),
    {ok, fun(Packet) -> send(Packet, Send_fun) end}.

send(Packet, Send_fun) ->
    Send_fun(encode(Packet)).

recv(Packet, Recv_fun) ->
    Recv_fun(decode(Packet)).

一旦我有一个有状态的例子,我也会发布它。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接