Phoenix.SocketClient API Documentation
View SourceA comprehensive guide to using the Phoenix Socket Client library with performance optimizations.
Table of Contents
- Installation
- Quick Start
- Core API
- Channel Management
- Message Handling
- Connection Management
- Performance Optimizations
- Monitoring and Metrics
- Error Handling
- Advanced Configuration
- Examples
Installation
Add phoenix_socket_client to your dependencies in mix.exs:
def deps do
[
{:phoenix_socket_client, "~> 0.7.0"}
]
endThen run:
mix deps.get
Quick Start
Basic Connection
# Start a socket connection
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
auto_connect: true
])
# Join a channel
{:ok, response, channel_pid} = Phoenix.SocketClient.Channel.join(
socket,
"rooms:lobby",
%{user: "alice"}
)In Supervision Tree
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
{Phoenix.SocketClient, [
name: MyApp.Socket,
url: "ws://localhost:4000/socket/websocket",
auto_connect: true
]}
]
opts = [strategy: :one_for_one, name: MyApp.Supervisor]
Supervisor.start_link(children, opts)
end
endCore API
Phoenix.SocketClient
The main module for socket operations.
start_link/1
Starts the socket client supervisor.
@spec start_link(keyword()) :: {:ok, pid()} | {:error, term()}Options:
:url- WebSocket URL (required):auto_connect- Auto-connect on start (default:true):params- Connection parameters (default:%{}):headers- Additional headers (default:[]):reconnect- Auto-reconnect on disconnect (default:true):reconnect_interval- Reconnect delay in ms (default:60000):vsn- Phoenix protocol version (default:"2.0.0"):name- Process name for registration:registry_name- Registry name for internal processes
Example:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
params: %{"token" => "your-token"},
headers: [{"Authorization", "Bearer token"}],
auto_connect: true
])connect/1
Manually connect the socket.
@spec connect(pid() | atom()) :: :ok | {:error, :no_socket_found}Example:
:ok = Phoenix.SocketClient.connect(socket)disconnect/1
Disconnect the socket.
@spec disconnect(pid() | atom()) :: :okExample:
:ok = Phoenix.SocketClient.disconnect(socket)connected?/1
Check if socket is connected.
@spec connected?(pid() | atom()) :: boolean()Example:
if Phoenix.SocketClient.connected?(socket) do
# Socket is connected
endpush/2
Send a message through the socket.
@spec push(pid() | atom(), Phoenix.SocketClient.Message.t()) ::
Phoenix.SocketClient.Message.t() | no_return()Example:
message = Phoenix.SocketClient.Message.push("rooms:lobby", "new_message", %{
body: "Hello, world!",
user: "alice"
})
Phoenix.SocketClient.push(socket, message)State Management
# Get socket state
{:ok, state} = Phoenix.SocketClient.get_state(socket)
# Get specific state value
url = Phoenix.SocketClient.get_state(socket, :url)
# Update state value
:ok = Phoenix.SocketClient.put_state(socket, :custom_data, %{key: "value"})Channel Management
Phoenix.SocketClient.Channel
join/3
Join a Phoenix channel.
@spec join(pid() | atom(), String.t(), map()) ::
{:ok, map(), pid()} | {:error, term()}Parameters:
socket- Socket processtopic- Channel topic (e.g.,"rooms:lobby")params- Join parameters (default:%{})
Example:
{:ok, response, channel_pid} = Phoenix.SocketClient.Channel.join(
socket,
"rooms:lobby",
%{user_id: 123, token: "auth-token"}
)leave/1
Leave a channel.
@spec leave(pid()) :: :okExample:
:ok = Phoenix.SocketClient.Channel.leave(channel_pid)push/3
Send a message to a channel.
@spec push(pid(), String.t(), any()) :: :okExample:
:ok = Phoenix.SocketClient.Channel.push(
channel_pid,
"new_message",
%{body: "Hello!", user: "alice"}
)Custom Channel Modules
Create custom channel behavior by using Phoenix.SocketClient.Channel and
implementing the handle_message/3 callback:
defmodule MyApp.ChatChannel do
use Phoenix.SocketClient.Channel
@impl true
def handle_message("new_message", payload, state) do
# Handle incoming message
IO.puts("Received: #{payload["body"]}")
{:noreply, state}
end
def handle_message(_event, _payload, state) do
# Catch-all for other events
{:noreply, state}
end
endThe handle_message/3 callback receives the event name, payload map, and
channel state (%Phoenix.SocketClient.Channel.State{}). It must return
{:noreply, new_state}.
Since the channel is a GenServer, you can also override standard GenServer
callbacks (init/1, handle_call/3, handle_cast/2, handle_info/2,
terminate/2) which are made overridable by the __using__ macro.
Map topics to custom channel modules using the :topic_channel_map option:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
topic_channel_map: %{
"rooms:lobby" => MyApp.ChatChannel
}
])
# Then join as usual
{:ok, response, channel_pid} = Phoenix.SocketClient.Channel.join(
socket,
"rooms:lobby",
%{user: "alice"}
)Message Handling
Phoenix.SocketClient.Message
Create Messages
# Join message
join_msg = Phoenix.SocketClient.Message.join("rooms:lobby", %{user: "alice"})
# Leave message
leave_msg = Phoenix.SocketClient.Message.leave("rooms:lobby")
# Push message
push_msg = Phoenix.SocketClient.Message.push(
"rooms:lobby",
"new_message",
%{body: "Hello, world!"}
)Message Structure
%Phoenix.SocketClient.Message{
topic: "rooms:lobby",
event: "new_message",
payload: %{body: "Hello"},
channel_pid: #PID<0.123.0>,
ref: "123",
join_ref: "123"
}Message Hooks
Register global message hooks:
defmodule MyApp.MessageHandler do
def handle_message(%Phoenix.SocketClient.Message{event: "new_message"} = msg, socket) do
# Process incoming message
IO.puts("New message: #{msg.payload["body"]}")
socket
end
def handle_message(msg, socket) do
# Handle other messages
socket
end
end
# Register hook (implementation depends on your setup)
Phoenix.SocketClient.register_message_hook(socket, MyApp.MessageHandler)Connection Management
Connection States
case Phoenix.SocketClient.get_state(socket, :status) do
:disconnected -> # Not connected
:connecting -> # Connection in progress
:connected -> # Fully connected
endReconnection Configuration
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
reconnect: true,
reconnect_interval: 30_000, # 30 seconds
max_reconnect_attempts: 10
])Connection Events
Monitor connection events:
defmodule MyApp.ConnectionMonitor do
use GenServer
def start_link(socket) do
GenServer.start_link(__MODULE__, socket)
end
def init(socket) do
# Monitor connection status
:timer.send_interval(5000, :check_connection)
{:ok, socket}
end
def handle_info(:check_connection, socket) do
case Phoenix.SocketClient.connected?(socket) do
true -> IO.puts("Connected")
false -> IO.puts("Disconnected")
end
{:noreply, socket}
end
endPerformance Optimizations
Binary Pooling
Optimize JSON encoding/decoding for repeated patterns:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
binary_pool_size: 2000, # Max patterns to cache
binary_max_age: 600_000, # 10 minutes TTL
binary_cleanup_interval: 30_000 # Cleanup interval
])Route Caching
Optimize channel-to-process mapping:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
route_cache_size: 2000, # Max routes to cache
route_cache_ttl: 600_000, # 10 minutes TTL
route_cleanup_interval: 60_000 # Cleanup interval
])Process Hibernation
Reduce memory usage for idle connections:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
hibernation_enabled: true,
hibernation_idle_timeout: 600_000, # 10 minutes
hibernation_memory_threshold: 50_000 # Memory threshold
])TCP Optimization
Enhanced WebSocket transport with TCP tuning:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
transport_opts: [
tcp_opts: [
nodelay: true, # Disable Nagle's algorithm
keepalive: true, # Enable TCP keepalive
buffer: 128 * 1024, # 128KB socket buffer
send_buffer: 64 * 1024, # 64KB send buffer
recv_buffer: 64 * 1024, # 64KB receive buffer
keepalive_idle: 7200, # 2 hours idle time
keepalive_interval: 75, # 75 seconds interval
keepalive_count: 9 # 9 keepalive probes
],
connect_timeout: 10_000, # 10 second connect timeout
compression: true # Enable message compression
]
])Monitoring and Metrics
Performance Monitor
Access performance metrics:
# Get current metrics
{:ok, metrics} = Phoenix.SocketClient.PerformanceMonitor.get_metrics(monitor_pid)
# Get performance summary
{:ok, summary} = Phoenix.SocketClient.PerformanceMonitor.get_summary(monitor_pid)
# Generate performance report
{:ok, report} = Phoenix.SocketClient.PerformanceMonitor.generate_report(
monitor_pid,
:text
)Component Statistics
Access individual component statistics:
# Binary pool stats
{:ok, pool_stats} = Phoenix.SocketClient.BinaryPool.stats(pool_pid)
# Route cache stats
{:ok, cache_stats} = Phoenix.SocketClient.RouteCache.stats(cache_pid)
# Hibernation stats
{:ok, hibernation_stats} = Phoenix.SocketClient.HibernationManager.stats(hibernation_pid)
# Message processor stats
{:ok, processor_stats} = Phoenix.SocketClient.MessageProcessor.stats(processor_pid)Telemetry Events
Subscribe to telemetry events:
:telemetry.attach_many("phoenix-socket-client", [
[:phoenix_socket_client, :socket, :connected],
[:phoenix_socket_client, :socket, :disconnected],
[:phoenix_socket_client, :channel, :joined],
[:phoenix_socket_client, :channel, :left],
[:phoenix_socket_client, :message, :sent],
[:phoenix_socket_client, :message, :received]
], &handle_telemetry/4, [])
defp handle_telemetry(event_name, measurements, metadata, config) do
IO.inspect({event_name, measurements, metadata})
endError Handling
Connection Errors
case Phoenix.SocketClient.start_link([url: "ws://invalid-host"]) do
{:ok, socket} ->
# Connected successfully
{:error, reason} ->
# Handle connection error
IO.puts("Connection failed: #{inspect(reason)}")
endChannel Join Errors
case Phoenix.SocketClient.Channel.join(socket, "rooms:private", %{token: "invalid"}) do
{:ok, response, channel_pid} ->
# Joined successfully
{:error, :unauthorized} ->
# Handle authorization error
{:error, reason} ->
# Handle other join errors
endMessage Errors
try do
Phoenix.SocketClient.push(socket, message)
catch
:exit, {:noproc, _} ->
# Socket process not found
:error, reason ->
# Other error
endTimeout Handling
# Add timeout to operations
Task.async(fn ->
Phoenix.SocketClient.Channel.join(socket, "rooms:lobby", %{})
end)
|> Task.await(5000) # 5 second timeoutAdvanced Configuration
Registry Configuration
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
registry_name: MyApp.SocketRegistry,
name: MyApp.Socket
])Channel Module Mapping
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
topic_channel_map: %{
"rooms:*" => MyApp.ChatChannel,
"users:*" => MyApp.UserChannel,
"notifications:*" => MyApp.NotificationChannel
},
default_channel_module: MyApp.DefaultChannel
])JSON Library Configuration
JSON is used by default. If your application depends on another compatible
JSON module, pass it explicitly:
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
json_library: Jason
])Custom Transport
defmodule MyApp.CustomTransport do
@behaviour Phoenix.SocketClient.Transport
def open(url, opts), do: # Custom implementation
def close(socket), do: # Custom implementation
end
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
transport: MyApp.CustomTransport
])Examples
Real-time Chat Application
defmodule ChatApp do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, %{})
end
def init(_state) do
# Start socket connection
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
auto_connect: true
])
# Join chat room
{:ok, _response, channel} = Phoenix.SocketClient.Channel.join(
socket,
"rooms:general",
%{username: "alice"}
)
{:ok, %{socket: socket, channel: channel}}
end
def send_message(pid, message) do
GenServer.call(pid, {:send_message, message})
end
def handle_call({:send_message, body}, _from, state) do
Phoenix.SocketClient.Channel.push(
state.channel,
"new_message",
%{body: body, username: "alice", timestamp: DateTime.utc_now()}
)
{:reply, :ok, state}
end
end
# Usage
{:ok, chat} = ChatApp.start_link()
ChatApp.send_message(chat, "Hello, everyone!")Live Data Streaming
defmodule DataStreamer do
use GenServer
def start_link(symbol) do
GenServer.start_link(__MODULE__, symbol)
end
def init(symbol) do
{:ok, socket} = Phoenix.SocketClient.start_link([
url: "ws://localhost:4000/socket/websocket",
auto_connect: true
])
{:ok, _response, channel} = Phoenix.SocketClient.Channel.join(
socket,
"stocks:#{symbol}",
%{symbol: symbol}
)
# Start processing messages
Process.send_after(self(), :process_messages, 1000)
{:ok, %{socket: socket, channel: channel, data: []}}
end
def handle_info(:process_messages, state) do
# Process accumulated data
if length(state.data) > 0 do
# Analyze or store data
IO.puts("Received #{length(state.data)} price updates")
end
Process.send_after(self(), :process_messages, 5000)
{:noreply, %{state | data: []}}
end
def handle_info(%Phoenix.SocketClient.Message{event: "price_update", payload: payload}, state) do
# Accumulate price data
new_data = [payload | state.data]
{:noreply, %{state | data: new_data}}
end
endMulti-socket Management
defmodule ConnectionManager do
use GenServer
def start_link do
GenServer.start_link(__MODULE__, %{})
end
def init(_state) do
# Start multiple socket connections
sockets = for env <- [:dev, :staging, :prod] do
{:ok, socket} = Phoenix.SocketClient.start_link([
url: get_url_for_env(env),
registry_name: :"Registry#{env}",
name: :"Socket#{env}"
])
{env, socket}
end
{:ok, %{sockets: Map.new(sockets)}}
end
def broadcast_to_all(pid, topic, event, payload) do
GenServer.call(pid, {:broadcast, topic, event, payload})
end
def handle_call({:broadcast, topic, event, payload}, _from, state) do
for {_env, socket} <- state.sockets do
message = Phoenix.SocketClient.Message.push(topic, event, payload)
Phoenix.SocketClient.push(socket, message)
end
{:reply, :ok, state}
end
defp get_url_for_env(:dev), do: "ws://dev.example.com/socket/websocket"
defp get_url_for_env(:staging), do: "ws://staging.example.com/socket/websocket"
defp get_url_for_env(:prod), do: "ws://prod.example.com/socket/websocket"
endThis comprehensive API documentation provides developers with everything they need to effectively use the Phoenix Socket Client library, from basic usage to advanced performance optimizations and real-world examples.