緣起
本人由於胃不太好,從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