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
相关产品推荐
相关产品推荐

