缘起

本人由于胃不太好,从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.这个异常麻烦请指教

当时做完了局域网的开关机功能后.想要是有时离家远了手机搜不到家里wifi信号了.想要关机的话就用不了了.干脆就加个功能.实现在广域网的开关机.

由于我们的家里上网都是通过猫(或者光猫).这样造成我们的家里上网的IP是动态变化的.也就是说IP是不确定的.我们要来联系家里的电脑是没有办法的.这样的话就有一项服务产生了,那就是动态域名,当我们的路由器一链接网络.路由器就会去通知动态域名提供商.告诉他我这里最新的IP是多少.然后您要做的是申请一个动态域名然后绑定到家里路由器.

具体DDNS绑定路由器的方法很简单. 打开浏览器 进入您的路由器管理界面

然后找到 DDNS相关的设置 如下图:

添加相关的信息就OK了.没有的话就注册一个.

我用的是tp-link提供的如:


把路由器绑定了动态域名后还要做的一个操作就是端口映射.由于是家用的网络.家用理由器为了安全.关闭了外面访问家里内部网络的端口.所以想要在外面访问内部端口有两种方法.

  1. 防火墙穿透(就是打洞,但是由于网络的复杂性打洞的成功率不是100%.而且还要外网的一个服务器最好要有两个独立的公网ip.当时我在保千里工作时做了大量的测试和优化代码还是没有达到100%的成功率.)

  2. 直接在路由器里放开相应的断口

    因为是自家用的网络所以果断选择了第二种,几分钟设置完成.(应为打洞的话,目前我只有一个公网IP就是本站的服务器.一个公网IP打洞的成功率不是非常高)

    具体设置如下:
    a.mac地址绑定内网IP

    b.开放相应的端口

    上面图片只供参考,不同的路由器设置不一样.具体请看路由器的说明书或者网上搜相关的资料.

    就这样几分钟的时间,设置完成.可以在外网用关机助手开关机了.(本人自己亲测有效)


17年11份离职后在在调养身体时写的一些小工具


头条视频采集

开发语言:  python

适用平台:  windows, linux, macos

详细地址: http://caipiao365.vip/article/11

时间同步助手  

开发语言:  C++ 

适用平台: windows (xp及其以上的系统)

老电脑的福音, cmos电池没电的笔记本.主板系统时钟坏的的电脑可以用这个时间同步工具.

详细地址: http://caipiao365.vip/article/MyTime


开关机助手:

开发语言:  C++ , java

适用平台:  windows, android

懒人福利,想想在大冬天躺在被窝里看电影,看完后不想起来去关机是多么痛苦的一件事.想想本来家里的电脑是开这的,有时候因为有点事出去了一下,就忙忘记关机了.就想要是在手机上可以关闭该多好呀.所以这个软件就产生了.

详细地址: http://caipiao365.vip/CloseAssistant


关机助手电脑端设置说明

 安装服务   把本程序注册成windows服务程序

 删除服务   从windows服务中删除本程序

启动服务   安装成功后,从windows服务中启动本程序   

关闭服务    从windows服务中停止本程序  


记录信息和错误      记录程序运行生成的调试信息和错误信息   

只记录错误             只记录程序运行产生的错误信息

不记录日志             不记录任何日志

UDP端口               手机端UDP广播扫描PC端的UDP的端口,默认是8910,不要设置冲突的 


本机备注: 在手机上查看的计算机备注

内网开机端口:  局域网唤醒的 端口地址,默认是9.

外网地址:         这个是您家里路由器的动态域名

外网开机端口:  这个是您从外面如(手机网络,不是自家里的wifi)唤醒(打开)你家电脑的端口

外网关断端口:  这个是您从外面如(手机网络,不是自家里的wifi)关闭你家电脑的端口

外网相关的设置具体看: http://caipiao365.vip/article/19


**电脑端改了后,手机端口也要修改 电脑端修改后要重启一下服务使配置文件生效**  

手机端软件的设置


安装后打开手机app界面如下

刷新 按钮 的功能是手动刷新 当前局域网中的计算机开关机状态 (应为广域网和自动刷新的功能还没有做好)

内网: 默认是内网  在点一下就是外网 (选择当前操作是 内网开关机 还是外网)

点左上面的 按钮打开菜单 如下界面


点击设置打开系统全局设置界面 如:


UDP扫描断口: 和PC端要一致, 其他的设置暂时保留,自动刷新功能要用到的.





要想在使关机助手来打开电脑先要在bios里设置 一下.

具体进BIOS的方法 稍微有些不一样,但都是在开机的时候(显示黑屏的状态下)一直按一个键.有的在开机时有提示;

不过一般都是按 del键或者是 esc 还有的可能是 F11  F12.自己 看开机提示.或者自己 去网上搜一下看自己 的主板按什么键进bios.

进入BIOS后找到 Wake on LAN 这个选项 启用他 (把disabled改成enabled)就可以了,如下图:

 Wake on LAN 这个选项不同的主板(BIOS)所在的位置不太一样,具体自己去网上搜一下.

在BIOS启用 Wake on LAN 后进入Windows系统后还要设置一个地方.

1.打开 控制面板 ==> 网络和 Internet ==>网络连接  如下图:


2.双击你的 插网线的网卡打开 网卡 状态对话框  

注意要是用的是无线网卡, 有的无线网卡也支持局域网唤醒功能,在BIOS里设置就是不 Wake on WLAN  了 而是 Wake on WLAN , 那么在这一步就选择的网卡是你的无线网卡了.

3. 点击左下面的 属性(P) 按钮  打开网卡属性对话框:   

4.点击 配置(C) 按钮 打开 网卡属性 对话框 选择 电源管理 标签


把这个界面上的复选框 全勾上 然后点 确定 按钮, 关闭所有的对话框. 到此开机设置完毕.