Exam Devices Sync Challenge

TLDR: Implement a WebSocket server and client to synchronize multiple devices for an exam environment.

This challenge involves synchronizing multiple devices for an exam environment.

Problem Description

In an exam environment, multiple devices need to be synchronized to ensure all students start and finish at the same time. The system needs to handle network latency, device time differences, and potential disconnections.

Solution via WebSockets

The core of our solution relies on WebSockets, which provide a persistent, full-duplex connection between the server and client devices. This approach offers several advantages for our synchronization challenge:

  • Real-time Communication: WebSockets maintain an open connection, allowing for immediate bidirectional communication without the overhead of HTTP requests.
  • Low Latency: Once established, WebSocket connections have minimal latency compared to traditional HTTP requests.
  • Efficient Resource Usage: WebSockets reduce server load by eliminating the need for repeated connection establishments.
  • Built-in Reconnection: Most WebSocket libraries provide automatic reconnection capabilities, essential for handling temporary network issues.

Implementation Details

Our WebSocket-based synchronization system consists of the following components:

  • Connection Establishment: Each client device establishes a WebSocket connection to the server before the exam begins.
  • Time Synchronization Protocol: The server sends its current timestamp to all connected clients, who calculate their local time offset.
  • Heartbeat Mechanism: Clients send periodic heartbeat messages to the server, which responds with the current server time to maintain synchronization.
  • Event Broadcasting: The server broadcasts exam events (start, pause, resume, end) to all connected clients simultaneously.
  • State Management: Each client maintains its local state but regularly syncs with the server to ensure consistency.
  • Offline Handling: When a connection is lost, the client continues with its local timer and attempts to reconnect, syncing its state when the connection is restored.

Code Example

// Server-side WebSocket implementation
const wss = new WebSocket.Server({ port: 8080 });

// Store connected clients
const clients = new Map();

wss.on('connection', (ws) => {
  const clientId = generateClientId();
  clients.set(clientId, ws);
  
  // Send initial server time to the client
  ws.send(JSON.stringify({
    type: 'timeSync',
    serverTime: Date.now()
  }));
  
  // Handle messages from clients
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    
    if (data.type === 'heartbeat') {
      // Respond with current server time
      ws.send(JSON.stringify({
        type: 'timeSync',
        serverTime: Date.now()
      }));
    }
  });
  
  // Handle client disconnection
  ws.on('close', () => {
    clients.delete(clientId);
  });
});

// Broadcast exam events to all clients
function broadcastExamEvent(eventType, data) {
  const message = JSON.stringify({
    type: eventType,
    ...data
  });
  
  clients.forEach(client => {
    if (client.readyState === WebSocket.OPEN) {
      client.send(message);
    }
  });
}

Client-side Implementation

// Client-side WebSocket implementation
class ExamSyncClient {
  constructor(serverUrl) {
    this.serverUrl = serverUrl;
    this.ws = null;
    this.timeOffset = 0;
    this.connected = false;
    this.reconnectAttempts = 0;
    this.maxReconnectAttempts = 5;
  }
  
  connect() {
    this.ws = new WebSocket(this.serverUrl);
    
    this.ws.onopen = () => {
      this.connected = true;
      this.reconnectAttempts = 0;
      console.log('Connected to exam server');
    };
    
    this.ws.onmessage = (event) => {
      const data = JSON.parse(event.data);
      
      if (data.type === 'timeSync') {
        // Calculate time offset between server and client
        const clientTime = Date.now();
        this.timeOffset = data.serverTime - clientTime;
      } else if (data.type === 'examStart') {
        this.handleExamStart(data);
      } else if (data.type === 'examEnd') {
        this.handleExamEnd(data);
      }
    };
    
    this.ws.onclose = () => {
      this.connected = false;
      this.attemptReconnect();
    };
    
    // Start heartbeat
    this.startHeartbeat();
  }
  
  // Get synchronized time
  getServerTime() {
    return Date.now() + this.timeOffset;
  }
  
  // Send heartbeat to server
  startHeartbeat() {
    setInterval(() => {
      if (this.connected) {
        this.ws.send(JSON.stringify({
          type: 'heartbeat',
          clientTime: Date.now()
        }));
      }
    }, 5000); // Every 5 seconds
  }
  
  // Attempt to reconnect
  attemptReconnect() {
    if (this.reconnectAttempts < this.maxReconnectAttempts) {
      this.reconnectAttempts++;
      setTimeout(() => {
        console.log(`Reconnecting... Attempt ${this.reconnectAttempts}`);
        this.connect();
      }, 2000 * this.reconnectAttempts); // Exponential backoff
    }
  }
}

Welcome to our site!

Let's start with a quick question:

What is the capital of Israel?