套接字应用程序

这章节是关于实现 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, , password, 

, mfa, , , }

里面的参数由原子 servicepassword 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 数据类型,也就是把它们转换成能通过套接字发送和接收的字节流。可以通过下图来进行理解:

套接字应用程序_第1张图片

带中间人的套接字通信
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

你可能感兴趣的:(服务器,网络,linux)