2

我见过很多使用 erlang 和 cowboy 实现的基于 websocket 的聊天室系统示例。

我见过的大多数示例都使用 gproc。实际上,每个 websocket 处理程序都会向 gproc 注册自己,然后从中广播/接收消息。

由于用户可能会意外关闭网页,我正在考虑连接到 websocket 处理程序的 gen_fsm,它实际上广播/接收来自 gproc 的所有消息。这样,只要用户退出,gen_fsm 就可以从“连接”状态切换到“断开”状态,并且仍然缓冲所有消息。一段时间后,如果用户没有重新联机,gen_fsm 将终止。

这是一个好的解决方案吗?如何使新的 websocket 处理程序恢复 gen_fsm 进程?我应该使用用户名注册 gen_fsm 还是有更好的解决方案?

4

2 回答 2

3

我要做的是以下内容:

当用户连接到该站点时,我会生成一个代表该用户的 gen_server。然后,gen 服务器在 gproc 中将自己注册为 {n,l, {user, UserName}}。(它可以注册像 {p,l, {chat, ChannelID}} 这样的属性来收听聊天频道。(参见gproc pub/sub))

好的,现在用户 websocket 连接启动了牛仔处理程序(我使用Bullet)。处理程序向 gproc 询问用户的 gen_server 的 pid() 并将注册器本身作为消息的接收者。所以现在,当用户 gen_server 接收到消息时,它会将它们重定向到 websocket 处理程序。

当 websocket 连接结束时,处理程序从用户 gen_server 注册,因此用户 gen_server 将保留消息,直到下一次连接,或下一次超时。在超时时,您可以简单地终止服务器(消息会丢失但没关系)。

请参阅:(未测试)

-module(user_chat).

-record(state, {mailbox,receiver=undefined}).

-export([start_link/1,set_receiver/1,unset_receiver/1]).
%% API

start_link(UserID) ->
    gen_server:start_link(?MODULE,[UserID],[]).

set_receiver(UserID) ->
    set_receiver(UserID,self()).

unset_receiver(UserID) ->
    %% Just set the receiver to undefined
    set_receiver(UserID,undefined).

set_receiver(UserID, ReceiverPid) ->
    UserPid = gproc:where({n,l,UserID}),
    gen_server:call(UserPid,{set_receiver,ReceiverPid}).


%% Gen server internals

init([UserID]) ->
    gproc:reg({n,l,{user,UserID}}),
    {ok,#state{mailbox=[]}}.

handle_call({set_receiver,ReceiverPid},_From,#state{mailbox=MB}=State) ->
    NewMB = check_send(MB,State),
    {reply,ok,State#state{receiver=ReceiverPid,mailbox=NewMB}}.

handle_info({chat_msg,Message},#state{mailbox=MB}=State) ->
    NewMB = check_send([Message|MB],State),
    {noreply, State#state{mailbox=NewMB}}.

%% Mailbox empty
check_send([],_) -> [];
%% Receiver undefined, keep messages
check_send(Mailbox,#state{receiver=undefined}) -> Mailbox
%% Receiver is a pid
check_send(Mailbox,#state{receiver=Receiver}) when is_pid(Receiver) ->
    %% Send all messages
    Receiver ! {chat_messages,Mailbox},
    %% Then return empty mailbox
    [].
于 2013-08-05T14:23:10.467 回答
1

使用您提出的解决方案,您可能有许多待处理的进程,您将不得不为所有永远不会回来的用户编写一个“进程清理器”。无论如何,它不支持关闭聊天服务器虚拟机,如果节点关闭,存储在活动 FSM 中的所有消息都会消失。

我认为更好的方法应该是将所有消息存储在像 mnesia 这样的数据库中,包括发送者、接收者、到期日期......并在连接时检查任何存储的消息,并有一个消息清理程序来销毁所有过期的消息到时间。

于 2013-08-04T13:35:10.210 回答