问题描述
我已经看到了许多用erlang和cowboy实现的Websocket聊天室系统的例子。
我看到的大多数示例都使用gproc。实际上,每个websocket处理程序都会向gproc注册自己,然后广播/接收来自gproc的消息。处理程序gen_fsm,实际上广播/接收来自gproc的所有消息。这样,只要用户退出并仍然缓冲所有消息,gen_fsm便可以从已连接状态切换到已断开状态。过一会儿,如果用户不在线,则gen_fsm将终止。
这是一个好的解决方案吗?如何制作新的websocket处理程序以恢复gen_fsm进程?我应该使用用户名注册gen_fsm还是有更好的解决方案?
我要做的是:
当用户连接到站点时,我生成了代表该用户的gen_server。然后,gen服务器将自己在gproc中注册为{n,l,{user,UserName}}。 (它可以注册{p,l,{chat,ChannelID}}之类的属性来收听聊天频道。(请参阅))
好,现在用户websocket连接启动了牛仔处理程序(我使用)。处理程序向gproc询问用户的gen_server的pid(),然后将自己注册为消息的接收者。因此,现在,当用户gen_server接收到消息时,它将消息重定向到websocket处理程序。
当websocket连接结束时,处理程序从用户gen_server进行注册,因此gen_server用户将保留消息,直到下一次连接或下一次超时为止。在超时时,您可以简单地终止服务器(消息将会丢失,但可以)。
请参阅:(未测试)
-module(user_chat)。
-record(状态,{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)->
%%只需将接收器设置为未定义
set_receiver(UserID,undefined)。
set_receiver(UserID,ReceiverPid)->
UserPid = gproc:where({n,l,UserID}),
gen_server:call(UserPid,{set_receiver,ReceiverPid})。
%% Gen服务器内部
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}}。
%%邮箱为空
check_send([],_)-> [];
%%收件人未定义,请保持邮件
check_send(Mailbox,#state {receiver = undefined})->邮箱
%%收件人是一个pid
check_send(Mailbox,#state {receiver = Receiver})is_pid(Receiver)->
%%发送所有消息
收件人! {chat_messages,Mailbox},
%%然后返回空邮箱
[]。
I have seen many examples of chat room systems over websocket implemented with erlang and cowboy.
Most of the examples I have seen use gproc. In practice each websocket handler registers itself with gproc and then broadcasts/receives messages from it.
Since a user could close by accident the webpage I am thinking about connecting to the websocket handler a gen_fsm which actually broadcasts/receives all the messages from gproc. In this way the gen_fsm could switch from a "connected" state to a "disconnected" state whenever the user exits and still buffer all the messages. After a while if the user is not back online the gen_fsm will terminate.
Is this a good solution? How can I make the new websocket handler to recover the gen_fsm process? Should I register the gen_fsm using the user name or is there any better solution?
What i do is the folowing :
When an user connects to the site, i swpawn a gen_server reprensenting the user. Then, the gen server registers itself in gproc as {n,l, {user, UserName}}. (It can register properties like {p,l, {chat, ChannelID}} to listen to chat channels. (see gproc pub/sub))
Ok so now the user websocket connection starts the cowboy handler (i use Bullet). The handlers asks gproc the pid() of the user's gen_server and registrers itself as a receiver of messages. So now, when the user gen_server receives messages, it redirects them to the websocket handler.
When the websocket connexion ends, the handler uregister from the user gen_server, so the user gen_server will keep messages until the next connection, or the next timeout. At the timeout, you can simply terminate the server (messages will be lost but it is ok).
See : (not tested)
-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
[].
这篇关于通过websocket的简单聊天系统,具有重新连接功能的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!