目录
第一步:设置主机的有线网卡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或唤醒魔包
远程开机(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。