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

Teltonika FMB965持续重复发送相同记录——ACK应答异常问题求助

解决Teltonika FMB965重复发送GPS记录的问题

你遇到的这个重复发送记录的问题,和你推测的ACK应答未成功返回完全吻合!我仔细看了你的服务端代码,发现几个关键问题导致设备收不到正确的ACK,所以一直重复上报数据。

问题分析

  • 错误的数据包读取逻辑:你用while( !feof( $conn ) )循环读取数据,这会让程序一直等待直到连接被设备主动关闭。但Teltonika设备发送完数据包后不会立刻断开连接,而是等待服务器的ACK,这就导致你的程序卡在读取循环里,根本没机会发送ACK,设备超时后就会重发数据,次数越多累积的待确认记录就越多,所以每次连接发送的条数递增。
  • ACK发送时机滞后:即使读取循环能结束,你也是在解析完所有数据后才发送ACK,但这时候设备可能已经因为超时触发重发机制了。

修正后的代码

$ip = env('SOCKET_SERVER_IP');
$port = env('SOCKET_SERVER_PORT');
$parser = new FmParser('tcp');
$socket = stream_socket_server("tcp://$ip:$port", $errno, $errstr);
$this->info("Listening to tcp://{$ip}:{$port}...");

if (!$socket) {
    throw new \Exception("$errstr ($errno)");
} else {
    while ($conn = stream_socket_accept($socket)) {
        // 设置连接超时,避免无限等待
        stream_set_timeout($conn, 5);
        
        // 读取IMEI
        $payload = fread($conn, 1024);
        $imei = $parser->decodeImei($payload);
        $this->info("Received IMEI: $imei");

        // 发送Accept应答
        fwrite($conn, Reply::accept());
        $this->info("Sent Accept reply to device");

        // 按照Teltonika协议读取数据包:先读4字节的大端序长度字段
        $lengthData = fread($conn, 4);
        if (strlen($lengthData) !== 4) {
            $this->error("Failed to read packet length");
            fclose($conn);
            continue;
        }
        
        // 转换长度为整数
        $packetLength = unpack('N', $lengthData)[1];
        $this->info("Expected packet length: $packetLength");

        // 读取对应长度的数据包内容
        $payload = '';
        $bytesRead = 0;
        while ($bytesRead < $packetLength && !feof($conn)) {
            $chunk = fread($conn, $packetLength - $bytesRead);
            if ($chunk === false) break;
            $payload .= $chunk;
            $bytesRead += strlen($chunk);
        }

        if ($bytesRead !== $packetLength) {
            $this->error("Incomplete packet received, expected $packetLength bytes, got $bytesRead");
            fclose($conn);
            continue;
        }

        // 解析并处理数据包
        try {
            $packet = $parser->decodeData($payload);
            $recordCount = count($packet->getAvlDataCollection()->getAvlData());
            $this->info("Successfully parsed $recordCount records");

            // 立即发送ACK应答,这是解决重复上报的核心
            $ack = $parser->encodeAcknowledge($packet);
            fwrite($conn, $ack);
            $this->info("Sent ACK for packet ID: ".$packet->getPacketId());

            // 存储数据到数据库
            foreach ($packet->getAvlDataCollection()->getAvlData() as $avlData) {
                $gps = $avlData->getGpsElement();
                // 执行你的数据库存储逻辑
                $this->info('Record created for timestamp: '.$gps->getTimestamp()->format('Y-m-d H:i:s'));
            }
        } catch (\Exception $e) {
            $this->error("Failed to parse data: ".$e->getMessage());
        }

        fclose($conn);
    }
    fclose($socket);
}

关键修改说明

  • 添加连接超时:用stream_set_timeout($conn, 5)设置5秒超时,避免程序在读取数据时无限等待。
  • 按协议规范读取数据包:Teltonika的AVL数据包开头是4字节的大端序长度,先读取这个长度,再读取对应长度的内容,彻底解决feof循环卡住的问题。
  • 提前发送ACK:解析成功后立刻发送ACK,确保设备能及时收到应答,不会触发重发机制。
  • 完善错误处理:添加了数据包长度校验、不完整包的处理,避免因为异常导致程序崩溃或遗漏ACK发送。

额外注意事项

  • 确保服务器防火墙没有阻止TCP端口的出站流量,设备需要能正常接收服务器的ACK应答。
  • 测试时可以用抓包工具确认ACK是否成功发送到设备,排查网络层面的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:27:46