Overview
WebSocket APIs provide a standardized way to establish persistent, bidirectional communication channels between clients and servers. Unlike HTTP's request-response model, WebSocket connections remain open after the initial handshake, allowing both parties to send data at any time without the overhead of repeated connection establishment.
The WebSocket protocol emerged from the need for real-time web applications that could push data to clients without polling. HTTP long-polling and server-sent events offered partial solutions, but introduced latency and complexity. WebSocket addresses these limitations by maintaining a single TCP connection upgraded from an initial HTTP handshake.
The protocol operates in two phases. First, the client sends an HTTP Upgrade request with specific headers indicating WebSocket support. If the server accepts, it responds with status 101 Switching Protocols, and the connection transitions to WebSocket mode. Second, the connection enters a message-passing phase where both sides exchange framed data packets. Each frame contains metadata about message type, length, and whether the message spans multiple frames.
# WebSocket handshake request headers
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
# Server response
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
WebSocket connections support text frames (UTF-8 encoded) and binary frames for arbitrary data. Control frames handle connection management: ping frames test connection liveness, pong frames respond to pings, and close frames initiate graceful shutdown. The protocol includes fragmentation for splitting large messages across multiple frames and masking requirements for client-to-server messages to prevent cache poisoning attacks.
Key Principles
The WebSocket protocol defines a message-oriented communication layer over TCP. After the initial HTTP upgrade, all communication occurs through discrete messages that preserve boundaries. A message consists of one or more frames, where the final frame has the FIN bit set to indicate completion. This framing mechanism enables streaming large payloads without requiring the sender to know the total size upfront.
Frame structure includes an opcode field that identifies the frame type: continuation (0x0), text (0x1), binary (0x2), close (0x8), ping (0x9), or pong (0xA). The payload length occupies 7 bits for messages up to 125 bytes, with extended encoding for larger payloads. Client-originated frames include a 32-bit masking key applied to the payload data through XOR operation, while server-originated frames remain unmasked.
Connection lifecycle management follows a state machine. Connections begin in CONNECTING state during the handshake, transition to OPEN when established, enter CLOSING when either party sends a close frame, and finally reach CLOSED when the TCP connection terminates. Both parties must handle unexpected disconnections, which can occur due to network issues, timeouts, or crashes without clean shutdown.
The protocol requires specific close codes to indicate termination reasons. Code 1000 signals normal closure, 1001 indicates an endpoint is going away, 1002 reports protocol errors, 1003 rejects unacceptable data types, 1008 detects policy violations, and 1009 flags messages too large to process. Close frames can include a UTF-8 encoded reason string for debugging purposes.
Message ordering guarantees that frames sent by one endpoint arrive at the other endpoint in the same order, but messages from different connections have no ordering relationship. The protocol does not provide delivery confirmation or automatic retransmission—these concerns belong to the application layer.
Subprotocol negotiation allows clients and servers to agree on application-level protocols. The client lists acceptable subprotocols in the Sec-WebSocket-Protocol header, and the server selects one (or none) in its response. This mechanism enables multiple applications to share the same WebSocket infrastructure.
# Subprotocol negotiation example
# Client request
Sec-WebSocket-Protocol: chat, superchat
# Server response
Sec-WebSocket-Protocol: chat
Extension negotiation operates similarly through Sec-WebSocket-Extensions headers. Extensions modify frame structure or semantics, such as permessage-deflate for compression. Both parties must support an extension for it to activate.
The protocol distinguishes between connection-level and message-level concerns. Connection failures trigger immediate CLOSED state, while message-level errors (like receiving text data in a binary-only context) should be handled through close frames with appropriate codes. This separation allows graceful degradation when possible and immediate termination when the connection becomes unusable.
Ruby Implementation
Ruby provides multiple WebSocket implementations at different abstraction levels. The websocket-driver gem implements the low-level protocol specification, handling frame parsing, masking, and state management. Higher-level libraries like faye-websocket add event-driven APIs, while frameworks like ActionCable integrate WebSockets into Rails applications.
The websocket-driver gem exposes the protocol implementation separately from I/O operations. Applications create a driver instance, feed it data from the network, and consume its output to send over the connection. This design works with any I/O framework, whether EventMachine, nio4r, or blocking sockets.
require 'websocket/driver'
# Server-side driver
driver = WebSocket::Driver.server(socket)
driver.on :connect do |event|
# Handshake successful
driver.start
end
driver.on :message do |event|
# event.data contains the message payload
puts "Received: #{event.data}"
driver.text("Echo: #{event.data}")
end
driver.on :close do |event|
# event.code and event.reason contain close details
puts "Closed: #{event.code} - #{event.reason}"
end
# Feed incoming data to driver
driver.parse(data_from_socket)
# Send driver output to socket
socket.write(driver.bytes_remaining)
The faye-websocket gem builds on websocket-driver with a simpler API suitable for common use cases. It handles I/O internally and provides EventMachine or asynchronous I/O support. The API mimics browser WebSocket objects.
require 'faye/websocket'
require 'eventmachine'
EM.run do
# Client connection
ws = Faye::WebSocket::Client.new('ws://echo.websocket.org/')
ws.on :open do |event|
ws.send('Hello, world!')
end
ws.on :message do |event|
puts event.data
end
ws.on :error do |event|
puts "Error: #{event.message}"
end
ws.on :close do |event|
puts "Closed: #{event.code} - #{event.reason}"
EM.stop
end
end
ActionCable integrates WebSocket support into Rails through channels—Ruby classes that handle subscriptions and broadcasting. Channels abstract connection management, routing messages to appropriate handlers based on identifiers. ActionCable supports multiple connection backends including Redis and PostgreSQL for horizontal scaling.
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room_id]}"
end
def receive(data)
# Handle incoming message
ActionCable.server.broadcast(
"chat_#{params[:room_id]}",
message: data['message'],
user: current_user.name
)
end
def unsubscribed
# Cleanup when channel is unsubscribed
end
end
Connection lifecycle requires careful resource management. WebSocket objects maintain buffers for partial frames and may hold references to large messages. Applications should implement timeouts for idle connections and limits on message sizes to prevent resource exhaustion.
require 'socket'
require 'websocket/driver'
class WebSocketServer
def initialize(port)
@server = TCPServer.new(port)
@clients = {}
end
def run
loop do
socket = @server.accept
Thread.new { handle_client(socket) }
end
end
def handle_client(socket)
driver = WebSocket::Driver.server(socket)
@clients[socket] = driver
driver.on :connect do
driver.start
end
driver.on :message do |event|
broadcast(event.data, exclude: socket)
end
driver.on :close do
@clients.delete(socket)
socket.close
end
loop do
data = socket.readpartial(4096)
driver.parse(data)
end
rescue EOFError, Errno::ECONNRESET
@clients.delete(socket)
socket.close
end
def broadcast(message, exclude: nil)
@clients.each do |sock, driver|
next if sock == exclude
driver.text(message)
sock.write(driver.bytes_remaining)
end
end
end
Message serialization requires explicit handling. WebSocket frames contain raw bytes (for binary frames) or UTF-8 strings (for text frames). Applications typically use JSON for text messages or protocol buffers for binary messages.
require 'json'
# Sending structured data
data = { type: 'chat', message: 'Hello', timestamp: Time.now.to_i }
driver.text(JSON.generate(data))
# Receiving structured data
driver.on :message do |event|
data = JSON.parse(event.data)
handle_message(data['type'], data)
rescue JSON::ParserError => e
driver.close(1003, "Invalid JSON")
end
Ruby's Thread model affects WebSocket implementations. Each connection typically requires a dedicated thread when using blocking I/O, limiting scalability. EventMachine or Async provide non-blocking alternatives that multiplex thousands of connections per process. ActionCable uses Concurrent Ruby for thread management.
Practical Examples
A basic echo server demonstrates WebSocket fundamentals. The server accepts connections, echoes back any received messages, and handles connection lifecycle properly.
require 'socket'
require 'websocket/driver'
require 'base64'
require 'digest/sha1'
class EchoServer
def initialize(port = 8080)
@server = TCPServer.new(port)
end
def start
puts "WebSocket echo server listening on port #{@server.addr[1]}"
loop do
socket = @server.accept
Thread.new { handle_connection(socket) }
end
end
private
def handle_connection(socket)
# Perform HTTP to WebSocket upgrade
request = socket.gets
headers = {}
while (line = socket.gets.chomp) != ""
key, value = line.split(": ", 2)
headers[key] = value
end
if headers["Upgrade"] == "websocket"
perform_handshake(socket, headers)
handle_websocket(socket)
else
socket.close
end
rescue => e
puts "Error: #{e.message}"
socket.close
end
def perform_handshake(socket, headers)
key = headers["Sec-WebSocket-Key"]
accept = Base64.strict_encode64(
Digest::SHA1.digest(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
)
response = [
"HTTP/1.1 101 Switching Protocols",
"Upgrade: websocket",
"Connection: Upgrade",
"Sec-WebSocket-Accept: #{accept}",
"\r\n"
].join("\r\n")
socket.write(response)
end
def handle_websocket(socket)
driver = WebSocket::Driver.server(socket)
driver.on :connect do
driver.start
end
driver.on :message do |event|
puts "Received: #{event.data}"
driver.text("Echo: #{event.data}")
socket.write(driver.bytes_remaining)
end
driver.on :close do |event|
puts "Connection closed: #{event.code}"
socket.close
end
loop do
data = socket.readpartial(4096)
driver.parse(data)
end
rescue EOFError, Errno::ECONNRESET
socket.close
end
end
EchoServer.new.start
A chat room application shows message broadcasting across multiple connections. The server maintains a list of active connections and distributes messages to all participants.
require 'faye/websocket'
require 'eventmachine'
require 'json'
class ChatRoom
def initialize
@clients = []
end
def add_client(ws)
@clients << ws
broadcast({ type: 'join', count: @clients.length })
end
def remove_client(ws)
@clients.delete(ws)
broadcast({ type: 'leave', count: @clients.length })
end
def broadcast(message, exclude: nil)
data = message.is_a?(String) ? message : JSON.generate(message)
@clients.each do |client|
next if client == exclude
client.send(data)
end
end
def handle_message(ws, data)
message = JSON.parse(data)
case message['type']
when 'chat'
broadcast({
type: 'chat',
user: message['user'],
text: message['text'],
timestamp: Time.now.to_i
}, exclude: ws)
when 'typing'
broadcast({
type: 'typing',
user: message['user']
}, exclude: ws)
end
rescue JSON::ParserError
ws.send(JSON.generate({ type: 'error', message: 'Invalid JSON' }))
end
end
EM.run do
room = ChatRoom.new
EM::WebSocket.run(host: '0.0.0.0', port: 8080) do |ws|
ws.onopen do
room.add_client(ws)
end
ws.onmessage do |message|
room.handle_message(ws, message)
end
ws.onclose do
room.remove_client(ws)
end
ws.onerror do |error|
puts "WebSocket error: #{error.message}"
end
end
puts "Chat room running on ws://localhost:8080"
end
Real-time data streaming requires handling backpressure when clients consume data slower than production rate. The server must either buffer messages, drop old data, or throttle producers.
class StreamingConnection
MAX_BUFFER_SIZE = 1000
def initialize(driver, socket)
@driver = driver
@socket = socket
@buffer = []
@flushing = false
end
def send_data(data)
@buffer << data
if @buffer.length > MAX_BUFFER_SIZE
# Drop oldest messages to prevent memory exhaustion
@buffer.shift
end
flush unless @flushing
end
private
def flush
return if @buffer.empty? || @flushing
@flushing = true
while message = @buffer.shift
@driver.text(JSON.generate(message))
begin
@socket.write_nonblock(@driver.bytes_remaining)
rescue IO::WaitWritable
# Socket buffer full, re-queue message
@buffer.unshift(message)
# Schedule retry after socket becomes writable
IO.select(nil, [@socket], nil, 0.1)
retry
end
end
@flushing = false
end
end
WebSocket clients must implement reconnection logic to handle temporary network failures. Exponential backoff prevents overwhelming the server during outages.
class ResilientWebSocketClient
MAX_RECONNECT_DELAY = 30
def initialize(url)
@url = url
@reconnect_delay = 1
@should_reconnect = true
end
def connect
@ws = Faye::WebSocket::Client.new(@url)
@ws.on :open do
puts "Connected"
@reconnect_delay = 1
on_open if respond_to?(:on_open)
end
@ws.on :message do |event|
on_message(event.data) if respond_to?(:on_message)
end
@ws.on :close do |event|
puts "Disconnected: #{event.code}"
schedule_reconnect if @should_reconnect
end
@ws.on :error do |event|
puts "Error: #{event.message}"
end
end
def disconnect
@should_reconnect = false
@ws.close if @ws
end
def send(data)
@ws.send(data) if @ws
end
private
def schedule_reconnect
sleep @reconnect_delay
@reconnect_delay = [@reconnect_delay * 2, MAX_RECONNECT_DELAY].min
puts "Reconnecting..."
connect
end
end
Security Implications
WebSocket connections inherit the security context of the page that created them. Cross-origin connections require explicit server authorization through the Sec-WebSocket-Protocol header check and origin validation during handshake. Servers must verify the Origin header matches expected values to prevent unauthorized domains from establishing connections.
def validate_origin(headers)
origin = headers['Origin']
allowed_origins = ['https://example.com', 'https://app.example.com']
unless allowed_origins.include?(origin)
return [403, { 'Content-Type' => 'text/plain' }, ['Forbidden']]
end
# Continue with handshake
end
Authentication must occur before or during the WebSocket handshake since the connection remains open afterward. Common approaches include passing authentication tokens in URL query parameters, using cookies from the initial HTTP request, or sending credentials in the first WebSocket message.
# ActionCable authentication example
module ApplicationCable
class Connection < ActionCable::Connection::Base
identified_by :current_user
def connect
self.current_user = find_verified_user
end
private
def find_verified_user
token = request.params[:token]
if user = User.find_by(auth_token: token)
user
else
reject_unauthorized_connection
end
end
end
end
WebSocket messages are not automatically sanitized. Applications must validate and escape all message content before processing or broadcasting to prevent XSS attacks. Text frames should be treated as untrusted input regardless of source.
require 'cgi'
def handle_chat_message(data)
message = JSON.parse(data)
# Validate message structure
unless message['text'].is_a?(String) && message['text'].length <= 1000
return { error: 'Invalid message' }
end
# Sanitize HTML content
sanitized_text = CGI.escapeHTML(message['text'])
broadcast({
type: 'chat',
text: sanitized_text,
user: current_user.name
})
end
Rate limiting prevents abuse from malicious or misbehaving clients. Servers should track message frequency per connection and close connections that exceed thresholds.
class RateLimitedConnection
MAX_MESSAGES_PER_MINUTE = 60
def initialize
@message_times = []
end
def check_rate_limit
now = Time.now
# Remove messages older than 1 minute
@message_times.reject! { |time| now - time > 60 }
if @message_times.length >= MAX_MESSAGES_PER_MINUTE
return false
end
@message_times << now
true
end
def handle_message(data)
unless check_rate_limit
close(1008, "Rate limit exceeded")
return
end
process_message(data)
end
end
WebSocket connections do not enforce same-origin policy for subresources. Messages can contain URLs to arbitrary resources, requiring careful validation before fetching referenced content. Applications should whitelist allowed domains or protocols.
TLS encryption (wss:// protocol) protects message content in transit. WebSocket connections established from HTTPS pages must use WSS to prevent mixed content warnings and maintain security guarantees. Certificate validation applies identically to HTTPS.
Compression through the permessage-deflate extension introduces security considerations. CRIME and BREACH attacks exploit compression side channels by observing compressed message sizes. Applications transmitting sensitive data alongside user-controlled content should disable compression or implement countermeasures like message padding.
# Disable compression for sensitive connections
driver = WebSocket::Driver.server(socket)
driver.add_extension(
WebSocket::Driver::Extension.new('permessage-deflate')
) unless sensitive_connection?
Connection hijacking can occur if an attacker obtains session credentials or cookies. WebSocket connections should implement additional authorization checks for sensitive operations rather than relying solely on connection authentication.
Denial of service attacks target connection resources or message processing. Servers must limit per-connection message sizes, overall connection counts, and CPU time spent processing messages. Memory exhaustion from large queues or fragmented messages requires explicit safeguards.
Performance Considerations
WebSocket connections maintain TCP state and application-level buffers, consuming more resources than stateless HTTP requests. Each connection requires file descriptors, memory for partial frames, and thread or event loop space. Servers typically handle 10,000 to 100,000 concurrent connections per process depending on architecture.
Message framing overhead varies by payload size. Short messages incur 2-6 bytes per frame for header information, making WebSockets less efficient than HTTP for tiny payloads sent infrequently. The protocol excels when sending frequent messages or maintaining bidirectional communication channels.
# Benchmark comparing HTTP polling vs WebSocket
require 'benchmark'
# HTTP polling: 1000 requests
http_time = Benchmark.measure do
1000.times do
# Each request requires full TCP handshake + HTTP overhead
# ~500-1000 bytes overhead per request
end
end
# WebSocket: 1000 messages over single connection
ws_time = Benchmark.measure do
# Single TCP connection + WebSocket handshake
# ~4-6 bytes overhead per message
1000.times { ws.send(small_message) }
end
# WebSocket is 10-50x faster for frequent small messages
Frame masking applied to client messages requires XOR operations on every byte, adding CPU overhead. Servers send unmasked frames to reduce processing requirements. For bulk data transfer, binary frames avoid UTF-8 validation costs present in text frames.
Fragmentation allows streaming large messages without buffering the entire payload, reducing memory consumption. Applications can begin processing data as fragments arrive rather than waiting for message completion.
def stream_large_file(driver, socket, file_path)
File.open(file_path, 'rb') do |file|
first_chunk = true
while chunk = file.read(4096)
is_final = file.eof?
if first_chunk
driver.binary(chunk, final: is_final)
first_chunk = false
else
driver.binary(chunk, final: is_final)
end
socket.write(driver.bytes_remaining)
end
end
end
The permessage-deflate extension compresses individual messages using DEFLATE. Compression ratios vary by data type—JSON and text compress well, while binary protocols may see minimal benefit. Compression adds CPU overhead during frame processing and increases latency for small messages.
# Configure compression parameters
driver = WebSocket::Driver.server(socket)
driver.add_extension(
WebSocket::Driver::Extension.new('permessage-deflate', {
server_max_window_bits: 15, # Memory vs compression tradeoff
client_max_window_bits: 15,
server_no_context_takeover: true # Don't persist state between messages
})
)
Connection pooling on clients reduces handshake overhead when creating multiple logical channels to the same server. A single WebSocket connection can multiplex multiple application-level streams through message routing.
Network conditions affect WebSocket performance more than HTTP due to connection persistence. Packet loss triggers TCP retransmission, blocking all messages until recovery completes. Mobile networks with frequent handoffs may cause repeated reconnections.
Heartbeat messages keep connections alive through intermediate proxies and firewalls that timeout idle connections. Ping/pong frames provide protocol-level keepalive, while application messages serve the same purpose for proxies that don't parse WebSocket frames.
def start_heartbeat(driver, socket, interval: 30)
Thread.new do
loop do
sleep interval
driver.ping('keepalive')
socket.write(driver.bytes_remaining)
end
end
end
Horizontal scaling requires session affinity (sticky connections) or shared state across servers. Load balancers must route all messages for a connection to the same backend. Redis or similar stores enable message broadcasting across multiple server processes.
# ActionCable with Redis adapter for scaling
# config/cable.yml
production:
adapter: redis
url: redis://localhost:6379/1
channel_prefix: app_production
# Messages broadcast through Redis reach all server processes
ActionCable.server.broadcast('channel', message: 'data')
Common Pitfalls
WebSocket connection state requires careful tracking. Applications often fail to handle the CLOSING state correctly, attempting to send messages after receiving a close frame but before the TCP connection terminates. Sends during CLOSING state should be silently ignored or queued for later reconnection.
class StatefulConnection
def initialize
@state = :connecting
end
def send_message(data)
case @state
when :open
@driver.text(data)
when :closing, :closed
# Ignore or queue message
puts "Cannot send on closed connection"
end
end
def handle_close
@state = :closing
# Flush pending messages before closing
flush_outbound_queue
@state = :closed
end
end
Forgetting to handle fragmented messages leads to data corruption. Applications must buffer incoming fragments until receiving a frame with the FIN bit set. The websocket-driver gem handles this automatically, but low-level implementations require explicit fragment management.
Insufficient error handling for malformed frames causes protocol violations. The WebSocket spec requires closing the connection with appropriate status codes when detecting protocol errors. Applications should never attempt to continue using a connection after detecting frame corruption.
Assuming message delivery works identically to HTTP creates reliability issues. WebSocket provides ordered delivery but no automatic retransmission or acknowledgment. Applications needing delivery guarantees must implement application-level acknowledgment protocols.
class ReliableMessaging
def initialize
@pending_messages = {}
@next_id = 0
end
def send_with_ack(data)
message_id = @next_id
@next_id += 1
@pending_messages[message_id] = {
data: data,
sent_at: Time.now,
retry_count: 0
}
send_message({ id: message_id, type: 'data', payload: data })
end
def handle_ack(message_id)
@pending_messages.delete(message_id)
end
def check_timeouts
@pending_messages.each do |id, message|
if Time.now - message[:sent_at] > 5
if message[:retry_count] < 3
message[:retry_count] += 1
message[:sent_at] = Time.now
send_message({ id: id, type: 'data', payload: message[:data] })
else
# Give up after 3 retries
handle_send_failure(id)
end
end
end
end
end
Memory leaks occur when connection objects maintain references to closed connections or accumulate message history. Event handlers should be explicitly removed when connections close, and message buffers should have size limits.
Blocking operations in message handlers prevent processing subsequent messages. EventMachine and similar frameworks require non-blocking operations or offloading work to background threads.
# Bad: blocks event loop
ws.on :message do |event|
# Blocking database query
result = Database.query("SELECT * FROM large_table")
ws.send(result)
end
# Good: offload to thread pool
ws.on :message do |event|
Thread.pool.process do
result = Database.query("SELECT * FROM large_table")
ws.send(result)
end
end
URL encoding issues arise when passing authentication tokens or parameters in the WebSocket URL. Standard URL encoding rules apply, but many implementations incorrectly handle special characters.
Race conditions occur during connection establishment when applications send messages before the open event fires. Message sends before connection establishment should be queued and transmitted after the handshake completes.
Proxy and firewall incompatibilities cause mysterious connection failures. Some middleboxes do not recognize the WebSocket upgrade or timeout connections based on inactivity. Applications should implement connection retry logic and consider fallback transports.
Reference
WebSocket States
| State | Description | Valid Operations |
|---|---|---|
| CONNECTING | Initial handshake in progress | None, wait for OPEN |
| OPEN | Connection established, ready for messages | send, close, ping |
| CLOSING | Close frame sent or received, waiting for closure | None, connection terminating |
| CLOSED | Connection terminated | None, create new connection |
Frame Types
| Opcode | Type | Description | Control Frame |
|---|---|---|---|
| 0x0 | Continuation | Continuation of fragmented message | No |
| 0x1 | Text | UTF-8 text data | No |
| 0x2 | Binary | Arbitrary binary data | No |
| 0x8 | Close | Connection close request | Yes |
| 0x9 | Ping | Connection liveness check | Yes |
| 0xA | Pong | Response to ping | Yes |
Close Status Codes
| Code | Name | Meaning |
|---|---|---|
| 1000 | Normal Closure | Successful operation complete |
| 1001 | Going Away | Endpoint unavailable (page unload, server shutdown) |
| 1002 | Protocol Error | Protocol violation detected |
| 1003 | Unsupported Data | Data type not acceptable |
| 1006 | Abnormal Closure | Connection lost without close frame |
| 1007 | Invalid Payload | Message data inconsistent with type |
| 1008 | Policy Violation | Message violates policy |
| 1009 | Message Too Big | Message exceeds size limit |
| 1011 | Server Error | Server encountered error |
Ruby WebSocket Libraries
| Library | Level | Use Case |
|---|---|---|
| websocket-driver | Low-level | Protocol implementation, custom I/O |
| faye-websocket | Mid-level | EventMachine integration, client/server |
| ActionCable | High-level | Rails integration, channels, broadcasting |
| websocket-ruby | Mid-level | Synchronous I/O, simple applications |
| em-websocket | Mid-level | EventMachine server implementation |
Connection Handshake Headers
| Header | Direction | Purpose |
|---|---|---|
| Upgrade: websocket | Request | Requests protocol upgrade |
| Connection: Upgrade | Request | Indicates upgrade request |
| Sec-WebSocket-Key | Request | Random value for handshake |
| Sec-WebSocket-Version | Request | WebSocket protocol version |
| Sec-WebSocket-Protocol | Both | Subprotocol negotiation |
| Sec-WebSocket-Extensions | Both | Extension negotiation |
| Sec-WebSocket-Accept | Response | Handshake validation hash |
Message Size Limits
| Implementation | Default Max Size | Configurable |
|---|---|---|
| Browser clients | 2GB theoretical | Implementation dependent |
| websocket-driver | No default limit | Application enforced |
| ActionCable | 256KB | Yes, cable.yml max_message_size |
| Nginx proxy | 1MB | client_max_body_size |
Performance Characteristics
| Operation | Overhead | Notes |
|---|---|---|
| Initial handshake | 1 RTT + TLS | Same as HTTPS request |
| Message frame | 2-14 bytes | Depends on payload length encoding |
| Client masking | 4 bytes + XOR per byte | Required for client-to-server |
| Ping/Pong | 2 bytes | Control frame overhead |
| Close handshake | 1 RTT | Clean connection termination |
Common Configuration Parameters
| Parameter | Typical Value | Purpose |
|---|---|---|
| Max connections per process | 10000-100000 | Resource limits |
| Message size limit | 1-10 MB | Prevent memory exhaustion |
| Idle timeout | 60-300 seconds | Detect dead connections |
| Ping interval | 30-60 seconds | Keep connection alive |
| Reconnect backoff | 1-30 seconds | Exponential backoff max |
| Message queue depth | 100-1000 | Backpressure handling |