Erlang + ranchでTCPサーバー

Erlang製サーバーcowboyから汎用サーバー部分を分離したranchを使ってTCPサーバー実装する方法のメモ

アプリケーション作成
rebarを落としてranch_sampleて名前のアプリケーションを作る
すでにあるアプリケーションに組み込む場合は不要ですが。

$ mkdir ranch_sample
$ cd ranch_sample
$ wget https://github.com/rebar/rebar/wiki/rebar
$ chmod 755 rebar
$ ./rebar create-app appid=ranch_sample

rebar.configを作成してdepsにranchを追加。こんな感じで

%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*-
%% ex: ts=4 sw=4 ft=erlang et

{erl_opts, [warnings_as_errors,
            warn_export_all,
            warn_unused_import,
            warn_untyped_record
            ]}.
{xref_checks, [undefined_function_calls]}.
{sub_dirs, ["rel"]}.

{edoc_opts, [{doclet, edown_doclet}, 
             {dialyzer_specs, all}, 
             {report_missing_type, true},
             {report_type_mismatch, true}, 
             {pretty_print, erl_pp},
             {preprocess, true}]}.

{validate_app_modules, true}.

{deps, [
	{ranch, "0.8.*", 
         {git, "git://github.com/extend/ranch.git", {branch, "master"}}}
       ]}.

とりあえずここまでのところで、ranchの取得、コンパイルが正常に行えることを確認。

$ ./rebar get-deps
$ ./rebar compile

サーバーの実装
まずはアプリケーション起動時にranch起動するようにする。

src/ranch_sample_app.erl

-module(ranch_sample_app).

-behaviour(application).

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

%% ===================================================================
%% Application callbacks
%% ===================================================================

start(_StartType, _StartArgs) ->
    NbAcceptors = 100,
    {ok, _} = ranch:start_listener(sample, NbAcceptors, ranch_tcp, 
				   [{port, 5555}], sample_protocol, []),
    rebar_sample_sup:start_link().

stop(_State) ->
    ok.

start/2の中でranchを起動しています。
NbAcceptorsは、クライアントからのコネクションを待機するプロセスの数。
それとsample_protocolは各コネクションに対する処理を移譲するモジュール名。

こんな感じ。ほぼexampleのecho_serverのままw

-module(sample_protocol).

%% API
-export([start_link/4, init/4]).

%%%===================================================================
%%% API
%%%===================================================================

%%--------------------------------------------------------------------
%% @doc
%% @end
%%--------------------------------------------------------------------
start_link(Ref, Socket, Transport, Opts) ->
    Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]),
    {ok, Pid}.

init(Ref, Socket, Transport, _Opts = []) ->
    ok = ranch:accept_ack(Ref),
    loop(Socket, Transport).

%%%===================================================================
%%% Internal functions
%%%===================================================================

loop(Socket, Transport) ->
    case Transport:recv(Socket, 0, 5000) of
	{ok, Data} ->
	    Response = list_to_binary([<<"You say ">>,  Data]),
	    Transport:send(Socket, Response),
	    loop(Socket, Transport);
	_ ->
	    ok = Transport:close(Socket)
    end.

動作確認
起動してみる

$ erl -pa ebin deps/*/ebin

ranchとrench_sampleを起動

> application:start(ranch).
ok
> application:start(ranch_sample).
ok

この状態でtelnetから接続してみる

$ telnet localhost 5555
hello #<- 入力
you say hello #<- サーバーからのレスポンス

あとはloopのところで実際の処理を書き足していく感じなのかな?