關機助手設計說明

緣起

本人由於胃不太好,從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() <1)  return -1;

    int len = sizeof(SOCKADDR) ;
    int nRet = sendto(sockSrv, sendDataStr.c_str(), sendDataStr.length(), 0,(SOCKADDR*)&addrClient,len);
    return nRet;
}

bool UDPService::Dispatcher(string &strCmd)
{
    LOG_INFO(C2W(strCmd).c_str());

    int nBuffSize = sizeof(cmdBuffer);

    ZeroMemory(cmdBuffer, nBuffSize);
    strcpy(cmdBuffer, strCmd.c_str()); 

    bool OpenSuccess = true;
    bool bNeedSend = true;  
    int nAt = strCmd.find(':');


    if ((stricmp(cmdBuffer, "GetSIP_android") == 0) || (stricmp(cmdBuffer, "GetSIP") == 0) || (stricmp(cmdBuffer, "GetSIP_IOS") == 0))
    { 
        string SMac = "";
        string Sip = "";

        if(IpMgr.GetMacByIps(strFromIP, Sip, SMac))
        { 
            sprintf_s(
                cmdBuffer,
                nBuffSize,
                JSON_STR, 
                Sip.c_str(),
                SMac.c_str(),
                ComputerName,
                Get_GSet()->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 <open computer: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 = "<GetSIP_android>";
    // 確定發送方的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 = "<GetSIP_android>";
                MainActivity.mainAct.histHandler.ReSetComputStatus();
                break;
            case 1:
                mes = "<shutdown:" + sMac + ">";
                break;
            case 2:
                mes = "<reboot:" + sMac + ">";
                break;
            case 3:
                mes = "<open computer:" + sMac + ">";
                break;
        }
    }

    @Override
    public void run() {
        int nCount = 0;
        try {
            group = InetAddress.getByName(host);
            // 創建發送方的套接字,IP默認爲本地,端口號隨機
            //DatagramSocket sendSocket = new DatagramSocket();
            sendSocket = new MulticastSocket();

            // 由於數據報的數據是以字符數組傳的形式存儲的,所以傳轉數據
            byte[] buf = mes.getBytes();

            // 創建發送類型的數據報:
            dj = new DatagramPacket(buf, buf.length, group, port);
            sendSocket.setTimeToLive(1);
            // 通過套接字發送數據:
            sendSocket.send(dj);

            // 將該socket加入指定的多點廣播地址
            //sendSocket.joinGroup(group);
            // 設置本MulticastSocket發送的數據報會被回送到自身
            sendSocket.setLoopbackMode(false);
            sendSocket.setSoTimeout(100);


            int ncount = 50;
            String strJson = "";

            while (ncount > 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地址配對上了就執行指令. 

注 要是那位知道如何解決  No route to host.這個異常麻煩請指教

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

電腦端下載:     https://gitee.com/zuzong/Projects/raw/master/CloseAssistant/PC/CloseAssistant.exe

詳細說明:         http://caipiao365.vip/CloseAssistant 

oschina:      https://my.oschina.net/canye/blog/1785094

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章