本地Node.js环境下客户端与服务器时间不一致问题排查
Hey there! Let's break down this problem—first off, the idea that your client and server clocks are out of sync on the same device is almost impossible, since all processes on a single machine share the same system clock. The real issue here is either when you're recording the timestamps or using a time source that can jump backward (looking at you, Date.now()). Let's fix this step by step.
1. Fix the Timestamp Recording Timing (Most Common Culprit)
Chances are you're recording the client's send time after calling ws.send()—but send() is asynchronous, meaning the message can hit the server before your client gets around to logging the time. That's why you're seeing server receive times that look earlier than client send times.
Client Code (Browser)
Always record the send time right before sending the message, and include it in the payload:
// Record time FIRST, then send const clientSendTime = Date.now(); ws.send(JSON.stringify({ payload: "Hello, server!", sendTime: clientSendTime, messageId: `msg-${Date.now()}-${Math.random()}` // Unique ID to track messages }));
Server Code (Node.js)
Log the receive time the moment the message hits the server, then calculate latency using the client's send timestamp:
ws.on('message', (data) => { const message = JSON.parse(data); // Record receive time immediately when the message event fires const serverReceiveTime = Date.now(); const latency = serverReceiveTime - message.sendTime; console.log(`Message ${message.messageId} latency: ${latency}ms`); // Optional: Send the result back to the client for validation ws.send(JSON.stringify({ messageId: message.messageId, latency: latency, serverReceiveTime: serverReceiveTime })); });
2. Use Monotonic Time to Avoid System Clock Adjustments
Date.now() relies on your system's wall-clock time, which can be adjusted backward by NTP sync or manual changes. This can cause weird negative latency values even if your timing is correct. Instead, use monotonic high-resolution time—a time source that only moves forward, regardless of system clock changes.
- Browser: Use
performance.now()(returns milliseconds since page load, microsecond precision) - Node.js: Use
performance.now()(returns milliseconds since Node process started, Node 8.5+) orprocess.hrtime.bigint()(nanosecond precision)
Since browser and Node.js monotonic times have different starting points, you'll need a quick initial sync to calculate an offset:
Step 1: Sync Time Offsets
Client (on connection):
let timeOffset = 0; // Stores the offset between server and client monotonic times ws.on('open', () => { const clientSyncMonotonic = performance.now(); const clientWallTime = Date.now(); ws.send(JSON.stringify({ type: 'sync', clientSyncMonotonic: clientSyncMonotonic, clientWallTime: clientWallTime })); }); ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.type === 'sync-response') { // Calculate the offset to align server and client monotonic times const currentClientWallTime = Date.now(); const timeSinceSyncRequest = currentClientWallTime - msg.clientWallTime; const clientMonotonicNow = performance.now(); const adjustedClientSyncMonotonic = clientMonotonicNow - timeSinceSyncRequest; timeOffset = msg.serverSyncMonotonic - adjustedClientSyncMonotonic; console.log('Time sync complete! Offset:', timeOffset); } });
Server:
ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.type === 'sync') { const serverSyncMonotonic = performance.now(); const serverWallTime = Date.now(); ws.send(JSON.stringify({ type: 'sync-response', serverSyncMonotonic: serverSyncMonotonic, serverWallTime: serverWallTime, clientWallTime: msg.clientWallTime })); } });
Step 2: Measure Latency with Monotonic Time
Client:
const clientSendMonotonic = performance.now(); ws.send(JSON.stringify({ type: 'data', payload: 'Test message', messageId: `msg-${Math.random()}`, clientSendMonotonic: clientSendMonotonic }));
Server:
ws.on('message', (data) => { const msg = JSON.parse(data); if (msg.type === 'data') { const serverReceiveMonotonic = performance.now(); // Calculate latency using the synced offset const latency = serverReceiveMonotonic - (msg.clientSendMonotonic + timeOffset); console.log(`Message ${msg.messageId} latency: ${latency.toFixed(2)}ms`); } });
3. Check for Event Loop Blocking
If you still see odd results, make sure neither your client nor server is blocked:
- Client: Avoid long-running sync code (like heavy DOM manipulation or loops) that blocks the main thread—this can delay timestamp logging even after the message is sent.
- Server: Use Node.js tools like
node --trace-event-categories v8,node.async_hooks,node.perfto check for CPU-heavy tasks blocking the event loop, which would delay when themessageevent fires.
内容的提问来源于stack exchange,提问作者m.mikolajczak

