【從零開始構建erlang服務器】-03用戶層和日誌

一、簡介
上一篇講了創建服務器項目以及添加ranch網絡庫,本篇利用網絡庫創建client socket消息處理的用戶層代碼以及服務器開發調試運維的日誌集成。

二、編寫用戶層代碼
前面知道網絡層處理客戶端連接,以及可以將客戶端socket文件描述符授權給其它worker進程來一對一爲客戶端服務(得益於erlang actor模型的輕進程,可以做到一個連接一個進程),而網絡庫我們使用的ranch,因此可以參照ranch的example程序編寫一個處理socket消息的進程,也就是爲用戶一對一服務器的用戶層代碼。

仿照ranch例子創建一個erlserver_user.erl文件

添加如下代碼:
-module(erlserver_user).

-behaviour(gen_server).
-behaviour(ranch_protocol).

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

%% gen_server.
-export([init/1]).
-export([handle_call/3]).
-export([handle_cast/2]).
-export([handle_info/2]).
-export([terminate/2]).
-export([code_change/3]).

-define(TIMEOUT, 5000).

-record(state, {socket, transport}).

%% API.

start_link(Ref, Socket, Transport, Opts) ->
    {ok, proc_lib:spawn_link(?MODULE, init, [{Ref, Socket, Transport, Opts}])}.

init({Ref, Socket, Transport, _Opts = []}) ->
    ok = ranch:accept_ack(Ref),
    ok = Transport:setopts(Socket, [{active, once}]),
    gen_server:enter_loop(?MODULE, [],
                          #state{socket=Socket, transport=Transport},
                          ?TIMEOUT).

handle_info({tcp, Socket, Data}, State=#state{
    socket=Socket, transport=Transport})
    when byte_size(Data) > 1 ->
    Transport:setopts(Socket, [{active, once}]),
    {ok, PeerName} = inet:peername(Socket),
   	io:format("receive from client[~w], msg:~p~n", [PeerName, Data]),
    {noreply, State, ?TIMEOUT};
handle_info({tcp_closed, _Socket}, State) ->
    {stop, normal, State};
handle_info({tcp_error, _, Reason}, State) ->
    {stop, Reason, State};
handle_info(timeout, State) ->
    {stop, normal, State};
handle_info(_Info, State) ->
    {stop, normal, State}.

handle_call(_Request, _From, State) ->
    {reply, ok, State}.

handle_cast(_Msg, State) ->
    {noreply, State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.
    閱讀過ranch源碼,就知道ranch_acceptor在監聽一個客戶端連接後,就會調用我們指定的模塊的start_link/4方法,當然,我們可以直接在start_link中啓動一個gen_server,不過ranch的這個例子因爲要有一個ranch:accept_ack操作,所以會阻塞在init,因此沒有用gen_server,用了prob_lib:spawn_link,立即返回一個可用的pid給ranch庫,然後在慢慢初始化,初始化完成後用gen_server:enter_loop將這個進程轉變爲一個gen_server進程。

三、啓動ranch監聽功能
上一篇只在app.src啓動了ranch,這只是表示應用啓動起來,要使ranch開始監聽某個端口,還需要顯示調用ranch:start_listener/5,這裏我們要監聽本機8888端口,並且使用我們在第二步寫好的客戶端socket套接字消息處理進程,其它tcp參數都使用默認,則我們在erlserver_app.erl啓動中添加以下代碼:

	{ok, _} = ranch:start_listener(erlserver,
                                   ranch_tcp, [{port, 8888}], erlserver_user, []),
    rebar3 shell啓動我們的應用測試一下是否可用,另開一臺機器進入erlang shell,輸入{ok, Fd} = gen_tcp:connect({xxx,xxx,xxx,xxx}, 8888, [binary, {active, false}, {packet, raw}]), gen_tcp:send(Fd, "for test").  看到輸出,就說明現在可以創建一個併發服務器了,並且每個客戶端連接都對應一個erlserver_user進程單獨處理。

四、添加lager日誌庫
在第二步的代碼中,我們用了io:format來打印收到的客戶端信息,這樣做其實是不好的,終端打印的消息無法輸出到文件記錄下來,並且io打印的消息都是原始消息,不方便調試,要同時輸出模塊、行號等信息,每一次的io打印還都要加上這些信息,總之,對於服務器來說,沒有一個專門的日誌記錄都是不好的。

1、rebar.config添加依賴:
{lager, {git, "https://github.com/erlang-lager/lager", {branch, "master"}}}

2、erlserver.app.src啓動lager(lager的順序儘量放在除erlang runtime庫之前,例如放在ranch之前,這樣我們就可以在其它應用啓動中也能使用lager輸出應用啓動的信息了)。

3、添加lager配置
這裏前期爲了調試方便,我們直接利用rebar3 shell加載一個本地調試配置文件
在項目根目錄創建一個config文件夾,並在裏面創建一個erlserver.config配置文件,輸入以下內容

[
    {lager, [
        {log_root, "./log"},
        {handlers, [
            {lager_console_backend,
             [{level, info}, {formatter, lager_default_formatter},
              {formatter_config, [date, "/", time, "[", severity, "][", module, ":", function, ":", line,"]", "|", message, "\n"]}]},
            {lager_file_backend,
             [{file, "error.log"}, {level, error}, {formatter, lager_default_formatter},
              {formatter_config, [date, "/", time, "[", module, ":", function, ":", line,"]", "|", message, "\n"]}]},
            {lager_file_backend,
             [{file, "console.log"}, {level, info}, {formatter, lager_default_formatter},
              {formatter_config, [date, "/", time, "[", module, ":", function, ":", line,"]", "|", message, "\n"]}]}
        ]}
    ]}
].
這就是一個lager的簡單配置,日誌都輸出到log目錄,並且info對應console.log文件(目前都是本地調試,並不是發佈的配置)。

        4、配置rebar3 shell讀取config文件以及添加lager的parse_transform
修改rebar.config的shell配置
{shell, [
    {apps, [erlserver, sync]},
    {config, "config/erlserver.config"}
]}.
修改rebar.config的erl_opts配置(這裏使用到的知識點可以參考我的另一篇erlang抽象語法樹博客
{erl_opts, [
    debug_info,
    {parse_transform, lager_transform}
]}.

五、使用日誌庫
修改第二步的erlserver_user的io:format打印爲lager:info("xxxxx"),再來嘗試客戶端連接並且傳輸信息。

六、後續
客戶端消息處理層(用戶層)以及日誌都寫好了,後續開始制定協議層了。


(project-https://github.com/xlkness/erlserver.git
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章