http://download.csdn.net/detail/a514534316/6340523
作为机房管理员,要管理的计算机较多,经常面临大量计算机要开启或关闭,如果每次逐一去开启或关闭,也是一项艰巨的任务,如果能从一台计算机上远程开启或关闭本局域网内的一台或多台计算机,将是一件轻松快乐的事。
一、远程开机
1.对被开启计算机的硬件要求
要实现网络远程开机,对被开启的计算机而言需要电源、主板、网卡3件设备的支持。首先电源必须是符合ATX 2.03标准的ATX电源,而且其+5V的备用电流必须在600mA以上,以便能唤醒网卡。其次是主板和网卡都必须支持Wake-up On LAN(WOL)技术(即远程唤醒)。可通过查看主板网卡使用说明书确认,对主板而言可直接查看BIOS设置中的“Power Management Setup”菜单中是否有“Wake on Lan”一项来确认,有则将“Wake on Lan”设置为“Enable”,
开启远程唤醒功能。另外查看BIOS设置中是否有“Wake on PCI Card”,有则说明主板可通过PCI插槽直接向网卡供电,将其设置为“Enable”;没有则需要在主板的WOL接口(3针)和网卡的WOL接口之间连一根三芯远程唤醒电缆,以便主板给网卡供电。
2.远程开机原理
远程开机的实现,主要是向目标计算机发送特殊格式的数据包(包含有6个字节的“FF”和重复16遍的目标计算机的MAC地址,共102个字节的数据),目标计算机的网卡只要检测到数据包中某个片段含有这102个字节的数据,便会将该计算机唤醒,它是AMD公司开发推广的技术。所以远程开机需要知道目标计算机的MAC地址,如果要开启的计算机只有一台,可直接在该计算机上查看MAC地址并记录下来,但是如果有多台计算机需要开启,用这种方式麻烦且容易出错,所以应考虑编程解决这个问题。
3.编程获取局域网内各计算机的MAC地址
怎么获取局域网内各计算机的MAC地址呢?了解网络通信原理的人都知道,网络中两台计算机要相互通信,看似只要相互知道IP地址即可,但那只是在网络层上,在数据链路层上最终必须知道对方计算机网卡的物理地址,即MAC地址。那么网络通信时如何知道其它计算机的MAC地址呢?靠ARP(Address Resolution Protocol)即地址解析协议,通过在局域网内广播ARP请求包,对方即会响应,告知其MAC地址,双方计算机都会将对方的MAC地址及IP地址对应保存在一张地址映射表中,以备通信使用。所以编程时要发送一个ARP请求包来获取指定计算机的MAC地址,Windows
API中已提供现成的函数SendARP,其声明如下:
DWORD SendARP(IPAddr DestIP, IPAddrSrcIP,PULONG pMacAddr, PULONG PhyAddrLen);
第一个参数为要获取其MAC地址的目标计算机机的IP地址,参数类型为IPAddr ,其实类型就是unsigned long (用户输入的目的主机的IP地址一般是字符串类型点式IP地址,需要将其转换成一个3 2位的无符号长整数,可用inet_addr函数完成);第二个参数为源机的IP地址;第三个参数为存放目标计算机MAC地址的指针变量;第四个参数为存放目标计算机MAC地址字节长度的指针变量。该函数的定义在iphlpapi.h头文件中,所以要包含#include
;该函数的实现在Iphlpapi.lib库文件中,要在项目设置的链接中加入库文件Iphlpapi.lib。(注意:VC6.0不含这两个文件,需网上下载,而VC7.0中含有。)关键代码如下:
//将用户输入的目的主机的字符串类型点式IP地址转换成一个3 2位的无符号长整数:
ULONG ULDestIP=inet_addr(strIPAddr);
//发送ARP请求包获得远程MAC地址:
iRusult=SendARP(ULDestIP,(unsignedlong)NULL,(PULONG)&ULMacAdd,&PhyAddrLen);
//由于获得的MAC地址是6字节的unsignedchar数值,不便阅读,所以需要将其转换为字符串:
sprintf(strMacAddr,"%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",ULMacAdd[0],ULMacAdd[1],ULMacAdd[2],ULMacAdd[3],ULMacAdd[4],ULMacAdd[5]);
为了实现获取机房内所有机器的MAC地址,可以采取循环的办法发送ARP请求包获得所有机器的MAC地址,考虑机房内机器的IP地址一般都是连续的,所以先获取IP地址最小的那台机器的MAC地址,然后逐一增加IP地址, 循环获取其它机器的IP地址。
//注意IP地址加一前先要将ULONG类型的IP地址从网络字节顺序转换为主机字节顺序,加一后再从主机字节顺序转换为网络字节顺序。
ULDestIP=htonl(ntohl(ULDestIP)+1);
为了使用户能对比观察及关机的需要,程序中还获取了远程机的机器名,并与IP地址、MAC地址一起显示在一个ListCtrl控件中。
//获取远程机器名:
struct hostent*RemoteHost;
RemoteHost=(structhostent*)malloc(sizeof(struct hostent));
RemoteHost=gethostbyaddr((char*)&ULDestIP,4,AF_INET);
strcpy(strRemoteHostName,RemoteHost->h_name);
//将3 2位的无符号长整数IP地址转换成字符串类型点式IP地址:
struct in_addr sAddr;
sAddr.s_addr=ULDestIP;
strcpy(strIPAddr,inet_ntoa(sAddr));
//将远程机的机器名、IP地址、MAC地址一起显示在一个ListCtrl控件中:
intiItemNumber=m_ListHostInfo.GetItemCount();
charstrNumber[4];
sprintf(strNumber,"%d",iItemNumber+1);
m_ListHostInfo.InsertItem(iItemNumber,strNumber);//第一列显示序号
m_ListHostInfo.SetItemText(iItemNumber,1,strRemoteHostName);//第二列显示机器名
m_ListHostInfo.SetItemText(iItemNumber,2,strIPAddr);//第三列显示IP地址
m_ListHostInfo.SetItemText(iItemNumber,3,strMacAddr);//第四列显示MAC地址
为了下次开机的需要,要将ListCtrl控件中显示的机器名、IP地址、MAC地址一一对应保存在一个文件中。远程开机前,需要将文件中的机器名、IP地址、MAC地址读出来显示在ListCtrl控件中,在程序启动后(比如在OnInitDialog函数中)就读出来显示,以便开机和关机都可以使用。文件读写的代码比较简单,这里就不再赘述。
4.发送远程开机数据包
已经知道了要开启计算机的MAC地址,接下来便可发送远程开机的数据包了,采用广播形式发送。关键代码如下:
SOCKETSocketData=socket(AF_INET, SOCK_DGRAM, 0); //创建套接字
bool bOptVal=true;
intiRusult=setsockopt(SocketData,SOL_SOCKET,SO_BROADCAST,(char FAR*)&bOptVal,sizeof(bOptVal));//设置发送方式为广播发送
SOCKADDR_IN RecvAddr;
RecvAddr.sin_family = AF_INET;
RecvAddr.sin_port = htons(0);
RecvAddr.sin_addr.s_addr=htonl(INADDR_BROADCAST);
为了将ListCtrl控件中所选择的计算机都开启,需要获取所有选择项中的MAC地址,然后构造远程开机数据包,逐机发送。关键代码如下:
POSITION pos=m_ListHostInfo.GetFirstSelectedItemPosition();
while(pos)
{ intnItem=m_AddrListCtrl.GetNextSelectedItem(pos);//获取选择项
strMacAddr=m_ListHostInfo.GetItemText(nItem,3);//获取选择项的第四列数据MAC地址
BYTEByteMacAddr[6];
//将字符串型式MAC地址转换为6个字节的数值:
sscanf(strMacAddr,"%2x-%2x-%2x-%2x-%2x-%2x",&ByteMacAddr[0], &ByteMacAddr[1],&ByteMacAddr[2], &ByteMacAddr[3], &ByteMacAddr[4],&ByteMacAddr[5]);
//构造远程开机数据包
BYTEbDataPacket[102];
memset(bDataPacket,0xFF,6);//先写入6个字节的FF
for (int i=1;i<=16; i++)//然后循环16次写入6字节的MAC地址
memcpy(bDataPacket+i*6,ByteMacAddr,6);
//发送远程开机数据包
iRusult=sendto(SocketData,(charFAR *)bDataPacket,102,0,(SOCKADDR *)&RecvAddr, sizeof(RecvAddr));
}
程序运行的主界面如图1所示。
图1 程序主界面
二、远程关机
远程关机的方法分两种:一种需要在被控制的计算机上编写软件(适用于任何系统)、一种不需要在被控制的计算机上编写软件(只适用于Windows2000、WindowsXP以上任何系统)。
1.有被控端软件
需要编写控制端软件和被控端软件,由控制端软件发送自定义的关机命令字符串,被控端软件收到相应命令后关闭本机。通信方式有TCP、UDP两种,TCP是面向连接的,为了保证可靠的传输可采用它,UDP是无连接的,为了提高传输速度可采用它。由于篇幅限制且UDP方式相对简单,我这里只谈TCP方式。
TCP方式需要通信的一端作为服务端,进行监听(Listen),等待接受(accept)另一端即客户端的连接(connect)。如果仅仅用于关机,将控制端或被控端作为服务端均无不可,但是为了软件的可扩展性,我将控制端作为服务端,关键代码如下:
(1)服务端:
先设定服务端地址和端口,创建套接字并绑定,然后将套接字置为监听模式,启动一个线程处理接收。
sockaddr_inServerSockAddr;
ServerSockAddr.sin_addr.s_addr=htonl(INADDR_ANY);
ServerSockAddr.sin_family=AF_INET;
ServerSockAddr.sin_port=htons(SERVER_PORT);
m_SockListen=socket(AF_INET,SOCK_STREAM,0);
if(bind(m_SockListen,(sockaddr*)&ServerSockAddr,sizeof(ServerSockAddr)))
MessageBox("绑定错误");
elselisten(m_SockListen,5);
AfxBeginThread(&thread,0);
在线程函数中接受客户端的连接,得到一个新的套接字,用于和刚接受连接的那个客户机通信。为了使用户能将在ListCtrl控件上所选择的计算机正确关机,需要将ListCtrl控件的行号与该行客户机的连接套接字对应,将与各客户机连接的所有套接字存放在一个套接字数组m_SockClient[]中,因此只要将客户机信息在ListCtrl控件中所在行号作为套接字数组m_SockClient []中的下标来对应该客户机的套接字即可。在accept函数的第二个参数中返回了发出连接请求的那个客户机的I
P地址信息,因此只要将该I P地址与ListCtrll控件上所列出的所有客户机的I P地址一一比较,找到该客户机信息所在行号,然后将该客户机的套接字保存在以该行号为下标的数组套接字元素中。关键代码如下:
UINT thread(LPVOID p)
{
SOCKETSockAccept;
structsockaddr_in clientaddr;
intiAddrLen=sizeof(struct sockaddr);
ULONGulClientIpAddr;
CStringstrIpAddr;
CRemoteOnOffDlg*PowerDlg=(CRemoteOnOffDlg*)AfxGetApp()->GetMainWnd();
while(1)
{
SockAccept=accept(PowerDlg->m_SockListen,(sockaddr*)&clientaddr,&iAddrLen);
ulClientIpAddr=clientaddr.sin_addr.s_addr;
for(inti=0;im_ListHostInfo.GetItemCount();i++)
{
strIpAddr=PowerDlg->m_ListHostInfo.GetItemText(i,2);
if(ulClientIpAddr==inet_addr(strIpAddr))
{
PowerDlg->m_SockClient[i]=SockAccept;
//为了知道哪些客户机已建立了连接,我顺便在ListCtrll控件中对应连接客户机那一行的第五列打"√"作为标记:
PowerDlg->m_ListHostInfo.SetItemText(i,4,"√");
break;
}
}
}
}
最后在用户点击关机按钮或菜单时发送自定义的关机命令字符串:
POSITIONpos=m_ListHostInfo.GetFirstSelectedItemPosition();
while(pos)
{
intnItem=m_ListHostInfo.GetNextSelectedItem(pos);//获取选择项
send(m_SockClient[nItem],"POWOFF",COM_STR_LEN,0);
closesocket(m_SockClient[nItem]);//关闭套接字
m_ListHostInfo.SetItemText(nItem,4,"×");
}
(2)客户端
先解析服务器名,然后用s o c k e t创建一个套接字,再用c o n n e c t创建与服务器的连接。最后等待接收关机命令字符串:
CStringstrServerIPAddr="192.168.1.1";//此处为服务端的IP地址
SOCKETSockClient;
sockaddr_inServerSockAddr;
ServerSockAddr.sin_addr.s_addr=inet_addr(strServerIPAddr);
ServerSockAddr.sin_family=AF_INET;
ServerSockAddr.sin_port=htons(SERVER_PORT);
SockClient=socket(AF_INET,SOCK_STREAM,0);
while(connect(SockClient,(sockaddr*)&ServerSockAddr,sizeof(ServerSockAddr))!=0);
intiAllRecvLen=0,iThisRecvLen=0;
charstrRecvBuf[COM_STR_LEN+1]="";
while(iThisRecvLen!=SOCKET_ERROR&&iAllRecvLen三、结语
本文实现了局域网中的远程开机功能和有客户端无客户端两种方式下的关机功能,在此基础上还可根据需要进行功能扩展。本程序在WindowsXP操作系统下用VC6.0或VS.NET都能调试通过,并投入使用。