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

TCP接收Teltonika FMB920 GPS追踪器数据时重复接收问题排查

问题描述

我用示例代码接收Teltonika FMB920 GPS追踪器的数据,按照Teltonika编解码器文档的TCP交互规则:

  • 追踪器先发送IMEI,服务器回复1字节的0x01(接受)或0x00(拒绝)
  • 之后追踪器发送GPS数据包,服务器必须回复已接收的数据包数量,否则追踪器会重复发送同一数据包直到收到回复

目前代码能正常接收并响应IMEI,但始终重复收到同一个GPS数据包。奇怪的是,在this.ResponseReverse(avlData.NumberOfData1, stream);这行打断点,取消断点继续运行后,追踪器就能正常发送新数据包。尝试用Thread.Sleep()模拟断点效果,但没有作用。

相关核心代码:

private void ClientHandler(object client)
{
    var tcpClient = (TcpClient)client;
    //tcpClient.NoDelay = true;

    var stream = tcpClient.GetStream();

    var imei = this.ReadImei(stream);

    var commArgs = this.OnDeviceCnnected(imei);
    if (commArgs.CommunicationAccepted)
    {
        this.Accept(stream);
        
        this.OnStatusChanged(imei, AvlTcpStatus.Accepted);
    }
    else
    {
        this.Reject(stream);
        this.OnStatusChanged(imei, AvlTcpStatus.Rejected);
    }
    

    while (true)
    {
        try
        {
            var memoryStream = this.ReadData(stream);
            if (memoryStream.Length == 0)
            {
                this.OnStatusChanged(imei, AvlTcpStatus.Terminated);
                break;
            }

            var packet = PacketParser.Parse<AvlTcpPacket>(memoryStream);
            if (memoryStream.Position != memoryStream.Length)
                throw new InvalidDataException("unfinished avl packet");

            var avlData = packet.GetAvlDataArray();
            this.OnDataFetched(imei, avlData);
            //Thread.Sleep(1000);
            this.ResponseReverse(avlData.NumberOfData1, stream);
            //Thread.Sleep(1000);

        }
        catch (Exception exception)
        {
            this.OnErrorOccured(imei, exception);
        }
    }
}

更新:补充相关方法代码

private void Accept(NetworkStream stream)
{
    // 1 as byte array response will determine if it would accept data from this module.
    this.Response(1, stream);
}

private void Reject(NetworkStream stream)
{
    // 0 as byte array response will determine if it would reject data from this module.
    this.Response(0, stream);
}

private void Response(int count, NetworkStream stream)
{
    var answer = BitConverter.GetBytes(count).ToArray();
    stream.Write(answer, 0, answer.Length);
}

private void ResponseReverse(int count, NetworkStream stream)
{
    var answer = BitConverter.GetBytes(count).Reverse().ToArray();
    stream.Write(answer, 0, answer.Length);
}

private string ReadImei(NetworkStream stream)
{
    var buffer = new byte[128]; //IMEI buffer
    var length = stream.Read(buffer, 0, buffer.Length);

    if (buffer[0] != 0 || buffer[1] != 0x0f) throw new InvalidDataException("Wrong IMEI");
    var imei = Encoding.ASCII.GetString(buffer, 2, length - 2);
    return imei;
}

private MemoryStream ReadData(NetworkStream stream)
{
    var buffer = new byte[4096];

    var memoryStream = new MemoryStream();

    while (true)
    {
        var length = stream.Read(buffer, 0, buffer.Length);
        if (length == 0) break;
        memoryStream.Write(buffer, 0, length);
        if (!stream.DataAvailable) break;
    }

    memoryStream.Position = 0;
    return memoryStream;
}
问题原因及解决方案

核心原因

断点暂停时,系统会自动刷新TCP缓冲区,把回复数据发送给追踪器;但正常运行时,NetworkStream.Write()只是把数据写入本地缓冲区,TCP的Nagle算法会等待更多数据再一起发送,导致追踪器迟迟收不到回复,因此重复发包。另外,还要确认回复的字节序和长度是否严格符合Teltonika的要求。

具体修复步骤

  1. 强制刷新TCP缓冲区
    在所有发送回复的方法中,写完数据后立即调用stream.Flush(),确保数据立刻发送:

    private void Response(int count, NetworkStream stream)
    {
        var answer = BitConverter.GetBytes(count).ToArray();
        stream.Write(answer, 0, answer.Length);
        stream.Flush(); // 新增:强制刷新缓冲区
    }
    
    private void ResponseReverse(int count, NetworkStream stream)
    {
        var answer = BitConverter.GetBytes(count).Reverse().ToArray();
        stream.Write(answer, 0, answer.Length);
        stream.Flush(); // 新增:强制刷新缓冲区
    }
    
  2. 确保回复的字节序和长度正确
    Teltonika要求回复是4字节大端序(Big-Endian)整数,表示已接收的AVL记录数。可以用更可靠的方式生成大端序数据,避免系统字节序影响:

    // 替换原有的ResponseReverse方法,显式生成4字节大端序数据
    private void SendAvlResponse(int recordCount, NetworkStream stream)
    {
        byte[] response = new byte[4];
        response[0] = (byte)(recordCount >> 24);
        response[1] = (byte)(recordCount >> 16);
        response[2] = (byte)(recordCount >> 8);
        response[3] = (byte)recordCount;
        stream.Write(response, 0, 4);
        stream.Flush();
    }
    

    然后把ClientHandler中的this.ResponseReverse(avlData.NumberOfData1, stream);替换为:

    this.SendAvlResponse(avlData.NumberOfData1, stream);
    
  3. 可选:禁用Nagle算法
    取消注释tcpClient.NoDelay = true;,禁用Nagle算法,减少小数据包的发送延迟:

    var tcpClient = (TcpClient)client;
    tcpClient.NoDelay = true; // 启用NoDelay,避免Nagle算法延迟
    

内容的提问来源于stack exchange,提问作者Pouya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.26 02:57:12