java 通過網絡喚醒實現遠程開機 頂 原 薦

1. 在BIOS設置支持網絡喚醒

大多數集成網卡都能實現網絡喚醒功能,不過需要事先進入BIOS中開啓網絡喚醒功能,不同主板的設置不一樣,以VIA 主板爲例,在BIOS中找到“OnBoard LAN”選項,將它設成“Enabled”。同時將“POWER MANAGEMENT SETUP(電源管理設置)”下的“Power On by LAN/Ring”選項設爲“Enabled”,最後將“Wake On LAN(網絡喚醒)”選項設置爲“Enabled”,設置好後保存退出。

不同系統可能還需要額外的操作才能保證網絡喚醒的可用性,以win10系統爲例:

打開設備管理器,進入網絡適配器中自己網卡的屬性設置,把相關的服務都啓用了。

2. 網絡喚醒的必備條件

  • 網絡喚醒需要終端的主板和網卡支持,需要先在BIOS設置支持網絡喚醒
  • 網絡喚醒要接通電源保證網卡能通電 要接網線 不能是wifi
  • 如果強制關機 可能不能通過網絡喚醒來開機
  • 跨交換機或者跨路由的話就有可能不支持喚醒
  • 跨多層交換機的話即使ping通也未必能喚醒
  • 在同一網段下進行網絡喚醒最爲省事

3. 網絡喚醒原理

這裏提到一個魔術包Magic Packet的概念,魔術包指AMD公司開發的喚醒數據包,其實是一種特定的數據格式。將喚醒魔術包發送的被喚醒機器的網卡上,具有遠程喚醒的網卡都支持這個標準,用16進製表示。

假設你的網卡物理地址爲00:15:17:53:d4:f9, 這段Magic Packet內容如下:

    FFFFFFFFFFFF00151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f9

這段數據轉化爲二進制的數據,通過socket技術發送數據包以及目的mac和目的廣播地址,就會喚醒目的網卡,從而喚醒主機。

數據包流向圖:

當數據包被廣播到192.168.1網段之後,根據數據攜帶的mac信息匹配到具體的主機。

 

4. 廣播地址

這裏主要講解廣播地址的概念和計算。

所謂廣播地址指同時向網上所有的主機發送報文。

對一個既定的ip來說,其網絡地址就是主機位全換成0,廣播地址就是主機位是全換成1

例子:先把子網掩碼化成二進制,再對應的把子網掩碼後面是0的部分對着Ip地址換成0和1就是網絡地址和主機地址,比如

192.168.1.3 (地址)/255.255.255.252(掩碼) ,換算一下成二進制

11111111.11111111.11111111.111111 00 /掩碼

11000000.10101000.00000001.000000 11 /地址

掩碼後兩位是0,那麼把地址的後兩位換成0就是網絡地址,換成1就是廣播地址

那麼就是:11000000.10101000.00000001.000000 00

11000000.10101000.00000001.000000 11

把上面的二進制轉換成10進制

得到192.168.1.0是網絡地址,192.168.1.3是廣播地址

 

5. java代碼-網絡喚醒

先計算被喚醒主機的廣播地址

   //根據子網掩碼和ip得到主機的廣播地址
    public static String getBroadcastAddress(String ip, String subnetMask){
        String ipBinary = toBinary(ip);
        String subnetBinary = toBinary(subnetMask);
        String broadcastBinary = getBroadcastBinary(ipBinary, subnetBinary);
        String wholeBroadcastBinary=spiltBinary(broadcastBinary);
        return binaryToDecimal(wholeBroadcastBinary);
    }

    //二進制的ip字符串轉十進制
    private static String binaryToDecimal(String wholeBroadcastBinary){
        String[] strings = wholeBroadcastBinary.split("\\.");
        StringBuilder sb = new StringBuilder(40);
        for (int j = 0; j < strings.length ; j++) {
            String s = Integer.valueOf(strings[j], 2).toString();
            sb.append(s).append(".");
        }
        return sb.toString().substring(0,sb.length()-1);
    }

    //按8位分割二進制字符串
    private static String spiltBinary(String broadcastBinary){
        StringBuilder stringBuilder = new StringBuilder(40);
        char[] chars = broadcastBinary.toCharArray();
        int count=0;
        for (int j = 0; j < chars.length; j++) {
            if (count==8){
                stringBuilder.append(".");
                count=0;
            }
            stringBuilder.append(chars[j]);
            count++;
        }
        return stringBuilder.toString();
    }

    //得到廣播地址的二進制碼
    private static String getBroadcastBinary(String ipBinary, String subnetBinary){
        int i = subnetBinary.lastIndexOf('1');
        String broadcastIPBinary = ipBinary.substring(0,i+1);
        for (int j = broadcastIPBinary.length(); j < 32 ; j++) {
            broadcastIPBinary=broadcastIPBinary+"1";
        }
        return broadcastIPBinary;
    }

    //轉二進制
    private static String toBinary(String content){
        String binaryString="";
        String[] ipSplit = content.split("\\.");
        for ( String split : ipSplit ) {
            String s = Integer.toBinaryString(Integer.valueOf(split));
            int length = s.length();
            for (int i = length; i <8 ; i++) {
                s="0"+s;
            }
            binaryString = binaryString +s;
        }
        return binaryString;
    }

執行網絡喚醒

    /**
     * 喚醒主機
     * @param ip         主機ip
     * @param mac     主機mac
     * @param subnetMask      主機子網掩碼
     */
    public static void wakeUpDevice(String ip,String mac,String subnetMask){
        ip=ip.trim();
        mac=mac.trim();
        subnetMask=subnetMask.trim();
        String broadcastAddress=getBroadcastAddress(ip,subnetMask);
        mac = mac.replace("-", "");
        wakeBy(broadcastAddress,mac,389);
    }

    /**
     *   網絡喚醒
     * @param ip            主機ip
     * @param mac        主機mac
     * @param port        端口
     */
    private static void wakeBy(String ip, String mac, int port) {
        //構建magic魔術包
        String MagicPacage = "FFFFFFFFFFFF";
        for (int i = 0; i < 16; i++) {
            MagicPacage += mac;
        }
        byte[] MPBinary = hexStr2BinArr(MagicPacage);
        try {
            InetAddress address = InetAddress.getByName(ip);
            DatagramSocket socket = new DatagramSocket(port);
            DatagramPacket packet = new DatagramPacket(MPBinary, MPBinary.length, address, port);
            //發送udp數據包到廣播地址
            socket.send(packet);
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static byte[] hexStr2BinArr(String hexString) {
        String hexStr = "0123456789ABCDEF";
        int len = hexString.length() / 2;
        byte[] bytes = new byte[len];
        byte high = 0;
        byte low = 0;
        for (int i = 0; i < len; i++) {
            high = (byte) ((hexStr.indexOf(hexString.charAt(2 * i))) << 4);
            low = (byte) hexStr.indexOf(hexString.charAt(2 * i + 1));
            bytes[i] = (byte) (high | low);
        }
        return bytes;
    }

 

注意:當跨網段進行喚醒時,即發起喚醒的地址和被喚醒的目的地址不在同一個網段,是否需要做一些調整取決於你的網絡配置。我這邊的情況是,比如當50網段的服務器發送網絡喚醒魔術包到62網段,是行不通的,需要在62網關下增加ip轉發廣播ip forward-broadcast。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章