Node.js TCP Socket如何将接收的数据包推送至网页端展示
实现TCP数据包结构化解析 + 网页端实时推送方案
核心实现思路
整体基于单Node进程完成三件事,不拆分独立服务:
- 保留原有9898端口TCP监听能力,优化数据包解析逻辑,解决TCP流传输的粘包/拆包问题
- 基于Node内置http模块启动8080端口Web服务,返回前端展示页面
- 实现轻量WebSocket通道,将解析完成的结构化数据包主动推送给所有在线的网页端,不需要前端轮询
1. 优化数据包解析逻辑
原有ExtractMessage函数仅简单split后打印数组长度,针对给定的典型数据包格式,需要做两点修正:
- 为每个TCP连接维护独立接收缓冲区,直到匹配到完整的包格式(以
$$开头、*结尾的完整段)再触发解析,避免流拆分导致的字段错位 - 将拆分后的数组字段映射为可读的结构化对象,替代无意义的数组长度打印
修正后的解析逻辑参考:
// 字段映射规则,和数据包顺序一一对应 const FIELD_MAP = [ 'packetType', 'deviceModel', 'deviceImei', 'firmwareVersion', 'protocolVersion', 'eventDate', 'eventTime', 'latitudeWithDirection', 'longitudeValue', 'longitudeDirection', 'signalStrength', 'emptyTail' ] function parseCompletePacket(rawPacketStr) { // 去掉首尾标记,按分隔符拆分 const cleanContent = rawPacketStr.replace(/^\$\$/, '').replace(/\*$/, '') const fields = cleanContent.split(',,') // 生成结构化对象 const parsedResult = {} FIELD_MAP.forEach((key, index) => { parsedResult[key] = fields[index] || 'N/A' }) return parsedResult }
2. 打通网页端实时推送通道
不需要引入外部第三方依赖,直接在进程内实现轻量WebSocket交互能力:
- 所有网页端打开8080端口页面时,自动和当前Node进程建立WebSocket连接,将连接实例存入全局在线客户端列表
- 当TCP服务完成一个数据包的解析后,遍历所有在线客户端列表,将结构化的解析结果序列化后逐一分发
3. 整合后的完整运行代码
const net = require('net'); const fs = require('fs'); const http = require('http'); const crypto = require('crypto'); // 全局存储在线网页端的WebSocket连接实例 const webClients = new Set(); // 字段映射规则 const FIELD_MAP = [ 'packetType', 'deviceModel', 'deviceImei', 'firmwareVersion', 'protocolVersion', 'eventDate', 'eventTime', 'latitudeWithDirection', 'longitudeValue', 'longitudeDirection', 'signalStrength', 'emptyTail' ] // 解析完整数据包 function parseCompletePacket(rawPacketStr) { const cleanContent = rawPacketStr.replace(/^\$\$/, '').replace(/\*$/, '') const fields = cleanContent.split(',,') const parsedResult = {} FIELD_MAP.forEach((key, index) => { parsedResult[key] = fields[index] || 'N/A' }) return parsedResult } // 原有日志写入逻辑 function WriteDataToLog(data) { fs.appendFile('Log.txt', '\n This is my message : ' + data.toString(), function(err) { if (err) throw err; console.log('Logged!'); }); } // TCP服务创建 const server = net.createServer((socket) => { // 为每个TCP连接初始化接收缓冲区,解决粘包问题 socket.receiveBuffer = Buffer.alloc(0); console.log('Connected.'); socket.on('data', (data) => { // 把新收到的数据拼到缓冲区 socket.receiveBuffer = Buffer.concat([socket.receiveBuffer, data]); const bufferStr = socket.receiveBuffer.toString(); // 循环从缓冲区提取完整的数据包 let startIndex = bufferStr.indexOf('$$'); let endIndex = bufferStr.indexOf('*', startIndex); while (startIndex !== -1 && endIndex !== -1) { // 切出完整数据包 const completePacket = bufferStr.slice(startIndex, endIndex + 1); // 解析数据包 const parsedData = parseCompletePacket(completePacket); console.log('解析完成的数据包:', parsedData); WriteDataToLog(completePacket); // 把解析后的数据推给所有网页端 webClients.forEach(client => { // 构造WebSocket文本帧发送数据 const payload = JSON.stringify(parsedData); const frame = Buffer.from([0x81, 0x80 | payload.length, ...crypto.randomBytes(4), ...Buffer.from(payload)]); client.write(frame); }) // 把已经处理完的内容从缓冲区移除 const remainStr = bufferStr.slice(endIndex + 1); socket.receiveBuffer = Buffer.from(remainStr); // 重新查找下一个包的位置 startIndex = remainStr.indexOf('$$'); endIndex = remainStr.indexOf('*', startIndex); } }); socket.write('SERVER: Hello! This is server speaking.\n'); }).on('error', (err) => { console.error(err); }); server.listen(9898, () => { console.log('opened TCP server on', server.address().port); }); // 启动Web服务,提供前端页面 const webServer = http.createServer((req, res) => { // 返回前端展示页面 res.writeHead(200, {'Content-Type': 'text/html; charset=utf-8'}); res.end(` <!DOCTYPE html> <html> <head> <title>TCP数据包实时监控</title> <style>table {border-collapse: collapse; width: 100%; margin-top: 20px;} td, th {border: 1px solid #ddd; padding: 8px;}</style> </head> <body> <h3>TCP接收数据包实时列表</h3> <table> <thead><tr><th>接收时间</th><th>设备型号</th><th>IMEI</th><th>纬度</th><th>经度</th><th>信号强度</th></tr></thead> <tbody id="dataList"></tbody> </table> <script> const ws = new WebSocket('ws://' + location.host); ws.onmessage = (e) => { const data = JSON.parse(e.data); const tr = document.createElement('tr'); tr.innerHTML = \` <td>\${new Date().toLocaleString()}</td> <td>\${data.deviceModel}</td> <td>\${data.deviceImei}</td> <td>\${data.latitudeWithDirection}</td> <td>\${data.longitudeValue + ' ' + data.longitudeDirection}</td> <td>\${data.signalStrength}</td> \` document.getElementById('dataList').prepend(tr); } </script> </body> </html> `); }); // 处理WebSocket升级请求 webServer.on('upgrade', (req, socket) => { // 完成WebSocket握手 const key = req.headers['sec-websocket-key']; const acceptKey = crypto.createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11').digest('base64'); socket.write('HTTP/1.1 101 Switching Protocols\r\n'); socket.write('Upgrade: websocket\r\n'); socket.write('Connection: Upgrade\r\n'); socket.write('Sec-WebSocket-Accept: ' + acceptKey + '\r\n\r\n'); // 把连接存入全局客户端列表 webClients.add(socket); // 连接断开时从列表移除 socket.on('close', () => { webClients.delete(socket); }) }) webServer.listen(8080, () => { console.log('Web服务启动,访问 http://localhost:8080 查看数据'); })
运行效果
- TCP设备向9898端口发送符合格式的数据包时,服务端自动处理粘包问题,将原始包写入Log.txt文件
- 浏览器打开8080端口页面,所有解析完成的数据包会自动追加到页面表格中,不需要手动刷新
- 多客户端同时访问页面时,所有客户端都会同步收到最新的数据包推送
内容的提问来源于stack exchange,提问作者Amruta Patil
相关产品推荐
相关产品推荐

