lib_chan库学习

 

-module(lib_chan_cs).
%% 实现服务器端结构和机制的模块

-export([start_raw_server/4, start_raw_client/3]).
-export([stop/1]).
-export([children/1]).

%% 客户端调用,用来连接服务器
start_raw_client(Host, Port, PacketLength) ->
    gen_tcp:connect(Host, Port,
            [binary, {active, true}, {packet, PacketLength}]).

%%启动服务器
%%以给定端口创建名字,如果该端口已经注册则服务器已经启动
%%如果端口未注册,则新建进程启动服务器,如果成功启动,则注册端口为新建进程号
%%调用cold_start新建进程来启动服务器,需要传入当前进程以便能获取新建进程的消息
%%新建进程传回进程id以确保接收到的信息是由新建进程传回的。
start_raw_server(Port, Fun, Max, PacketLength) ->
    Name = port_name(Port),
    case whereis(Name) of
    undefined ->
        Self = self(),
        Pid = spawn_link(fun() ->
                 cold_start(Self,Port,Fun,Max,PacketLength)
                 end),
        receive
        {Pid, ok} ->
            register(Name, Pid),
            {ok, self()};
        {Pid, Error} ->
            Error
        end;
    _Pid ->
        {error, already_started}
    end.

stop(Port) when integer(Port) ->
    Name = port_name(Port),
    case whereis(Name) of
    undefined ->
        not_started;
    Pid ->
        exit(Pid, kill),
        (catch unregister(Name)),
        stopped
    end.


%%获取连接到某端口的socket
children(Port) when integer(Port) ->
    port_name(Port) ! {children, self()},
    receive
    {session_server, Reply} -> Reply
    end.


port_name(Port) when integer(Port) ->
    list_to_atom("portServer" ++ integer_to_list(Port)).

%%监听端口
%%开始新建进程接受客户端连接,如果接收到连接,则调用Fun(Socket)
%%如果一个进程已经接收了一个连接,则需要再次新建进程接收客户端连接
%%最多只能接受Max个连接,超过了后将不再新建进程接收客户端连接,直到有其他的连接结束
cold_start(Master, Port, Fun, Max, PacketLength) ->
    process_flag(trap_exit, true),
    %% io:format("Starting a port server on ~p...~n",[Port]),
    case gen_tcp:listen(Port, [binary,
                   %% {dontroute, true},
                   {nodelay,true},
                   {packet, PacketLength},
                   {reuseaddr, true}, 
                   {active, true}]) of
    {ok, Listen} ->
        %% io:format("Listening to:~p~n",[Listen]),
        Master ! {self(), ok},
        New = start_accept(Listen, Fun),
        %% Now we're ready to run
        socket_loop(Listen, New, [], Fun, Max);
    Error ->
        Master ! {self(), Error}
    end.


socket_loop(Listen, New, Active, Fun, Max) ->
    receive
    {istarted, New} ->
        Active1 = [New|Active],
        possibly_start_another(false,Listen,Active1,Fun,Max);
    {'EXIT', New, _Why} ->
        %% io:format("Child exit=~p~n",[Why]),
        possibly_start_another(false,Listen,Active,Fun,Max);
    {'EXIT', Pid, _Why} ->
        %% io:format("Child exit=~p~n",[Why]),
        Active1 = lists:delete(Pid, Active),
        possibly_start_another(New,Listen,Active1,Fun,Max);
    {children, From} ->
        From ! {session_server, Active},
        socket_loop(Listen,New,Active,Fun,Max);
    _Other ->
        socket_loop(Listen,New,Active,Fun,Max)
    end.


possibly_start_another(New, Listen, Active, Fun, Max) 
  when pid(New) ->
    socket_loop(Listen, New, Active, Fun, Max);
possibly_start_another(false, Listen, Active, Fun, Max) ->
    case length(Active) of
    N when N < Max ->
        New = start_accept(Listen, Fun),
        socket_loop(Listen, New, Active, Fun,Max);
    _ ->
        socket_loop(Listen, false, Active, Fun, Max)
    end.

start_accept(Listen, Fun) ->
    S = self(),
    spawn_link(fun() -> start_child(S, Listen, Fun) end).

start_child(Parent, Listen, Fun) ->
    case gen_tcp:accept(Listen) of
    {ok, Socket} ->
        Parent ! {istarted,self()},            % tell the controller
        inet:setopts(Socket, [{packet,4},
                  binary,
                  {nodelay,true},
                  {active, true}]), 
        %% before we activate socket
        %% io:format("running the child:~p Fun=~p~n", [Socket, Fun]),
        process_flag(trap_exit, true),
        case (catch Fun(Socket)) of
        {'EXIT', normal} ->
            true;
        {'EXIT', Why} ->
            io:format("Port process dies with exit:~p~n",[Why]),
            true;
        _ ->
            %% not an exit so everything's ok
            true
        end
    end.
lib_chan_cs

 

 

%%验证模块
-module(lib_chan_auth).

-export([make_challenge/0, make_response/2, is_response_correct/3]).

make_challenge() ->
    random_string(25).

make_response(Challenge, Secret) ->
    lib_md5:string(Challenge ++ Secret).

is_response_correct(Challenge, Response, Secret) ->
    case lib_md5:string(Challenge ++ Secret) of
    Response -> true;
    _        -> false
    end.

%% random_string(N) -> a random string with N characters.

random_string(N) -> random_seed(), random_string(N, []).

random_string(0, D) -> D;
random_string(N, D) ->
    random_string(N-1, [random:uniform(26)-1+$a|D]).

random_seed() ->
    {_,_,X} = erlang:now(),
    {H,M,S} = time(),
    H1 = H * X rem 32767,
    M1 = M * X rem 32767,
    S1 = S * X rem 32767,
    put(random_seed, {H1,M1,S1}).
lib_chan_auth

 

 

-module(lib_chan_mm).
%% TCP中转
%% 导入该模块的模块可以应用send发送信息,并接收{chan, self(), Term}形式的信息

-export([loop/2, send/2, close/1, controller/2, set_trace/2, trace_with_tag/2]).

send(Pid, Term)       -> Pid ! {send, Term}.
close(Pid)            -> Pid ! close.
controller(Pid, Pid1) -> Pid ! {setController, Pid1}.
set_trace(Pid, X)     -> Pid ! {trace, X}.
    
trace_with_tag(Pid, Tag) ->
    set_trace(Pid, {true, 
            fun(Msg) -> 
                io:format("MM:~p ~p~n",[Tag, Msg]) 
            end}).
    
loop(Socket, Pid) ->
    %% trace_with_tag(self(), trace),
    process_flag(trap_exit, true),
    loop1(Socket, Pid, false).

%%实现对TCP发送信息,接收信息的封装
loop1(Socket, Pid, Trace) ->
    receive
    {tcp, Socket, Bin} ->
        Term = binary_to_term(Bin),
        trace_it(Trace,{socketReceived, Term}),
        Pid ! {chan, self(), Term},
        loop1(Socket, Pid, Trace);
    {tcp_closed, Socket} ->  
        trace_it(Trace, socketClosed),
        Pid ! {chan_closed, self()};
    {'EXIT', Pid, Why} ->
        trace_it(Trace,{controllingProcessExit, Why}),
        gen_tcp:close(Socket);
    {setController, Pid1} ->
        trace_it(Trace, {changedController, Pid}),
        loop1(Socket, Pid1, Trace);
    {trace, Trace1} ->
        trace_it(Trace, {setTrace, Trace1}),
        loop1(Socket, Pid, Trace1);
    close ->
        trace_it(Trace, closedByClient),
        gen_tcp:close(Socket);
    {send, Term}  ->
        trace_it(Trace, {sendingMessage, Term}),
        gen_tcp:send(Socket, term_to_binary(Term)),
        loop1(Socket, Pid, Trace);
    UUg ->
        io:format("lib_chan_mm: protocol error:~p~n",[UUg]),
        loop1(Socket, Pid, Trace)
    end.
trace_it(false, _)     -> void;
trace_it({true, F}, M) -> F(M).
lib_chan_mm

 

 

%%主要模块
-module(lib_chan).
-export([cast/2, start_server/0, start_server/1,
     connect/5, disconnect/1, rpc/2]).
-import(lists, [map/2, member/2, foreach/2]).
-import(lib_chan_mm, [send/2, close/1]).


start_server() ->
    case os:getenv("HOME") of
    false ->
        exit({ebadEnv, "HOME"});
    Home ->
        start_server(Home ++ "/.erlang_config/lib_chan.conf")
    end.

%%根据配置文件启动服务器
%%配置文件形如
%%{port, 2223}.
%%{service, chat, password,"AsDT67aQ",mfa,mod_chat_controller,start,[]}.
%% file:consult(ConfigFile)读取config文件
start_server(ConfigFile) ->
    io:format("lib_chan starting:~p~n",[ConfigFile]),
    case file:consult(ConfigFile) of
    {ok, ConfigData} ->
        io:format("ConfigData=~p~n",[ConfigData]),
        case check_terms(ConfigData) of
        [] ->
            start_server1(ConfigData);
        Errors ->
            exit({eDaemonConfig, Errors})
        end;
    {error, Why} ->
        exit({eDaemonConfig, Why})
    end.


check_terms(ConfigData) ->
    L = map(fun check_term/1, ConfigData),
    [X || {error, X} <- L].
            
check_term({port, P}) when is_integer(P)     -> ok;
check_term({service,_,password,_,mfa,_,_,_}) -> ok;
check_term(X) -> {error, {badTerm, X}}.

%%新建服务器进程并注册为lib_chan
start_server1(ConfigData) ->
    register(lib_chan, spawn(fun() -> start_server2(ConfigData) end)).


start_server2(ConfigData) ->
    %%从ConfigData中提取Port
    [Port] = [ P || {port,P} <- ConfigData],
    start_port_server(Port, ConfigData).

%%启动服务器,对每一个连接进入服务器的Socket调用start_port_instance(Socket,ConfigData)
start_port_server(Port, ConfigData) ->
    lib_chan_cs:start_raw_server(Port, 
                fun(Socket) -> 
                    start_port_instance(Socket, 
                                ConfigData) end,
                100,
                4).

start_port_instance(Socket, ConfigData) ->
    %% This is where the low-level connection is handled
    %% We must become a middle man
    %% But first we spawn a connection handler
    S = self(),
    Controller = spawn_link(fun() -> start_erl_port_server(S, ConfigData) end),
    lib_chan_mm:loop(Socket, Controller).

start_erl_port_server(MM, ConfigData) ->
    receive
    {chan, MM, {startService, Mod, ArgC}} ->
        case get_service_definition(Mod, ConfigData) of
        {yes, Pwd, MFA} ->
            case Pwd of
            none ->
                send(MM, ack),
                really_start(MM, ArgC, MFA);
            _ ->
                do_authentication(Pwd, MM, ArgC, MFA)
            end;
        no ->
            io:format("sending bad service~n"),
            send(MM, badService),
            close(MM)
        end;
    Any ->
        io:format("*** ErL port server got:~p ~p~n",[MM, Any]),
        exit({protocolViolation, Any})
    end.

%%服务器用do_authentication进行验证,验证成功才对socket真正启动服务器的服务
do_authentication(Pwd, MM, ArgC, MFA) ->
    %%生成25位随机码
    C = lib_chan_auth:make_challenge(),
    send(MM, {challenge, C}),
    receive
    {chan, MM, {response, R}} ->
        case lib_chan_auth:is_response_correct(C, R, Pwd) of
        true ->
            send(MM, ack),
            really_start(MM, ArgC, MFA);
        false ->
            send(MM, authFail),
            close(MM)
        end
    end.


%% MM is the middle man
%% Mod is the Module we want to execute ArgC and ArgS come from the client and
%% server respectively

really_start(MM, ArgC, {Mod, Func, ArgS}) ->
    %% authentication worked so now we're off
    case (catch apply(Mod,Func,[MM,ArgC,ArgS])) of
    {'EXIT', normal} ->
        true;
    {'EXIT', Why} ->
        io:format("server error:~p~n",[Why]);
    Why ->
        io:format("server error should die with exit(normal) was:~p~n",
              [Why])
    end.

%% get_service_definition(Name, ConfigData)
%%获取服务的配置信息
get_service_definition(Mod, [{service, Mod, password, Pwd, mfa, M, F, A}|_]) ->
    {yes, Pwd, {M, F, A}};
get_service_definition(Name, [_|T]) ->
    get_service_definition(Name, T);
get_service_definition(_, []) ->
    no.

%%客户端连接服务器,先连接再进行验证
connect(Host, Port, Service, Secret, ArgC) ->
    S = self(),
    MM = spawn(fun() -> connect(S, Host, Port) end),
    receive
    {MM, ok} ->
        case authenticate(MM, Service, Secret, ArgC) of
        ok    -> {ok, MM};
        Error -> Error
        end;
    {MM, Error} ->
        Error
    end.

connect(Parent, Host, Port) ->
    case lib_chan_cs:start_raw_client(Host, Port, 4) of
    {ok, Socket} ->
        Parent ! {self(), ok},
        lib_chan_mm:loop(Socket, Parent);
    Error ->
        Parent ! {self(),  Error}
    end.

authenticate(MM, Service, Secret, ArgC) ->
    send(MM, {startService, Service, ArgC}),
    %% we should get back a challenge or a ack or closed socket
    receive
    {chan, MM, ack} ->
        ok;
    {chan, MM, {challenge, C}} ->
        R = lib_chan_auth:make_response(C, Secret),
        send(MM, {response, R}),
        receive
        {chan, MM, ack} ->
            ok;
        {chan, MM, authFail} ->
            wait_close(MM),
            {error, authFail};
        Other ->
            {error, Other}
        end;
    {chan, MM, badService} ->
        wait_close(MM),
        {error, badService};
    Other ->
        {error, Other}
    end.

wait_close(MM) ->
    receive
    {chan_closed, MM} ->
        true
    after 5000 ->
        io:format("**error lib_chan~n"),
        true
    end.

disconnect(MM) -> close(MM).

rpc(MM, Q) ->
    send(MM, Q),
    receive
    {chan, MM, Reply} ->
        Reply
    end.

cast(MM, Q) ->
    send(MM, Q).
lib_chan

 

该功能的使用方法为:
服务器端:
-import(lib_chan_mm, [send/2, controller/2]).
启动服务器
lib_chan:start_server("D:\\code\\erlcode\\socket_dist\\test.conf").
chat.conf为服务器的配置,如
{port, 2223}.
{service, test, password,"AsDT67aQ",mfa,mod_test_controller,start,[]}.

新启进程来接收消息


客户端:
调用lib_chan:connect(Host, Port, chat, Pwd, []) 连接服务器
将会调用配置文件中的mod_test_controller:start
连接好后用lib_chan_mm:send(MM, Msg).来给服务器发消息

 

使用方法如下例

 

-module(test_server).
-import(lib_chan_mm, [send/2, controller/2]).

-compile(export_all).


start() ->
    start_server(),
    lib_chan:start_server("D:\\code\\erlcode\\socket_dist\\test.conf").

start_server() ->
    register(test_server, 
         spawn(fun() ->
               process_flag(trap_exit, true),
               Val= (catch server_loop([])),
               io:format("Server terminated with:~p~n",[Val])
           end)).


server_loop(L) ->
    receive
    {mm, Channel, Msg} ->
        io:format("test_server received Msg=~p~n",[{Channel,Msg}]);
    Msg ->
        io:format("Server received Msg=~p~n",
              [Msg]),
        server_loop(L)
    end.
test_server

 

-module(mod_test_controller).
-export([start/3]).
-import(lib_chan_mm, [send/2]).

start(MM, _, _) ->
    process_flag(trap_exit, true),
    io:format("mod_test_controller start...~p~n",[MM]),
    loop(MM).

loop(MM) ->
     receive
     {chan, MM, Msg} ->
         test_server ! {mm, MM, Msg},
         loop(MM);
     Other ->
         io:format("mod_test_controller unexpected message =~p (MM=~p)~n",
               [Other, MM]),
         loop(MM)
    end. 
mod_test_controller

 

-module(test_client).

-export([start/0, connect/4]).


start() -> 
    connect("localhost", 2223, "AsDT67aQ", "a test msg").

connect(Host, Port, Pwd, Msg) ->
    io:format("test_client connect host:~p~n",[Msg]),
    case lib_chan:connect(Host, Port, test, Pwd, []) of
    {error, _Why} ->
        io:format("test_client can not connected:~p~n",[Msg]);
    {ok, MM} ->
          io:format("test_client connected:~p~n",[Msg]),
          lib_chan_mm:send(MM, Msg)
end.
test_client

 



转载于:https://www.cnblogs.com/studynote/p/3345222.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Libchan 是一个超轻量级的网络,能让使用不同通道的 goroutines 传递在网络服务中在相同的通道中交流:简单信息传递 异步编程同步 Nesting: channels can send channelsLibchan 支持下列传递:In-memory Go channelUnix socketRaw TCPTLSHTTP2/SPDYWebsocket通过分解应用程序为松散的耦合型并发服务为应用程序扩展提供了巨大便利。同样的应用程序可以在信道内存通道中组成 goroutines 传递;然后,过渡到独立 unix 进程中,每个都分配到进程核心处理器中,且通过高性能 IPC 进行信息传递;然后,通过身份验证 TLS 会话来实现集群信息通信。正是得益于并发模型,所以使得 Go 语言如此流行。并不是所有的传递都有相同的语法语义。内存 Go 通道只能确保一次精确的信息传输; TCP, TLS, 以及不同 HTTP 包之间不能保证传输包是否能能够顺利到达。按照顺序到达的包可能被延迟或丢失。 标签:libchan 分享 window._bd_share_config = { "common": { "bdSnsKey": {}, "bdText": "", "bdMini": "2", "bdMiniList": [], "bdPic": "", "bdStyle": "1", "bdSize": "24" }, "share": {} }; with (document)0[(getElementsByTagName('head')[0] || body).appendChild(createElement('script')).src = 'http://bdimg.share.baidu.com/static/api/js/share.js?v=89860593.js?cdnversion=' ~(-new Date() / 36e5)];\r\n \r\n \r\n \r\n \r\n \u8f6f\u4ef6\u9996\u9875\r\n \u8f6f\u4ef6\u4e0b\u8f7d\r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\n \r\nwindow.changyan.api.config({\r\nappid: 'cysXjLKDf', conf: 'prod_33c27aefa42004c9b2c12a759c851039' });
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值