今天分享一個局域網(wǎng)喚醒Windows,F(xiàn)lutter局域網(wǎng)關(guān)閉Windows的方案窿锉。
這個方案是我根據(jù)我的需求而定制氮唯,并不一定適用所有人,僅供參考良风。? ? ? 因為我有一個局域網(wǎng)NAS谊迄,安裝的是Windows Server 2019,用作家庭的電影文件存儲烟央,跨設(shè)備傳輸统诺,掛載了2個機械盤,耗電是25-30W疑俭,平時沒有使用的時候,習(xí)慣將它關(guān)機粮呢,但是因為放在柜子內(nèi)部,而且沒有外接顯卡钞艇,之前都是手動開機啄寡,使用其他Windows連入進(jìn)行關(guān)機,非常的不方便香璃,所以自己做了一套開關(guān)機方案这难。
【開機方案】
關(guān)于開機方案,方案有很多種葡秒。
1.網(wǎng)卡喚醒
2.主板通電喚醒
3.主板開關(guān)跳線
第一種方案好像是支持外網(wǎng)的姻乓,因為我沒有外網(wǎng)開機需求嵌溢,所以這次沒做,它的原理是通過網(wǎng)絡(luò)給指定網(wǎng)卡發(fā)送數(shù)據(jù)包蹋岩,收到數(shù)據(jù)包的網(wǎng)卡調(diào)用電腦開機赖草。
第三種方案需要購買設(shè)備,如果會硬件開發(fā)那是最優(yōu)方案剪个,這次我沒選秧骑。
我選擇的第二種方案,我購買了一個 小米智能插座扣囊,大概是幾十塊錢乎折,然后在電腦主板Bios里面設(shè)置 通電開機,在米家里面進(jìn)行通電操作侵歇,這樣就完成了 遠(yuǎn)程開機的功能骂澄。
【關(guān)機方案】
關(guān)于關(guān)機方案,也有幾種
1.小米插座直接斷電(直接斷電惕虑,傷電腦)
2.開柜子按關(guān)機鍵(太麻煩了)
3.遠(yuǎn)程桌面進(jìn)入系統(tǒng)坟冲,點關(guān)機(...)
最后我選擇了使用軟件關(guān)機的方案,順便也 宣傳一波flutter溃蔫。
設(shè)計的方案是健提,在目標(biāo)客戶端上面啟動一個進(jìn)程,監(jiān)聽某個端口伟叛,然后用手機連入端口發(fā)送指令私痹,這樣就可以完成關(guān)機和其他操作。
【技術(shù)方案】
一開始準(zhǔn)備Windows上面使用WPF開發(fā)痪伦,這個我比較熟悉侄榴,手機端使用Flutter。
后面決定用flutter 完全開發(fā)Windows+Android网沾。最終也是滿足了我的需求癞蚕。
【網(wǎng)絡(luò)協(xié)議】選擇
? ? ? ? 網(wǎng)絡(luò)協(xié)議方面,首先是放棄TCP辉哥,然后我使用的是Http監(jiān)聽桦山,覺得結(jié)構(gòu)有點重了(Http協(xié)議優(yōu)勢是,flutter端也不用寫了醋旦,直接瀏覽器訪問一個網(wǎng)址就可以恒水,但是后面我發(fā)現(xiàn)因為NAS主機沒有固定IP,IP地址會變化饲齐,只有主機名钉凌,在手機上Linux核心的系統(tǒng)好像原生就不支持解析hostname,考慮到以后這個功能可能會給多個電腦使用捂人,最終放棄http協(xié)議)御雕,最后選擇了UDP通訊矢沿。
【W(wǎng)indows端】
? ? ? ? ? ? 啟動后UDP監(jiān)聽19999端口,當(dāng)收到固定消息包的消息時酸纲,將自己的信息封裝后發(fā)給對方主機捣鲸,當(dāng)收到關(guān)機指令,使用MethodChannel 調(diào)用Windows cpp里面的函數(shù)進(jìn)行關(guān)機
```
RawDatagramSocket.bind(InternetAddress.anyIPv4, 19999).then(
? ? ? (RawDatagramSocket udpSocket) {
? ? ? ? udpSocket.forEach((RawSocketEvent event) async {
? ? ? ? ? if (event == RawSocketEvent.write) {
? ? ? ? ? ? state.value = "服務(wù)已啟動";
? ? ? ? ? }
? ? ? ? ? if (event == RawSocketEvent.read) {
? ? ? ? ? ? Datagram? dg = udpSocket.receive();
? ? ? ? ? ? if (dg != null) {
? ? ? ? ? ? ? //dg.data.forEach((x) => print(x));
? ? ? ? ? ? ? if (dg.data.first == 0) {
? ? ? ? ? ? ? ? //廣播消息闽坡,回發(fā)自己的計算機信息
? ? ? ? ? ? ? ? List<int> data = const Utf8Encoder()
? ? ? ? ? ? ? ? ? ? .convert(windowsDeviceInfo?.computerName ?? "");
? ? ? ? ? ? ? ? udpSocket.send(data, dg.address, dg.port);
? ? ? ? ? ? ? } else if (dg.data.first == 1) {
? ? ? ? ? ? ? ? //關(guān)機
? ? ? ? ? ? ? ? state.value = "已收到開機指令";
? ? ? ? ? ? ? } else if (dg.data.first == 2) {
? ? ? ? ? ? ? ? //關(guān)機
? ? ? ? ? ? ? ? state.value = "已收到關(guān)機指令";
? ? ? ? ? ? ? ? platform.invokeMethod("CloseWindows");
? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? state.value = "已收到指令:${const Utf8Decoder().convert(dg.data)}";
? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? }
? ? ? ? });
? ? ? },
? ? );
```
```
#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,所有系統(tǒng)會分配一個隨機的端口號疾嗅,這個用來發(fā)送數(shù)據(jù)外厂,不需要知道具體的端口號,所以無所謂宪迟。數(shù)據(jù)包比較簡單酣衷,只用了一個 int 長度。
```
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());
? ? }
? }
? /* 發(fā)送廣播 - 在線 */
? void sendBroadcast() {
? ? if (socket != null && port != 0) {
? ? ? socket!.send([0], InternetAddress("255.255.255.255"), port);
? ? }
? }
? /* 關(guān)閉電腦 */
? void sendShutDown(InternetAddress ip) async {
? ? socket.send([2], InternetAddress(ip.address), port);
? }
```
【開機自啟】遇到的問題
? ? shell:startup 不登錄桌面次泽,程序不打開,
最終使用的是:
1.右鍵點擊此電腦圖標(biāo)席爽,在彈出菜單中選擇“管理”菜單項意荤。
2.然后在打開的計算機管理窗口中,找到“任務(wù)計劃程序”菜單項只锻。
3.右鍵玖像,點擊創(chuàng)建基本任務(wù),選擇開機啟動一個進(jìn)程齐饮。
4.修改這個計劃捐寥,在設(shè)置中修改為不登錄桌面就啟動,輸入電腦的開機密碼祖驱。
【廣域網(wǎng)擴展】
這套方案中握恳,只需要將端口映射到公網(wǎng)中,將Flutter端的廣播改為固定IP捺僻,就可以實現(xiàn)外網(wǎng)關(guān)機乡洼,當(dāng)然你的手機必須要在公網(wǎng)網(wǎng)段上,不然NAT地址無法回發(fā)到真實的手機19999端口上匕坯。