【问题标题】:Accessing HTTP server from any network Flutter从任何网络 Flutter 访问 HTTP 服务器
【发布时间】:2020-04-17 16:12:59
【问题描述】:

我正在尝试创建一个应用程序来远程控制一些灯。 至于现在一切顺利,我可以通过按下我在应用程序上设计的按钮来控制控制灯的继电器的状态,而不管安装应用程序的设备连接的网络是什么。

我想要的是:
多个设备将安装此应用程序并连接到同一个地方(我的意思是这将控制相同的灯)。因此,当一个人更改灯的状态时,应用程序的所有其他实例(包括进行更改的实例)都应该使用新的灯状态进行更新。

我想通过在应用程序中放置一个服务器监听来实现这一点,这样当发生变化时,托管在控制一堆中继的 Raspberry 上的真实服务器可以向所有应用程序发送消息并更新它们。每次应用打开或发生 IP 更改时,应用都会发送其 IP 和存储在服务器上的 ID。

我面临的问题是:
在本地工作时一切正常,但是当安装了应用程序的设备连接到另一个网络时,它将不再接收更新。当然,我知道这是因为由于我使用的 Flutter 插件,我只能获得设备的本地 IP,因此通过互联网我无法访问它。我可以检索设备的公共 IP 地址和私有 IP 地址,但是我不知道如何“询问”“拥有”公共 IP 的路由器,让我无需端口转发即可使用私有 IP 访问设备(无法进行端口转发,因为我使用的是移动设备)。

我的问题:
有没有办法实现我想要的?还是在有变化时动态更新应用程序的不同方法?或者一种同时获取“外部”和本地 IP 然后使用它调用应用程序的方法? 提前致谢。

pubspec.yaml 依赖项:

dependencies:
  flutter:
    sdk: flutter
  get_ip: ^0.3.0
  shared_preferences: ^0.4.3

AndroidManifest.xml 权限:

<uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>

Flutter服务器及ip发送方式:

void listenForUpdates()
  {
    HttpServer.bind(InternetAddress.anyIPv4, int.parse(port)).then((server)
    {
      server.listen((HttpRequest request)
      {
        MapEntry<String, String> parameter = request.uri.queryParameters.entries.single;
        print(parameter);
        setState(() {
          lightsOn[int.parse(parameter.key) - 1] = parameter.value.contains('true');
          noResponse = false;
        });
        request.response.close();
      });
    });
  }

void initPlatformState(bool resync) async
  {
    String ipAddress;
    ipAddress = await GetIp.ipAddress;
    HttpClient client = new HttpClient();
    SharedPreferences prefs = await SharedPreferences.getInstance();
    setState((){ deviceId = resync ? "new" : (prefs.getString('deviceId') ?? "new"); });

    client.postUrl(Uri.parse('http://NameOfTheServer:Port/?q=device&ip=' + ipAddress + ':' + port + '&id=' + deviceId))
    .catchError((onError)
    {
      setState((){ noResponse = true; });
    })
    .then((HttpClientRequest request)
    {
      request.headers.set(HttpHeaders.userAgentHeader, 'LumenApp - Dart:Flutter');
      return request.close();
    })
    .then((HttpClientResponse response)
    {
      response.transform(utf8.decoder).listen((contents) async
      {
        if(response.statusCode == 200)
        {
          setState((){
            deviceId = contents.split('|')[1];
            noResponse = false;
          });
          await prefs.setString('deviceId', deviceId);

        }
        else
          setState((){ noResponse = true; });
      });
    });
}

处理来自应用程序(Raspberry、Java)的请求的类的方法:

@Override
    public void run()
    {
        System.out.println("Successfully started thread with ThreadId " + this.threadId + ".");

        if(http.res.getCurrentResponse().size() > 0)
        {
            http.res.send();
        }
        else
        {
            http.res.setHeaders(http.req.getHttpVersion(), 200);

            try
            {
                String qParam = http.req.getParameterValueByName("q");
                String deviceId = http.req.getParameterValueByName("id");
                Boolean devPerm = AppUpdater.getDevicesId().contains(deviceId);
                if(!devPerm && !deviceId.equalsIgnoreCase("new"))
                {
                    http.res.setBody("ID EXPIRED");
                }
                else
                {
                    if(qParam.equalsIgnoreCase("device"))
                    {
                        String deviceIp = http.req.getParameterValueByName("ip");
                        String toDelete = http.req.getParameterValueByName("delete");
                        if(toDelete == null)
                        {
                            toDelete = "false";
                        }
                        Integer id = AppUpdater.updateDevicesIp(deviceId, deviceIp, Boolean.parseBoolean(toDelete));
                        http.res.setBody("DeviceId|" + id);
                    }
                    else if(Integer.parseInt(qParam) >= 0 && devPerm)
                    {
                        Integer lParam = Integer.parseInt(http.req.getParameterValueByName("l"));
                        gpioHandler.changeGpiosState(qParam, lParam);
                    }
                    else if(qParam.equals("-1") && devPerm)
                    {
                        HashMap<Integer, Boolean> gpiosState = gpioHandler.getGpiosState();
                        http.res.setBody(qParam);
                        for(HashMap.Entry<Integer, Boolean> gpio : gpiosState.entrySet())
                        {
                            http.res.addBody("|" + gpio.getKey().toString() + "-" + gpio.getValue().toString());
                        }
                    }
                }
            }
            catch(RuntimeException e)
            {
                e.printStackTrace();
                System.err.println("One or more required parameters were missing.");
                http.res.setHeaders(http.req.getHttpVersion(), 400);
            }
            finally
            {
                http.res.send();
            }
        }
public class AppUpdater
{
    static final GpioHandler gpioHandler = new GpioHandler();

    static HashMap<String, String> devices = new HashMap<String, String>();

    public static Integer updateDevicesIp(String deviceId, String deviceIp, boolean toDelete)
    {
        Integer id = 0;
        if(toDelete)
        {
            devices.remove(deviceIp);
        }
        else if(deviceId.equalsIgnoreCase("new"))
        {
            id = devices.size() + 1;
            devices.put(id.toString(), deviceIp);
        }
        else if(devices.containsKey(deviceId))
        {
            devices.replace(deviceId, deviceIp);
            id = Integer.parseInt(deviceId);
        }
        return id;
    }

    public static void notifyApp(String lightIndex, String lightStatus) throws IOException
    {
        for(HashMap.Entry<String, String> device : devices.entrySet())
        {
            String urlParameters = "?" + lightIndex + "=" + lightStatus;
            URL url = new URL("http://" + device.getValue() + urlParameters);
            System.out.println(url);
            url.openStream();
        }
    }

    public static ArrayList<String> getDevicesId()
    {
        ArrayList<String> devicesId = new ArrayList<String>();
        for(HashMap.Entry<String, String> device : devices.entrySet())
        {
            devicesId.add(device.getKey());
        }
        return devicesId;
    }

    public static HashMap<String, String> getDevices()
    {
        return devices;
    }
}
public void gpioListener()
    {
        for(GpioPinDigitalOutput pin : gpios)
        {
            pin.addListener(new GpioPinListenerDigital() {
                @Override
                public void handleGpioPinDigitalStateChangeEvent(GpioPinDigitalStateChangeEvent event)
                {
                    String gpioName = event.getPin().getName();
                    System.out.println(" --> GPIO PIN STATE CHANGE: " + gpioName + " = " + event.getState());
                    try
                    {
                        String lightStatus = event.getState().toString().equals("HIGH") ? "true" : "false";
                        String pinIndex = pinIndexes.get(gpioName.substring(gpioName.length() - 1));
                        AppUpdater.notifyApp(pinIndex, lightStatus);
                    }
                    catch(IOException e)
                    {
                        e.printStackTrace();
                    }
                }
            });
        }
    }

【问题讨论】:

  • 每个 Android 应用都包含一个服务器和一个客户端?
  • 连接到另一个网络到底是什么意思?
  • @blackapps 是的,每个应用程序都包含一个服务器和一个客户端。连接到另一个网络是指设备更改了之前连接的网络,例如当设备从 wifi 切换到移动数据或另一个 wifi 网络时。
  • 如果设备更改为移动网络,则其上的服务器将无用。
  • @blackapps 那么当应用发生变化时如何更新所有应用实例?

标签: java android networking flutter ip-address


【解决方案1】:

我找到了解决方案。 网络套接字。
我用 Node JS 写了一个小小的“双”服务器,用 Java 写了另一个服务器,这次 Java 服务器比以前简单了。 我使用 NodeJs 是因为在 Java 中实现 websocket 很痛苦。 我称它为“双服务器”,因为我没有找到(也没有搜索)一种将简单的 Java ServerSocket 与 NodeJs WebSocket 通信的方法,所以我还在同一个 NodeJs 文件中创建了一个小型 HTTP 服务器。 我使用颤振 websocket 教程(使用 web_socket_channel 插件)修改了应用程序。 这也使应用程序变得非常简单。

所以,总结一下:
我在 NodeJs 中使用 ws 库为 WebSocket 服务器编写了代码,在 NodeJs 中也使用 expresshttp 库为 HTTP 服务器编写了代码。 我编写了自己的 Java 类来处理 HTTP 连接,并使用 Pi4j 来处理 RaspberryPi 的引脚。 该应用是在 Android Studio 中使用 Flutter 框架编写的,仅具有 web_socket_channel 依赖项。

流程是:
1) 应用程序连接到 WebSocket 服务器。
2) WebSocket 服务器读取文件并将当前中继的状态发送给新连接的应用程序。
3) 当应用请求更改中继状态时,请求通过 WebSocket 发送并使用 NodeJs http 库重定向到 Java 服务器。
4) Java 服务器进行所需的更改,使用新中继的状态更新文件并返回 200 HTTP 响应。
5) 变化被 Pi4j 库提供的 GPIO 监听器 捕获,并以字符串的形式发送到 NodeJs HTTP Express服务器。
6) NodeJs HTTP Express 服务器告诉 WebSocket 服务器以通知所有应用程序发生更改。
7) 只要有变化,应用就会成功更新!

【讨论】:

    【解决方案2】:

    欢迎使用 stackoverflow。

    您描述了在工业自动化或家庭自动化领域非常常见的东西,一个或多个设备监控机器/系统的状态,在您的情况下它们只是几个灯,但系统是通常配备一个也执行程序的 PLC(可编程逻辑控制器)。

    Raspberry PI 可以作为可编程 PLC 使用 Codesys 软件,该软件非常强大且免费/便宜。

    这是许多人用来构建基于 Raspberry 的小型自动化或机器人的方法。

    您还可以使用 Modbus 和 OPC UA 等标准协议与 PLC 进行通信。

    这是一个可以通过这些协议进行监控的安卓应用:https://www.suppanel.com/index.php/en/

    虽然我承认我的建议是进入一个非常不同的编程领域。

    【讨论】:

    • 好的,我明白了。但我仅限于使用 Java(以及导入 Java 库)以及 Flutter 及其插件的使用。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-26
    • 1970-01-01
    • 2012-06-28
    • 2017-12-01
    相关资源
    最近更新 更多