Android实现wake-on-lan

Wake-On-LAN简称WOL,是一种电源管理功能;如果存在网络活动,则允许设备将操作系统从待机或休眠模式中唤醒。Wake-On-LAN的实现,主要是向目标主机发送特殊格式的数据包,俗称魔术包(Magic Packet)。MagicPacket格式虽然只是AMD公司开发推广的技术,并非世界公认的标准,但是仍然受到很多网卡制造商的支持,因此许多具有网络唤醒功能的网卡都能与之兼容。

魔术包的格式

在Magic Packet内,每次都会先有连续6个"FF",即:FF FF FF FF FF FF(没有空格,这个只是为了方便阅读),在连续6个"FF"后则连续重复16次Mac地址。

假设你的网卡物理地址为00:15:17:53:d4:f9, 这段Magic Packet内容如下:

    FFFFFFFFFFFF00151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f900151753d4f900151753d4f900151753d4f9
    00151753d4f900151753d4f9

使用工具测试

在windows的应用商店里面搜wake on lan,就会出现一些可用的工具:

Android实现wake-on-lan_第1张图片

我选择了第一个,下载后安装,然后打开,截图如下:


第一个设备名随便取,没有关系,第二个是mac地址,一定要正确。然后点击wake即可唤醒局域网中特定mac地址的设备。

广播地址

广播地址(Broadcast Address)是专门用于同时向网络中所有工作站进行发送的一个地址。在使用TCP/IP 协议的网络中,主机标识段host ID 为全1 的IP 地址为广播地址,广播的分组传送给host ID段所涉及的所有计算机。例如,对于10.1.1.0 (255.255.255.0 )网段,其广播地址为10.1.1.255 (255 即为2 进制的11111111 ),当发出一个目的地址为10.1.1.255 的分组(封包)时,它将被分发给该网段上的所有计算机。
广播地址主要有两类编辑
广播地址应用于网络内的所有主句

受限广播

它不被路由发送,但会被送到相同物理网段段上的所有主机
IP地址的网络字段和主机字段全为1就是地址255.255.255.255

直接广播

网络广播会被路由,并会发送到专门网络上的每台主机
IP地址的网络字段定义这个网络,主机字段通常全为1,如 192.168.10.255

java udp 编程的基础知识回顾

InetAddress类

   它一个IP地址,它可以是IPV4或者IPV6,如果你想明确区分他们,你可以使用Inet4Address或者Inet6Address。但大部分情况下没有必有,这个类可以表示他们中的任何一个。

创建InetAddress实例的时候可以传入一个“主机名”,主机名可以通过getHostName()方法获得,但是也可以什么都不传。

该类提供以下方法:

  • getByName(String s):获得一个InetAddress 类的对象,该对象中含有主机的IP地址和域名,该对象用如下格式表示它包含的信息:www.sina.com.cn/202.108.37.40;
  • String getHostName():获取InetAddress对象的域名;
  • String getHostAddress():获取InetAddress对象的IP地址;
  • getLocalHost():获得一个InetAddress对象,该对象含有本地机的域名和IP地址。

DatagramPacket类

这个类代表了一个数据包,这个数据包被DatagramSocket的实例发送和接受。它除了包含需要发送和接受的数据外,还包含了发送和接受主机的IP地址等信息。
public byte[] getData():获取存放在数据报中的数据。
public int getLength():获取数据的长度。
public InetAddress getAddress():获取数据报中的IP地址。
public int getPort():获取数据报中的端口号。
public void setData(byte []buf):设置数据报中的内容为buf所存储的内容。 

DatagramSocket类

这个类实现了一个用于发送和接受DatagramPacket的Socket,它的对象可以用于发送和接受包的任何一端。就是说它既可以用来发送包,也可以用来接受包。

关于这三个类的使用,可以总结位一下简单的四步:

<span style="font-family:SimSun;font-size:14px;">       //1、创建Socket用于UDP数据传送。  
        DatagramSocket socket = new DatagramSocket();  
           
        //2、创建数据包,注意,数据包包含了目的主机的ip地址  
        byte[] buf = "haha".getBytes();  
        DatagramPacket packet = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 8888);  
           
        //3、发送  
        socket.send(packet);  
           
        //4、关闭
        socket.close();  </span>


接受与之类似

<span style="font-family:SimSun;font-size:14px;">        //1、创建Socket;  
        DatagramSocket socket = new DatagramSocket(8888);  
           
        //2、创建数据包,用于接收内容。  
        byte[] buf = new byte[1024];  
        DatagramPacket packet = new DatagramPacket(buf, buf.length);  
           
        //3、接收数据          
        socket.receive(packet);  
        System.out.println(packet.getAddress().getHostAddress()+":"+packet.getPort());   
        System.out.println(new String(packet.getData(), 0, packet.getLength()));  
           
        //4、关闭连接。  
        socket.close(); </span>

这两端程序没有做异常的处理,加上异常处理就可以工作了。


java编程

通过以上尝试,应该有信心写一个简单的测试程序,用于WOL了,写法很简单,主要需要严格按照魔术包的格式构造一个数据包,然后把这个数据包发送使用广播的方式发送出去。

下面是java的WOL代码,很简单,不多说了:

<span style="font-family:SimSun;font-size:14px;">import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class WakeOnLan {

    public static final int PORT = 7778;    
    
    public static void main(String[] args) {
        
        if (args.length != 2) {
            System.out.println("Usage: java WakeOnLan <broadcast-ip> <mac-address>");
            System.out.println("Example: java WakeOnLan 192.168.0.255 00:0D:61:08:22:4A");
            System.out.println("Example: java WakeOnLan 192.168.0.255 00-0D-61-08-22-4A");
            System.exit(1);
        }
        
        String ipStr = args[0];
        String macStr = args[1];
        
        try {
            byte[] macBytes = getMacBytes(macStr);
            byte[] bytes = new byte[6 + 16 * macBytes.length];
            //写入连续6个FF
            for (int i = 0; i < 6; i++) {
                bytes[i] = (byte) 0xff;
            }
            //写入mac地址16次
            for (int i = 6; i < bytes.length; i += macBytes.length) {
                System.arraycopy(macBytes, 0, bytes, i, macBytes.length);
            }
            
            InetAddress address = InetAddress.getByName(ipStr);
            DatagramPacket packet = new DatagramPacket(bytes, bytes.length, address, PORT);
            DatagramSocket socket = new DatagramSocket();
            socket.send(packet);
            socket.close();
            StringBuilder sb = new StringBuilder();
            for(int i = 0;i<bytes.length;i++){
            	sb.append(String.valueOf(Integer.valueOf(bytes[i])));
            }
            System.out.println("Wake-on-LAN packet sent:  "+sb);
        }
        catch (Exception e) {
            System.out.println("Failed to send Wake-on-LAN packet: + e");
            System.exit(1);
        }
        
    }
    
    private static byte[] getMacBytes(String macStr) throws IllegalArgumentException {
        byte[] bytes = new byte[6];
        String[] hex = macStr.split("(\\:|\\-)");
        if (hex.length != 6) {
            throw new IllegalArgumentException("Invalid MAC address.");
        }
        try {
            for (int i = 0; i < 6; i++) {
                bytes[i] = (byte) Integer.parseInt(hex[i], 16);
            }
        }
        catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid hex digit in MAC address.");
        }
        return bytes;
    }
    
}
</span>


 
 用法举例: 
 

java WakeOnLan 192.168.0.255   00-00-A0-00-E0-FF

android代码

先看下界面效果图:

布局文件:

<span style="font-family:SimSun;font-size:14px;"><?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="com.konka.wakeonlan.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Wake On Lan" />
    <EditText
        android:id="@+id/ip"
        android:text="192.168.0.255"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <EditText
        android:id="@+id/mac_addr"
        android:text="00-00-A0-00-E0-FF"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />
    <Button
        android:id="@+id/wake_button"
        android:text="wake"
        android:layout_gravity="right"
        android:background="@color/colorAccent"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</LinearLayout></span>

Activity:

public class MainActivity extends AppCompatActivity {
    EditText ip = null;
    EditText macAddr = null;
    Button button = null;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        button = (Button) findViewById(R.id.wake_button);
        ip = (EditText) findViewById(R.id.ip);
        macAddr = (EditText) findViewById(R.id.mac_addr);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                String lip = ip.getText().toString();
                String lmacAddr = macAddr.getText().toString();
                if (lip == null){
                    ip.setText("please input ip!");
                }
                Log.d("hello",lip);
                if(lmacAddr == null){
                    macAddr.setText("please input mac!");
                }
                Log.d("hello",lmacAddr);
                if(lip != null && lmacAddr != null){
                    new WakeThread(lip,lmacAddr).start();
                }
                Toast.makeText(MainActivity.this,"send wake package",Toast.LENGTH_SHORT);
            }
        });
    }
}

发送魔术包线程:

public class WakeThread extends Thread{
    String ip = null;
    String macAddr = null;
    public WakeThread(String ip,String macAddr){
        this.ip = ip;
        this.macAddr = macAddr;
    }
    @Override
    public void run() {
        super.run();
        wakeOnLan(ip,macAddr);
    }
    public void wakeOnLan(String ip,String macAddr){
        DatagramSocket datagramSocket = null;
        try {
            byte[] mac = getMacBytes(macAddr);
            byte[] magic = new byte[6+16*mac.length];
            //1.写入6个FF
            for (int i=0;i<6;i++){
                magic[i] = (byte)0xff;
            }
            //2.写入16次mac地址
            for(int i=6;i<magic.length; i += mac.length){
                System.arraycopy(mac,0,magic,i,mac.length);
            }
            datagramSocket = new DatagramSocket();
            DatagramPacket datagramPacket = new DatagramPacket(magic,magic.length, InetAddress.getByName(ip),8888);
            datagramSocket.send(datagramPacket);
        } catch (SocketException e) {
            e.printStackTrace();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(datagramSocket != null)
                datagramSocket.close();
        }
    }
    private byte[] getMacBytes(String macStr) throws IllegalArgumentException {
        byte[] bytes = new byte[6];
        String[] hex = macStr.split("(\\:|\\-)");
        if (hex.length != 6) {
            throw new IllegalArgumentException("Invalid MAC address.");
        }
        try {
            for (int i = 0; i < 6; i++) {
                bytes[i] = (byte) Integer.parseInt(hex[i], 16);
            }
        }
        catch (NumberFormatException e) {
            throw new IllegalArgumentException("Invalid hex digit in MAC address.");
        }
        return bytes;
    }
}



注意1:不要忘记添加网络访问权限:

<uses-permission android:name="android.permission.INTERNET"></uses-permission>

注意2:一定要设置要唤醒的主机WOL使能。这个具体的设备不一样,大家自行百度。
注意3:要唤醒设备的网络一定要接有线网连接。无线网就不行,至于为什么就不深究了。

在Android模拟器和手机中测试,都能成功唤醒电视。




你可能感兴趣的:(android,网络,UDP,wol,Wake-on-LAN)