转自:http://blog.csdn.net/wwh578867817/article/details/49935199
lib_chan 是《Programming Erlang》 作者写的一种构建分布式系统的组件,思想很值得学习。
刚开始学习 Erlang,感觉编写代码的风格不是很适应,但感觉思想非常 nice。
看到书的中间部分用到了它,不想用的不明不白,所以打算阅读下,并做了简单地注释。若有错误还望指正。
我的一点看法:
我们如果要使用 lib_chan 构建一个分布式系统非常简单,只需要写上服务器的功能代码即可(注意编写一定要符合lib_chan的规范),任意客户端都可以在远程调用服务器的功能函数并取得结果。感觉类似 RPC,书上有例子,这里就不举例了(注意:lib_chan_auth 中的 lib_md5:string 函数不存在,替换为erlang:md5 即可)。
我觉得最好的一点就是中间人思想。
(图片来源《Programming Erlang》)
假设 P1 是客户端, P2 是服务器,MM1 和 MM2 是中间人。客户端和服务器与中间人通过 Pid ! Message 进行通信,中间人与中间人之间通过 Socket 进行通信。中间人负责编码/解码,认证等操作。它对应用程序隐藏了套接字相关内容,客户端和服务端获得的信息都是 Term 而不是 Binary。
另一点好处如上图,中间人和服务器可以并行的运行,中间负责编码/解码,服务器只负责结果,相比仅有服务器进行编码/解码以及响应请求的工作,添加中间人可以增加并发性。
其次,我们可以添加多个中间人,因为不同的请求请求的内容可能相同,添加不同的中间人我们可以解析不同的请求,解析后转化成相同的请求给服务器即可。
lib_chan
-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({ebanEnv, "HOME"});
Home ->
start_server(Home ++ "/.erlang_config/lib_chan.conf")
end.
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}}.
start_server1(ConfigData) ->
register(lib_chan, spawn(fun() -> start_server2(ConfigData) end)).
start_server2(ConfigData) ->
[Port] = [P || {port, P} <- ConfigData],
start_port_server(Port, 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) ->
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(Pwd, MM, ArgC, MFA) ->
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.
really_start(MM, ArgC, {Mod, Func, ArgS}) ->
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 dir with exit(normal) was:~p~n",
[Why])
end.
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}),
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).
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 138
- 139
- 140
- 141
- 142
- 143
- 144
- 145
- 146
- 147
- 148
- 149
- 150
- 151
- 152
- 153
- 154
- 155
- 156
- 157
- 158
- 159
- 160
- 161
- 162
- 163
- 164
- 165
- 166
- 167
- 168
- 169
- 170
- 171
- 172
- 173
- 174
- 175
- 176
- 177
- 178
- 179
- 180
- 181
- 182
- 183
- 184
- 185
- 186
- 187
- 188
- 189
- 190
- 191
- 192
- 193
- 194
- 195
- 196
- 197
- 198
- 199
- 200
- 201
- 202
- 203
- 204
- 205
- 206
- 207
- 208
- 209
- 210
- 211
- 212
- 213
- 214
- 215
- 216
- 217
- 218
- 219
- 220
- 221
- 222
- 223
- 224
- 225
- 226
- 227
- 228
- 229
- 230
- 231
- 232
- 233
- 234
- 235
- 236
- 237
- 238
lib_chan_cs
-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}]).
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 is_integer(Port) ->
Name = port_name(Port),
case whereis(Name) of
undefined ->
not_started;
Pid ->
exit(Pid, kill),
(catch unregister(Name)),
stopped
end.
children(Port) when is_integer(Port) ->
port_name(Port) ! {children, self()},
receive
{session_server, Reply} ->Reply
end.
port_name(Port) when is_integer(Port) ->
list_to_atom("portServer" ++ integer_to_list(Port)).
cold_start(Master, Port, Fun, Max, PacketLength) ->
process_flag(trap_exit, true),
case gen_tcp:listen(Port, [binary,
{nodelay, true},
{packet, PacketLength},
{reuseaddr, true},
{active, true}]) of
{ok, Listen} ->
Master ! {self(), ok},
New = start_accept(Listen, Fun),
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} ->
possibly_start_another(false, Listen, Active, Fun, Max);
{'EXIT', Pid, _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 is_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()},
inet:setopts(Socket, [{packet, 4},
binary,
{nodelay, true},
{active, true}]),
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;
_ ->
true
end
end.
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 105
- 106
- 107
- 108
- 109
- 110
- 111
- 112
- 113
- 114
- 115
- 116
- 117
- 118
- 119
- 120
- 121
- 122
- 123
- 124
- 125
- 126
- 127
- 128
- 129
- 130
- 131
- 132
- 133
- 134
- 135
- 136
- 137
lib_chan_mm
-module(lib_chan_mm).
-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) ->
process_flag(trap_exit, true),
loop1(Socket, Pid, false).
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).
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
lib_chan_auth
-module(lib_chan_auth).
-export([make_challenge/0, make_response/2, is_response_correct/3]).
make_challenge() ->
random_string(25).
make_response(Challenge, Secret) ->
erlang:md5(Challenge ++ Secret).
is_response_correct(Challenge, Response, Secret) ->
case erlang:md5(Challenge ++ Secret) of
Response -> true;
_ -> false
end.
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}).