66 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
			
		
		
	
	
			66 lines
		
	
	
		
			1.7 KiB
		
	
	
	
		
			Elixir
		
	
	
	
	
	
defmodule Broadcast.Server do
 | 
						|
  @moduledoc """
 | 
						|
    Implements the server. The server spawns a new process
 | 
						|
    for every client - the processes usually don't really talk to
 | 
						|
    each other, so this is trivial.
 | 
						|
  """
 | 
						|
  use GenServer
 | 
						|
  use Agent
 | 
						|
 | 
						|
 | 
						|
 | 
						|
  def init(port) do
 | 
						|
    {:ok, sock} = :gen_tcp.listen(port, [:binary, packet: :line, active: false, reuseaddr: true])
 | 
						|
    spawn_link(fn -> accept_loop(sock) end)
 | 
						|
    {:ok, {sock, %{}}}
 | 
						|
  end
 | 
						|
 | 
						|
  defp accept_loop(sock) do
 | 
						|
    case :gen_tcp.accept(sock) do
 | 
						|
      {:ok, client_socket} ->
 | 
						|
        {:ok, pid} = Broadcast.ClientHandler.start(client_socket)
 | 
						|
        GenServer.cast(__MODULE__, {:new_client, pid, client_socket})
 | 
						|
        accept_loop(sock)
 | 
						|
      {:error, _} ->
 | 
						|
        accept_loop(sock)
 | 
						|
    end
 | 
						|
  end
 | 
						|
 | 
						|
  def handle_cast({:new_client, pid, client_sock}, {sock, clients}) do
 | 
						|
    {:noreply, {sock, clients |> Map.put(pid, client_sock)}}
 | 
						|
  end
 | 
						|
 | 
						|
  def handle_cast({:remove, client}, {sock, clients}) do
 | 
						|
    new_clients = Map.delete(clients, client)
 | 
						|
    {:noreply, {sock, new_clients}}
 | 
						|
  end
 | 
						|
 | 
						|
  def handle_cast({:broadcast, msg, sender}, {sock, clients}) do
 | 
						|
    clients |> Enum.map(fn {p, s} ->
 | 
						|
      if sender != p do
 | 
						|
        :gen_tcp.send(s, msg)
 | 
						|
      end
 | 
						|
    end)
 | 
						|
    {:noreply, {sock, clients}}
 | 
						|
  end
 | 
						|
 | 
						|
  # Print out the memory usage for the client processes specifically
 | 
						|
 | 
						|
  # From this point onwards is the actual API for this module,
 | 
						|
  # that should be called by ClientHandlers and main
 | 
						|
 | 
						|
  @spec start(integer()) :: pid()
 | 
						|
  def start(port) when is_integer(port) do
 | 
						|
    GenServer.start_link(__MODULE__, port, name: __MODULE__)
 | 
						|
  end
 | 
						|
 | 
						|
  def broadcast(msg) do
 | 
						|
    GenServer.cast(__MODULE__, {:broadcast, msg, self()})
 | 
						|
  end
 | 
						|
 | 
						|
  def remove_client() do
 | 
						|
    GenServer.cast(__MODULE__, {:remove, self()})
 | 
						|
  end
 | 
						|
 | 
						|
end
 |