@@ -109,10 +109,192 @@ true
109109> 然后即可执行命令 `erl - boot start_sasl - config elog4 - smp + S 12 - pa _build / default / lib / math_server / ebin ` 启动 Erlang shell ,并在其下执行上面的命令。
110110
111111
112+ ### 透过网络访问服务器
112113
113114
115+ 我们可以一台机器上,测试此代码。
114116
115117
118+ ```erlang
119+ 2 > {ok , S } = lib_chan :connect (" localhost" ,2233 ,math ," qwerty" ,{yes ,go }).
120+ {ok ,< 0.100 .0 > }
121+ mod_math : run starting
122+ ArgC = {yes ,go } ArgS = []
123+ 3 > lib_chan :rpc (S , {factorial ,20 }).
124+ 2432902008176640000
125+ 4 > lib_chan :rpc (S , {fibonacci ,15 }).
126+ 610
127+ 5 > lib_chan :disconnect (S ).
128+ close
129+ mod_math stopping
130+ ```
131+
132+ ## `lib_chan ` 工作原理
133+
134+
135+ `lib_chan ` 是使用四个模组中的代码构建的。
136+
137+ - `lib_chan ` 充当 “主模组”。程序员需要了解的例程,仅限 `lib_chan ` 中导出那些。其他三个模组(将在下文讨论)在 `lib_chan ` 实现内部用到;
138+ - `lib_chan_mm ` 会编码及解码 Erlang 消息,并管理套接字通信;
139+ - `lib_chan_cs ` 会建立该服务器,并管理客户端连接。他的主要任务之一,是限制客户端同时连接的最大数量;
140+ - `lib_chan_auth ` 包含着简单挑战/ 响应式验证的代码。
116141
117142
143+ ### `lib_chan `
144+
145+
146+ `lib_chan ` 有着如下结构:
147+
148+
149+ ```erlang
150+ - module (lib_chan ).
151+
152+
153+ start_server (ConfigFile ) ->
154+ % % read configuration file - check syntax
155+ % % call start_port_server(Port, ConfigData)
156+ % % where Port is the required Port and ConfigData
157+ % % contains the configuration data
158+
159+ start_port_server (Port , ConfigData ) ->
160+ lib_chan_cs :start_raw_server (Port ,
161+ fun (Socket ) ->
162+ start_port_instance (Socket , ConfigData ) end ,
163+ % % lib_chan_cs manages the connection
164+ % % when a new connection comes the fun which is an
165+ % % argument to start_raw_server will be called
166+
167+
168+ start_port_instance (Socket , ConfigData ) ->
169+ % % this is spawned when the client connects
170+ % % to the server. Here we setup a middle man,
171+ % % then perform authentication. If everything works call
172+ % % really_start(MM, ArgC, {Mod, Func, ArgS})
173+ % % (the last three arguments come from the configuration file
174+
175+
176+ really_start (MM , ArgC , {Mod , Func , ArgS }) ->
177+ apply (Mod , Func , [MM , ArgC , ArgS ]).
178+
179+
180+ connect (Host , Port , Service , Secret , ArgC ) ->
181+ % % client side code
182+ ```
183+
118184### `lib_chan_mm `:中间人
185+
186+ `lib_chan_mm ` 实现了一个中间人。他对应用隐藏了套接字通信,将 TCP 套接字上的数据流转换成 Erlang 消息。中间人负责组装消息(其可能已被分片),以及将 Erlang 项编码和解码为可在套接字上发送和接收的字节流。
187+
188+
189+ ! [中间人下的套接字通信](../ images / lib_chan_mm .png )
190+
191+ < a name = " fig-8" ></ a >
192+ ** 图 8 ** -- ** 中间人下的套接字通信**
193+
194+
195+ 现在快速浏览一下 [图 8 ,* 中间人下的套接字通信](# fig -8) 的好时机,其展示了我们的中间人架构。当机器 `M1` 上的某个进程 `P1` 想要发送一条消息 `T` 到机器 `M2` 上的进程 `P2`时,他会执行 `MM1 !{send , T }`。`MM1 ` 充当 `P2 ` 的 * 代理* 。发送到 `MM1 ` 的任何消息,都会被编码并写入套接字,并被发送到 `MM2 `。`MM2 ` 会解码他在套接字上接收到的任何东西,并将消息 `{chan , MM2 , T }` 发送给 `P2 `。
196+
197+
198+ 在机器 `M1 ` 上,进程 `MM1 ` 充当了 `P2 ` 的代理,而在 `M2 ` 上,进程 `MM2 ` 则会充当 `P1 ` 的代理。
199+
200+ `MM1 ` 和 `MM2 ` 是两个中间人进程的 PID 。中间人进程的代码,看起来像下面这样:
201+
202+ ```erlang
203+ loop (Socket , Pid ) ->
204+ receive
205+ {tcp , Socket , Bin } ->
206+ Pid ! {chan , self (), binary_to_term (Bin )},
207+ loop (Socket , Pid );
208+ {tcp_closed , Socket } ->
209+ Pid ! {chan_closed , self ()};
210+ close ->
211+ gen_tcp :close (Socket );
212+ {send , T } ->
213+ gen_tcp :send (Socket , [term_to_binary (T )]),
214+ loop (Socket , Pid )
215+ end .
216+ ```
217+
218+ 这个循环被用作套接字数据世界,与 Erlang 消息传递世界的接口。咱们可在 [`lib_chan_mm `](# lib_chan_mm ) 中,找到 `lib_chan_mm` 的完整代码。相比这里给出的代码,完整代码稍微复杂一些,但原理是一样的。唯一的区别在与,我们添加了一些用于跟踪消息的代码,以及一些接口例程。
219+
220+
221+ ### `lib_chan_cs`
222+
223+
224+ `lib_chan_cs` 负责建立客户端与服务器的通信。他导出的两个重要例程如下:
225+
226+
227+ - `start_raw_server(Port, Max, Fun, PacketLength)`
228+
229+ 这个例程会在 `Port` 上启动一个监听连接的监听器。最多 `Max` 个同时会话被允许。`Fun` 是一个 1 元函数;当某个连接启动时,`Fun(Socket)` 会被执行。套接字通信会假设一个长度为 `PacketLength` 的数据包。
230+
231+
232+ - `start_raw_client(Host, Port, PacketLength) -> {ok , Socket } | {error , Why }`
233+
234+ 这个例程会尝试连接到某个以 `start_raw_server ` 打开的端口。
235+
236+
237+ `lib_chan_cs ` 的代码,遵循了 [并行服务器](../ part - iv / Ch17 - programming_with_sockets .md # parall_server ) 中描述的模式,但此外他还会追踪同时打开连接的最大数量。这个小细节虽然在概念上很简单,却增加了二十多行看起来相当奇怪,捕获退出等的代码。像这样的代码一团糟,不过不用担心;他完成了他的工作,并对该模组的用户隐藏了复杂性。
238+
239+ ### `lib_chan_auth`
240+
241+
242+ 该模组实现了一种简单形式的挑战/响应认证。挑战/响应的认证,基于与服务名名字相关联的共享秘密概念。要演示其工作原理,我们将假定有项名为 `math`,有着共享秘密 `qwerty` 的服务。
243+
244+ 当某个客户端打算使用服务 `math` 时,那么该客户端就必须向服务器证明,他们知道共享秘密。这个过程如下进行:
245+
246+ 1. 客户端发送一次请求到服务器,表明他想要使用 `math` 服务;
247+
248+ 2. 服务器会计算出一个随机字符串 `C` 并将其发送给客户端。这就是 *挑战*。该字符串由 `lib_chan_ auth:make_challenge()` 函数计算得出。我们可以交互式地使用他,看看他做了些什么。
249+
250+ ```erlang
251+ 1> lib_chan_auth:make_challenge().
252+ "ynsbrxyuavrknszkretvjvqao"
253+ ```
254+
255+ 3. 客户端收到这个字符串 (`C`) ,并计算出响应 (`R`),其中 `R = MD5(C ++ Secret)`。`R` 是使用 `lib_chan_auth:make_response` 计算出的。下面是个示例:
256+
257+ ```erlang
258+ 2> R = lib_chan_auth:make_response(C, "qwerty").
259+ "58565dbdf3328f7d9b9869a2c47eb699"
260+ ```
261+
262+ 4. 响应会被发回服务器。服务器收到该响应,并通过计算该响应的预期值,检查其是否正确。这是在 `lib_chan_auth:is_response_correct` 中完成的。
263+
264+ ```erlang
265+ 3> lib_chan_auth:is_response_correct(C, R, "qwerty").
266+ true
267+ ```
268+
269+ ## `lib_chan` 的代码
270+
271+
272+ 现在是代码时间。
273+
274+
275+ ### `lib_chan`
276+
277+ ```erlang
278+ {{# include ../../projects/appendix/src/lib_chan.erl}}
279+ ```
280+
281+ ### `lib_chan_cs `
282+
283+ ```erlang
284+ {{# include ../../projects/appendix/src/lib_chan_cs.erl}}
285+ ```
286+
287+
288+ ### `lib_chan_mm `
289+
290+ ```erlang
291+ {{# include ../../projects/appendix/src/lib_chan_mm.erl}}
292+ ```
293+
294+
295+ ### `lib_chan_auth `
296+
297+ ```erlang
298+ {{# include ../../projects/appendix/src/lib_chan_auth.erl}}
299+ ```
300+
0 commit comments