[elixir! #0024] 引擎盖下, `Channel.push` 如何运作

Phoenix.Channel.push

第一次接触 phoenix 框架时, 照着官网的教程做了一个聊天网页. 服务器可以在 channel 里广播消息, 还可以 push 消息给单个用户. phoenix 究竟是如何做到的. 我们先看看该函数的定义

  @doc """
  Sends event to the socket.
  The event's message must be a serializable map.
  ## Examples
      iex> push socket, "new_message", %{id: 1, content: "hello"}
      :ok
  """
  def push(socket, event, message) do
    %{transport_pid: transport_pid, topic: topic} = assert_joined!(socket)
    Server.push(transport_pid, topic, event, message, socket.serializer)
  end

首先, 调用了 assert_joined!/1 函数, 来看看它有什么用

  defp assert_joined!(%Socket{joined: true} = socket) do
    socket
  end

  defp assert_joined!(%Socket{joined: false}) do
    raise """
    `push`, `reply`, and `broadcast` can only be called after the socket has finished joining.
    To push a message on join, send to self and handle in handle_info/2, ie:
        def join(topic, auth_msg, socket) do
          ...
          send(self, :after_join)
          {:ok, socket}
        end
        def handle_info(:after_join, socket) do
          push socket, "feed", %{list: feed_items(socket)}
          {:noreply, socket}
        end
    """
  end

原来只是确认一下该 socket 是否已加入频道, 会返回一个很详细的错误提示, 教你如何在 join 时 push 消息, 也许是因为很多人提了这个问题吧.

从 socket 中获取到 transport_pid, topicserializer 之后, 调用了 Server.push 函数.

Phoenix.Channel.Server.push

  @doc """
  Pushes a message with the given topic, event and payload
  to the given process.
  """
  def push(pid, topic, event, payload, serializer)
      when is_binary(topic) and is_binary(event) and is_map(payload) do

    encoded_msg = serializer.encode!(%Message{topic: topic,
                                              event: event,
                                              payload: payload})
    send pid, encoded_msg
    :ok
  end
  def push(_, _, _, _, _), do: raise_invalid_message()

Server.push 函数将收到的 topic , eventpayload 编码之后, 发送给了 transport. 那么 transport 会对这些信息作何处理呢?

cowboy_websocket:handler_loop

我们先暂停一下, 说说客户端加入某个 channel 时究竟发生了什么. 当客户端与服务器的连接建立后, 会生成一个 transport 进程, 随后客户端加入某个 channel 时, transport 进程又会生成一个 channel 进程. 当客户端断开连接后, transport 进程会死亡, 其下的 channel 进程也会随之死去. 所以每个用户加入的每个 channel 都有一个独立的进程, 用于保存一个 %Phoenix.Socket{} 结构体.

让我们继续之前的线索, 通过 observer, 我们找到了 tansport 进程中正在运行的函数 -- cowboy_websocket:handler_loop. 此后便是 cowboy 将消息通过 websocket 发送给客户端了.

总结

phoenix channel 中的消息 push 机制很简单, 与关注同一个 channel 的其他人没有关系. 进程关系如图

下回我们将看看 phoenix 是如何广播消息的.

你可能感兴趣的:(elixir,phoenix)