玩轉(zhuǎn)WakeOnLan(遠(yuǎn)程開機(jī))

WakeOnLan

首先簡單介紹一下什么是WakeOnLan
Wake-On-LAN簡稱WOL,是一種電源管理功能;它是由IBM公司提出的網(wǎng)絡(luò)喚醒標(biāo)準(zhǔn),目前該標(biāo)準(zhǔn)已被大多數(shù)主板廠商支持。支持該標(biāo)準(zhǔn)的主板允許從遠(yuǎn)程通過網(wǎng)絡(luò)喚醒計算機(jī),也就是遠(yuǎn)程開機(jī)。

介于大多數(shù)人只想實現(xiàn)遠(yuǎn)程開機(jī)而不深究原理,因此原理我們放在后面講。


如何實現(xiàn)遠(yuǎn)程開機(jī)

簡單來說只需要兩步

  1. 需要遠(yuǎn)程喚醒的計算機(jī)設(shè)置好允許遠(yuǎn)程WOL喚醒
  2. 通過軟件向遠(yuǎn)端計算機(jī)發(fā)送喚醒請求

先說第一步

首先你需要確認(rèn)自己主板的網(wǎng)卡是否支持WOL標(biāo)準(zhǔn)并開啟它?,F(xiàn)今幾乎所有的主板都是支持該標(biāo)準(zhǔn)的,不過WOL功能則有些默認(rèn)開啟,有些默認(rèn)關(guān)閉,需要自行確認(rèn)。

以Win10為例,打開網(wǎng)絡(luò)和共享中心(任務(wù)欄圖標(biāo)如下)

右鍵打開網(wǎng)絡(luò)和共享中心

找到你的網(wǎng)絡(luò)連接,一般它可能叫以太網(wǎng)或本地連接。

以太網(wǎng)狀態(tài)

打開屬性->配置

以太網(wǎng)屬性

到這里因為系統(tǒng)和驅(qū)動不同,可能導(dǎo)致WOL設(shè)置的位置不同,例如我的WOL設(shè)置在網(wǎng)卡屬性面板的電源管理選項卡中并且默認(rèn)開啟,但有些計算機(jī)上則WOL設(shè)置可能在高級選項卡的屬性中,屬性名一般為Wake On Lan或者類似的名稱,你可以在屬性值中將其設(shè)置為啟用。

另外個別主板還需要在BIOS中開啟WOL支持和設(shè)置電源策略才可以支持遠(yuǎn)程喚醒,具體可以參考主板的說明書進(jìn)行設(shè)置。

電源管理
網(wǎng)卡高級屬性

第二步
這里我們需要一些WakeOnLan的軟件幫助我們發(fā)送喚醒請求。
(如果你對遠(yuǎn)程感興原理感興趣,并想自己實現(xiàn),后面我會講到)

這里介紹幾個WakeOnLan軟件并附上下載地址。

Wake on Lan for Windows GUI

這是一款具有圖形界面的WakeOnLan軟件,操作非常簡單,功能較為單一,但可以滿足需求。

從上到下的填寫內(nèi)容依次為:
遠(yuǎn)端計算機(jī)的網(wǎng)卡MAC地址
遠(yuǎn)端計算機(jī)的IP地址或域名
遠(yuǎn)端計算機(jī)的子網(wǎng)掩碼
發(fā)送選項(互聯(lián)網(wǎng)或者本地子網(wǎng))
遠(yuǎn)端計算機(jī)端口號

填好后點(diǎn)擊Wake Up執(zhí)行喚醒
點(diǎn)擊下載

Wake On Lan Command Line

這是一款命令行WakeOnLan軟件,使用也相對簡單,你可以通過cmd命令或者創(chuàng)建批處理文件執(zhí)行遠(yuǎn)程喚醒。

wolcmd [mac address] [ip address] [subnet mask] [port number]

例如:

wolcmd 009027a324fe 195.188.159.20 255.255.255.0 8900

點(diǎn)擊下載

當(dāng)然手機(jī)上也有很多WakeOnLan軟件,大家可以自行搜索下載,操作基本都類似。


測試

我們不能為了測試而去反復(fù)開關(guān)計算機(jī),那么如何得知遠(yuǎn)程計算機(jī)是否收到了喚醒請求呢?

Wake on Lan Monitor/Sniffer

我們可以通過Wake on Lan Monitor/Sniffer來檢測計算機(jī)是否收到了喚醒請求。
它界面非常簡潔,我們只需設(shè)置好UDP端口號點(diǎn)擊Start即可。UDP端口號就是用來接收喚醒請求的那個端口號。

如圖當(dāng)接收到發(fā)送給本機(jī)4343端口的喚醒請求時,該軟件會顯示收到請求的具體封包內(nèi)容。(后面會講解喚醒(魔術(shù))封包)
點(diǎn)擊下載


如果你只想玩玩WOL遠(yuǎn)程喚醒那么一般到這里就可以了,以下內(nèi)容適合有一定計算機(jī)基礎(chǔ)并且好奇心旺盛的讀者。

WakeOnLan原理

Wake-On-LAN的實現(xiàn),主要是向目標(biāo)主機(jī)發(fā)送特殊格式的數(shù)據(jù)包,俗稱魔術(shù)包(Magic Packet)。

MagicPacket格式的數(shù)據(jù)包是由AMD公司開發(fā)推廣的技術(shù),雖然其并非世界公認(rèn)的標(biāo)準(zhǔn),但是仍然受到很多網(wǎng)卡制造商的支持,因此許多具有網(wǎng)絡(luò)喚醒功能的網(wǎng)卡都能與之兼容。

MagicPacket

魔法數(shù)據(jù)包(Magic Packet)是一個廣播性的幀(frame),通過端口7或端口9進(jìn)行發(fā)送,且可以用無連接(Connectionless protocol)的通信協(xié)議(如UDP)來傳遞。
在魔法數(shù)據(jù)包內(nèi),每次都會先有連續(xù)6個"FF"(十六進(jìn)制,換算成二進(jìn)制即:11111111)的數(shù)據(jù),即:FF FF FF FF FF FF,在連續(xù)6個"FF"后則開始帶出MAC地址信息(MAC地址重復(fù)16次),有時還會帶出4字節(jié)或6字節(jié)的密碼,一旦經(jīng)由網(wǎng)卡偵測、解讀、研判(廣播)魔法數(shù)據(jù)包的內(nèi)容,內(nèi)容中的MAC地址、密碼若與電腦自身的地址、密碼吻合,就會引導(dǎo)喚醒、開機(jī)的程序。

MagicPacket 魔術(shù)數(shù)據(jù)包的格式一般看上去像下面這個樣子
假設(shè)MAC地址為:00-00-00-00-00

序號 MagicPacket
1 FF FF FF FF FF FF
2 00 00 00 00 00 00
3 00 00 00 00 00 00
4 00 00 00 00 00 00
5 00 00 00 00 00 00
6 00 00 00 00 00 00
7 00 00 00 00 00 00
8 00 00 00 00 00 00
9 00 00 00 00 00 00
10 00 00 00 00 00 00
11 00 00 00 00 00 00
12 00 00 00 00 00 00
13 00 00 00 00 00 00
14 00 00 00 00 00 00
15 00 00 00 00 00 00
16 00 00 00 00 00 00
17 00 00 00 00 00 00

魔法數(shù)據(jù)包(Magic Packet)結(jié)構(gòu)上非常簡單。

下面我們使用C#語言去實現(xiàn)一個WakeOnLan軟件的功能(能夠構(gòu)建并發(fā)送魔法數(shù)據(jù)包喚醒遠(yuǎn)程計算機(jī))

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Net.Sockets;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;

namespace WoL
{
    /// <summary>
    /// 網(wǎng)絡(luò)喚醒
    /// </summary>
    public class WakeOnLan
    {
        #region WakeOnLan

        /// <summary>
        /// 發(fā)送一個WOL魔術(shù)包到遠(yuǎn)程計算機(jī)
        /// </summary>
        /// <param name="macAddress">MAC地址</param>
        /// <param name="hostNameOrAddress">Host主機(jī)名或IP地址</param>
        /// <param name="subnetMask">子網(wǎng)掩碼</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存時間</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, string hostNameOrAddress, string subnetMask, int udpPort = 9, short ttl = 128) {
            // 獲取主機(jī)的IP地址
            var hostIPs = Dns.GetHostAddresses(hostNameOrAddress).Where(a=>a.AddressFamily == AddressFamily.InterNetwork);

            foreach (var hostIP in hostIPs) {
                // 獲取該主機(jī)的廣播地址
                var broadcastAddress = GetBroadcast(hostIP.ToString(), subnetMask);
                
                WakeUp(macAddress, broadcastAddress, udpPort, ttl);
            }
            
        }

        /// <summary>
        /// 發(fā)送一個WOL魔術(shù)包到遠(yuǎn)程計算機(jī)
        /// </summary>
        /// <param name="macAddress">MAC地址</param>
        /// <param name="broadcastAddress">網(wǎng)絡(luò)廣播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存時間</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, string broadcastAddress = null, int udpPort = 9, short ttl = 128)
        {

            if (string.IsNullOrWhiteSpace(macAddress))
            {
                throw new ArgumentNullException("macAddress", "必須提供MAC地址!");
            }

            if (!string.IsNullOrWhiteSpace(broadcastAddress) && !Regex.IsMatch(broadcastAddress, @"(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)\.(25[0-5]|2[0-4]\d|[0-1]\d{2}|[1-9]?\d)"))
            {
                throw new ArgumentNullException("broadcastAddress", "網(wǎng)絡(luò)廣播地址格式錯誤!");
            }

            // 獲取MAC地址對應(yīng)的字節(jié)數(shù)組
            var bytesMac = GetMac(macAddress);

            // 廣播地址
            var broadcastIP = IPAddress.Broadcast;

            if (!string.IsNullOrWhiteSpace(broadcastAddress))
            {
                broadcastIP = IPAddress.Parse(broadcastAddress);
            }

            WakeUp(bytesMac, broadcastIP, udpPort, ttl);
        }

        /// <summary>
        /// 發(fā)送一個WOL魔術(shù)包到遠(yuǎn)程計算機(jī)
        /// </summary>
        /// <param name="macAddress">喚醒MAC地址</param>
        /// <param name="broadcastIPAddress">網(wǎng)絡(luò)廣播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存時間</param>
        /// <remarks></remarks>
        public void WakeUp(string macAddress, IPAddress broadcastIPAddress = null, int udpPort = 9, short ttl = 128)
        {

            if (string.IsNullOrWhiteSpace(macAddress))
            {
                throw new ArgumentNullException("macAddress", "必須提供MAC地址!");
            }

            var bytesMac = GetMac(macAddress);

            WakeUp(bytesMac, broadcastIPAddress, udpPort, ttl);
        }

        /// <summary>
        /// 發(fā)送一個WOL魔術(shù)包到遠(yuǎn)程計算機(jī)
        /// </summary>
        /// <param name="bytesMac">MAC地址字節(jié)數(shù)組</param>
        /// <param name="broadcastIPAddress">網(wǎng)絡(luò)廣播地址</param>
        /// <param name="udpPort">WOL UDP 端口</param>
        /// <param name="ttl">WOL生存時間</param>
        /// <remarks></remarks>
        public void WakeUp(byte[] bytesMac, IPAddress broadcastIPAddress = null, int udpPort = 9, short ttl = 128) {

            if (!(udpPort > 0 && udpPort < 65535))
            {
                throw new ArgumentNullException("udpPort", "端口范圍錯誤,端口號的范圍從0到65535!");
            }

            #region 構(gòu)造魔術(shù)封包
            // 局域網(wǎng)喚醒魔術(shù)包包含一個6字節(jié)的頭和目標(biāo)的MAC地址6字節(jié),重復(fù)16次。
            var wolPacket = new byte[17 * 6];

            var ms = new MemoryStream(wolPacket, true);

            // 寫入6字節(jié)的0xFF頭
            for (int i = 0; i < 6; i++)
            {
                ms.WriteByte(0xFF);
            }

            // 寫MAC地址16次
            for (int i = 0; i < 16; i++)
            {
                ms.Write(bytesMac, 0, bytesMac.Length);
            } 
            #endregion

            // 創(chuàng)建UDP客戶端
            var udp = new UdpClient();

            // 廣播地址
            var broadcast = broadcastIPAddress ?? IPAddress.Broadcast;
            // 設(shè)置udp連接的地址和端口
            udp.Connect(broadcast, udpPort);
            // 設(shè)置TTL
            udp.Ttl = ttl;
            // 發(fā)送魔法數(shù)據(jù)包
            udp.Send(wolPacket, wolPacket.Length);
        }

        /// <summary>
        /// 處理字符串的MAC地址
        /// </summary>
        /// <param name="mac">以空格,:,-,分隔的mac地址</param>
        /// <returns>mac地址的字節(jié)數(shù)組</returns>
        public byte[] GetMac(string mac)
        {
            // 地址格式判斷并不嚴(yán)謹(jǐn),以空格,:,-,分隔的mac地址,也可以是混用分隔符的地址。
            if (!Regex.IsMatch(mac, @"^([0-9a-fA-F]{2})(([\s:-][0-9a-fA-F]{2}){5})$"))
            {
                throw new ArgumentNullException("mac", "MAC地址格式錯誤!");
            }

            // 去除分隔符
            var mMac = mac.Replace(" ", "")
                .Replace(":", "")
                .Replace("-", "");

            byte[] bytesMac = new byte[6];

            for (int i = 0; i < 6; i++)
            {
                //bytesMac[i] = (byte)Int32.Parse(mMac.Substring((i * 2), 2), NumberStyles.HexNumber);
                // 將字符串轉(zhuǎn)化為字節(jié)
                bytesMac[i] = Convert.ToByte(mMac.Substring((i * 2), 2), 16);
            }

            return bytesMac;
        }
        #endregion

        #region 計算地址

        /// <summary> 
        /// 獲得廣播地址 
        /// </summary> 
        /// <param name="ipAddress">IP地址</param> 
        /// <param name="subnetMask">子網(wǎng)掩碼</param> 
        /// <returns>廣播地址</returns> 
        public static IPAddress GetBroadcast(string ipAddress, string subnetMask)
        {
            return GetBroadcast(IPAddress.Parse(ipAddress), IPAddress.Parse(subnetMask));
        }

        /// <summary> 
        /// 獲得廣播地址 
        /// </summary> 
        /// <param name="ipAddress">IP地址</param> 
        /// <param name="subnetMask">子網(wǎng)掩碼</param> 
        /// <returns>廣播地址</returns> 
        public static IPAddress GetBroadcast(IPAddress ipAddress, IPAddress subnetMask)
        {

            byte[] ip = ipAddress.GetAddressBytes();
            byte[] sub = subnetMask.GetAddressBytes();

            // 廣播地址=子網(wǎng)按位求反 再 或IP地址 
            for (int i = 0; i < ip.Length; i++)
            {
                ip[i] = (byte)((~sub[i]) | ip[i]);
            }

            return new IPAddress(ip);
        }

        #endregion

        #region Ping
        /// <summary>
        /// 默認(rèn)超時時間
        /// </summary>
        private const int PING_TIMEOUT = 1000;

        /// <summary>
        /// 檢測目標(biāo)主機(jī)是否處于可訪問的狀態(tài)
        /// </summary>
        /// <param name="hostNameOrAddress">主機(jī)名稱或IP地址</param>
        /// <returns></returns>
        public static bool IsComputerAccessible(string hostNameOrAddress)
        {
            return IsComputerAccessible(hostNameOrAddress, PING_TIMEOUT);
        }

        /// <summary>
        /// 檢測目標(biāo)主機(jī)是否處于可訪問的狀態(tài)
        /// </summary>
        /// <param name="hostNameOrAddress">主機(jī)名稱或IP地址</param>
        /// <param name="timeout">超時時間</param>
        /// <returns></returns>
        public static bool IsComputerAccessible(string hostNameOrAddress, int timeout)
        {
            var pingSender = new Ping();
            var reply = pingSender.Send(hostNameOrAddress, timeout);
            // 這里只判斷ping成功的情況,如果需要更詳細(xì)的狀態(tài)可以自行處理
            return reply.Status == IPStatus.Success;
        }
        #endregion

        #region Arp

        /// <summary>
        /// 本地方法
        /// </summary>
        internal static class NativeMethods
        {
            /// <summary>
            /// 發(fā)送arp封包
            /// </summary>
            /// <param name="DestIP">目標(biāo)地址</param>
            /// <param name="SrcIP">發(fā)送者IP,可以為0</param>
            /// <param name="pMacAddr">返回的遠(yuǎn)端IP的Mac地址</param>
            /// <param name="PhyAddrLen">返回MAC地址長度</param>
            /// <returns></returns>
            [DllImport("iphlpapi.dll", ExactSpelling = true)]
            internal static extern int SendARP(int DestIP, int SrcIP, byte[] pMacAddr, ref uint PhyAddrLen);
        }

        /// <summary>
        /// 獲取MAC地址
        /// </summary>
        /// <param name="ipAddress">IP地址</param>
        /// <returns></returns>
        public static string GetMACAddress(IPAddress ipAddress)
        {
            var addressBytes = ipAddress.GetAddressBytes();
            var address = BitConverter.ToInt32(addressBytes, 0);

            var macAddr = new byte[6];
            var macAddrLen = (uint)macAddr.Length;

            if (NativeMethods.SendARP(address, 0, macAddr, ref macAddrLen) != 0)
            {
                return null;
            }

            var macAddressString = new StringBuilder();

            for (int i = 0; i < macAddr.Length; i++)
            {
                if (macAddressString.Length > 0)
                {
                    macAddressString.Append(":");
                }
                macAddressString.AppendFormat("{0:x2}", macAddr[i]);
            }

            return macAddressString.ToString();
        }

        /// <summary>
        /// 獲取MAC地址
        /// </summary>
        /// <param name="hostName">主機(jī)名稱</param>
        /// <returns></returns>
        public static string GetMACAddress(string hostName)
        {

            IPAddress[] mIPAddress = null;
            try
            {
                mIPAddress = Dns.GetHostAddresses(hostName);
            }
            catch
            {

                return null;
            }

            if (mIPAddress.Length == 0)
            {
                return null;
            }

            // 為該主機(jī)找到第一個地址的IPV4地址
            #region .Net 2 方法
            /*
                IPAddress ipAddress = null;

                foreach (IPAddress ip in hostEntry.AddressList)
                {
                    if (ip.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
                    {
                        ipAddress = ip;
                        break;
                    }
                }
                */
            #endregion

            // 如果在.net 3.5上運(yùn)行,你可以用LINQ來做
            var ipAddress = mIPAddress.First(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            return GetMACAddress(ipAddress);

        }

        /// <summary>
        /// 獲取MAC地址列表
        /// </summary>
        /// <param name="hostName">主機(jī)名稱</param>
        /// <returns></returns>
        public static IList<string> GetMACAddressArrray(string hostName)
        {

            IPHostEntry mIPHostEntry = null;
            try
            {
                mIPHostEntry = Dns.GetHostEntry(hostName);
            }
            catch
            {
                return null;
            }

            if (mIPHostEntry.AddressList.Length == 0)
            {
                return null;
            }

            var ipAddressList = mIPHostEntry.AddressList.Where(ip => ip.AddressFamily == AddressFamily.InterNetwork);

            var macList = new List<string>();

            foreach (var ipAddress in ipAddressList)
            {
                macList.Add(GetMACAddress(ipAddress));
            }

            return macList;

        }

        #endregion
    }
}

static void Main(string[] args)
{
    var wol = new WakeOnLan();
    // 發(fā)送魔術(shù)數(shù)據(jù)包,喚醒遠(yuǎn)程計算機(jī)
    wol.WakeUp("A9-F8-02-FE-94-D0", "192.168.1.100", "255.255.255.0", 40000);

    // 判斷遠(yuǎn)程計算機(jī)是否開啟(由于防火墻等原因不一定有效,同時由于開機(jī)需要時間,通常等待數(shù)秒到一兩分鐘不等才能檢測到遠(yuǎn)程計算機(jī)的狀態(tài))
    var computerAccessible = WakeOnLan.IsComputerAccessible("192.168.1.100");
    
    // 通過ARP協(xié)議嘗試獲取遠(yuǎn)程計算機(jī)的mac地址(通常局域網(wǎng)內(nèi)有效)
    var mac = WakeOnLan.GetMACAddress("192.168.1.100");
    
    Console.WriteLine($"遠(yuǎn)程計算機(jī)的mac地址:{mac}");

    Console.ReadKey();
}

其它語言實現(xiàn)也大多類似,關(guān)鍵是構(gòu)筑一個魔術(shù)數(shù)據(jù)包并把它發(fā)送給需要喚醒的目標(biāo)計算機(jī)。

WakeOnLan的介紹上魔術(shù)數(shù)據(jù)包是可以包含密碼的,但我并未找到類似的實現(xiàn),尚不清楚是否能夠喚醒有bios啟動密碼或硬盤密碼的計算機(jī)。如果有讀者知道還請留言告知,在此先行謝過。


以上內(nèi)容是我無聊時鼓搗總結(jié)的產(chǎn)物,如有錯誤之處歡迎各位指出。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 國家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報批稿:20170802 前言: 排版 ...
    庭說閱讀 12,321評論 6 13
  • 1.這篇文章不是本人原創(chuàng)的,只是個人為了對這部分知識做一個整理和系統(tǒng)的輸出而編輯成的,在此鄭重地向本文所引用文章的...
    SOMCENT閱讀 13,340評論 6 174
  • 個人認(rèn)為,Goodboy1881先生的TCP /IP 協(xié)議詳解學(xué)習(xí)博客系列博客是一部非常精彩的學(xué)習(xí)筆記,這雖然只是...
    貳零壹柒_fc10閱讀 5,185評論 0 8
  • 簡介 用簡單的話來定義tcpdump,就是:dump the traffic on a network,根據(jù)使用者...
    保川閱讀 6,069評論 1 13
  • linux資料總章2.1 1.0寫的不好抱歉 但是2.0已經(jīng)改了很多 但是錯誤還是無法避免 以后資料會慢慢更新 大...
    數(shù)據(jù)革命閱讀 13,187評論 2 33

友情鏈接更多精彩內(nèi)容