Flutter 使用 dartzmq 与本机 WSL 上的服务进行通信

655 阅读2分钟

背景

车载平板与车上服务之间需要用 ZeroMQ 通信,使用 Flutter 开发APP时,需要模拟连接 Linux 上的服务进行 zmq 通信。使用平板真机调试,并在电脑本地使用 WSL 运行一个 zmq 服务,接收消息并返回信息

相关链接

流程

  1. Flutter 项目使用 dartzmq 官方 demo,连接 Windows 主机 tcp://192.168.xx.xx:5556, 端口号自定义
  2. Windows 主机使用 WSL 运行 ZeroMQ官方 demo,绑定 tcp://*:5555 端口,端口号自定义
  3. WSL 配置 networkingMode=mirrored 这样 WSL 的网络与 Windows 主机共享同一 IP 地址空间,可以直接进行本地回环访问。但来自局域网其他主机的访问不能直接访问到 WSL 网络,因此需要端口转发
  4. Windows 主机配置端口转发,将 0.0.0.0:5556 转发至 127.0.0.1:5555 端口

Flutter 使用 dartzmq 库发送信息

import 'dart:async';
import 'dart:developer';

import 'package:dartzmq/dartzmq.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final ZContext _context = ZContext();
  late final MonitoredZSocket _socket;
  String _receivedData = '';
  late StreamSubscription _subscription;
  int _presses = 0;

  @override
  void initState() {
    _socket = _context.createMonitoredSocket(SocketType.dealer);
	// Windows 主机IP端口,自定义
    _socket.connect("tcp://192.168.xxx.xxx:5556");
    // host ip address in android simulator is 10.0.2.2
    // _socket.connect("tcp://10.0.2.2:5566");
    // _socket.connect("tcp://192.168.2.34:5566");

    // listen for messages
    _subscription = _socket.messages.listen((message) {
      setState(() {
        _receivedData = message.toString();
      });
    });

    // listen for frames
    // _subscription = _socket.frames.listen((frame) {
    //   setState(() {
    //     _receivedData = frame.toString();
    //   });
    // });

    // listen for payloads
    // _subscription = _socket.payloads.listen((payload) {
    //   setState(() {
    //     _receivedData = payload.toString();
    //   });
    // });
    super.initState();
  }

  @override
  void dispose() {
    _socket.close();
    _context.stop();
    _subscription.cancel();
    super.dispose();
  }

  void _sendMessage() {
    ++_presses;
    // NOTE: if you're using dealer/rep, an empty message for identification is required.
    _socket.send([], flags: ZMQ_DONTWAIT | ZMQ_SNDMORE);
    _socket.sendString("Hello");
    // _socket.send([_presses], flags: ZMQ_DONTWAIT);
    //
    // or you can use ZFrame to build an message:
    //
    // var newMessage = ZMessage();
    // newMessage.add(ZFrame(Uint8List(0)));
    // newMessage.add(ZFrame(Uint8List.fromList([_presses])));
    // _socket.sendMessage(newMessage, flags: ZMQ_DONTWAIT);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("dartzmq demo"),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Press to send a message'),
            MaterialButton(
              onPressed: _sendMessage,
              color: Colors.blue,
              child: const Text('Send'),
            ),
            StreamBuilder<SocketEvent>(
              stream: _socket.events,
              builder: (context, snapshot) {
                if (snapshot.hasData) {
                  final event = snapshot.data!;
                  log('Socket event: ${event.event}, value: ${event.value}');
                  return Text('Event: ${event.event}, value: ${event.value}');
                }
                return const LinearProgressIndicator();
              },
            ),
            const Text('Received'),
            Text(_receivedData),
          ],
        ),
      ),
    );
  }
}

ZeroMQ 使用 cpp 和 zmqpp 的 demo

//  Hello World server

#include <zmqpp/zmqpp.hpp>
#include <string>
#include <iostream>
#include <chrono>
#include <thread>

using namespace std;

int main(int argc, char *argv[]) {
  // 自定义绑定的端口号
  const string endpoint = "tcp://*:5555";

  // initialize the 0MQ context
  zmqpp::context context;

  // generate a pull socket
  zmqpp::socket_type type = zmqpp::socket_type::reply;
  zmqpp::socket socket (context, type);

  // bind to the socket
  socket.bind(endpoint);

  cout << "socket.bind tcp://*:5555" << endl;
  while (1) {
    // receive the message
    zmqpp::message message;
    // decompose the message
    socket.receive(message);
    string text;
    message >> text;

    //Do some 'work'
    std::this_thread::sleep_for(std::chrono::seconds(1));
    cout << "Received " << text << endl;
    socket.send("World");
  }

}

设置 Windows 主机端口映射

PowerShell 管理员模式执行端口映射,将5556端口转发至127.0.0.1:5555

$ netsh interface portproxy add v4tov4 listenport=5556 listenaddress=0.0.0.0 connectport=5555 connectaddress=127.0.0.1

将 Windows 主机转发的端口添加到 Windows 防火墙许可名单

$ New-NetFirewallRule -DisplayName "Allow Port 5556 Inbound" -Direction Inbound -Protocol TCP -LocalPort 5556 -Action Allow

OVER

现在平板和 Windows 主机处于同一个局域网内时,APP 可以通过 ZeroMQ 与 WSL 服务收发消息了