iTranslated by AI
Building a TCP Server in Elixir to Broadcast Client Input to All Other Clients
What I am trying to do is exactly what the title says. To achieve this, I implemented it using mtrudel/thousand_island: Thousand Island is a pure Elixir socket server, which is a library for handling socket communication that forms the base layer for mtrudel/bandit: Bandit is a pure Elixir HTTP server for Plug applications, a project that has recently become a hot topic in the Elixir community.
What I want to achieve
- The actors are as follows:
- Server (one)
- Clients (multiple)
- The server waits for TCP connections on port 1234, so each client connects via
nc localhost 1234. - When a client sends a message, that message is sent to all clients except the sender.
I've recorded the behavior in an animated GIF. It might be hard to see, so please click the image to enlarge it while watching.

Implementation
Starting the ThousandIsland process
First, we set up the process startup configuration in the Application module. This is done in the usual way.
We will use Registry in addition to ThousandIsland. Note that Registry is started with the keys: :duplicate option, so when you register with the same key, the values passed will be added as a list (refer to the implementation details below).
defmodule Broadcast.Application do
use Application
@impl true
def start(_type, _args) do
children = [
{ThousandIsland, port: 1234, handler_module: Broadcast.Handler},
{Registry, keys: :duplicate, name: Broadcast.Registry}
]
opts = [strategy: :one_for_one, name: Broadcast.Supervisor]
Supervisor.start_link(children, opts)
end
end
Handler Implementation
A key feature of thousand_island is that it allows you to build a nice TCP server just by implementing the ThousandIsland.Handler behavior.
First, we implement handle_connection/2, which is required by the ThousandIsland.Handler behavior. This function is called whenever a client connects. Within this function, we use Registry to record the client's socket information.
Next, we implement handle_data/3, also required by the ThousandIsland.Handler behavior. This function is called when a message is sent from a client. Since it contains the message content from the client, we send that message to all clients except the sender.
For sending messages, we can simply use the handle_cast/2 callback from the GenServer behavior as usual. We retrieve the sockets of connected clients recorded in the Registry and send the message to all clients except the source.
defmodule Broadcast.Handler do
use ThousandIsland.Handler
def send_message(pid, from, msg) do
GenServer.cast(pid, {:send, from, msg})
end
@impl ThousandIsland.Handler
def handle_connection(socket, state) do
Registry.register(Broadcast.Registry, "clients", socket)
{:continue, state}
end
@impl ThousandIsland.Handler
def handle_data(msg, socket, state) do
send_message(self(), socket, msg)
{:continue, [socket | state]}
end
@impl GenServer
def handle_cast({:send, from, msg}, {socket, state}) do
Registry.lookup(Broadcast.Registry, "clients")
|> Enum.filter(fn {_pid, socket} ->
socket != from
end)
|> Enum.each(fn {_pid, socket} ->
ThousandIsland.Socket.send(socket, msg)
end)
{:noreply, {socket, state}}
end
end
Source Code
The source code is available in the following repository.
kentaro/broadcast: An Example Broadcast Server for TCP Connection
Conclusion
It's a nice library that is easy to use. I'd like to eventually look into the implementation of Bandit, the HTTP server built using this.
Discussion