基于TCP的RPC服务器分析

本文旨在分享笔者分析RPC服务器的本质以及实践过程中的一些现象。
先上源码

%% 基于TCP的RPC服务器
%% TR server
-module(tr_server).
-behaviour(gen_server).

%% gen_server callback API
-export([init/1,handle_call/3,handle_cast/2,handle_info/2,terminate/2,code_change/3]).
%% 自定义API
-export([start_link/1,start_link/0,get_count/0,stop/0]).

-define(SERVER,?MODULE).
-define(DEFAULT_PORT,8081).

-record(state,{port,lsock,request_count = 0}).

start_link(Port) ->
    gen_server:start_link({local,?SERVER},?MODULE,[Port],[]).
start_link() ->
    start_link(?DEFAULT_PORT).

get_count() ->
    gen_server:call(?SERVER,get_count).

stop() ->
    gen_server:cast(?SERVER,stop).


%% gen_server
init([Port])->
    {ok, LSock} = gen_tcp:listen(Port,[{active,true}]),
    {ok, #state{port = Port, lsock = LSock}, 0}.

handle_call(get_count, _From, State) ->
    {reply, {ok ,State#state.request_count},State}.

handle_cast(stop, State) ->
    {stop, normal ,State}.

handle_info({tcp,Socket,RawData},State) ->
    do_rpc(Socket,RawData),
    RequestCount = State#state.request_count,
    {noreply,State#state{request_count = RequestCount + 1}};
handle_info(timeout,#state{lsock = LSock} = State) ->
    {ok, _Sock} = gen_tcp:accept(LSock),
    {noreply ,State}.

terminate(_Reason, _State) ->
    ok.

code_change(_OldVsn, State, _Extra) ->
    {ok, State}.


do_rpc(Socket, RawData) ->
    try
        {M, F, A} = split_out_mfa(RawData),
        Result = apply(M, F, A),
        gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Result]))
    catch
        _Class:Err ->
            gen_tcp:send(Socket, io_lib:fwrite("~p~n", [Err]))
    end.

split_out_mfa(RawData) ->
    MFA = re:replace(RawData, "\r\n$", "", [{return, list}]),
    {match, [M, F, A]} =
        re:run(MFA,
               "(.*):(.*)\s*\\((.*)\s*\\)\s*.\s*$",
                   [{capture, [1,2,3], list}, ungreedy]),
    {list_to_atom(M), list_to_atom(F), args_to_terms(A)}.

args_to_terms(RawArgs) ->
    {ok, Toks, _Line} = erl_scan:string("[" ++ RawArgs ++ "]. ", 1),
    {ok, Args} = erl_parse:parse_term(Toks),
    Args.

整个RPC服务器分为两部分组成,API以及回调函数。
我们直接先从Api开始:
用户实际上并不关心服务器如何运行,用户只关心服务器如何启用,并达到他想要的响应的效果
所以这里提供了四个方法(实际上是三个)给用户使用自定义API

  1. start_link 用户启动服务
  2. get_count 发送消息 并获取回复
  3. stop 停止服务
    这些是提供给用户的 用户可以通过TCP请求建立连接后 使用命令获得相应的服务
    通过我们的调用也可以分析出我们的RPC服务器其实本质上是一种gen_server通用服务器的派生

深入分析其作用机制如下:
start_link(Port) -> 启动服务器并链接服务器进程,当执行这个调用时,会派生出一个新的gen_server容器进程,新进程以SERVER宏展开并在本地节点完成注册,然后行为模式调用init完成初始化,服务器正式完成启动工作。值得注意的是,这些对于用户来说,全都是屏蔽的,用户只需要关心监听的接口即可!
get_count() -> 向服务器发送原子get_count,并获取应答结果。很简单,只需要注意不要随心所欲的设置交流的原子即可,会破坏服务器的可读性。
stop() -> 内部是一个cast,调用cast不会产生reply,对于即将关闭的服务,我们并不关心其返回值,所以调用cast而不是call。

第二部分就是回调函数段:
主要的处理函数
这几个是和开发给用户的三个接口是对应的,负责处理其中的逻辑问题。
init -> 初始化回调,启动gen_server容器进程时函数调用,首先其接受一个参数,参数代表用户想要监听的端口(值得注意的是,哪怕只有一个参数,也需要使用列表传递,这是一种规范),然后使用标准库中的tcp模块建立一个TCP监听,然后返回一个三元组,包含原子ok,初始化状态,和一个超时值0。(为什么是0,是希望init结束后立刻触发一次超时,立刻调用一次handle_info)。
handle_call -> 主要的处理函数,处理用户想要的业务。
handle_cast -> 异步回调,处理用户不太关心的操作,比如这里的stop
handle_info -> 带外消息,采用call和cast以外的手段发送的信息都由该方法处理,当我们的服务器需要与第三方模块通信,但是这种通信依赖直接消息通信而不是OTP库调用时,就需要使用这种方式。
do_rpc
内部函数区,这里的三个函数都是内部函数,因为不是本次分析的重点,所以这里简单介绍下功能,主要是四个作用:切分输入解析函数参数执行请求调用以及回传结果

接下来就是编译启动服务器:
编译与启动
很简单 一次启动成功,接下来我们用telnet测试tcp服务器是否运作
不过使用telnet需要该项服务处于启用状态
启动telnet
如图所示我们需要去系统中打开telnet才可以使用
打开后我们在cmd或者其他shell中使用
telnet localhost 8081
即可链接到tcp服务器,我们关闭shell
tcp关闭
可以看到erl的shell中成功打印了因为主动断开产生的tcp_closed信号,虽然我们没有处理tcp_closd的信号,但是也成功证明我们的服务器架设成功并且通过tcp成功链接

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值