Lesson 03-WebSocket Basic API

WebSocket Basic Usage

Constructor

The WebSocket constructor is used to create a new WebSocket instance, serving as the entry point for establishing a WebSocket connection with a server. The constructor accepts two parameters:

var socket = new WebSocket(url, [protocols]);
  • url: Required parameter, specifies the URL of the WebSocket server. The protocol prefix should be ws:// (unencrypted) or wss:// (encrypted).
  • protocols: Optional parameter, a string or array of strings specifying acceptable subprotocols. The server can select one protocol to respond with, or if none are provided or accepted, the connection may fail or default to no-protocol mode.

Instance Properties

WebSocket instances provide several properties to check the connection status and configuration:

  • readyState: Read-only property, returns the connection state, with values 0 (CONNECTING), 1 (OPEN), 2 (CLOSING), or 3 (CLOSED).
  • bufferedAmount: Read-only property, indicates the number of bytes of UTF-8 text or binary data that have not yet been sent to the network.
  • extensions: Read-only property, returns the extensions used by the connection, or an empty string if none are specified or used.
  • protocol: Read-only property, returns the selected subprotocol if the connection is successful and a protocol was specified; otherwise, an empty string.

Code Example:

// Create WebSocket instance
var socket = new WebSocket('ws://your-websocket-server.com');

// Listen for connection open event
socket.addEventListener('open', function(event) {
    console.log('WebSocket connected!');

    // Check connection state using readyState property
    console.log('Current readyState:', socket.readyState); // Should output 1, indicating connection is open

    // Check buffer size before sending large data
    var largeData = new Array(100000).fill('a').join('');
    console.log('BufferedAmount before sending:', socket.bufferedAmount);
    socket.send(largeData);
    console.log('BufferedAmount after sending:', socket.bufferedAmount);

    // View negotiated subprotocol
    console.log('Negotiated protocol:', socket.protocol);
});

// Listen for close event, check final readyState
socket.addEventListener('close', function(event) {
    console.log('Connection closed. Final readyState:', socket.readyState); // Should output 3, indicating connection is closed
});

Instance Methods

WebSocket instances provide several core methods to control the connection and data transfer:

  • send(data): Sends data to the server. data can be a string, ArrayBuffer, or Blob object. WebSocket automatically sets the appropriate opcode based on the data type.
  • close([code[, reason]]): Closes the WebSocket connection. code is an optional close status code (typically 1000 for normal closure), and reason is an optional human-readable close reason string.
  • addEventListener(event, listener): Adds an event listener to the WebSocket instance for events such as open, message, error, and close.

Code Example:

var socket = new WebSocket('ws://your-websocket-server.com');

// Dynamically add open event listener
socket.addEventListener('open', function(event) {
    console.log('Connection opened!');
    socket.send('Initial message from client.');
});

// Example of sending binary data
function sendBinaryData() {
    var binaryData = new Uint8Array([0, 255, 127, 64]);
    socket.send(binaryData);
    console.log('Binary data sent.');
}

// Add custom close logic
function customClose() {
    socket.close(1001, 'User requested close.'); // 1001 is a recommended status code for normal closure
}

// Dynamically add close event listener
socket.addEventListener('close', function(event) {
    console.log('Connection closed with code:', event.code, 'and reason:', event.reason);
});

// Call sendBinaryData or customClose at some point, e.g., when a user clicks a button
document.getElementById('sendBinaryBtn').addEventListener('click', sendBinaryData);
document.getElementById('closeBtn').addEventListener('click', customClose);

Event Handling

WebSocket instances provide several properties and events to track connection status and handle data transfer.

onopen: Event handler triggered when the connection is established.

socket.onopen = function(event) {
  console.log('Connection opened!');
};

onmessage: Triggered when a message is received from the server, with event.data containing the message content.

socket.onmessage = function(event) {
  console.log('Received:', event.data);
};

onerror: Triggered when an error occurs.

socket.onerror = function(error) {
  console.error('Error occurred:', error);
};

onclose: Triggered when the connection is closed, with event.code and event.reason providing closure details.

socket.onclose = function(event) {
  if (event.wasClean) {
    console.log(`[close] Clean close, code=${event.code} reason=${event.reason}`);
  } else {
    console.log('[close] Connection died');
  }
};

Inheritance

The EventTarget interface is implemented by objects that can receive events and create listeners. In other words, any event target implements the three methods associated with this interface.

Element and its children, document, and window are the most common event targets, but other objects can also be event targets, such as XMLHttpRequest, AudioNode, and AudioContext.

  • EventTarget.addEventListener()
    Registers an event handler for a specific event type on the EventTarget.
  • EventTarget.removeEventListener()
    Removes an event listener from the EventTarget.
  • EventTarget.dispatchEvent()
    Dispatches an event to this EventTarget.

Sending and Receiving Data

WebSocket instances provide the send() method to send data to the server, which can be text or binary data.

socket.send('Hello Server!');
socket.send(new Uint8Array());

Complete WebSocket Class Encapsulation

class CustomWebSocket extends WebSocket {
    constructor(url, protocols) {
        super(url, protocols);
        this.reconnectInterval = 5000; // Reconnect interval in milliseconds
        this.isReconnecting = false;
        this.addEventListener('close', this.handleClose.bind(this));
    }

    handleClose(event) {
        if (!this.isReconnecting && (event.code !== 1000)) { // 1000 code means normal closure, no reconnect attempt
            console.log('Connection closed unexpectedly. Reconnecting in', this.reconnectInterval/1000, 'seconds...');
            this.isReconnecting = true;
            setTimeout(() => {
                this.connect();
                this.isReconnecting = false;
            }, this.reconnectInterval);
        }
    }

    connect() {
        super.open(); // WebSocket's open method is internal; this simulates reconnect logic
        console.log('Reconnected to WebSocket server.');
    }

    sendMessage(message) {
        if (this.readyState === WebSocket.OPEN) {
            super.send(message);
            console.log('Sent:', message);
        } else {
            console.warn('WebSocket is not open, message not sent:', message);
        }
    }

    onMessage(callback) {
        this.addEventListener('message', function(event) {
            callback(event.data);
        });
    }
}

Next, we use the CustomWebSocket class defined above to create a WebSocket connection and demonstrate how to send and receive data.

// Instantiate custom WebSocket
const ws = new CustomWebSocket('ws://your-websocket-server.com');

// Set message reception handler
ws.onMessage((data) => {
    console.log('Received:', data);
});

// Send a message
ws.sendMessage('Hello, WebSocket Server!');

// Close the connection at some point
// ws.close(1000, 'User logout');
  • Inheritance and Extension: By extending WebSocket, we created a CustomWebSocket class that inherits from the native WebSocket class, allowing custom logic like automatic reconnection.
  • Sending Data: The sendMessage method encapsulates sending logic, adding a connection state check to ensure data is only sent when the connection is open.
  • Receiving Data: The onMessage method provides a user-friendly interface to register callbacks for message reception events, simplifying message handling.
  • Error Handling and Reconnection: In the handleClose method, we implemented basic error handling and reconnection logic to attempt re-establishing the connection when it closes unexpectedly.

Client API

WebSocket Object Creation (new WebSocket(url))

WebSocket Client Creation Process:

// Basic creation
const socket = new WebSocket('ws://example.com/socket');

// Creation with protocol versions
const socketWithProtocol = new WebSocket('ws://example.com/socket', ['v1.protocol', 'v2.protocol']);

// Creation with custom headers (Note: Custom headers cannot be set directly in browser environments)
// Requires extensions or proxy servers

Underlying Implementation Principle:

// Pseudocode showing internal logic of WebSocket constructor
function WebSocket(url, protocols) {
  // 1. Parse URL
  const parsedUrl = parseUrl(url);

  // 2. Validate protocols
  if (protocols) {
    validateProtocols(protocols);
  }

  // 3. Create underlying connection
  this._connection = new UnderlyingConnection(parsedUrl, protocols);

  // 4. Initialize event system
  this._events = {
    open: new EventTarget(),
    message: new EventTarget(),
    error: new EventTarget(),
    close: new EventTarget()
  };

  // 5. Start handshake process
  this._startHandshake();
}

Event Listening (onopen, onmessage, onerror, onclose)

Complete Event Listening Example:

const socket = new WebSocket('ws://example.com/socket');

// Connection open event
socket.onopen = function(event) {
  console.log('WebSocket connection established');
  // Send initial message here
  socket.send('Hello Server!');
};

// Message reception event
socket.onmessage = function(event) {
  if (event.data instanceof Blob) {
    // Handle binary data
    processBinaryData(event.data);
  } else {
    // Handle text data
    console.log('Received message:', event.data);
  }
};

// Error handling event
socket.onerror = function(error) {
  console.error('WebSocket error:', error);
  // Implement reconnection logic
  scheduleReconnect();
};

// Connection close event
socket.onclose = function(event) {
  console.log('WebSocket connection closed');
  console.log('Close code:', event.code);
  console.log('Close reason:', event.reason);

  // Decide whether to reconnect based on close code
  if (shouldReconnect(event.code)) {
    scheduleReconnect();
  }
};

Event Types Details:

Event TypeTrigger TimingEvent Object Properties
openConnection successfully established
messageMessage receiveddata (message content), origin (source), lastEventId (last event ID)
errorError occurserror (error object)
closeConnection closedcode (close code), reason (close reason), wasClean (whether closed cleanly)

Message Sending (send() Method)

Message Sending Method Details:

// Send text message
socket.send('Hello, WebSocket!');

// Send JSON data
const data = { type: 'message', content: 'Hello' };
socket.send(JSON.stringify(data));

// Send binary data (Blob)
const blob = new Blob(['binary data'], { type: 'application/octet-stream' });
socket.send(blob);

// Send binary data (ArrayBuffer)
const buffer = new ArrayBuffer(128);
socket.send(buffer);

// Send binary data (Uint8Array)
const uint8Array = new Uint8Array([1, 2, 3, 4]);
socket.send(uint8Array);

Underlying Sending Process:

// Pseudocode showing internal logic of send() method
WebSocket.prototype.send = function(data) {
  // 1. Check connection state
  if (this.readyState !== WebSocket.OPEN) {
    throw new Error('WebSocket is not open');
  }

  // 2. Create WebSocket frame based on data type
  let frame;
  if (typeof data === 'string') {
    // Text frame
    frame = createTextFrame(data);
  } else if (data instanceof Blob) {
    // Binary frame (Blob)
    frame = createBinaryFrame(data);
  } else if (data instanceof ArrayBuffer) {
    // Binary frame (ArrayBuffer)
    frame = createBinaryFrame(new Uint8Array(data));
  } else if (data instanceof ArrayBufferView) {
    // Binary frame (ArrayBufferView)
    frame = createBinaryFrame(data);
  } else {
    throw new Error('Unsupported data type');
  }

  // 3. Add frame to send queue
  this._sendQueue.push(frame);

  // 4. Start sending if no data is currently being sent
  if (!this._isSending) {
    this._processSendQueue();
  }
};

Connection Closure (close() Method)

Connection Closure Method Details:

// Normal connection closure (code 1000 indicates normal closure)
socket.close(1000, 'Normal closure');

// Abnormal closure (code 1001 indicates endpoint going away)
socket.close(1001, 'Endpoint going away');

// No code or reason specified (defaults to 1005)
socket.close();

Close Code Standards:

CodeMeaning
1000Normal closure
1001Endpoint going away
1002Protocol error
1003Received unacceptable data
1005No status code (reserved)
1006Connection closed abnormally
1011Server error

Underlying Closure Process:

// Pseudocode showing internal logic of close() method
WebSocket.prototype.close = function(code, reason) {
  // 1. Validate parameters
  if (code && (code < 1000 || code > 4999)) {
    throw new Error('Invalid close code');
  }

  // 2. Return if connection is already closing or closed
  if (this.readyState === WebSocket.CLOSING || this.readyState === WebSocket.CLOSED) {
    return;
  }

  // 3. Set connection state to CLOSING
  this.readyState = WebSocket.CLOSING;

  // 4. Create close frame
  const closeFrame = createCloseFrame(code || 1005, reason || '');

  // 5. Send close frame
  this._sendFrame(closeFrame);

  // 6. Start close timeout timer (if no peer close confirmation received)
  this._closeTimeout = setTimeout(() => {
    if (this.readyState === WebSocket.CLOSING) {
      this._forceClose();
    }
  }, this._closeTimeoutDuration);
};

// Force close connection (when no peer response received)
WebSocket.prototype._forceClose = function() {
  this.readyState = WebSocket.CLOSED;
  this._connection.close();
  this._events.close.dispatchEvent(new CloseEvent('close', {
    code: 1006,
    reason: 'Connection was closed abnormally',
    wasClean: false
  }));
};

Error Handling and Reconnection Mechanism

Error Handling Best Practices:

// 1. Basic error handling
socket.onerror = function(error) {
  console.error('WebSocket error:', error);
};

// 2. Reconnection mechanism implementation
class WebSocketClient {
  constructor(url) {
    this.url = url;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
    this.reconnectDelay = 1000; // Initial reconnect delay of 1 second
    this.socket = null;
    this.connect();
  }

  connect() {
    this.socket = new WebSocket(this.url);

    this.socket.onopen = () => {
      console.log('WebSocket connection established');
      this.reconnectAttempts = 0; // Reset reconnect counter
    };

    this.socket.onclose = (event) => {
      console.log('WebSocket connection closed');
      if (this.shouldReconnect(event.code)) {
        this.scheduleReconnect();
      }
    };

    this.socket.onerror = (error) => {
      console.error('WebSocket error:', error);
    };
  }

  shouldReconnect(code) {
    // Reconnect only for non-normal closure or network errors
    return code !== 1000; // 1000 indicates normal closure
  }

  scheduleReconnect() {
    if (this.reconnectAttempts >= this.maxReconnectAttempts) {
      console.log('Maximum reconnect attempts reached');
      return;
    }

    // Exponential backoff algorithm
    const delay = Math.min(
      this.reconnectDelay * Math.pow(2, this.reconnectAttempts),
      30000 // Maximum delay of 30 seconds
    );

    this.reconnectAttempts++;
    console.log(`Will attempt to reconnect in ${delay}ms (attempt ${this.reconnectAttempts}/${this.maxReconnectAttempts})`);

    setTimeout(() => {
      this.connect();
    }, delay);
  }
}

// Usage example
const client = new WebSocketClient('ws://example.com/socket');

Advanced Reconnection Strategies:

  1. Exponential Backoff Algorithm: Double the delay time for each reconnect attempt until the maximum delay is reached.
  2. Network Status Detection: Check network connectivity before attempting to reconnect.
  3. Heartbeat Detection: Periodically send Ping frames to check connection health.
  4. Multi-level Reconnection: Adopt different reconnection strategies based on error types.

Membership Required

You must be a member to access this content.

View Membership Levels

Already a member? Log in here

Share your love