关机助手设计说明

缘起

本人由于胃不太好,从17年11月份离职一直在家里养病.由于父母在家里养了些猪.所以有时,可能自己在房间用电脑做点东西.忽然要去猪圈里干话.一忙就是一上午或者一下午,结果忘记关电脑了.不是心疼电的问题,而是家里猪圈由于多年了,一些电路或者插线板老化了,就要停电去重新接.这样造成了忽然断电.对电脑硬盘损伤很大(已经报废了一个老硬盘了).本是自己安装一个teamviewer可以搞定的事,但是不知道为什么最近都是登录不进去(还有就是父母也不太会用).所以就想写一个通过手机可以控制电脑开关机的工具.还有一个问题,有时候孩子要在电脑看动画片或者他喜欢看的视频,本来是陪这他一起看,但是临时有事,出去了做事(如母猪产崽).结果时间长了孩子就过来找我.结果电脑就忘记关了.所以就更加迫使想做一个在手机上可以开关家里电脑的工具.可以在我自己我家人的手机上都安装一个.这样就可以不用在回到房间去关电脑了.只要拿出手机打开app几秒中就可以关闭家里的电脑省事.

设计分析

由于在局域网中手机是不知道每台电脑的IP,所以我们就用UDP广播的方式来确定每台开机的电脑的IP和MAC地址(要mac地址的目的就是为了开机用的)

然后说干就干C++写个WIN的简单UDP服务 核心代码如下:

   
DWORD UDPService::DoTask()
{
    sockSrv = socket( AF_INET , SOCK_DGRAM , 0 ) ;
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY) ;
    addrSrv.sin_family = AF_INET ;
    addrSrv.sin_port = htons(Get_GSet()->GetUdpPort()) ;

    bind(sockSrv , (SOCKADDR*)&addrSrv , sizeof(SOCKADDR));

    DWORD dwWaitRet = 3;
    while ( 1 )	 
    { 
        dwWaitRet = WaitForMultipleObjects(3, h_Events, FALSE, 100);
        if (dwWaitRet == WAIT_OBJECT_0 + 1)	 break;
        DoDispatcher(); 
    }

    closesocket( sockSrv );
    bIsRun  =  false;
    return 0;
} 
    
    
void UDPService::DoDispatcher()
{
    string strCmd;
    do 
    {
        if(!GetRecvData(strCmd)) break;

        if(!Dispatcher(strCmd)) break;

    } while (0);

}

bool UDPService::GetRecvData(string &strCmd)
{
    int len = sizeof(SOCKADDR); 
    int recvSize = sizeof(recvBuf); 

    ZeroMemory(recvBuf, recvSize);

    recvfrom(sockSrv,recvBuf, recvSize,0,(SOCKADDR*)&addrClient, &len);

    strFromIP = inet_ntoa(addrClient.sin_addr);
    strCmd = recvBuf; 

    LOG_INFO(L"recvfrom[%s]: %s\n", C2W(strFromIP).c_str(), C2W(strCmd).c_str());

    if(!GetCmdStrs(strCmd))
    {
        LOG_ERR(L"获取命令错误!");
        return false;
    }

    ZeroMemory(recvBuf, recvSize);
    strcpy(recvBuf, strCmd.c_str()); 
    return strCmd.length() > 2;
}

int UDPService::SendData(const string &sendDataStr)
{
    if (sendDataStr.length() GetCNameA(),
                Get_GSet()->GetURLA(),
                Get_GSet()->GetLOpenPort(),
                Get_GSet()->GetOpenPort(),
                Get_GSet()->GetClosePort()
                );  
        }      
        else 
        {
            sprintf_s(cmdBuffer,nBuffSize, "GetMacByIps Fial"); 
            OpenSuccess = false;
        } 

    }else if(nAt != string::npos)
    {
        //<shutdown:sToIp <reboot:sToIp        
        do 
        {
            string strCuuMac = strCmd.substr(nAt+1);
            string  strCmd1 = strCmd.substr(0, nAt); 
            ZeroMemory(cmdBuffer, nBuffSize);
            strcpy(cmdBuffer, strCmd1.c_str()); 

            LOG_INFO(C2W(cmdBuffer).c_str());


            if(!IpMgr.IsCuuPcByMac(strCuuMac))
            {
                bNeedSend = false;
                sprintf_s(cmdBuffer, sizeof(cmdBuffer), "IpMgr.IsCuuPcByMac(%s) == false ", strCuuMac.c_str());
                break;
            }

            if(stricmp(cmdBuffer, "shutdown") == 0)
            {  
                if(SystemShutdown())
                {
                    strcpy(cmdBuffer, "shutdown windows success");            
                }else{ 
                    sprintf_s(cmdBuffer, sizeof(cmdBuffer), "shutdown windows failed! ,error code is: %d", GetLastError());
                    OpenSuccess = false;
                }
            }else  if(stricmp(cmdBuffer, "reboot") == 0)
            {  
                if(SystemShutdown(true))
                {
                    strcpy(cmdBuffer, "reboot windows success"); 
                }else{ 
                    sprintf_s(cmdBuffer, sizeof(cmdBuffer), "reboot windows failed! ,error code is: %d", GetLastError());
                    OpenSuccess = false;
                }
            }
        } while (0); 

    }else {          
        sprintf_s(cmdBuffer, nBuffSize, "unkonw command [%s]", cmdBuffer); 
        OpenSuccess = false; 
    }

    string strData = cmdBuffer;
    if (OpenSuccess) LOG_INFO(C2W(strData).c_str());
    else LOG_ERR(C2W(strData).c_str()); 

    if(!bNeedSend) strData = "";

    return SendData(strData); 
}

手机端的核心代码如下:

package vip.caipiao365.CloseAssistant;

import android.os.Handler;
import android.os.Message;
import android.util.Log;

import org.json.JSONObject;

import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.SocketTimeoutException;


public class udpBroadCast extends Thread {
    static public Handler uiHandler;
    MulticastSocket sendSocket = null;
    DatagramPacket dj = null;
    InetAddress group = null;
    // 确定要发送的消息:
    String mes = "";
    // 确定发送方的IP地址及端口号,地址为本地机器地址
    int port = 8910;
    String host = "255.255.255.255";// 广播地址
    int mType = 0;  // 0获取信息 // 1 关机  2 重启 3开机
    String sMac = "";


    // 接受反馈数据的缓冲存储器
    byte[] getBuf;
    // 接受类型的数据报
    DatagramPacket getPacket;

    public udpBroadCast(int nType, String _sToIp, String _sMac, int _port) {

        mType = nType;
        host = _sToIp;
        port = _port;
        sMac = _sMac;

        // 确定接受反馈数据的缓冲存储器,即存储数据的字节数组
        getBuf = new byte[2048];
        // 创建接受类型的数据报
        getPacket = new DatagramPacket(getBuf, getBuf.length);

        GetCmd();
    }

    protected HistoryItem JS2HistoryItem(JSONObject jsObj) {
        HistoryItem _item = new HistoryItem();

        _item.id = 0;
        try {
            _item.ip = jsObj.getString("I");
            _item.mac = jsObj.getString("M");
            _item.title = jsObj.getString("N");
            _item.cname = jsObj.getString("D");
            _item.url = jsObj.getString("U");
            _item.oport = jsObj.getInt("O");
            _item.port = jsObj.getInt("LO");
            _item.cport = jsObj.getInt("C");
        } catch (Exception e) {
            e.printStackTrace();
            _item = null;
        }
        return _item;
    }

    private HistoryItem parseJSONData(String jsonData) {
        HistoryItem _item = null;
        try {
            JSONObject jsonObject = new JSONObject(jsonData);
            _item = JS2HistoryItem(jsonObject);
        } catch (Exception e) {
            e.printStackTrace();
            _item = null;
        }
        return _item;
    }

    protected String GetData() {
        String strJson = "";
        try {
            // 通过套接字接受数据
            sendSocket.receive(getPacket);
            // 解析反馈的消息,并打印
            strJson = new String(getBuf, 0, getPacket.getLength());
        } catch (SocketTimeoutException e) {
            strJson = "";
        } catch (Exception e) {
            e.printStackTrace();
            strJson = "";
        }
        return strJson;
    }

    protected void GetCmd() {
        switch (mType) {
            // 0获取信息 // 1 关机  2 重启 3开机
            case 0:
                mes = "";
                MainActivity.mainAct.histHandler.ReSetComputStatus();
                break;
            case 1:
                mes = " 0) {
                ncount--;
                strJson = GetData();
                if (strJson.length() < 2) {
                    sleep(50);
                    continue;
                }

                HistoryItem _item = null;
                if (mType == 0) {
                    _item = parseJSONData(strJson);
                    if (_item != null) {
                        _item.starred = true;
                        Message msg = new Message();
                        msg.what = 1;
                        msg.obj = _item;
                        uiHandler.sendMessage(msg);
                        nCount++;
                    }
                } else if (mType != 0) {
                    Message msg = new Message();
                    msg.what = 6;
                    msg.obj = strJson;
                    uiHandler.sendMessage(msg);
                }
            }
            //sendSocket.leaveGroup(group);
            sendSocket.close();
        } catch (SocketTimeoutException e) {

        } catch (Exception e) {
            e.printStackTrace();
        }
        Message msg1 = new Message();
        msg1.what = 4;
        msg1.arg1 = nCount;
        uiHandler.sendMessage(msg1);
    }
}


本来设计的是从UDP获取的IP地址,然后在用TCP去连接发送关机重启指令,但是在我调试程序时候,有时候会报错误 No route to host.

想想UDP可以扫描有应答的报文,TCP链接有时候却抛这个异常.反正是局域网,而且数据量也不大就干脆用UDP广播得了.里面加上MAC地址,要是那台MAC地址配对上了就执行指令.


android端下载:  https://gitee.com/zuzong/Projects/raw/master/CloseAssistant/android/CloseAssistant.apk

电脑端下载:     https://gitee.com/zuzong/Projects/raw/master/CloseAssistant/PC/CloseAssistant.exe


注 要是哪位知道如何解决  No route to host.这个异常麻烦请指教