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