评论

干货-局域网通信UDP和TCP连接发送接受

刚开始做硬件设备连接时,心里特别慌,第一网上查找很多方法不适用,用了也无法解决,第二算法看不懂,特别是数据包十六进制谁懂呢?第三没经验初次接触。其实都不要害怕慢慢搞,一步一步解决,最好发现其实就那样。

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

思路图

开发思路解析

前期条件

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

UPD广播

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

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

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消息
 const tcp = wx.createTCPSocket()
 tcp.connect({address: '192.168.193.2', port: 8848})
 TCPSocket.onMessage((res) => {})
  • 通过以上步骤,我们已经完成了设备之间的链接与通信了。

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

  • 发送与接收,十进制与十六进制相互转换
// 进制转换 fromBase - 当前进制,toBase - 将要变成的进制
function convertBase(number, fromBase, toBase) {
  const decimalNumber = parseInt(number, fromBase)
  return decimalNumber.toString(toBase)
}
  • 十六进制转换成buffer数组,数据包上传参数
// 示例: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转换成十六进制,数据返回解析数据包
// 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(/^%|%$/, '');
  • 获取数组最大的值
function numMaxFn (arr) {
  return Math.max(...arr)
}

注意点

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

代码呈现

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

})
  • 连接相关代码connect.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
/*
 * @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,
}

结束

  • 总结保持细心和耐心,问题都是一步一步解决的,不要焦急~
  • 小程序代码链接
  • 喜欢就点赞下吧,鼓励作者,也方便需要时找到~
最后一次编辑于  07-09  
点赞 2
收藏
评论
登录 后发表内容