美文网首页FlutteriOS 进阶
flutter udp multicast 组播

flutter udp multicast 组播

作者: 微风_10a5 | 来源:发表于2020-06-29 14:15 被阅读0次

    写这篇文章的缘由:在Android原生端,iOS原生端,实现udp 组播相信很多小伙伴都会的,但在flutter 端实现这个对我们来说还是一个未知之数吧;

    前期是想着去网上找一个第三方库 或者找一个第三方的插件 来完成任务;结果经过几天的调研下来,最终都是Android手机上可以正常使用,但在iOS手机上都不能正常使用,报以下的错误

    Unhandled Exception: OS Error: Can't assign requested address, errno = 49
    

    沿着这个错误跟踪下去 ,网上讨论的结果貌似是Dart SDK层报出来的错误,很多热心人事也与Dart团队在网上沟通,希望他们能解决此bug;不过大半年过去了,这个问题依旧存在。

    看来指望Dart团队解决此事,有点遥遥无期的感觉,怎么办呢,我们不能因为这个bug ,就停止不前了吧;最终决定自己来造轮子----写udp组播插件;

    写flutter插件入门,在这里就不多说了,小伙伴们自己去探索一下吧。下面就开始今天真正的代码环节了

    import 'dart:async';
    
    import 'package:flutter/services.dart';
    
    class Pluginudp {
      static const MethodChannel _channel =
      const MethodChannel('pluginudp');
    
      static Future<void> startScan(String ipString) async {
        await _channel.invokeMethod('startScan',{"data":ipString});
      }
    
      static Future<void> stopScan() async {
        await _channel.invokeMethod('stopScan');
      }
    
      static Future<void> onlyToReceive() async {
        await _channel.invokeMethod('onlyToReceive');
      }
    }
    

    上面的代码是flutter层, 定义了3个方法,分别为

    开始udp扫描--startScan

    停止udp扫描--stopScan

    只接收数据--onlyToReceive

    iOS层的核心代码

    #import "PluginudpPlugin.h"
    #import "ScanX3UdpSocket.h"
    @interface PluginudpPlugin ()<FlutterStreamHandler,ScanX3UdpSocketDelegate>
    @property (nonatomic, strong) NSTimer *timer;
    @property (nonatomic, strong) FlutterEventSink eventSink;
    @property (nonatomic, copy) NSString *ipString;
    
    @end
    @implementation PluginudpPlugin
    + (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
      FlutterMethodChannel* channel = [FlutterMethodChannel
          methodChannelWithName:@"pluginudp"
                binaryMessenger:[registrar messenger]];
      PluginudpPlugin* instance = [[PluginudpPlugin alloc] init];
      [registrar addMethodCallDelegate:instance channel:channel];
    
    
        FlutterEventChannel *eventChannel = [FlutterEventChannel
                                    eventChannelWithName:@"FlutterUdpEvent"
                                    binaryMessenger:[registrar messenger]];
        [eventChannel setStreamHandler:instance];
    }
    #pragma mark - FlutterStreamHandler methods
    
    - (FlutterError* _Nullable)onListenWithArguments:(id _Nullable)arguments
                                           eventSink:(nonnull FlutterEventSink)sink {
        _eventSink = sink;
        return nil;
    }
    
    - (FlutterError* _Nullable)onCancelWithArguments:(id _Nullable)arguments {
        _eventSink = nil;
        return nil;
    }
    - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
      if ([@"getPlatformVersion" isEqualToString:call.method]) {
        result([@"iOS " stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
      }else if ([@"startScan" isEqualToString:call.method]) {
          NSDictionary *dict = call.arguments;
          NSString *ipString = [dict valueForKey:@"data"];
          _ipString = ipString;
          [self startToScan];
      }else if ([@"stopScan" isEqualToString:call.method]) {
          [self stopToScan];
      }else if ([@"onlyToReceive" isEqualToString:call.method]) {
             [self onlyToReceive];
         }
      else {
        result(FlutterMethodNotImplemented);
      }
    }
    
    #pragma mark - ScanX3UdpSocketDelegate methods
    
    - (void)udpSocket:(GCDAsyncUdpSocket *_Nullable)sock didReceiveData:(NSData *_Nullable)data
          fromAddress:(NSData *_Nullable)address
    withFilterContext:(nullable id)filterContext {
        NSString *host = [GCDAsyncUdpSocket hostFromAddress:address];
    
        NSString *str = [[NSString alloc]initWithData:data encoding:NSUTF8StringEncoding];
        NSString *str2 = [self trimIndicatorWithString:str];
        NSError *error;
        NSMutableDictionary<NSString*,id> *dict = [NSJSONSerialization JSONObjectWithData:[str2 dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingMutableContainers error:&error];
        [dict setValue:host forKey:@"host"];
        NSLog(@"udpSocket didReceiveData dict = %@",dict);
        if (!error) {
            if(_eventSink){
                _eventSink(dict);
            }
    
        }
    }
    
    - (void)startToScan {
        [self stopTimer];
        [self startTimer];
    }
    - (void)stopToScan {
        [self stopTimer];
        [ScanX3UdpSocket shareScanX3UdpSocket].delegate = nil;
        [[ScanX3UdpSocket shareScanX3UdpSocket] cutOffUdpSocket];
    
    }
    - (void)onlyToReceive {
           [ScanX3UdpSocket shareScanX3UdpSocket].delegate = self;
           [[ScanX3UdpSocket shareScanX3UdpSocket] toReceiveData];
    }
    
    #pragma mark - private method
    
    - (void)startTimer {
        [self timer];
    }
    
    - (void)stopTimer {
        if (self.timer) {
             [self.timer invalidate];
             self.timer = nil;
         }
    }
    
    - (NSString *)trimIndicatorWithString: (NSString *)string {
        string = [string stringByReplacingOccurrencesOfString:@"\x02" withString:@""];
        string = [string stringByReplacingOccurrencesOfString:@"\x03" withString:@""];
        return string;
    }
    
    - (void)scanX3 {
        [ScanX3UdpSocket shareScanX3UdpSocket].delegate = self;
        [[ScanX3UdpSocket shareScanX3UdpSocket] startScanX3WithIpString:self.ipString];
    }
    
    #pragma mark - getter/setter
    -(NSTimer *)timer{
        if (!_timer) {
            _timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(scanX3) userInfo:nil repeats:YES];
        }
        return _timer;
    }
    
    @end
    

    安卓层的核心代码

    import android.annotation.SuppressLint;
    import android.os.Handler;
    import android.os.Message;
    
    import androidx.annotation.NonNull;
    
    import org.json.JSONException;
    import org.json.JSONObject;
    import org.json.JSONTokener;
    
    import java.io.IOException;
    import java.net.DatagramPacket;
    import java.net.InetAddress;
    import java.net.MulticastSocket;
    import java.net.SocketException;
    import java.net.UnknownHostException;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.Iterator;
    import java.util.List;
    import java.util.Map;
    
    import io.flutter.embedding.engine.plugins.FlutterPlugin;
    import io.flutter.plugin.common.EventChannel;
    import io.flutter.plugin.common.MethodCall;
    import io.flutter.plugin.common.MethodChannel;
    import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
    import io.flutter.plugin.common.MethodChannel.Result;
    import io.flutter.plugin.common.PluginRegistry.Registrar;
    
    /** PluginudpPlugin */
    public class PluginudpPlugin implements FlutterPlugin, MethodCallHandler {
      /// The MethodChannel that will the communication between Flutter and native Android
      ///
      /// This local reference serves to register the plugin with the Flutter Engine and unregister it
      /// when the Flutter Engine is detached from the Activity
      private MulticastSocket multicastSocket = null;
      private InetAddress group;
      private boolean isReceiving = true;/// when the Flutter Engine is detached from the Activity
    
    
      private MethodChannel channel;
      private static EventChannel.EventSink mEventSink = null;
      @Override
      public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "pluginudp");
        channel.setMethodCallHandler(this);
    
        final EventChannel eventChannel = new EventChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "FlutterUdpEvent");
        eventChannel.setStreamHandler(new EventChannel.StreamHandler() {
          @Override
          public void onListen(Object o, EventChannel.EventSink eventSink) {
    
            mEventSink = eventSink;
          }
    
          @Override
          public void onCancel(Object o) {
            mEventSink = null;
          }
        });
      }
    
      // This static function is optional and equivalent to onAttachedToEngine. It supports the old
      // pre-Flutter-1.12 Android projects. You are encouraged to continue supporting
      // plugin registration via this function while apps migrate to use the new Android APIs
      // post-flutter-1.12 via https://flutter.dev/go/android-project-migration.
      //
      // It is encouraged to share logic between onAttachedToEngine and registerWith to keep
      // them functionally equivalent. Only one of onAttachedToEngine or registerWith will be called
      // depending on the user's project. onAttachedToEngine or registerWith must both be defined
      // in the same class.
      public static void registerWith(Registrar registrar) {
        final MethodChannel channel = new MethodChannel(registrar.messenger(), "pluginudp");
        channel.setMethodCallHandler(new PluginudpPlugin());
      }
    
      @Override
      public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        if (call.method.equals("getPlatformVersion")) {
          result.success("Android " + android.os.Build.VERSION.RELEASE);
        }else if(call.method.equals("startScan")) {
          Map<String,String> m = (Map<String, String>) call.arguments;
          String ipString = m.get("data");
          System.out.println("ipString = " + ipString);
    
          stop();
          initMultiCaseSocket();
          startScan(ipString);
          receive();
    
        }else if(call.method.equals("stopScan")) {
          stop();
    
        } else {
          result.notImplemented();
        }
      }
    
      @Override
      public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
        channel.setMethodCallHandler(null);
      }
    
      public void  initMultiCaseSocket() {
        try {
          group = InetAddress.getByName("231.0.1.1");
        } catch (UnknownHostException e) {
          e.printStackTrace();
        }
        try {
          multicastSocket = new MulticastSocket(11555);
        } catch (IOException e) {
          e.printStackTrace();
        }
        try {
          multicastSocket.joinGroup(group);// 加入该组
        } catch (IOException e) {
          e.printStackTrace();
        }
      }
    
      public  void startScan(String ipString) {
        isReceiving = true;
        new Thread(new Runnable() {
          public void run() {
            int I=0;
            System.out.println("run send");
            while(true && i<10 && isReceiving){
    
              String sendMessage = null;
              try {
                sendMessage = UDPServer("192.168.0.111","11555");
                System.out.println(sendMessage);
              } catch (JSONException e) {
                e.printStackTrace();
              }
              I++;
              System.out.println("i ="+ i);
    
              DatagramPacket datagramPacket = new DatagramPacket(
                      sendMessage.getBytes(), sendMessage.length(), group,
                      21555);
              try {
                multicastSocket.send(datagramPacket);
              } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
              }
    
    
              try {
                Thread.sleep(3000);
              } catch (InterruptedException e1) {
                // TODO Auto-generated catch block
                e1.printStackTrace();
              }
            }
            System.out.println("send over");
          }
        }).start();
      }
    
      public void receive() {
        new Thread(new Runnable() {
          public void run() {
            int I=0;
            byte[] arb = new byte[200];
            System.out.println("run receive");
            while (true && i<20 && isReceiving) {
              DatagramPacket datagramPacket = new DatagramPacket(arb,
                      arb.length);
    
              try {
                multicastSocket.receive(datagramPacket);
                Thread.sleep(1000);
    
              } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
              }
              I++;
              String host = datagramPacket.getAddress().getHostName();
              System.out.println("host====++++++" + host);
    
              System.out.println("re i ="+ i);
              System.out.println(arb.length);
              Map<String, Object> m = changeByteToMap(arb);
              m.put("host",host);
              Message msg = new Message();
              msg.obj = m;
              msg.what = 1000;
              handler.sendMessage(msg);
            }
          }
        }).start();
    
      }
      @SuppressLint("HandlerLeak")
      Handler handler = new Handler() {
        public void handleMessage(Message msg) {
          Map<String, Object> mm = (Map<String, Object>) msg.obj;
          switch (msg.what){
            case 1000:
              System.out.println("=====>"+ mm);
              if (mEventSink != null) {
                System.out.println("send before");
                mEventSink.success(mm);
                System.out.println("send after");
              }
              break;
          }
        }
      };
    
      public void stop() {
        isReceiving = false;
        if (multicastSocket != null) {
          try {
            multicastSocket.leaveGroup(group);
            multicastSocket.close();
          } catch (IOException e) {
            e.printStackTrace();
          }
    
        }
      }
    
      byte[] startFlag = {0x02};
      byte[] endFlag = {0x03};
      String start = new String(startFlag);
      String end = new String(endFlag);
    
      public String UDPServer(String ip, String port) throws JSONException {
        JSONObject mapBuf = new JSONObject();
        mapBuf.put("type", "scan");
        mapBuf.put("ip", ip);
        mapBuf.put("port", port);
        String value = start + mapBuf.toString() + end;
        return value;
      }
    
      public Map<String,Object> UDPServer(String data) throws JSONException {
        Map<String,Object> map=new HashMap<String,Object>();
        JSONTokener jsonParser = new JSONTokener(data);
        JSONObject jsonObj = (JSONObject) jsonParser.nextValue();
        map.put("result",jsonObj.optBoolean("result"));
        map.put("description",jsonObj.optString("description"));
    
        Map<String,Object> tempMap=new HashMap<String,Object>();
        JSONObject job=jsonObj.getJSONObject("response");
        tempMap.put("mac", job.optString("mac"));
        tempMap.put("name", job.optString("name"));
        tempMap.put("version",job.optString("version"));
        tempMap.put("netMode",job.optString("netMode"));
        map.put("response",tempMap);
    
        return map;
      }
    
      //将byte[]转换成map
      public Map<String,Object> changeByteToMap(byte[] bytes) {
        ArrayList<Byte> tempList = new ArrayList<>();
        boolean isStart = false;
        boolean isEnd = false;
        for (int i = 0; i < bytes.length; i++) {
          Byte item = bytes[I];
          if (item == 0x02) {
            isStart = true;
            continue;
          }
          if (isStart) {
    
            if (item == 0x03) {
              isEnd = true;
              break;
            }
            if (!isEnd) {
              tempList.add(item);
            }
          }
        }
        System.out.println("------>" + tempList.size());
        Map<String, Object> retmap = null;
        try {
          retmap = UDPServer(new String(listTobyte(tempList)));
        } catch (JSONException e) {
          e.printStackTrace();
        }
        return retmap;
      }
    
    
      private byte[] listTobyte(List<Byte> list) {
        if (list == null || list.size() < 0)
          return null;
        byte[] bytes = new byte[list.size()];
        int i = 0;
        Iterator<Byte> iterator = list.iterator();
        while (iterator.hasNext()) {
          bytes[i] = iterator.next();
          I++;
        }
        return bytes;
      }
    }
    

    安卓里面

    开始udp扫描--startScan

    停止udp扫描--stopScan

    只接收数据--onlyToReceive (这个暂时没有实现)

    插件写好了,在需要使用的地方使用起来

    import 'dart:convert';
    import 'dart:typed_data';
    
    import 'package:flutter/material.dart';
    import 'dart:async';
    import 'package:flutter/services.dart';
    import 'package:res/constant.dart';
    import 'package:utils/api_helper.dart';
    import 'package:pluginudp/pluginudp.dart';
    import 'utils/socket_client.dart';
    import 'utils/http.dart';
    
    void main() {
      runApp(MyApp());
    }
    
    class MyApp extends StatefulWidget {
      @override
      _MyAppState createState() => _MyAppState();
    }
    
    class _MyAppState extends State<MyApp> {
      final EventChannel _eventChannel = EventChannel('FlutterUdpEvent');
      String _host = "";
      @override
      void initState() {
        super.initState();
        Pluginudp udp = Pluginudp();
        Future.delayed(Duration(seconds: 2), () {
          _eventChannel.receiveBroadcastStream().listen(eventListener,
              onError: (Object obj) => throw obj as PlatformException);
        });
      }
    
      void onData(dynamic data) {
        print("data:$data");
        Uint8List responseData = data;
        List<int> responseDataList = responseData.toList();
        responseDataList.removeAt(0);
        responseDataList.removeAt(responseDataList.length - 1);
        String str = String.fromCharCodes(responseDataList);
        Map map = jsonDecode(str);
        print(map);
      }
    
      void eventListener(dynamic data) {
        if (data.runtimeType.toString() == '(dynamic) => Null') {
          return;
        }
        Map<dynamic, dynamic> event = data as Map<dynamic, dynamic>;
        print("object=${event['host']}");
        print("object = $event");
        String mac = event["response"]['MAC'];
        if (mac.contains("EF71A")) {
          _host = event['host'];
          print("destines host = $_host");
        }
      }
    
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: Scaffold(
            appBar: AppBar(
              title: const Text('Plugin example app'),
            ),
            body: Container(
              width: double.infinity,
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.center,
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
                  Text('Udp scan'),
                  Row(
                    mainAxisAlignment: MainAxisAlignment.center,
                    children: <Widget>[
                      RaisedButton(
                          child: Text("start"),
                          onPressed: () {
                            Pluginudp.startScan("192.168.2.245");
                          }),
                      SizedBox(
                        width: 1,
                      ),
                      RaisedButton(
                          child: Text("stop"),
                          onPressed: () {
                            Pluginudp.stopScan();
                          }),
                      SizedBox(
                        width: 1,
                      ),
                      RaisedButton(
                          child: Text("onlyToReceive"),
                          onPressed: () {
                            Pluginudp.onlyToReceive();
                          })
                    ],
    
                  ),
                  SizedBox(
                    height: 50,
                  ),
                  Column(
                    children: <Widget>[
                      RaisedButton(child: Text("建立Tcp链接"), onPressed: _connectTcp),
                      RaisedButton(
                          child: Text("配置服务器信息"), onPressed: _setServerInfo),
                      RaisedButton(
                          child: Text("配置设备owner"), onPressed: _setOwnerInfo),
                      RaisedButton(
                          child: Text("配置设备时区与位置"),
                          onPressed: _setTimeZoneLocation),
                      RaisedButton(
                          child: Text("启动设备向服务器注册"), onPressed: _startRegister),
                    ],
                  )
                ],
              ),
            ),
          ),
        );
      }
    
      _connectTcp() {
        OwonSocketClient.getInstance()
            .startConnect(_host, OwonConstant.tcpReceivePort)
            .then((value) {
          OwonSocketClient.getInstance().addListener(onData);
        });
      }
    
      _setServerInfo() {
    //  Map map = Map();
    //  map["username"] = "xiaoming";
    //  map["password"] = "123456";
    //
    //  Map apiDict = Map();
    //  apiDict["type"] = "login";
    //  apiDict["argument"] = map;
    //  apiDict["sequence"] = 10000;
    
        Map<String, dynamic> apiDict = Map();
        apiDict["host"] = "192.168.0.111";
        apiDict["sslport"] = 8883;
        apiDict["port"] = 1883;
    
        Map p = ApiHelper.apiHelper(
            type: "wizardConfig", command: "serverCfg", argument: apiDict);
    
        print("p = $p");
    
        SocketClient.getInstance().sendData(p);
      }
    
      _setOwnerInfo() {
        Map apiDict = Map();
        apiDict["Owner"] = "86-18559697013";
        apiDict["agentid"] = "Test";
        Map p = ApiHelper.apiHelper(
            type: "wizardConfig", command: "DevOwnerCfg", argument: apiDict);
    
        print("p = $p");
    
        SocketClient.getInstance().sendData(p);
      }
    
      _setTimeZoneLocation() {
        Map apiDict = Map();
        apiDict["Area"] = "Asia/Shanghai";
        apiDict["Addid"] = "11100000";
        Map p = ApiHelper.apiHelper(
            type: "wizardConfig", command: "DevLocationCfg", argument: apiDict);
    
        print("p = $p");
    
        SocketClient.getInstance().sendData(p);
      }
    
      _startRegister() {
    
        Map p = ApiHelper.apiHelper(
            type: "wizardConfig", command: "startRegister");
    
        print("p = $p");
    
        SocketClient.getInstance().sendData(p);
      }
    }
    
    image1.png

    注意使用真机来调试,点击start按钮,控制台打印信息

    image2.png

    写在结尾

    代码里面会有一部分是与我司的业务挂钩,大家可以忽略,这也是发布的第一篇文章,可能有诸多考虑不周的地方,请见谅,只想在flutter udp 组播 这个地方做一个抛砖引玉的作用.需要源码的话,请在zz7516018@163.com上留言吧
    http://note.youdao.com/s/DnDiFfGT

    相关文章

      网友评论

        本文标题:flutter udp multicast 组播

        本文链接:https://www.haomeiwen.com/subject/tjmofktx.html