这章节是关于实现
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}).
2432902008176640000
4> lib_chan:rpc(S, {fibonacci, 15}).
610
5> 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