微信小程序与安卓设备进行UDP数据交互

UDP协议

Internet 协议集支持一个无连接的传输协议,该协议称为用户数据报协议(UDP,User Datagram Protocol)。UDP 为应用程序提供了一种无需建立连接就可以发送封装的 IP 数据包的方法。

参考百度百科: https://baike.baidu.com/item/UDP/571511?fr=aladdin

功能需求

微信小程序需要在局域网中与安卓设备进行数据交互。微信小程序发送数据给安卓设备后,安卓设备可以调用票据打印机,进行数据打印。
安卓设备:是商米的T2_minis.自带打印设备,已经封装好80mm打印指令。

安卓设备作为服务端,监听数据


package udp;

import android.app.Activity;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdServiceInfo;
import android.util.Log;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;

import wendu.dsbridge.DWebView;
import wendu.dsbridge.OnReturnValue;

public class UdpService {
    private static final String TAG = "ServerActivity";
    private NsdManager mNsdManager = null;
    private NsdManager.RegistrationListener mRegistrationListener;

    private final int CHAT_PORT = 6613;
    private Thread udpListenerThread;
    private boolean running = false;
    private DatagramSocket socket;
    private DatagramPacket packet;
    private final int BUFFER_SIZE = 1024*6;
    private byte[] buffer = new byte[BUFFER_SIZE];

    DWebView webView;
    Activity activity;

    /**
    *  安卓设备是套壳程序(Dwebview),会将数据发送给前端页面。
    *  这里会将webview 引入到 该类中,调用相应方法。
    */
    public UdpService(DWebView webView, Activity activity){
        this.webView = webView;
        this.activity = activity;
    }


    public void mDNSRegister(){
        mRegistrationListener = new NsdManager.RegistrationListener() {
            @Override
            public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.i(TAG,"onRegistrationFailed ");
            }

            @Override
            public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
                Log.i(TAG,"onUnregistrationFailed ");

            }

            @Override
            public void onServiceRegistered(NsdServiceInfo serviceInfo) {
                Log.i(TAG,"onServiceRegistered ");

            }

            @Override
            public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
                Log.i(TAG,"onServiceUnregistered ");

            }
        };
        mNsdManager = (NsdManager) this.activity.getSystemService(Activity.NSD_SERVICE);
        NsdServiceInfo serviceInfo = new NsdServiceInfo();
        serviceInfo.setServiceName("lianhe");
        serviceInfo.setPort(CHAT_PORT);
        // 设置服务类型,要与微信小程序对接一致。
        serviceInfo.setServiceType("_lianheyouxian._udp.");
        mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
    }

    public void mDNSUnregister() {
        mNsdManager.unregisterService(mRegistrationListener);
    }

    /**
     * 开启UDP服务
     */
    public void startUdpListener(){

        if(running){
            return;
        }
        try {
            socket = new DatagramSocket(CHAT_PORT);
            packet = new DatagramPacket(buffer,BUFFER_SIZE);
        } catch (SocketException e) {
            e.printStackTrace();
            return;
        }

        udpListenerThread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (running){
                    Log.i(TAG,"开启UDP服务");
                    try {
                        socket.receive(packet);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    if (packet == null || packet.getLength() == 0) {
                        Log.e(TAG, "无法接收UDP数据或者接收到的UDP数据为空");
                        continue;
                    }

                    final String strReceive = new String(packet.getData(), 0, packet.getLength());
                    final String ip = packet.getAddress().getHostAddress();
                    final int port = packet.getPort();
                    Log.d(TAG, strReceive + " from " + ip + ":" + port);
                    activity.runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            Log.i(TAG,"接受UDP信息:"+strReceive);
                            // 如果发送的udp数据太频繁,前端的”jsEvent.udp"可能未做好准备,调用失败。所以捕捉一下异常,防止出现异常导致程序中断。
                            try{
                                webView.callHandler("jsEvent.udp",new Object[]{strReceive});
                            }catch (Exception e){
                                e.printStackTrace();
                            }

                        }
                    });
                    sendUdpMsg(ip,port,"received you message:"+strReceive);
                }
            }
        });
        running = true;
        udpListenerThread.start();
    }

    /**
     * 发送 UDP 信息
     * @param ip
     * @param port
     * @param message
     */
    private void sendUdpMsg(String ip,int port,String message){
        InetAddress targetAddress = null;
        try {
            targetAddress = InetAddress.getByName(ip);
            DatagramPacket packet = new DatagramPacket(message.getBytes(), message.length(), targetAddress, port);
            socket.send(packet);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 停止UDP监听服务
     */
    public void stopUdpListener(){
        if(!running){
            return;
        }
        running = false;/*  */
        udpListenerThread.interrupt();
        udpListenerThread = null;
    }

    public void stopNSDServer() {
        mNsdManager.unregisterService(mRegistrationListener);
    }

}

微信小程序端

// pages/udp/index.js
const udp = wx.createUDPSocket()
udp.bind()

import Toast from '../../vant/toast/toast';
Page({

  /**
   * 页面的初始数据
   */
  data: {
    ip:'10.30.6.68',
    port:6613,
    message:'{"type":"","order_id":1}',
    lists:[],
    resolveFailList:[],
    receiveMessage:'',
    fromIp:''
  },

  onChangeIP(e){
    this.setData({ip:e.detail})
  },
  onChangePort(e){
    this.setData({port:e.detail})
  },
  onChangeMessage(e){
    this.setData({message:e.detail})
  },
  onClickSearch(){
    wx.startLocalServiceDiscovery({
      // 服务类型不要变
      serviceType: '_lianheyouxian._udp.',
      success: function (res) {
        console.log(res)
      },
      fail: function (err) {
        console.log(err)
      },
      complete: function () {
        console.log('complete')
      }
    })
 },
  onClickStopSearch(){
    let that = this
    wx.stopLocalServiceDiscovery({
      success:()=>{
        serviceList = []
        resolveFailList = []
        that.setData({
          lists: [],
          resolveFailList: []
        })
      },
      fail: ()=>{
       
      },
      complete:  ()=>{
      }
    })
  },
  onClickSend(){
    console.log(this.data.message);
    if(!this.data.ip || !this.data.port || !this.data.message){
      Toast.fail('请将IP,端口,信息填写完整');
      return;
    }
    console.log("发送UDP消息");

    udp.send({
      address: this.data.ip,
      port: this.data.port,
      message: this.data.message
    })

  },
  onLocalService: function () {
    let that = this
    // 监听服务发现事件
    wx.onLocalServiceFound( (obj)=> {
      console.log(obj)
      serviceList.forEach(function () {

      })
      serviceList.push(obj);
      that.setData({
        lists: serviceList
      })
    })

    // 监听服务解析失败事件
    wx.onLocalServiceResolveFail( (obj)=>{
      resolveFailList.push(obj)
      that.setData({
        resolveFailList: resolveFailList
      })
    })

    // 监听服务离开
    wx.onLocalServiceLost((obj)=>{
      console.log(obj)
    })

    // 监听搜索停止
    wx.onLocalServiceDiscoveryStop(function (obj) {
      console.log('监听到搜索停止事件')
    })

  },
  offLocalService: function () {
    console.log('是否执行此部分数据')
    // 取消监听服务发现事件
    wx.offLocalServiceFound(function () {
      console.log('取消监听服务发现事件 成功')
    })

    // 取消监听服务解析失败事件
    wx.offLocalServiceResolveFail(function () {
      console.log('取消监听 mDNS 服务解析失败的事件 成功')
    })

    // 取消监听服务离开
    wx.offLocalServiceLost(function () {
      console.log('取消监听服务离开事件 成功')
    })

    // 取消监听搜索停止
    wx.offLocalServiceDiscoveryStop(function () {
      console.log('取消监听 mDNS 服务停止搜索的事件 成功')
    })
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {

  },

  /**
   * 生命周期函数--监听页面初次渲染完成
   */
  onReady: function () {

  },

  /**
   * 生命周期函数--监听页面显示
   */
  onShow: function () {
    this.onLocalService()
    this.offLocalService();
    udp.onMessage( (res)=>{ 
      let unit8Arr = new Uint8Array(res.message);
      let encodedString = String.fromCharCode.apply(null, unit8Arr);
      let message = decodeURIComponent(escape((encodedString)));
      console.log(message);
      console.log(res.remoteInfo);
      this.setData({
        receiveMessage: message,
        fromIp: res.remoteInfo.address
      })
    });
    udp.onError((errMsg)=>{
      console.log("udp 错误");
      console.log(errMsg);
    })
  },

  /**
   * 生命周期函数--监听页面隐藏
   */
  onHide: function () {

  },

  /**
   * 生命周期函数--监听页面卸载
   */
  onUnload: function () {

  },

  /**
   * 页面相关事件处理函数--监听用户下拉动作
   */
  onPullDownRefresh: function () {

  },

  /**
   * 页面上拉触底事件的处理函数
   */
  onReachBottom: function () {

  },

  /**
   * 用户点击右上角分享
   */
  onShareAppMessage: function () {

  }
})

小程序具体说明

// 创建udp socket
const udp = wx.createUDPSocket()
// 随机绑定端口
udp.bind()
// 监听返回消息
udp.onMessage( (res)=>{ 
      let unit8Arr = new Uint8Array(res.message);
      let encodedString = String.fromCharCode.apply(null, unit8Arr);
      let message = decodeURIComponent(escape((encodedString)));
      console.log(message);
      console.log(res.remoteInfo);
      this.setData({
        receiveMessage: message,
        fromIp: res.remoteInfo.address
      })
    });
    udp.onError((errMsg)=>{
      console.log("udp 错误");
      console.log(errMsg);
    })
// 向服务端发送消息
    udp.send({
      address: this.data.ip,
      port: this.data.port,
      message: this.data.message
    })

参考资料

https://blog.csdn.net/weixin_38320056/article/details/95372843  
https://gitee.com/yygood1/wechat_demo_udp