记录:通过Socket实现局域网内,手机关闭电脑

摘要:看见各种面试的信息都有一个socket。对于socket只能说一句:what,这是神马东东。兴趣是最好的老师,决定这个小功能很有趣,也由于一些需要。在这里转载一下。把其两篇和成一片,对于原作者的第三篇把服务端做成windows请自行查看。

博客源地址:socket实现客户端与服务端通信(一)服务端 

  开门见上,我们直接贴服务端代码。其中的意思很好理解都在注释里。

import java.io.DataInputStream;  
import java.io.DataOutputStream;  
import java.io.IOException;  
import java.net.ServerSocket;  
import java.net.Socket;  


public class PCService{  

    static ServerSocket serversocket = null;// 服务socket  
    static DataInputStream data_input = null;// 输入流  
    static DataOutputStream data_output = null;// 输出流  

    public static void main(String[] args) {  
        try {  
            // 监听30000端口  
            serversocket = new ServerSocket(30000);  
            System.out.println("listening 30000 port");  

            while (true) {  
                // 获取客户端套接字  
                Socket client_socket = serversocket.accept();  
                String send_msg = "";  
                try {  

                    // 获取输入流,读取客户端传来的数据  
                    data_input = new DataInputStream(client_socket.getInputStream());  
                    String msg = data_input.readUTF();  
                    System.out.println(msg);  
                    // 判断输入,进行相应的操作  
                    data_output = new DataOutputStream(client_socket.getOutputStream());  
                    if ("shutdown".equals(msg)) {  
                         Shutdown();  
                        // 发送消息回Android端  
                        send_msg = "60秒后关机 ";  
                    } else if ("restart".equals(msg)) {  
                         Restart();  
                        send_msg = "60秒后重启";  
                    } else if ("cancel".equals(msg)) {  
                        cancel();  
                        send_msg = "关机已取消";  
                    }  
                } catch (Exception e) {  
                } finally {  
                    try {  
                        if (data_output != null) {  
                            data_output.writeUTF(send_msg);  
                            data_output.close();  
                        }  
                        data_input.close();  
                        // 关闭连接  
                        client_socket.close();  
                    } catch (IOException e) {  
                        e.printStackTrace();  
                    }  
                }  
            }  
        } catch (Exception e) {  
            // TODO: handle exception  
            e.printStackTrace();  
        }  
    }  

    // 关机  
    private static void Shutdown() throws IOException {  
        Runtime.getRuntime().exec("shutdown -s -t 60");  
        System.out.println("shutdown ,60 seconds later ");  
    }  

    // 重启  
    private static void Restart() throws IOException {  
        Runtime.getRuntime().exec("shutdown -r -t 60");  
        System.out.println("restart ,60 seconds later ");  
    }  

    // 取消关机或重启  
    private static void cancel() throws IOException {  
        Runtime.getRuntime().exec("shutdown -a");  
        System.out.println("cancel ");  
    }  
} 

  服务端会根据发过来的数据,三种数据shutdown、restart、cancel 分别对应关机、重启、取消操作。具体的实现是通过 Runtime.getRuntime()得到运行的环境。然后在执行具体的指令。

  这篇刚好也是对上一篇 记录:WiFi的链接 中,Wifi里面的 DhcpInfo的一个使用。在写客服端的时候,先查看一下 InetSocketAddress 链接 : 类 InetAddress。因为其中的写法是通过 循环ip最后一个(1~255)+ 30000的端口号。开始时候我以为这样会每一个都尝试一下,很慢,没想到还是比较理想,不是很慢。

  在扫描的过程中,值通过handler发送了一次消息。因为在查询到第一个就 break了。代码如下,检查当前连接的所在网络内的所有设备,看是否有开启30000的端口服务。

  第一步,获取我们需要扫描的网段。这里利用 Wifi的 DhcpInfo ,因为在 DhcpInfo里面有一个serverAddress,这里面存着连接主机的 ip地址。直接返回的类型是 int,需要我们自己去重新拼装。具体方法如下:

public class MyWifi {

    private WifiManager wifiManager;
    private DhcpInfo dhcpInfo;
    public MyWifi(Context context){
        context = context.getApplicationContext() ;
        wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
        dhcpInfo = wifiManager.getDhcpInfo();
    }
    //得到本机ip
    public String getLocalIp(){
        return FormatString(dhcpInfo.ipAddress);
    }
    //得到服务器ip(热点ip)
    public String getServerIp(){
        return FormatString(dhcpInfo.serverAddress);
    }
    //转换ip格式为*.*.*.*
    public String FormatString(int value){
        String strValue="";
        byte[] ary = intToByteArray(value);
        for(int i=ary.length-1;i>=0;i--){
            strValue+=(ary[i]&0xFF);
            if(i>0){
                strValue+=".";
            }
        }
        LogUtils.i("value = "+value+" ,strValue = "+strValue);
        return strValue;
    }
    public byte[] intToByteArray(int value){
        byte[] b=new byte[4];
        for(int i=0;i<4;i++){
            int offset = (b.length-1-i)*8;
            b[i]=(byte) ((value>>>offset)&0xFF);
        }
        return b;
    }
}

  在知道了我们要查找的网段对象时候。就可以开始进行查找,在该网段之中,查找谁开启了 30000的端口。

 //扫描连接的WiFi所在网段开启了30000端口的C类ip
    //例如,wifi的ip是192.168.1.1 则扫描 192.168.1.1-192.168.1.254
    class ScanIPThread extends Thread {
        @Override
        public void run() {

            serverIP = myWifi.getServerIp();
            int t = serverIP.lastIndexOf(".") + 1;
            resultIP = serverIP.substring(0, t);
            boolean flag = false;
            for (int i = 1; i < 255; i++) {
                try {
                    Socket socket = new Socket();
                    LogUtils.i("InetSocketAddress start ");
                    InetSocketAddress s = new InetSocketAddress(resultIP + i, 30000);
                    socket.connect(s, 50);
                    LogUtils.i("resultIP = "+resultIP+i);
                    //这里只会发送一次信心。在log中,start在一直执行。而结果就只有连接上的一个结果
                    Message message = new Message();
                    message.what = SCAB_RESULT_SUCCESS;
                    message.obj = resultIP + i;
                    handler.sendMessage(message);
                    flag = true;
                    socket.close();
                    break;
                } catch (IOException e) {
                    handler.sendEmptyMessage(i);
                }
            }
            if (!flag) {
                handler.sendEmptyMessage(SCAB_RESULT_FAIL);
            }
            super.run();

        }
    }

   在此就可查找出该网段中,ip最有一个数字靠前开启30000端口的 ip了。

  在找到这个 ip之后,我们就可以通过socket向该地址发送对应的字符串了。本示例中的三个字段为:shutdown、restart、cancel 分别对应关机、重启、取消操作。在测试示例的时候,有时间第一个并不能查找到 在该网段开启30000端口的 ip地址。需要进行第二次查找。原博客中有个重新扫描,就是重新扫描的Activity。

  socket一定要写在子线程中,不然会报错,毕竟这也是网络操作。之前,测试socket之前写主线程。连模拟器都没有过过去。当然刚在服务端在收到消息的时候还有个反馈信息。这里可以用来做友好提示。反馈的内容为: send_msg = “60秒后关机 “、send_msg = “60秒后重启”、 send_msg = “关机已取消”。

  开一个线程,与服务端建立连接,然后就一直循环来维持与服务端的持续通信。循环时,首先拿到客户端的输入输出流,当data_putput和发送的内容都不为空时,就将内容写出去,然后用data_input获取服务端发过来的信息。

 private class ConnThread extends Thread {
        private String ip;
        private int port;
        private String content;
        public ConnThread(String ip,int port,String content){
            this.ip = ip;
            this.port = port;
            this.content = content;
        }
        @Override
        public void run() {
            try {
                client_socket = new Socket(ip, port);
                while(true){
                    data_output = new DataOutputStream(client_socket.getOutputStream());
                    data_input = new DataInputStream(client_socket.getInputStream());
                    String msg="";
                    if ((data_output != null) && (!content.equals(""))) {
                        data_output.writeUTF(content);
                    }
                    msg = data_input.readUTF();
                    System.out.println(msg);
                    if(msg!=null&&!"".equals(msg)){
                        Message message = new Message();
                        message.what=1;
                        message.obj=msg;
                        handler.sendMessage(message);
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }finally{
                try {
                    if(data_output!=null){
                        data_output.close();
                    }
                    if(data_input!=null){
                        data_input.close();
                    }
                    if(client_socket!=null){
                        client_socket=null;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            super.run();
        }
    }

  对于去点击按钮进行关机,重启,取消操作的代码如下:

        // 连接服务器
        switch (v.getId()) {
            case R.id.btnShutdown:
                final String shutdown = "shutdown";
                if (connThread != null) {
                    //interrupt()终端线程,有弊端
                    connThread.interrupt();
                }
                connThread = new ConnThread(connIP, 30000, shutdown);
                connThread.start();
                break;
            case R.id.btnReboot:
                final String restart = "restart";
                if (connThread != null) {
                    connThread.interrupt();
                }
                connThread = new ConnThread(connIP, 30000, restart);
                connThread.start();
                break;
            case R.id.btnCancel:
                final String cancel = "cancel";
                if (connThread != null) {
                    connThread.interrupt();
                }
                connThread = new ConnThread(connIP, 30000, cancel);
                connThread.start();
                break;
            case R.id.btnClose:
                finish();
                break;
            default:
                break;
        }

  在通过线程传递消息个人认为我看见的了另一种方法更好一些,具体代码如下 :

/**
 * 发送消息的队列,每次发送数据时,只需要调用putMsg(byte[] data)方法
 */
public class SendSearchSSIDThread extends Thread {
    // 发送消息的队列
    private Queue<byte[]> sendMsgQuene = new LinkedList<byte[]>();
    // 是否发送消息
    private boolean send = true;

    private SearchSSID ss;

    public SendSearchSSIDThread(SearchSSID ss) {
        this.ss = ss;
    }

    public synchronized void putMsg(byte[] msg) {
        // 唤醒线程
        if (sendMsgQuene.size() == 0)
            notify();
        sendMsgQuene.offer(msg);
    }

    @Override
    public void run() {
        synchronized (this) {
            while (send) {
                // 当队列里的消息发送完毕后,线程等待
                while (sendMsgQuene.size() > 0) {
                    byte[] msg = sendMsgQuene.poll();
                    if (ss != null)
                        ss.sendMsg(msg);
                }
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public void setSend(boolean send) {
        this.send = send;
    }
}

  这只是提供一个实现的方式,并不是完整代码。只是抽出来的一部分。

代码链接(便于自己以后查阅,还是上传一份):http://download.csdn.net/download/guyuelin123/9961506

参考文章

  1. InetSocketAddress类
  2. 类 InetAddress
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值