{"id":2432,"date":"2022-08-30T15:24:49","date_gmt":"2022-08-30T15:24:49","guid":{"rendered":"https:\/\/unknownerror.org\/index.php\/2014\/01\/12\/simple-chat-system-over-websockets-with-reconnection-feature-collection-of-common-programming-errors\/"},"modified":"2022-08-30T15:24:49","modified_gmt":"2022-08-30T15:24:49","slug":"simple-chat-system-over-websockets-with-reconnection-feature-collection-of-common-programming-errors","status":"publish","type":"post","link":"https:\/\/unknownerror.org\/index.php\/2022\/08\/30\/simple-chat-system-over-websockets-with-reconnection-feature-collection-of-common-programming-errors\/","title":{"rendered":"Simple chat system over websockets with reconnection feature-Collection of common programming errors"},"content":{"rendered":"<ul>\n<li>\n<p>What i do is the folowing :<\/p>\n<p>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))<\/p>\n<p>Ok so now the user websocket connection starts the cowboy handler (i use Bullet). The handlers asks gproc the pid() of the user&#8217;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.<\/p>\n<p>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).<\/p>\n<p>See : (not tested)<\/p>\n<pre><code>-module(user_chat).\n\n-record(state, {mailbox,receiver=undefined}).\n\n-export([start_link\/1,set_receiver\/1,unset_receiver\/1]).\n%% API\n\nstart_link(UserID) -&gt;\n    gen_server:start_link(?MODULE,[UserID],[]).\n\nset_receiver(UserID) -&gt;\n    set_receiver(UserID,self()).\n\nunset_receiver(UserID) -&gt;\n    %% Just set the receiver to undefined\n    set_receiver(UserID,undefined).\n\nset_receiver(UserID, ReceiverPid) -&gt;\n    UserPid = gproc:where({n,l,UserID}),\n    gen_server:call(UserPid,{set_receiver,ReceiverPid}).\n\n\n%% Gen server internals\n\ninit([UserID]) -&gt;\n    gproc:reg({n,l,{user,UserID}}),\n    {ok,#state{mailbox=[]}}.\n\nhandle_call({set_receiver,ReceiverPid},_From,#state{mailbox=MB}=State) -&gt;\n    NewMB = check_send(MB,State),\n    {reply,ok,State#state{receiver=ReceiverPid,mailbox=NewMB}}.\n\nhandle_info({chat_msg,Message},#state{mailbox=MB}=State) -&gt;\n    NewMB = check_send([Message|MB],State),\n    {noreply, State#state{mailbox=NewMB}}.\n\n%% Mailbox empty\ncheck_send([],_) -&gt; [];\n%% Receiver undefined, keep messages\ncheck_send(Mailbox,#state{receiver=undefined}) -&gt; Mailbox\n%% Receiver is a pid\ncheck_send(Mailbox,#state{receiver=Receiver}) when is_pid(Receiver) -&gt;\n    %% Send all messages\n    Receiver ! {chat_messages,Mailbox},\n    %% Then return empty mailbox\n    [].\n<\/code><\/pre>\n<\/li>\n<li>\n<p>With the solution you propose you may have many processes pending and you will have to write a &#8220;process cleaner&#8221; for all user that never come back. Anyway it will not support a shutdown of the chat server VM, all messages stored in living FSM will vanish if the node is down.<\/p>\n<p>I think that a better way should be to store all messages in a database like mnesia, with sender, receiver, expiration date&#8230; and check for any stored message at connection, and have a message cleaner process to destroy all expired messages from time to time.<\/p>\n<\/li>\n<\/ul>\n<p id=\"rop\"><small>Originally posted 2014-01-12 20:26:18. <\/small><\/p>","protected":false},"excerpt":{"rendered":"<p>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 [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-2432","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts\/2432","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/comments?post=2432"}],"version-history":[{"count":0,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/posts\/2432\/revisions"}],"wp:attachment":[{"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/media?parent=2432"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/categories?post=2432"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/unknownerror.org\/index.php\/wp-json\/wp\/v2\/tags?post=2432"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}