Flutter局域网关闭Windows

今天分享一个局域网唤醒Windows,Flutter局域网关闭Windows的方案。

这个方案是我根据我的需求而定制,并不一定适用所有人,仅供参考。 因为我有一个局域网NAS,安装的是Windows Server 2019,用作家庭的电影文件存储,跨设备传输,挂载了2个机械盘,耗电是25-30W,平时没有使用的时候,习惯将它关机,但是因为放在柜子内部,而且没有外接显卡,之前都是手动开机,使用其他Windows连入进行关机,非常的不方便,所以自己做了一套开关机方案。


【开机方案】

关于开机方案,方案有很多种。 1.网卡唤醒 2.主板通电唤醒 3.主板开关跳线

第一种方案好像是支持外网的,因为我没有外网开机需求,所以这次没做,它的原理是通过网络给指定网卡发送数据包,收到数据包的网卡调用电脑开机。 第三种方案需要购买设备,如果会硬件开发那是最优方案,这次我没选。

我选择的第二种方案,我购买了一个 小米智能插座,大概是几十块钱,然后在电脑主板Bios里面设置 通电开机,在米家里面进行通电操作,这样就完成了 远程开机的功能。

【关机方案】

关于关机方案,也有几种 1.小米插座直接断电(直接断电,伤电脑) 2.开柜子按关机键(太麻烦了) 3.远程桌面进入系统,点关机(...)

最后我选择了使用软件关机的方案,顺便也 宣传一波flutter。 设计的方案是,在目标客户端上面启动一个进程,监听某个端口,然后用手机连入端口发送指令,这样就可以完成关机和其他操作。

【技术方案】

一开始准备Windows上面使用WPF开发,这个我比较熟悉,手机端使用Flutter。 后面决定用flutter 完全开发Windows+Android。最终也是满足了我的需求。

【网络协议】选择

网络协议方面,首先是放弃TCP,然后我使用的是Http监听,觉得结构有点重了(Http协议优势是,flutter端也不用写了,直接浏览器访问一个网址就可以,但是后面我发现因为NAS主机没有固定IP,IP地址会变化,只有主机名,在手机上Linux核心的系统好像原生就不支持解析hostname,考虑到以后这个功能可能会给多个电脑使用,最终放弃http协议),最后选择了UDP通讯。

【Windows端】

启动后UDP监听19999端口,当收到固定消息包的消息时,将自己的信息封装后发给对方主机,当收到关机指令,使用MethodChannel 调用Windows cpp里面的函数进行关机

ini 复制代码
RawDatagramSocket.bind(InternetAddress.anyIPv4, 19999).then(
      (RawDatagramSocket udpSocket) {
        udpSocket.forEach((RawSocketEvent event) async {
          if (event == RawSocketEvent.write) {
            state.value = "服务已启动";
          }
          if (event == RawSocketEvent.read) {
            Datagram? dg = udpSocket.receive();
            if (dg != null) {
              //dg.data.forEach((x) => print(x));
              if (dg.data.first == 0) {
                //广播消息,回发自己的计算机信息
                List<int> data = const Utf8Encoder()
                    .convert(windowsDeviceInfo?.computerName ?? "");
                udpSocket.send(data, dg.address, dg.port);
              } else if (dg.data.first == 1) {
                //关机
                state.value = "已收到开机指令";
              } else if (dg.data.first == 2) {
                //关机
                state.value = "已收到关机指令";
                platform.invokeMethod("CloseWindows");
              } else {
                state.value = "已收到指令:${const Utf8Decoder().convert(dg.data)}";
              }
            }
          }
        });
      },
    );
c 复制代码
 #include "flutter/method_channel.h"
  #include "flutter/standard_method_codec.h"

  void configMethodChannel(flutter::FlutterEngine *engine)
{
        const std::string test_channel("DMSkin.Channel");
        const flutter::StandardMethodCodec &codec = flutter::StandardMethodCodec::GetInstance();
        flutter::MethodChannel method_channel_(engine->messenger(), test_channel, &codec);
        method_channel_.SetMethodCallHandler([](const auto &call, auto result)
                                       {
            std::cout << "Inside method call" << std::endl;
            if (call.method_name().compare("CloseWindows") == 0) {
            std::cout << "Close window message recieved!" << std::endl;
            system("shutdown -s -t 3");
            std::cout << "Close window Success!" << std::endl;
         result->Success();
    }
         else if (call.method_name().compare("goToNativeScanPage") == 0) {
            std::cout << "goToNativeScanPage!" << std::endl;

        result->Success();
    } });
}

【手机端】

启动后使用bind绑定本机端口,因为是any + 0,所有系统会分配一个随机的端口号,这个用来发送数据,不需要知道具体的端口号,所以无所谓。数据包比较简单,只用了一个 int 长度。

ini 复制代码
void init() async {
    port = 19999;
    socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
    if (socket != null) {
      socket!.broadcastEnabled = true;
      socket!.listen((RawSocketEvent event) {
        if (event == RawSocketEvent.write) {
          sendBroadcast();
        }
        else if (event == RawSocketEvent.read) {
          Datagram? datagram = socket!.receive();
          if (datagram != null) {
            String name = const Utf8Decoder().convert(datagram.data);
            if (!data.any(
                (element) => element.ip!.address == datagram.address.address)) {
              Windows windows = Windows()
                ..name = name
                ..ip = datagram.address;
              data.add(windows);
              change(null, status: RxStatus.success());
            }
          }
        }
      });
      change(null, status: RxStatus.success());
    }
  }

  /* 发送广播 - 在线 */
  void sendBroadcast() {
    if (socket != null && port != 0) {
      socket!.send([0], InternetAddress("255.255.255.255"), port);
    }
  }

  /* 关闭电脑 */
  void sendShutDown(InternetAddress ip) async {
    socket.send([2], InternetAddress(ip.address), port);
  }

【开机自启】遇到的问题

shell:startup 不登录桌面,程序不打开, 最终使用的是: 1.右键点击此电脑图标,在弹出菜单中选择"管理"菜单项。 2.然后在打开的计算机管理窗口中,找到"任务计划程序"菜单项。 3.右键,点击创建基本任务,选择开机启动一个进程。 4.修改这个计划,在设置中修改为不登录桌面就启动,输入电脑的开机密码。

【广域网扩展】

这套方案中,只需要将端口映射到公网中,将Flutter端的广播改为固定IP,就可以实现外网关机,当然你的手机必须要在公网网段上,不然NAT地址无法回发到真实的手机19999端口上。

Github源码

相关推荐
leluckys5 小时前
flutter 专题四十四 关于MacOs Catalina “无法打开***,因为无法验证开发者...”的解决方案
flutter·macos
leluckys15 小时前
flutter 专题四十七 Flutter 应用启动流程分析
flutter
小龙在山东2 天前
Flutter Scaffold 页面结构
flutter
TangAcrab3 天前
vscode flutter 项目连接 mumu 浏览器
ide·vscode·flutter
小龙在山东3 天前
Flutter常用Widget小部件
flutter
yangshuo12813 天前
git安装flutter
git·flutter
Kevin Coding3 天前
Flutter使用Flavor实现切换环境和多渠道打包
android·flutter·ios
字节全栈_BjO3 天前
Flutter Raw Image Provider
flutter
字节全栈_rJF4 天前
Flutter Candies 一桶天下
前端·javascript·flutter
pengyu5 天前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart