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

此时该如何创建服务器、客户端进程并正常运行?

  1. 先改造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.
  1. 客户端进程链接服务端进程(应该叫相互连接
net_adm:ping('server@TianyiLuo'). 该节点通过节点名链接另一个节点,使双方处于互相连通状态

  1. 在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,无关进程。

原语

一些常用的分布式原语:

  1. Pid =spawn(Node,Fun)
Pid =spawn(Node,Fun), 在某个节点上创建进程
  1. Pid =spawn_link(Node,Fun)
Pid =spawn_link(Node,Fun), 在某个节点上创建进程并连接该进程
  1. bool = disconnect_node(Node)
bool = disconnect_node(Node),强制断开某个节点。
  1. Node = node()/Node =node(Pid)
 Node = node(),返回当前节点名称。node(Pid),返回Pid所在的节点。
  1. bool = is_alive()
bool = is_alive() 检查该节点是否存活。
  1. pang/pong = net_adm:ping(Node)
pang/pong = net_adm:ping(Node),本节点连接Node节点,pang:连接失败,pong:连接成功

posted on 2025-05-05 23:32  依只  阅读(18)  评论(0)    收藏  举报

导航