Erlang02_并发编程(3) 分布式编程
总篇:9
编辑于 2025/5/5 22:39
截稿于: 2025/5/5 23:30
简介
将上节聊天室服务端在一个‘节点’上运行,不同的客户端在不同的其他‘节点’上运行,这样整个聊天室程序由不同节点的程序一起运行共同构成,称为分布式。核心概念是 节点。
创建多个节点
在打开erl环境前通过 erl -sname node_name 为节点命名,将上一篇中的聊天室分为服务器节点,客户端节点(两个节点需编辑各自需要的erl文件,相同的文件也需要各自编译):
erl -sname server_node;
erl -sname client_node;


'server_node@TianyiLuo' :才是一个完整的节点名, 称为 Node
此时该如何创建服务器、客户端进程并正常运行?
- 先改造chater.erl和chat_server.erl,注册进程在分布式下有所区别:
-module(chater).
-export([start/2, send_message/3, send_message/4]).
start(Name, LinkPid) ->
Pid = spawn(fun() -> loop(Name) end),
link(LinkPid),
% 在分布式环境中,应该使用global:register_name而不是register
global:register_name(Name, Pid),
Pid.
send_message(Name, Message, ServerPid) ->
ServerPid ! {Name, Message}.
send_message(Name, Message, ServerPid, Pids) ->
ServerPid ! {Name, Message, Pids}.
loop(Name) ->
receive
{_From, exit} ->
exit(io:format("chater ~p stopped by user~n", [Name]));
{From, Message} ->
io:format("~p Received message from ~p: ~p~n", [Name, From, Message]),
loop(Name)
end.
-module(chat_server).
-export([start/0]).
start() ->
Pid = spawn(fun() ->
process_flag(trap_exit, true),
loop([])
end),
% 在分布式环境中,应该使用global:register_name而不是register
global:register_name(chat_server, Pid),
Pid.
loop(Clients) ->
receive
stop ->
io:format("chat_server stopped by admin~n"),
exit("chat_server stopped by admin");
{'EXIT', Pid, Why} ->
io:format("chat_server received exit signal from ~p with reason ~p~n", [Pid, Why]),
loop(lists:delete(Pid, Clients));
{From, exit} ->
io:format("chat_server received exit command from ~p~n", [From]),
From ! {self(), exit},
loop(lists:delete(From, Clients));
{From, Message} ->
NewClients = add_unique(From, Clients),
io:format("chat_server received message from ~p: ~p~n", [From, Message]),
lists:foreach(fun(Pid) -> Pid ! {From, Message} end, Clients),
loop(NewClients);
{From, Message, Pids} ->
NewClients = add_unique_list(Pids, Clients),
io:format("chat_server received message from ~p: ~p~n", [From, Message]),
lists:foreach(fun(Pid) -> Pid ! {From, Message} end, Pids),
loop(NewClients)
end.
add_unique_list([], List) ->
List;
add_unique_list([H | T], List) ->
NewList = add_unique(H, List),
add_unique_list(T, NewList).
add_unique(Element, List) ->
case lists:member(Element, List) of
true -> List;
false -> [Element | List]
end.
- 客户端进程链接服务端进程(应该叫相互连接)
net_adm:ping('server@TianyiLuo'). 该节点通过节点名链接另一个节点,使双方处于互相连通状态

- 在server_node启动chat_server,在client_node查询Spid,启动chater,执行send_message方法,得到单点一样的结果:
global:register_name(chat_server, Pid), 全局注册,注册的进程名互相连接的节点均可见,
global:whereis_name(chat_server) ,通过注册进程名查找对应Pid,<10296,97,0>,第一位发生了变化,
因为这是其他节点的进程

通常服务器客户端会在两台不同的机器上运行,这里又多出来个cookie保护机制,即双方均需相同值的cookie才能互相通信
erl -setcookie xxxx


分布式编程会常用到叫 "rpc"的库,其中最频繁使用的函数call,需要留意。
R |{badrpc,Result} = rpc:call(Node,M,F,A),就是调用某个节点上的某个MFA,无关进程。
原语
一些常用的分布式原语:
- Pid =spawn(Node,Fun)
Pid =spawn(Node,Fun), 在某个节点上创建进程
- Pid =spawn_link(Node,Fun)
Pid =spawn_link(Node,Fun), 在某个节点上创建进程并连接该进程
- bool = disconnect_node(Node)
bool = disconnect_node(Node),强制断开某个节点。
- Node = node()/Node =node(Pid)
Node = node(),返回当前节点名称。node(Pid),返回Pid所在的节点。
- bool = is_alive()
bool = is_alive() 检查该节点是否存活。
- pang/pong = net_adm:ping(Node)
pang/pong = net_adm:ping(Node),本节点连接Node节点,pang:连接失败,pong:连接成功
浙公网安备 33010602011771号