使用JAVA发送UDP报文实现WOL远程唤醒

WOL简介

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

实现原理

局域网内的任意一台PC可通过发送魔术包的方式远程唤醒目标主机,魔术包是用16进制表示的数据包,它由固定的前缀数据以及固定重复次数的目标主机MAC地址所组成。所谓固定前缀数据即6对“FF”,所谓固定重复次数即16次,也就是说魔术包是由12个“F”加重复16次的主机MAC地址组成,例如本文所用试验机的MAC地址为“74-86-7A-71-D5-0A”,所以使该机远程开机的魔术包为:

0xFFFFFFFFFFFF74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A74867A71D50A

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

编码与调试

首先下载标准版的 MagicPacket 输入目标主机的MAC地址,观察是否能顺利唤醒关机状态的目标主机。
如果一切正常,下载网络包嗅探工具smsniff,运行后,紧接着再用MagicPacket发送一次报文,这时候可以通过smsniff观察这次报文的结构。

然后我们编写JAVA代码,直至模仿出一样的报文,即可实现在JAVA代码中唤醒目标主机,今后可以移植到Android,树莓派等环境,实现更丰富的功能。代码如下:

package com.newflypig.wol;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

public class WOL {
    /**
     * main方法,发送UDP广播,实现远程开机,目标计算机的MAC地址为:28D2443568A7
     */
    public static void main(String[] args) {
        String ip = "255.255.255.255";//广播IP地址
        String mac= "FCAA14D0C108";
        int port = 2304;//端口号
        //魔术包数据
        String magicPackage = "0xFFFFFFFFFFFF";
        for(int i = 0; i < 16; i++){
            magicPackage += mac;
        }
        //转换为2进制的魔术包数据
        byte[] command = hexToBinary(magicPackage);
        for(Byte b : command){
            System.out.print(b.byteValue());
        }

        //广播魔术包
        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-1, address, port);
            //4.发送数据
            socket.send(packet);
            //5.关闭socket
            socket.close();
        } catch (UnknownHostException e) {
            //Ip地址错误时候抛出的异常
            e.printStackTrace();
        } catch (IOException e) {
            //获取socket失败时候抛出的异常
            e.printStackTrace();
        }
    }

    /**
     * 将16进制字符串转换为用byte数组表示的二进制形式
     * @param hexString:16进制字符串
     * @return:用byte数组表示的十六进制数
     */
    private static byte[] hexToBinary(String hexString){
        //1.定义变量:用于存储转换结果的数组
        byte[] result = new byte[hexString.length()/2];

        //2.去除字符串中的16进制标识"0X"并将所有字母转换为大写
        hexString = hexString.toUpperCase().replace("0X", "");

        //3.开始转换
        //3.1.定义两个临时存储数据的变量
        char tmp1 = '0';
        char tmp2 = '0';
        //3.2.开始转换,将每两个十六进制数放进一个byte变量中
        for(int i = 0; i < hexString.length(); i += 2){
            tmp1 = hexString.charAt(i);
            tmp2 = hexString.charAt(i+1);
            result[i/2] = (byte)((hexToDec(tmp1)<<4)|(hexToDec(tmp2)));
        }
        return result;
    }

    /**
     * 用于将16进制的单个字符映射到10进制的方法
     * @param c:16进制数的一个字符
     * @return:对应的十进制数
     */
    private static byte hexToDec(char c){
        int index = "0123456789ABCDEF".indexOf(c);
        return (byte)index;
    }
}

丁丁生于 1987.07.01 ,30岁,英文ID:newflydd

  • 现居住地 江苏 ● 泰州 ● 姜堰
  • 创建了 Jblog 开源博客系统
  • 坚持十余年的 独立博客 作者
  • 大学本科毕业后就职于 中国电信江苏泰州分公司,前两年从事Oracle数据库DBA工作,两年后公司精简技术人员,被安排到农村担任支局长(其本质是搞销售),于2016年因志向不合从国企辞职,在小城镇找了一份程序员的工作。
  • Git OSChina 上积极参与开源社区