2015-11-26 31 views
7

我有一个Elixir/Phoenix服务器应用程序,客户端通过websockets通过内置通道系统连接。现在我想检测用户何时离开频道。如何检测用户是否因网络断开而离开Phoenix频道?

旁注:我在Google Chrome扩展中使用JavaScript客户端库。为此,我从Phoenix提取了ES6代码,将其转换为javascript,并稍微调整了一下,以便它可以独立运行。

现在当我刚刚关闭弹出窗口时,服务器立即触发terminate/2函数reason = {:shutdown, :closed}。在扩展端没有任何类型的关闭回调,所以这太棒了!

但是,当客户端只是丢失网络连接(我连接第二台计算机,只是拔出网络插头),然后terminate/2将不会触发。

为什么以及如何解决这个问题?

我玩过timeout选项transport :websocket, Phoenix.Transports.WebSocket,但这并没有解决。

更新: 随着新真棒凤凰1.2 Presence的东西,这不应该不再需要。

+0

我刚刚注意到,服务器并不总是识别弹出窗口关闭时。所以我希望我的问题的解决方案也能解决这个问题。 –

回答

16

这样做的正确方法是不要在您的频道中捕捉退出,而是让另一个进程监视您。当你下楼时,它可以调用回调。下面是一个片段,让你开始:

# lib/my_app.ex 

children = [ 
    ... 
    worker(ChannelWatcher, [:rooms]) 
] 

# web/channels/room_channel.ex 

def join("rooms:", <> id, params, socket) do 
    uid = socket.assigns.user_id] 
    :ok = ChannelWatcher.monitor(:rooms, self(), {__MODULE__, :leave, [id, uid]}) 

    {:ok, socket} 
end 

def leave(room_id, user_id) do 
    # handle user leaving 
end 

# lib/my_app/channel_watcher.ex 

defmodule ChannelWatcher do 
    use GenServer 

    ## Client API 

    def monitor(server_name, pid, mfa) do 
    GenServer.call(server_name, {:monitor, pid, mfa}) 
    end 

    def demonitor(server_name, pid) do 
    GenServer.call(server_name, {:demonitor, pid}) 
    end 

    ## Server API 

    def start_link(name) do 
    GenServer.start_link(__MODULE__, [], name: name) 
    end 

    def init(_) do 
    Process.flag(:trap_exit, true) 
    {:ok, %{channels: HashDict.new()}} 
    end 

    def handle_call({:monitor, pid, mfa}, _from, state) do 
    Process.link(pid) 
    {:reply, :ok, put_channel(state, pid, mfa) 
    end 

    def handle_call({:demonitor, pid}, _from, state) do 
    case HashDict.fetch(state.channels, pid) do 
     :error  -> {:reply, :ok, state} 
     {:ok, _mfa} -> 
     Process.unlink(pid) 
     {:reply, :ok, drop_channel(state, pid)} 
    end 
    end 

    def handle_info({:EXIT, pid, _reason}, state) do 
    case HashDict.fetch(state.channels, pid) do 
     :error -> {:noreply, state} 
     {:ok, {mod, func, args}} -> 
     Task.start_link(fn -> apply(mod, func, args) end) 
     {:noreply, drop_channel(state, pid)} 
    end 
    end 

    defp drop_channel(state, pid) do 
    %{state | channels: HashDict.delete(state.channels, pid)}} 
    end 

    defp put_channel(state, pid, mfa) do 
    %{state | channels: HashDict.put(channels, pid, mfa)}} 
    end 
end 
+1

我得到它的工作,我有两个后续问题:1.为什么不是核心(这太好了)? 2.网络断开和离开触发之间的时间约为90秒。这是以任何方式定制? (我想将传输超时设置为20秒,并且每隔10秒就会ping服务器......但是当然会有额外的资源被烧毁) –

相关问题