You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何计算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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 07:22:43