如何编辑回显服务器以允许许多客户端交换消息

问题描述

我创建的服务器没有客户端代码。我只需要在终端telnet 127.0.0.1 4001和另一个终端telnet 127.0.0.2 4001上键入,因此当我在第一个终端上键入一条消息时,它出现在同一终端上,我知道这是一个回显服务器,我想要的是尽可能修改代码,以便其他终端上的其他客户端可以接收到该消息。 这是一个回显服务器代码

defmodule Multichat.Server do
  require Logger

  def accept(port) do
    {:ok,socket} = :gen_tcp.listen(port,[:binary,packet: :line,active: false,reuseaddr: true])
    Logger.info "Accepting connections on port #{port}"
    loop_acceptor(socket)
  end

  defp loop_acceptor(socket) do
    {:ok,client} = :gen_tcp.accept(socket)
    {:ok,pid} = Task.Supervisor.start_child(Multichat.Server.TaskSupervisor,fn -> serve(client) end)
    :ok = :gen_tcp.controlling_process(client,pid)
    loop_acceptor(socket)
  end

  defp serve(socket) do
    socket
    |> read_line()
    |> write_line(socket)

    serve(socket)
  end

  defp read_line(socket) do
    {:ok,data} = :gen_tcp.recv(socket,0)
    data
  end

  defp write_line(line,socket) do
    :gen_tcp.send(socket,line)
  end
end

我应该改变什么,所以当我使用telnet打开许多客户端时,它们会收到彼此的消息

解决方法

我认为最简单的方法是在active模式下设置套接字,并在GenServer中为单个客户端处理消息。然后,通过维护所有客户端处理程序的列表,可以遍历该列表并将消息发送给每个客户端。一个可行但不是很干净的版本:

defmodule Multichat.ClientConnection do
  use GenServer

  def start_link(socket),do: GenServer.start_link(__MODULE__,socket)

  def handle_call({:send,message},_from,socket) do
    :gen_tcp.send(socket,message)
    {:reply,:ok,socket}
  end

  def handle_info({:tcp,_socket,socket) do
    for {_,pid,_,_} <- DynamicSupervisor.which_children(Multichat.Server.ConnectionSupervisor) do
      if pid != self() do
        GenServer.call(pid,{:send,message})
      end
    end

    {:noreply,socket}
  end
end

defmodule Multichat.Server do

  # ...

  def accept(port) do
    {:ok,socket} = :gen_tcp.listen(port,[:binary,packet: :line,active: true,reuseaddr: true])
    Logger.info "Accepting connections on port #{port}"
    loop_acceptor(socket)
  end

  defp loop_acceptor(socket) do
    {:ok,client} = :gen_tcp.accept(socket)
    {:ok,pid} = DynamicSupervisor.start_child(Multichat.Server.ConnectionSupervisor,{Multichat.ClientConnection,client})
    :ok = :gen_tcp.controlling_process(client,pid)
    loop_acceptor(socket)
  end

  # ...

end

在这里,我正在使用DynamicSupervisor.which_children/1来获取已启动的客户端处理程序的列表。为此,最好使用Registry