实操-局域网通信UDP和TCP连接发送(软硬件连接)

前言:最近有需求需要对接一个WiFi设备,基本逻辑操作是,设备和手机同时连接同一个WiFi,然后手机和设备进行局域网通信。当操作设备时,设备会向手机发送数据包,小程序接受到数据包需要解释成十六进制,然后进行数据解析,根据需要的内容进行数据处理。

思路图

开发思路解析

前期条件

  • 确保设备和手机连接同一个路由
  • 设备会默认自动连接设定的WiFi名称和密码
  • 其实路由器也可以是手机热点,反正形成局域网就行

UPD广播

  • 需要知道设备会不会自动发送广播,需要知道广播端口是多少,然后小程序使用UDP去监听端口 例如:设备会默认向公共频段255.255.255.255广播端口8088发送消息,那我们只需要这样写监听即可
javascript 复制代码
const udp = wx.createUDPSocket()
udp.bind(8088)
udp.onMessage((res)=>{})

如果需要发送信息才会向小程序回传信息,这样写

javascript 复制代码
const udp = wx.createUDPSocket()
// 如果手机或者调试工具没写端口会报错,下面需要自己写一个随机端口号即可
udp.bind()
udp.onMessage((res)=>{})
udp.send({
    address: '255.255.255.255',  // 示例
    port: 8848,  // 示例
    message: 'hello, how are you' // 很多设备接收都需要转成16进制,后面写转换方法
  })
  • 通过上面的UDP消息监听,会接收到设备返回的数据包括ip地址和端口(这里主要拿ip地址,端口有些设备是写固定的),拿到ip后通过TCP去连接和监听TCP消息
javascript 复制代码
 const tcp = wx.createTCPSocket()
 tcp.connect({address: '192.168.193.2', port: 8848})
 TCPSocket.onMessage((res) => {})
  • 通过以上步骤,我们已经完成了设备之间的链接与通信了。

相关算法或者数据转换( 重点难点在于buffer转换 )

  • 发送与接收,十进制与十六进制相互转换
typescript 复制代码
// 进制转换 fromBase - 当前进制,toBase - 将要变成的进制
function convertBase(number, fromBase, toBase) {
  const decimalNumber = parseInt(number, fromBase)
  return decimalNumber.toString(toBase)
}
  • 十六进制转换成buffer数组,数据包上传参数
ini 复制代码
// 示例:data = 'FF FF 01 00 0E 01 00 08 00 01 00 00 FE FE'
function switchBuffer(data) {
  let arr = data.split(' ')
  let length = arr.length
  let array = new Int8Array(length);
  arr.map((item, index) => {
    array[index] = Number(convertBase(arr[index], 16, 10))
  })
  return array.buffer;
}
  • buffer转换成十六进制,数据返回解析数据包
javascript 复制代码
// arrayBuffer设备返回的数据
function bufferToString(arrayBuffer) {
  let unit8Arr = new Uint8Array(arrayBuffer);
  const array = []
  unit8Arr.map((item) => {
  	// 这里用到了十进制转换十六进制
    array.push(convertBase(item, 10, 16).toUpperCase().padStart(2, '0'))
  })
  return array;
}
// 这里没有用通用方法解析,是因为测试过,解析不稳定,会出现数据包内容有点错误,下面就是网上找到的解析数据包的方法,有需要可以用
  // let encodedString = String.fromCharCode.apply(null, unit8Arr)
  // let str = escape(encodedString)
  // let handStr = str.replace(/^%|%$/, '');
  • 获取数组最大的值
lua 复制代码
function numMaxFn (arr) {
  return Math.max(...arr)
}

注意点

  • TCP连接后,需要保持心跳包一直动,否则会断链,心跳包可以是设备发送,也可以小程序发
  • TCP连接成功后,不要立马发送消息给设备(可以延长1s左右),发送太快会导致设备TCP断链(目前我会出现这个问题,还排除了很久很久,很意外的问题)
  • 用小程序测试工具不好测试,因为测试工具不能创建多个udp和tcp,所以连接一次就要刷新项目,在连接
  • 其它具体都是逻辑问题,数据包解析,这些都要和通信协议进行处理即可了

代码呈现

  • 文件结构
  • 页面index.js
js 复制代码
import {init} from '../../utils/connetc'
const app = getApp()
Page({
  data: {
  },
  onLoad() {
    init()
  },

})
  • 连接相关代码connect.js
js 复制代码
const tcpStart = 'FF FF 01 00 0E 01 00 08 00 01 00 00 FE FE'
let UDPSocket = null
let TCPSocket = null
export const init = () => {
  UDPSocket = wx.createUDPSocket()
  TCPSocket = wx.createTCPSocket()
  // const ports = Math.floor(Math.random() * (60000 - 10000 + 1)) + 10000
  const p = UDPSocket.bind(9090)
  console.log(p, 'udp端口')
  onUDPFn()
  onTcpFn()
}
// 监听upd端口-事件
export const onUDPFn = () => {
  UDPSocket.onError((err) => {
    console.error(err, 'udp-错误')
  })
  UDPSocket.onClose((err) => {
    console.log(err, 'upd-Err')
  })
  UDPSocket.onMessage((res) => {
    let strArr = tool.bufferToString(res.message)
    console.log('UDP消息', res, strArr)
    let udpRemoteInfo = res.remoteInfo
    // 连接tcp
    tcpConnect(udpRemoteInfo)
  })
}
// 监听tcp
export const onTcpFn = () => {
  if (!TCPSocket) TCPSocket = wx.createTCPSocket()
  TCPSocket.onClose(() => {
  })
  TCPSocket.onConnect(() => {
    console.log('tcp-连接成功')
    // 连接成功后,发送消息
    TCPSocket.write(tool.switchBuffer(tcpStart))
  })
  TCPSocket.onError((err) => {
  })
  TCPSocket.onMessage((res) => {
    let strArr = tool.bufferToString(res.message)
    console.log('tcp数据', res, strArr)
  })
}
// 连接tcp
export const tcpConnect = (udpRemoteInfo) => {
  if (!TCPSocket) TCPSocket = wx.createTCPSocket()
  if (!udpRemoteInfo.address) return
  TCPSocket &&
    TCPSocket.connect({
      address: udpRemoteInfo.address,
      port: 8089,
      timeout: 15000
    })
}
  • 算法相关tool.js
javascript 复制代码
/*
 * @Description: cpr设备连接相关
 * @Date: 2023-12-21 11:40:04
 */
// 进制转换 fromBase - 当前进制,toBase - 将要变成的进制
function convertBase(number, fromBase, toBase) {
  const decimalNumber = parseInt(number, fromBase)
  return decimalNumber.toString(toBase)
}
// 校验和计算,返回16进制2字节
function checksum(data) {
  let arr = data.split(' ')
  let sum = 0
  for (let i = 0; i < arr.length; i++) {
    sum += Number(convertBase(arr[i], 16, 10))
  }
  let sum16 = convertBase(sum, 10, 16)
  let bit4 = padZero(sum16)
  return addSpace(bit4)
}
// 补位,如果不足4位字符串,需开头补0
function padZero(numStr) {
  return numStr.padStart(4, '0')
}
//  4位字符串中间加空格,与之前数据保持一致
function addSpace(numStr) {
  return numStr.slice(0, 2) + ' ' + numStr.slice(2)
}
// 拼接完整上传数据字符串
function pingStr(data) {
  let str = `${data} ${checksum(data)} FE FE`
  return str
}
// 16进制转换成buffer数组,上传参数
function switchBuffer(data) {
  let arr = data.split(' ')
  let length = arr.length
  let array = new Int8Array(length);
  arr.map((item, index) => {
    array[index] = Number(convertBase(arr[index], 16, 10))
  })
  return array.buffer;
}
// buffer转换成十六进制
function bufferToString(arrayBuffer) {
  let unit8Arr = new Uint8Array(arrayBuffer);
  // let encodedString = String.fromCharCode.apply(null, unit8Arr)
  // let str = escape(encodedString)
  // let handStr = str.replace(/^%|%$/, '');

  const array = []
  unit8Arr.map((item) => {
    array.push(convertBase(item, 10, 16).toUpperCase().padStart(2, '0'))
  })

  return array;
}


// 数组取最大值
function numMaxFn (arr) {
  return Math.max(...arr)
}

module.exports = {
  pingStr: pingStr,
  switchBuffer: switchBuffer,
  bufferToString: bufferToString,
  numMaxFn: numMaxFn,
}

结束

  • 总结保持细心和耐心,问题都是一步一步解决的,不要焦急~
  • 小程序代码链接
  • 喜欢就点赞下吧,鼓励作者,也方便需要时找到~
相关推荐
weifont5 小时前
聊一聊Electron中Chromium多进程架构
javascript·架构·electron
大得3695 小时前
electron结合vue,直接访问静态文件如何跳转访问路径
javascript·vue.js·electron
it_remember7 小时前
新建一个reactnative 0.72.0的项目
javascript·react native·react.js
敲代码的小吉米8 小时前
前端上传el-upload、原生input本地文件pdf格式(纯前端预览本地文件不走后端接口)
前端·javascript·pdf·状态模式
da-peng-song8 小时前
ArcGIS Desktop使用入门(二)常用工具条——数据框工具(旋转视图)
开发语言·javascript·arcgis
低代码布道师9 小时前
第五部分:第一节 - Node.js 简介与环境:让 JavaScript 走进厨房
开发语言·javascript·node.js
满怀101510 小时前
【Vue 3全栈实战】从响应式原理到企业级架构设计
前端·javascript·vue.js·vue
伟笑10 小时前
elementUI 循环出来的表单,怎么做表单校验?
前端·javascript·elementui
确实菜,真的爱11 小时前
electron进程通信
前端·javascript·electron
魔术师ID12 小时前
vue 指令
前端·javascript·vue.js