`
hideto
  • 浏览: 2652172 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

Erlang:一个通用的网络服务器

阅读更多
原文: Erlang: A Generalized TCP Server

前面几篇文章里谈到了Erlang的gen_tcp网络编程和Erlang/OPT的gen_server模块,现在让我们将它们两者绑定在一起

大多数人认为“服务器”意味着网络服务器,但Erlang使用这个术语时表达的是更抽象的意义
gen_serer在Erlang里是基于它的消息传递协议来操作的服务器,我们可以在此基础上嫁接一个TCP服务器,但这需要一些工作

网络服务器的结构
大部分网络服务器有相似的架构
首先它们创建一个监听socket来监听接收的连接
然后它们进入一个接收状态,在这里一直循环接收新的连接,直到结束(结束表示连接已经到达并开始真正的client/server工作)

先看看前面网络编程里的echo server的例子:
-module(echo).
-author('Jesse E.I. Farmer <jesse@20bits.com>').
-export([listen/1]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

% Call echo:listen(Port) to start the service.
listen(Port) ->
    {ok, LSocket} = gen_tcp:listen(Port, ?TCP_OPTIONS),
    accept(LSocket).

% Wait for incoming connections and spawn the echo loop when we get one.
accept(LSocket) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    spawn(fun() -> loop(Socket) end),
    accept(LSocket).

% Echo back whatever data we receive on Socket.
loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            loop(Socket);
        {error, closed} ->
            ok
    end.

你可以看到,listen会创建一个监听socket并马上调用accept
accept会等待进来的连接,创建一个新的worker(loop)来处理真正的工作,然后等待下一个连接

在这部分代码里,父进程拥有listen socket和accept loop两者
后面我们会看到,如果我们集成accept/listen loop和gen_server的话这样做并不好

抽象网络服务器
网络服务器有两部分:连接处理和业务逻辑
上面讲到,连接处理对每个网络服务器都是几乎一样的
理想状态下我们可以这样做:
-module(my_server).
start(Port) ->
  connection_handler:start(my_server, Port, businees_logic).

business_logic(Socket) ->
  % Read data from the network socket and do our thang!

让我们继续完成它

实现一个通用网络服务器
使用gen_server来实现一个网络服务器的问题是,gen_tcp:accept调用是堵塞的
如果我们在服务器的初始化例程里调用它,那么整个gen_server机制都会堵塞,直到客户端建立连接

有两种方式来绕过这个问题
一种方式为使用低级连接机制来支持非堵塞(或异步)accept
有许多方法来支持这样做,最值得注意的是gen_tcp:controlling_process,它帮你管理当客户端建立连接时谁接受了什么消息

我认为另一种比较简单而更优雅的方式是,一个单独的进程来监听socket
该进程做两件事:监听“接收连接”消息以及分配新的接收器
当它接收一条新的“接收连接”的消息时,就知道该分配新的接收器了

接收器可以任意调用堵塞的gen_tcp:accept,因为它允许在自己的进程里
当它接受一个连接后,它发出一条异步消息传回给父进程,并且立即调用业务逻辑方法

这里是代码,我加了一些注释,希望可读性还可以:
-module(socket_server).
-author('Jesse E.I. Farmer <jesse@20bits.com>').
-behavior(gen_server).

-export([init/1, code_change/3, handle_call/3, handle_cast/2, handle_info/2, terminate/2]).
-export([accept_loop/1]).
-export([start/3]).

-define(TCP_OPTIONS, [binary, {packet, 0}, {active, false}, {reuseaddr, true}]).

-record(server_state, {
        port,
        loop,
        ip=any,
        lsocket=null}).

start(Name, Port, Loop) ->
    State = #server_state{port = Port, loop = Loop},
    gen_server:start_link({local, Name}, ?MODULE, State, []).

init(State = #server_state{port=Port}) ->
    case gen_tcp:listen(Port, ?TCP_OPTIONS) of
        {ok, LSocket} ->
            NewState = State#server_state{lsocket = LSocket},
            {ok, accept(NewState)};
        {error, Reason} ->
            {stop, Reason}
    end.

handle_cast({accepted, _Pid}, State=#server_state{}) ->
    {noreply, accept(State)}.

accept_loop({Server, LSocket, {M, F}}) ->
    {ok, Socket} = gen_tcp:accept(LSocket),
    % Let the server spawn a new process and replace this loop
    % with the echo loop, to avoid blocking
    gen_server:cast(Server, {accepted, self()}),
    M:F(Socket).
   
% To be more robust we should be using spawn_link and trapping exits
accept(State = #server_state{lsocket=LSocket, loop = Loop}) ->
    proc_lib:spawn(?MODULE, accept_loop, [{self(), LSocket, Loop}]),
    State.

% These are just here to suppress warnings.
handle_call(_Msg, _Caller, State) -> {noreply, State}.
handle_info(_Msg, Library) -> {noreply, Library}.
terminate(_Reason, _Library) -> ok.
code_change(_OldVersion, Library, _Extra) -> {ok, Library}.

我们使用gen_server:cast来传递异步消息给监听进程,当监听进程接受accepted消息后,它分配一个新的接收器

目前,这个服务器不是很健壮,因为如果无论什么原因活动的接收器失败以后,服务器会停止接收新的连接
为了让它变得更像OTP,我们因该捕获异常退出并且在连接失败时分配新的接收器

一个通用的echo服务器
echo服务器是最简单的服务器,让我们使用我们新的抽象socket服务器来写它:
-module(echo_server).
-author('Jesse E.I. Farmer <jesse@20bits.com>').

-export([start/0, loop/1]).

% echo_server specific code
start() ->
    socket_server:start(?MODULE, 7000, {?MODULE, loop}).
loop(Socket) ->
    case gen_tcp:recv(Socket, 0) of
        {ok, Data} ->
            gen_tcp:send(Socket, Data),
            loop(Socket);
        {error, closed} ->
            ok
    end.

你可以看到,服务器只含有自己的业务逻辑
连接处理被封装到socket_server里面
而这里的loop方法也和最初的echo服务器一样

希望你可以从中学到点什么,我觉得我开始理解Erlang了

欢迎回复,特别关于是如何改进我的代码,cheers!
分享到:
评论
1 楼 山脚下的农民 2011-06-24  
非常好,文章很清晰,代码可执行

相关推荐

    gen-batch-server:用于Erlang和Elixir的通用批处理服务器

    gen-batch-server:用于Erlang和Elixir的通用批处理服务器

    aberth:Erlang中的通用BERT-RPC服务器

    aberth-用Erlang编写的通用BERT-RPC服务器版权所有(c)2014 Aleksandar Radulovic。 版本: 0.9 aberth是Erlang中的通用BERT-RPC服务器。 它公开了常规的erlang模块,并使用作为TCP接受者...创建一个简单的模块(或两

    gen_tcp_server:Erlang 应用程序的通用 TCP 服务器

    通用 TCP 服务器 通用 TCP 服务器( gen_tcp_server ) 是一种 Erlang 行为,提供快速简便的方法将 TCP 服务器功能添加到您的应用程序。 它被实现为管理 TCP 连接的主管,因为它是孩子。如何使用它? 运行make来构建。...

    esockd:Erlang通用非阻塞TCPSSL套接字服务器

    特征通用非阻塞TCP / SSL套接字服务器接受者池和异步TCP接受UDP / DTLS服务器最大连接管理通过对等地址允许/拒绝代理协议V1 / V2 Keepalive支持速率限制IPv6支持用法一个简单的TCP Echo服务器: -module(echo_server...

    elli:简单,健壮和高性能的Erlang Web服务器

    elli-用于HTTP API的Erlang Web服务器 Elli是一个网络服务器,您可以在您的Erlang应用程序中运行以公开HTTP API。 Elli专门致力于构建高吞吐量,低延迟的HTTP API。 如果健壮性和性能比通用功能更重要,那么elli可能...

    stun:用于Erlang Elixir的STUN和TURN库

    建造这是一个纯Erlang的实现,因此您不需要为STUN,TURN,ICE代码安装特定的C库。 但是,此代码取决于ProcessOne ,后者取决于OpenSSL 1.0.0+库。通用构建您可以使用以下命令触发构建: make用法以下序列描述了STUN...

    gen_smtp:可扩展的Erlang SMTP客户端和服务器库

    提供通用的Erlang SMTP服务器框架,可以通过OTP样式的回调模块进行扩展。 还包括一个纯Erlang SMTP客户端。 目的是使在Erlang中收发电子邮件变得容易,而又省却POP / IMAP的麻烦。 这不是一个完整的邮件服务器-尽管...

    mdns:适用于Erlang的更通用的mDNS,Zeroconf,Avahi客户端服务器

    用于Erlang的mDNS库 用于Erlang的多播DNS库。 这使您可以通过与DNS用于单播DNS相同的协议来自动配置小型网络。

    lunatic:Lunatic是受Erlang启发的WebAssembly运行时

    Lunatic是用于快速,健壮和可扩展的服务器端应用程序的通用运行时。 它受Erlang的启发,可以从任何编译为语言中使用。 您可以在阅读有关Lunatic背后动机的更多信息。 当前,我们提供的图书馆可充分利用Lunatic的...

    barrel_tcp:bucket_tcp是通用的TCP接受器池,在Erlang中具有低延迟

    桶-通用TCP接受器池版权所有(c)2013BenoîtChesneau。 版本: 2.1 桶是通用的TCP接受器池,在Erlang中具有低延迟。...用法创建一个简单的TCP回显服务器。 创建一个简单的回声处理程序 -module(echo_handler).-ex

    erlang——gen-server.pdf

    Gen_server实现了通用服务器client_server原理,几个不同的客户端去分享服务端管理的资源(如图),gen_server提供标准的接口函数和包含追踪功能以及错误报告来实现通用的服务器,同时可以作为OTP监控树的一部分。...

    us-common:这是构建各种通用服务元素的基础

    通用网络服务器:请参阅 该存储库的确收集了美国基础设施的基础设施。 因此,它是总括项目的一部分。 美国通用取决于项目的各个要素,即: 请参阅以获取更多信息,否则请参阅其。 “主”分支旨在成为该层的当前...

    designing-for-scalability-with-erlang-otp-exercises:“Designing for Scalability with ErlangOTP”书籍练习

    在这个存储库中,我将阅读 Francesco Cesarini 和 Steve Vinoski 所著的“Designing for scaling with Erlang and Otp”一书时编写的代码组合在一起,你可以在购买这本书。 指数 第 3 章:行为 第 4 章:通用服务器

    sumo_rest:通用牛仔处理程序与Sumo合作

    相扑休息 通用Cowboy处理程序可与Sumo DB一起使用 ... 这样,我们可以在每个应用程序中都有一个base_handler ,所有通用处理程序逻辑都存在于该应用程序中。 最终,所有应用程序都共享相同的base_han

    kraken:实时应用程序的分布式Pubsub服务器

    概述Kraken是一种分布式pubsub服务器,旨在为诸如类的协作实时应用程序提供支持。 应用程序使用Kraken通过主题发送和接收消息。 这些消息通常仅包含足够的信息,以标识在消息发布之前客户端更改的数据集。 当其他...

    Microsoft开源Orleans云计算web框架 Orleans.zip

     Orleans是一种新的编程模式,用来提升微软通用语言运行库(CLR)的抽象水平,它引入了“grains”的概念,这是一个可以在数据中心之 间迁移的计算和数据存储单元。Orleans自身还将提供很多运行时,包括Geo-...

    talk:UDP数据处理,用于选择性确认和重新发送

    讲话==== talk是一个erlang通用服务器,该书负责通过UDP进行对话。 这在游戏中最有用。 UDP不保证数据包的顺序或其传递,因此必须由应用程序处理。 通过不做这种保证,与TCP相比,它需要更少的往返,因此速度要快得...

    中间件RabbitMQ之运维篇

    采用Erlang实现的工业级的消息队列(MQ)服务器。AMQP(高级消息队列协议)是一个异步消息传递所使用的应用层协议规范,作为线路层协议,而不是API (例如JMS),AMQP客户端能够无视消息的来源任意发送和接受信息。AMQP的...

    gen_http:具有可插拔客户端实现的实验性通用HTTP接口

    gen_http 具有可插入客户端实现的实验性通用HTTP接口。 在原则上,服务器接口也很好,但是就目前而言,我没有什么好主意,也没有任何具体的计划(或需求)来提出一些建议。

Global site tag (gtag.js) - Google Analytics