The simplest example could be written in a single module, but the one below makes use of OTP supervision tree to restart the server when something goes wrong.
defmodule Socket.Application do
@moduledoc false
use Application
@doc """
This is the main function that starts our application's supervision tree.
"""
def start(_type, _args) do
# List all child processes to be supervised
children = [
{Task.Supervisor, name: Socket.ConnectionSupervisor},
{Socket.Listener, 3001}
]
# See https://hexdocs.pm/elixir/Supervisor.html
# for other strategies and supported options
opts = [strategy: :one_for_all, name: Socket.Supervisor]
Supervisor.start_link(children, opts)
end
end
defmodule Socket.Listener do
@moduledoc """
This module implements a long-running Task that will listen on a port for TCP connections.
We're using the Task abstraction to create the listener process, but we want it to restart when
it fails (Tasks don't restart by default).
"""
use Task, restart: :permanent
require Logger
@doc """
This is the function that will be called by the Supervisor to start the Listener process.
We're using `Task.start_link/3` to make the configuration easier.
"""
def start_link(port) do
Task.start_link(__MODULE__, :run, [port])
end
@doc """
The main function of our Listener. It will try to listen on a given port and start accepting
connections if it succeeds. Otherwise it will fail (and be restarted by the Supervisor).
"""
def run(port) do
opts = [:binary, packet: 0, active: false, reuseaddr: true, ip: {127, 0, 0, 1}]
case :gen_tcp.listen(port, opts) do
{:ok, listen_socket} ->
Logger.info("Listening for connections on #{port}...")
accept_connections(listen_socket)
{:error, reason} ->
raise "Could not listen on #{port}: #{reason}"
end
end
@doc """
A recursive function that aceepts new connections and delegates them to new processes.
"""
defp accept_connections(listen_socket) do
# This process will hang until a new connection is opened...
{:ok, socket} = :gen_tcp.accept(listen_socket)
# ...then spawn a new process to handle the connection...
Task.Supervisor.start_child(Socket.ConnectionSupervisor, Socket.Connection, :run, [socket])
# ...and continue listening for new connections
accept_connections(listen_socket)
end
end
defmodule Socket.Connection do
@moduledoc """
This module creates an abstraction around a TCP connection.
"""
require Logger
@doc """
A recursive function (or a loop) that waits for data from the client, reverses it (as a string)
and sends it back. The loop ends when trying to receive data fails.
"""
def run(socket) do
case :gen_tcp.recv(socket, 0) do
{:ok, msg} ->
:gen_tcp.send(socket, transform_message(msg))
run(socket)
{:error, reason} ->
Logger.info("Connection closed: #{reason}")
end
end
defp transform_message(msg) do
msg
|> String.trim()
|> String.reverse()
end
end
Comments
Empty! You must sign in to add comments.