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(魔术包)即可唤醒。
问题排查
- 没有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。
还没有评论,来说两句吧...