必须用裸tcpconnection,自己做粘包/拆包和nmea解析;nmea帧以$开头、\r\n结尾,需维护连接缓冲区并正则提取完整帧,解析时须校验定位质量、转换度分格式、补全utc日期,高并发下解析逻辑须轻量并异步落库。

Workerman 能高效处理车载GPS数据,但必须避开裸TCP解析HTTP的坑——它不认 $_POST,也不自动拆NMEA帧,得自己定协议或选对协议栈。
用 Workerman\Protocols\Http 还是纯 TCP?
车载设备发的是原始 NMEA 语句(如 $GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47),不是 HTTP 请求。强行套 Workerman\Protocols\Http 会导致解析失败、$request->rawBody() 拿不到完整帧、甚至连接被重置。
- HTTP 协议栈会尝试按 HTTP 头+body 解包,但 GPS 设备发的只是换行分隔的 ASCII 行,没
Content-Length、没Host头,直接报错或截断 - 真实场景中,99% 的 GPS 终端(如 GT06、TK103 协议)走的是自定义 TCP 二进制或明文协议,不是 HTTP
- 结论:必须用裸
TcpConnection,自己做粘包/拆包和 NMEA 解析
如何安全接收并拆分 NMEA 数据帧?
NMEA 帧以 $ 开头、\r\n 结尾,但网络传输可能粘包(多帧连发)或半包(一帧被截断)。不能直接 explode("\r\n", $data),否则会漏掉跨 onMessage 边界的帧。
- 给每个
$connection绑定缓冲区:$connection->buffer = $connection->buffer ?? ''; - 拼接新数据:
$connection->buffer .= $data; - 用正则提取完整帧:
preg_match_all('/\$\w{4,6},[^\r\n]*\r\n/', $connection->buffer, $matches); - 删掉已处理部分:
$connection->buffer = substr($connection->buffer, strlen(implode('', $matches[0]))); - 注意:有些设备用
\n结尾,需统一适配;部分私有协议还带校验字节,不能跳过
解析 NMEA 时最容易忽略的三个点
拿到 $GPGGA 或 $GPRMC 后,别急着 str_getcsv() —— NMEA 字段空缺、逗号数量不固定、校验位错误都会让解析崩掉。
-
$GPGGA第 6 字段是定位质量指示(0=无效,1=GPS,2=DGPS),值为 0 时整条数据应丢弃,不能入库 - 经纬度是度分格式(如
4807.038),要转成十进制度:$lat = intval($str/100) + fmod($str, 100)/60; - 时间字段(如
123519)是 UTC,不是本地时间;且无日期,需结合系统时间补全Y-m-d部分,否则跨日轨迹会错乱
高并发下怎么避免解析阻塞整个进程?
Workerman 是单线程事件循环,onMessage 里做耗时操作(比如写数据库、调远程 API)会让所有连接卡住。GPS 数据量大时,这点尤其致命。
- 解析逻辑本身要轻量:只做字符串切分、格式转换、基础校验,不做 PDO 插入、Redis 写入、HTTP 请求
- 把落库动作交给异步进程:用
Worker::sendToWorkerProcess()推给子进程,或投递到 Redis List / Kafka,由独立消费者处理 - 连接数超 5000 时,建议关闭
heartbeat_idle_time,改用应用层心跳(如每 30 秒收一次$GPZDA),减少内核检测开销
真正难的不是接数据,而是保证每一帧都按顺序、不丢失、不重复地进入解析流水线——缓冲区管理、帧边界识别、时序一致性,这三个环节只要一个松动,轨迹就会漂移或断裂。











