如何计算Node.js Socket服务器与Unity客户端间的Ping值?
Node.js Socket与Unity客户端Ping值计算问题分析
问题描述
我搭建了一个Node.js Socket服务器和Unity客户端,尝试通过以下代码计算两者间的网络延迟(Ping值),但即使在同一台电脑上测试,日志显示的延迟数值也很高(20-100ms),请问这种计算方式是否正确?
服务器代码(Node.js)
socket.on("readable", () => { let data: Buffer = socket.read(); if(data !== null) { let length: number = parseInt(bufferStream.subarray(0, maxDataLength).toString("hex"), 16); let dataBuffer : Buffer = bufferStream.subarray(maxDataLength, maxDataLength + length); let data: string = dataBuffer.toString('utf-8'); let json: any = JSON.parse(data); switch (json._event.event_name) { case 'ping': let pingData = { event_name : "pong" } socket.write((PrepareData(pingData)); break; } } });
客户端代码(Unity C#)
class PingData { public static float start = 0; public static bool pinged = false; } private async void SocketReading() { if (socket.Available == 0) { return; } byte[] buf = new byte[socket.Available]; await socket.ReceiveAsync(buf, SocketFlags.None); buffer.AddRange(buf); } private void ProcessBuffer() { byte[] lengthField = buffer.Take(4).ToArray(); int dataLength = BitConverter.ToInt32(lengthField, 0); byte[] dataField = buffer.Skip(4).Take(dataLength).ToArray(); string response = Encoding.UTF8.GetString(dataField); EventName _event = JsonUtility.FromJson<EventName>(response); switch (_event.event_name) { case "pong": Debug.Log((Time.time - PingData.start) * 1000f); PingData.pinged = false; break; //other cases } } public void Ping() { SendData<PingEvent> data = new SendData<PingEvent>(new PingEvent()); Send(JsonUtility.ToJson(data)); PingData.start = Time.time; PingData.pinged = true; }
问题分析与修复方案
你的Ping计算核心逻辑是对的(客户端发ping时记时间,收pong算时间差),但代码里的几个缺陷导致了延迟偏高:
1. 服务器端缓冲区处理错误
代码里用了bufferStream但没做累积处理,直接用socket.read()的单次数据解析,会导致TCP粘包问题,甚至数据解析失败,触发额外延迟。正确的做法是维护全局缓冲区,循环解析完整数据包:
// 新增全局缓冲区 let bufferStream = Buffer.alloc(0); socket.on("readable", () => { let chunk; while ((chunk = socket.read()) !== null) { bufferStream = Buffer.concat([bufferStream, chunk]); // 循环解析所有完整包 while (bufferStream.length >= maxDataLength) { let length = parseInt(bufferStream.subarray(0, maxDataLength).toString("hex"), 16); if (bufferStream.length < maxDataLength + length) break; let dataBuffer = bufferStream.subarray(maxDataLength, maxDataLength + length); let data = dataBuffer.toString('utf-8'); let json = JSON.parse(data); if (json._event.event_name === 'ping') { let pingData = { event_name: "pong" }; socket.write(PrepareData(pingData)); } // 移除已解析的部分 bufferStream = bufferStream.subarray(maxDataLength + length); } } });
2. Unity客户端Socket读取逻辑低效
SocketReading只在socket.Available>0时才读取,且没有持续监听,会导致数据接收不及时。应该改成持续异步循环读取:
private async Task SocketReadingLoop() { byte[] buf = new byte[4096]; // 固定缓冲区,减少内存分配 while (socket.Connected) { try { int bytesRead = await socket.ReceiveAsync(buf, SocketFlags.None); if (bytesRead > 0) { lock (buffer) // 多线程环境下加锁,避免并发修改 { buffer.AddRange(buf.Take(bytesRead)); } ProcessBuffer(); } } catch (Exception e) { Debug.LogError("Socket读取错误: " + e.Message); break; } } }
同时ProcessBuffer要移除已处理的缓冲区数据,避免重复解析:
private void ProcessBuffer() { lock (buffer) { while (buffer.Count >= 4) { byte[] lengthField = buffer.Take(4).ToArray(); int dataLength = BitConverter.ToInt32(lengthField, 0); if (buffer.Count < 4 + dataLength) break; byte[] dataField = buffer.Skip(4).Take(dataLength).ToArray(); string response = Encoding.UTF8.GetString(dataField); EventName _event = JsonUtility.FromJson<EventName>(response); if (_event.event_name == "pong") { Debug.Log((Time.time - PingData.start) * 1000f); PingData.pinged = false; } // 移除已处理的字节 buffer.RemoveRange(0, 4 + dataLength); } } }
3. 主线程与异步线程同步问题
SocketReading在后台线程执行,ProcessBuffer直接修改静态变量和调用Debug.Log可能导致时序错误,建议把解析逻辑同步到Unity主线程(比如用UnityMainThreadDispatcher工具)。
总结
修复上述问题后,本地测试的往返延迟应该能降到10ms以内,甚至1-2ms左右。核心Ping计算思路没问题,问题出在TCP粘包处理、Socket读取逻辑这些细节上。
内容的提问来源于stack exchange,提问作者TIng
相关产品推荐
相关产品推荐

