這章節是關于實現 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