【从零开始构建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
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值