这章节是关于实现 lib_chan 库的 。 lib_chan 的代码在 TCP/IP 之上实现了一个完整的网络层,能够提供认证和Erlang 数据流功能。一旦理解了 lib_chan 的原理,就能量身定制我们自己的通信基础结构,并把它叠加在TCP/IP 之上了。 就lib_chan 本身而言,它是一种构建分布式系统的有用组件。
一:简单示例:
用一个简单的示例来展示如何使用 lib_chan 。我们会创建一个简单的服务器,让它 计算阶乘和斐波那契数,并用一个密码来保护它。 这个服务器将在2233 端口工作。
创建服务器的过程共分四步。(1) 编写配置文件。(2) 编写服务器代码。(3) 启动服务器。(4) 通过网络访问服务器。
1. 编写配置文件
下述代码时这个示例的配置文件:
%% socket_dist/config1
{port,2233}.
{service, math, password, "qwerty", mfa, mod_math, run, []}.
这个配置文件里有一些 service 元组,它们的形式如下:
{service, <Name>, password, <P>, mfa, <Mod>, <Func>, <ArgList>}
里面的参数由原子 service、password 和 mfa 分隔。 mfa 是“ module, function, args ”的缩写, 意思是接下来的三个参数应当被解释为模块名、函数名和一个用来调用函数的参数列表。 在我们的示例里,配置文件指定了一个名为math (数学)的服务,它的工作端口是 2233 。这个服务由密码qwerty 保护,实现它的模块名为 mod_math ,启动方式是调用 mod_math:run/3 , run/3的第三个参数是 [ ]
2.编写服务器代码
这个数学服务器的代码如下:
%% socket_dist/mod_math.erl
-module(mod_math).
-export([run/3]).run(MM, ArgC, Args) ->io:format("mod_math:run_starting~n""Argc = ~p Args = ~p~n",[ArgC, Args]),loop(MM).loop(MM) ->receive{chan, MM, {factorial, N}} ->MM !{send, fac(N)},loop(MM);{chan, MM, {fibonacci, N}} ->MM !{send, fib(N)},loop(MM);{chan_closed, MM} ->io:format("mod_math stopping~n"),exit(normal)end.fac(0) -> 1;
fac(N) -> N*fac(N-1).
fib(1) -> 1;
fib(2) -> 1;
fib(N) -> fib(N-1) + fib(N-2).
当某个客户端连接到 2233 端口并请求 math 服务时, lib_auth 会对它进行认证,如果密码正确,就会通过mod_math:run(MM, ArgC, ArgS) 函数分裂出一个处理进程。 MM 是 中间人 的 PID , ArgC来自客户端, ArgS 则来自配置文件。这个数学服务器很简单,它所做的就是等待一个 {chan, MM, {factorial, N}}消息,然后执行 MM ! {send, fac(N)}来把结果发回客户端。
3.启动服务器
像下面这样启动服务器:
1> lib_chan:start_server("./configl").
ConfigData = [{port,2233},{service,math,password,"qwerty",mfa,mod_math,run,[]}
true
4.通过网络访问服务器
可以在单台机器上进行代码测试:
2> {ok, S} = lib_chan:connect("localhost", 2233, math,"qwerty", {yes, go}).
{ok,<0.47.0>}3> lib_chan:rpc(S, {factorial, 20}).
24329020081766400004> lib_chan:rpc(S, {fibonacci, 15}).
6105> lib_chan:disconnect(S).
close
二:lib_chan的原理
构建 lib_chan 使用了四个模块里的代码。
(1) lib_chan 扮演“主模块”的角色。程序员只需要了解 lib_chan 所导出的那些方法。其他三个模块(稍后讨论)会在 lib_chan 的内部使用。(2) lib_chan_mm 负责编码和解码 Erlang 消息,并管理套接字通信。(3)lib_chan_cs 负责设立服务器并管理客户端连接。它的主要工作之一是限制同时连接的最大客户端数量。(4) lib_chan_auth 包含的代码用于进行简单的质询 / 响应认证。
1. lib_chan
lib_chan 的结构如下:
-module(lib_chan).
start_server(ConfigFile) ->%% 读取配置文件并检查语法%% 调用start_port_server(Port, ConfigData)%% 其中Port是所需的端口,ConfigData包含配置数据...start_port_server(Port, ConfigData) ->lib_chan_cs:start_raw_server(fun(Socket) ->start_port_instance(Socket, ConfigData),end, ...).%% Lib_chan_cs负责管理连接。%% 新连接建立后会胡用start_raw_server的参数,%% 也就是这个fUn。
start_port_instance(Socket, ConfigData) ->%% 它会在客户瑞连接服务器时执行分裂。%% 我们会设立一个中间人并执行认证,%% 如果一切顺利就调用%% really_start (MM, ArgC, {Mod,Func,ArgS})%% (后三个参数来自配置文件)....really_start(MM, ArgC, {Mod, Func, Args}) ->apply(Mod, Func, [MM, ArgC, Args]).connect(Host,Port,Service,Password,Argc)->%% 客户瑞代码...
2.lib_chan_mm:中间人
lib_chan_mm 实现了一个中间人。它能对应用程序隐藏套接字通信,并把 TCP 套接字上的数据流转变成Erlang 消息。中间人负责组装消息(它可能是碎片化的)和编码 / 解码 Erlang 数据类型,也就是把它们转换成能通过套接字发送和接收的字节流。可以通过下图来进行理解:
带中间人的套接字通信
M1 机器上的 MM1 进程表现得就像是 P2 的代理,而在 M2 机器上的 MM2 进程表现得就像是 P1 的
代理。 MM1和 MM2 都是中间人进程的 PID 。中间人进程的代码如下:
loop(Socket, Pid) ->receive{tcp, Socket, Bin} ->Pid ! {chan, self(), binary_to_term(Bin)},loop(Socket, Pid);{tcp_closed, Socket} ->Pid ! {chan_closed, self()};close ->gen_tcp:close(Socket);{send, T} ->gen_tcp:send(Socket, [term_to_binary(T)]),loop(Socket, Pid)end.
这个循环是套接字数据和 Erlang 消息传输这两个世界之间的接口。
3.lib_chan_cs
lib_chan_cs 负责设立客户端和服务器通信。下面是它导出的两个重要方法:
(1) start_raw_server(Port, Max, Fun, PacketLength)
它会启动一个监听器来监听Port上的连接。允许的最大同时会话数是Max。Fu是一个元数为1的fin,Fun(Socket)会在连接开始时执行。套接字通信会假定包长度为PacketLength。(2) start:raw_client(Host, Port, PacketLength) => {ok, Socket} | {error, Why}
它会尝试连接由start_raw_server打开的端口。
4. lib_chan_auth
如果某个客户端想使用 math 服务,就必须向服务器证明它知道共享秘密。这个过程如下所示:
(1) 客户端向服务器发送一个请求来表示它希望使用 math 服务。(2) 服务器计算出一个随机字符串 C ,然后把它发给客户端。这就是 质询 。字符串是由 lib_chan_auth:make_challenge()函数生成的。可以用交互方式来看它是如何工作的:1> C = lib_chan_auth:make_challenge(). "qnyrgzqefvnjdombanrsmxikc"
(3) 客户端接收字符串( C )并计算出响应( R ),其中 R = MD5(C ++ Secret) ,它是由lib_chan_auth:make_response 生成的。这里有一个例子:2> R = lib_chan_auth:make_response(C,"qwerty"). "e759ef3778228beae988d91a67253873"
(4)这个响应被发回服务器。服务器接收响应并检查它是否正确,做法是算出预期的响应值。 这是由lib_chan_auth:is_response_correct实现的。如下:
3> lib_chan_auth:is_response_correct(C, R, "qwerty"). true
完整的lib_chan代码我打算单独列一篇来记录,因此本章就介绍一个例子和原理。
想要看lib_chan的详细代码可以跳转到下一篇:
http://t.csdnimg.cn/MXOOy