Java通过WOL,3步实现远程开机

简介: Java通过WOL,3步实现远程开机

目录

远程开机(Wake onLAN)

WOL实现远程开机分为3大步。

魔术包Magic Packet

第一步:设置主机的有线网卡Wake on Magic Package属性为Enable。

1.首先进入cmd命令窗口,查看自己的有线网卡的ip地址和mac地址,写java程序时需要使用。

2.网卡要开启唤醒魔包(Wake on magic packet)

第二步:进入BIOS,设置Wake On Lan属性为Enable

        第三步:远程端启动java程序,发送MagicPacket(魔术包)即可唤醒。

问题排查

1. 没有Wake on magic packet或唤醒魔包

在线更新

驱动包更新

2. 在路由器环境下,想在公网实现对内网电脑开机

3. 跨网段进行唤醒



远程开机(Wake onLAN)

远程开机也被称为远程唤醒技术(Wake on Lan: WOL),是指可以通过局域网、互联网或者通讯网实现远程开机,无论目标主机离用户有多远、处于什么位置,只要其与发送命令主机可以通信,就能够被随时启动,该技术被现在的大多数主板与网卡所支持。


远程开机的实现主要依靠向目标主机发送特定格式的数据包,最初AMD公司推出的MagicPackage用于生成远程唤醒所需的特殊数据包,俗称魔术包(Magic Package)。MagicPackage技术只是AMD公司开发并推广的技术,尚未成为一项国际标准,但是该技术受到大多数网卡制造商的支持,因此具有远程唤醒功能的网卡都兼容这项技术。


WOL实现远程开机分为3大步。

第一步,设置主机的有线网卡Wake on Magic Package属性为Enable

第二步,进入BIOS,设置Wake On Lan属性为Enable

第三步,远程端启动java程序,发送MagicPacket(魔术包)即可唤醒。


魔术包Magic Packet

这里提到一个魔术包Magic Packet的概念,魔术包指AMD公司开发的唤醒数据包,其实是一种特定的数据格式。将唤醒魔术包发送的被唤醒机器的网卡上,具有远程唤醒的网卡都支持这个标准,用16进制表示。

魔术包是用16进制表示的数据包,它由固定的前缀数据以及固定重复次数的目标主机MAC地址所组成。    

所谓固定前缀数据即6对“FF”,所谓固定重复次数即16次,也就是说魔术包是由12个“F”加重复16次的主机MAC地址组成,例如本文所用试验机的MAC地址为“66-00-6A-8F-1D-64”,

所以使该机远程开机的魔术包为:

String magicPacageData = "FFFFFFFFFFFF" +

                     "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +

                     "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +

                     "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +

                     "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64";

   

     在Windows系统中,主机的MAC地址可以通过在命令窗口中输入“ipconfig -all”命令查看。

这段数据转化为二进制的数据,通过socket技术发送数据包以及目的mac和目的广播地址,就会唤醒目的网卡,从而唤醒主机。


第一步:设置主机的有线网卡Wake on Magic Package属性为Enable

1.首先进入cmd命令窗口查看自己的有线网卡的ip地址和mac地址,写java程序时需要使用。

 

2.网卡要开启唤醒魔包(Wake on magic packet)

需要找到有线网卡右键点击属性找到高级将Wake on magic packet属性的值改为Enabled。找到网卡的方式有很多。

第一种:

 

 

 

 

 

 

 

如果为中文参考下图

 

以上以设置好第一步,可以开始第二步。

下面提供一些其他找到有线网卡属性的方式

第二种

键盘点击windwos或鼠标点击左下角搜索设备管理器--win10电脑自带就有搜索框

 

 

 


第三种

找到更改适配器之后选择有线网卡,具体步骤参看下图

 

 

 

 

第一步已经全部结束。



第二步:进入BIOS,设置Wake On Lan属性为Enable

配置需要进入BIOS界面。

电脑重启,如果是DELL的主板,开机时一直按F12,不同的主板进入BIOS的操作不同,可以到网上搜索。

 

 

 

 

 

 

另一种BIOS界面

 

 

 


第三步:远程端启动java程序,发送MagicPacket(魔术包)即可唤醒。

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Collections;
/**
 * @Title: 发送魔术包:实现电脑远程开机(WOL)
 * @ClassName: com.devicemag.core.utils.MagicPackageUtils.java
 * @Description:
 *
 * @Copyright 2020-2021  - Powered By 研发中心
 * @author: FLY
 * @date: 2021/1/8 9:32
 * @version V1.0
 */
@Slf4j
public class MagicPackageUtils {
    /**
     * main方法,发送UDP广播,实现远程开机,目标计算机的MAC地址为:D8-9E-F3-95-AC-74
     */
    public static void main(String[] args) {
        // 10.0.50.138  D8-9E-F3-88-B6-3C
        // 10.0.50.129  66-00-6A-8F-1D-64
        // 10.0.50.186  D8-9E-F3-95-AC-74
        // 单播
//        sendMagicPackage("10.0.50.186", "D8-9E-F3-95-AC-74");
        // 广播,需要先根据子网掩码和ip得到主机的广播地址
        String broadcastAddress=getBroadcastAddress("10.0.50.186","255.255.255.0");
        System.out.println(broadcastAddress);
    }
    /**
     * @Title: 组装魔术包数据
     * @MethodName: assembleMagicData
     * @param mac
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * 魔术包是用16进制表示的数据包,它由固定的前缀数据以及固定重复次数的目标主机MAC地址所组成。
     *  所谓固定前缀数据即6对“FF”,所谓固定重复次数即16次,也就是说魔术包是由12个“F”加重复16次的主机MAC地址组成,例如本文所用试验机的MAC地址为“66-00-6A-8F-1D-64”,
     *  所以使该机远程开机的魔术包为:
     *         String magicPacageData = "FFFFFFFFFFFF" +
     *                 "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
     *                 "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
     *                 "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
     *                 "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64";
     *
     * 在Windows系统中,主机的MAC地址可以通过在命令窗口中输入“ipconfig -all”命令查看。
     *
     * @author: FLY
     * @date: 2021/1/11 18:02
     */
    private static String assembleMagicData(String mac) {
        String macR = null;
        if (mac.contains("-")) {
            macR = mac.replaceAll("-", "");
        } else if (mac.contains(":")) {
            macR = mac.replaceAll(":", "");
        }
        String repeatedStr = createRepeatedStr(macR, 16);
        String magicData = new StringBuilder("FFFFFFFFFFFF").append(repeatedStr).toString();
        return magicData;
    }
    /**
     * @Title: 远程开机
     * @MethodName: sendMagicPackage
     * @param ip
     * @param mac
     * @Return void
     * @Exception
     * @Description: 发送UDP广播,实现远程开机
     *
     * @author: FLY
     * @date: 2021/1/11 17:19
     */
    public static void sendMagicPackage(String ip, String mac) {
        log.info("【魔包远程开机】,IP地址:{},MAC地址:{}", ip, mac);
        if (StringUtils.isBlank(ip)
                || StringUtils.isBlank(mac)) {
            return;
        }
        //端口号
        int port = 9;
        //魔术包数据
        /*String magicPacageData = "FFFFFFFFFFFF" +
                "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
                "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
                "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" +
                "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64" + "64006A8F1D64";*/
        String magicPacageData = assembleMagicData(mac);
        log.info("【魔包远程开机】,IP地址:{},MAC地址:{},魔术包数据:{}", ip, mac, magicPacageData);
        // mac地址转换为2进制的魔术包数据
        byte[] command = hexStr2Byte(magicPacageData);
        //广播魔术包
        try {
            //1.获取ip地址
            InetAddress address = InetAddress.getByName(ip);
            //2.获取广播socket
            MulticastSocket socket = new MulticastSocket(port);
            //3.封装数据包
            /*public DatagramPacket(byte[] buf,int length
             *      ,InetAddress address
             *      ,int port)
             * buf:缓存的命令
             * length:每次发送的数据字节数,该值必须小于等于buf的大小
             * address:广播地址
             * port:广播端口
             */
            DatagramPacket packet = new DatagramPacket(command, command.length, address, port);
            //4.发送数据
            socket.send(packet);
            //5.关闭socket
            socket.close();
        } catch (UnknownHostException e) {
            //Ip地址错误时候抛出的异常
            log.error("【魔包远程开机】异常-Ip地址错误,IP地址:{},MAC地址:{},异常信息:{}", ip, mac, e);
        } catch (IOException e) {
            //获取socket失败时候抛出的异常
            log.error("【魔包远程开机】异常-获取socket失败,IP地址:{},MAC地址:{},异常信息:{}", ip, mac, e);
        }
    }
    /**
     * @Title: 将16进制字符串转换为用byte数组表示的二进制形式
     * @MethodName: hexStr2Byte
     * @param hex
     * @Return byte[]
     * @Exception
     * @Description:
     *
     * @author: FLY
     * @date: 2021/1/11 17:22
     */
    private static byte[] hexStr2Byte(String hex) {
        ByteBuffer bf = ByteBuffer.allocate(hex.length() / 2);
        for (int i = 0; i < hex.length(); i++) {
            String hexStr = String.valueOf(hex.charAt(i));
            i++;
            hexStr += hex.charAt(i);
            byte b = (byte) Integer.parseInt(hexStr, 16);
            bf.put(b);
        }
        return bf.array();
    }
    /**
     * @Title: 将一个字符串重复n次
     * @MethodName: createRepeatedStr
     * @param seed
     * @param n
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * @author: FLY
     * @date: 2021/1/11 17:22
     */
    private static String createRepeatedStr(String seed, int n) {
        return String.join("", Collections.nCopies(n, seed));
    }
    /**
     * @Title: 根据子网掩码和ip得到主机的广播地址
     * @MethodName:  getBroadcastAddress
     * @param ip
     * @param subnetMask  子网掩码
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * @author: FLY
     * @date:  2021/1/12 19:02
     */
    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);
    }
    /**
     * @Title: 二进制的ip字符串转十进制
     * @MethodName:  binaryToDecimal
     * @param wholeBroadcastBinary
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * @author: FLY
     * @date:  2021/1/12 19:03
     */
    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);
    }
    /**
     * @Title: 按8位分割二进制字符串
     * @MethodName:  spiltBinary
     * @param broadcastBinary
     * @Return java.lang.String
     * @Exception
     * @Description:
     *
     * @author: FLY
     * @date:  2021/1/12 19:03
     */
    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;
    }
}


问题排查

1. 没有Wake on magic packet或唤醒魔包

步骤1中遇到问题为选择高级之后,发现属性里没有Wake on magic packet或唤醒魔包,此处需要的操作是更新网卡的驱动。

更新网卡的驱动有两种,一种是在线更新,前提为连接互联网,操作也相对简单

另一种是驱动包安装,前提为需要到网上下载好有线网卡对应的驱动包然后本地安装。

在线更新

 

在线更新页面如图,不需要多余的操作。

如果电脑上有驱动精灵之类的软件,在这类软件里也是可以更新驱动的,前提为连接互联网。


驱动包更新

需要选择有线网卡驱动包的位置。

有线网卡驱动包可以在网上下载,也可以在网卡制造商的官网下载,需要首先确定网卡型号


2. 在路由器环境下,想在公网实现对内网电脑开机

需要设置路由器的ip映射,将外网地址映射为内网地址,比如tp-link的dmz主机设置.

将目标主机(mac:01-12-43-44-D5-56)的ip地址设置为静态ip,比如192.168.0.99, 然后在路由器也绑定mac和ip


3. 跨网段进行唤醒

注意:当跨网段进行唤醒时,即发起唤醒的地址和被唤醒的目的地址不在同一个网段,是否需要做一些调整取决于你的网络配置。

我这边的情况是,比如当50网段的服务器发送网络唤醒魔术包到62网段,是行不通的,需要在62网关下增加ip转发广播ip forward-broadcast。


目录
相关文章
|
Java
Java 实现汉字按照首字母分组排序
Java 实现汉字按照首字母分组排序
571 0
|
分布式计算 Java Hadoop
Java实现单词计数MapReduce
本文分享实现单词计数MapReduce的方法
302 0
|
Java 数据安全/隐私保护
JAVA 实现上传图片添加水印(详细版)(上)
JAVA 实现上传图片添加水印(详细版)
959 0
JAVA 实现上传图片添加水印(详细版)(上)
|
存储 Java
Java实现图书管理系统
本篇文章是对目前Java专栏已有内容的一个总结练习,希望各位小主们在学习完面向对象的知识后,可以阅览本篇文章后,自己也动手实现一个这样的demo来加深总结应用已经学到知识并进行巩固。
376 0
Java实现图书管理系统
|
Java Windows Spring
java实现spring boot项目启动时,重启Windows进程
java实现spring boot项目启动时,重启Windows进程
476 0
|
数据可视化 Java
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
如果要在某一个界面里面添加功能的话,都在一个类中,会显得代码难以阅读,而且修改起来也会很困难,所以我们将游戏主界面、登录界面、以及注册界面都单独编成一个类,每一个类都继承JFrame父类,并且在类中创建方法来来实现页面
462 0
Java实现拼图小游戏(1)—— JFrame的认识及界面搭建
|
网络协议 Java
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
ip地址的分类: 1、ipv4、ipv6 127.0.0.1:4个字节组成,0-255,42亿;30亿都在北美,亚洲就只有4亿 2011年就用尽了。
Java网络编程:UDP/TCP实现实时聊天、上传图片、下载资源等
|
数据可视化 Java 容器
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
注意由于我们计步功能的步数要在重写方法中用到,所以不能将初始化语句写在方法体内,而是要写在成员位置。在其名字的时候也要做到“见名知意”,所以我们给它起名字为step
269 0
Java实现拼图小游戏(7)—— 计步功能及菜单业务的实现
|
Java
Java实现拼图小游戏(7)—— 作弊码和判断胜利
当我们好不容易把拼图复原了,但是一点提示也没有,完全看不出来是成功了,那么我们就需要有判断胜利的功能去弹出“成功”类的图片,以便于玩家选择是重新开始还是退出小游戏
240 0
Java实现拼图小游戏(7)—— 作弊码和判断胜利
|
Java
Java实现拼图小游戏(7)——查看完整图片(键盘监听实例2)
由于在移动和图片中我们已经添加了键盘监听,也继承了键盘监听的接口,那么我们只需要在重写方法内输入我们的代码即可
175 0