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.
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.
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:
Our WebSocket-based synchronization system consists of the following components:
// 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 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
}
}
}
Let's start with a quick question:
What is the capital of Israel?