65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Erlang 应用中处理 WebSocket 连接

starIconstarIconstarIconemptyStarIconemptyStarIcon

3.00/5 (1投票)

2016年9月23日

CPOL

3分钟阅读

viewsIcon

10843

如何在 Erlang 应用中处理 WebSocket 连接

基本上,当需要 Erlang/OTP 应用程序通过 HTTP 或 WebSockets 与外部世界通信时,使用 Cowboy Web 服务器是一个非常好的主意。以下是使用它的一些优点:

  • 模块化,小巧但功能强大。 Cowboy 包括用于 http、rest、spdy 和 websockets 的不同基本处理程序。 你所需要做的就是更新你的应用程序依赖项,配置 Cowboy,创建你自己的“继承”自基本处理程序的处理程序;
  • 非常快。 Cowboy 将自定义处理程序作为单独的 Erlang 进程启动。 因此,如果你有很多 http 或 websocket 连接,它们将在 Erlang VM 中并发处理,这非常棒! 在我们的一个项目中,Cowboy 在具有 4 个 CPU 内核的单个 Amazon 实例上处理了 5 万个并发 websocket 连接;
  • 可以轻松地嵌入到另一个 Erlang 应用程序中。 并且在具有选择性调度和 fastCGI 支持的同时,可以将一些请求重定向到 PHP、Ruby、Python 应用程序。

更新应用程序依赖项

在 Oxagile,我们使用 rebar 作为构建工具。 基本上,它就像 PHP 应用程序的 composer,但更加高级。 因此,要将 cowboy 作为依赖项添加,我们应该通过将以下行添加到 deps 部分来更新我们的 rebar.config 文件

{deps, [
  {
    cowboy, 
    ".*",
    {git, "https://github.com/extend/cowboy.git", {tag, "1.0.3"}}
  },

  % other dependencies
  {
    mochijson2,
    ".*",
    {git, "https://github.com/tel/mochijson2.git", {tag, "2.3.2"}}
  },
  {
    amqp_client,
    ".*",
    {git, "git://github.com/jbrisbin/amqp_client.git", {tag, "3.5.2"}}
  }
]}

然后只需转到控制台并运行:$ rebar get deps

然后,请通过将 cowboy 条目添加到 applications 部分来更新你的 *_app.src 文件。 因此,当你将应用程序打包到 release 中时,Cowboy 将在发布启动时自动启动。

{application, demo,
[
 {description, "ws demo app"},
 {vsn, "0.0.1"},
 {registered, []},
 {applications, [
                 kernel,
                 stdlib,
                 inets,
                 cowboy,
                 mnesia
                ]},
 {mod, { demo_app, []}},
 {env, [  ]}
]}.

在你的应用程序中配置 Cowboy

假设我们的应用程序名称是 demo。 因此,你应该在你的 src 文件夹中拥有 demo_app.erl 文件。 让我们通过为 Cowboy 添加一些配置来修改它。 首先,我们将配置路由和 Cowboy 将侦听的端口。

-module(demo_app).

-behaviour(application).

-export([start/2, stop/1]).

start(_StartType, _StartArgs) ->

 Dispatch = cowboy_router:compile([
   {'_', [
       {"/ws", demo_cowboy_handler_ws, []},
       {"/auth/:token/:user", demo_cowboy_handler_rest, [auth]},
   ]}
 ]),

 {ok, _} = cowboy:start_http(http, 100, [{port, 8889}], [{env, [{dispatch, Dispatch}]}]),
 demo_sup:start_link().

stop(_State) ->
  ok.

在上面的示例中,我们添加了两个具有不同处理程序的路由,这些处理程序应该被实现,并告诉 Cowboy 监听 8889 端口。 此外,还可以监听多个端口。 你所需要做的就是多次调用 cowboy:start_http,但请注意,该函数的第一个参数(监听器名称)每次都应该不同。

start(_StartType, _StartArgs) ->

 DispatchWs = cowboy_router:compile([
   {'_', [
       {"/ws", demo_cowboy_handler_ws, []}
   ]}
 ]),

 DispatchRest = cowboy_router:compile([
   {'_', [
       {"/auth/:token/:user", demo_cowboy_handler_rest, [auth]}
   ]}
 ]),

 {ok, _} = cowboy:start_http(ws, 100, [{port, 8889}], [{env, [{dispatch, DispatchWs}]}]),
 {ok, _} = cowboy:start_http(http, 100, [{port, 8888}], [{env, [{dispatch, DispatchRest}]}]),
 demo_sup:start_link().

创建你的自定义 Cowboy 处理程序

剩下的最后一件事就是为 websocket 连接创建一个处理程序。 它是一个常规的 Erlang 模块,但应该“继承”自 cowboy_websocket_handler。 Erlang 行为很棒,因为它们具有明确定义的接口:一组具有自定义实现但具有已定义输入和输出的回调函数。 因此,对于 cowboy_websocket_handler,这些函数是

  • init - 初始化 websocket 连接(见下文)
  • websocket_init - 初始化 ws 连接的状态。

可以通过 cowboy_router:compile 传递一些初始化参数,如下所示

DispatchWs = cowboy_router:compile([
   {'_', [
       {"/ws", demo_cowboy_handler_ws, [SomeConfigParams]}
   ]}
 ]),

并且 websocket_init 回调中的第 3 个参数 Opts 将包含这些参数

  • websocket_info - 处理 Erlang 消息。 可以像任何其他 erlang 进程一样向处理程序的进程发送消息,例如 WsPid ! {data, SomeData}
  • websocket_handle - 处理从客户端接收的帧;
  • websocket_terminate - 处理 ws 连接的终止。 此函数在 websocket 终止之前被调用;

详细的回调规范可以在 这里 找到。 这是我们的演示处理程序的代码

-module(demo_cowboy_handler_ws).

-behaviour(cowboy_websocket_handler).

-export([init/3]).
-export([
   websocket_init/3, websocket_handle/3,
   websocket_info/3, websocket_terminate/3
]).

init({tcp, http}, _Req, _Opts) ->
 {upgrade, protocol, cowboy_websocket}.

websocket_init(_TransportName, Req, Opts) ->
 {ok, Req, undefined_state}.

websocket_handle({text, Msg}, Req, State) ->
 {reply, {text, << "responding to ", Msg/binary >>}, Req, State, hibernate }.

websocket_info({data, SomeData}, Req, State) ->
 {reply, {text, SomeData}, Req, State};
websocket_info(_Info, Req, State) ->
 {ok, Req, State, hibernate}.

websocket_terminate(_Reason, _Req, _State) ->
 ok.

基本上,这就是 websockets 的全部内容。 Cowboy 使事情变得更加容易,并允许我们关注应用程序逻辑。 为了更好地理解 cowboy_websocket_handler 行为,请参考文档,你可以在其中找到回调函数的输入和输出的详细说明。

我们认为,Cowboy websocket 处理程序中只存在一个小问题:无法在 websocket_init 回调中终止(带或不带消息)WS 连接,但是可以通过向 WS 进程发送 Erlang 消息并使用 websocket_info 回调处理它来轻松解决此问题。

总结

假设我们有一个软件解决方案,其中包括使用不同技术实现的多个模块,如下例所示:

因此,我们有 WebSocket 客户端和 PHP App 客户端(很可能这是 JavaScript 应用程序的 2 个部分)。 此应用程序通过 8080 端口发出 HTTP 请求,并使用 8089 端口建立 WS 连接。

为了处理这些连接,我们创建了 2 个独立的 Cowboy 处理程序。 所有其他连接都被防火墙禁止。 但是,在我们的集群(局域网)内部,我们允许所有连接。 因此,我们的内部应用程序可以向 Cowboy 和它的 rest 处理程序发出请求,该处理程序对外部世界是隐藏的。

© . All rights reserved.