VC++实现对计算机远程监控

 

服务器端程序设计实现

由于我们的目的是通过在位于中心机房的客户端来监控远程的服务器端,而根据前面介绍的面向连接套接字应用程序的工作方式,要求服务器必须先于客户端而运行。所以根据实际需要,我们应当让服务器程序能自启动。一般有三种方法:在Autoexec.bat里添加代码;在Win.ini的Run项里添加启动路径;在注册表里添加键值。本文在此采用后一种方法,通过向注册表的Software//Microsoft//Windows//CurrentVersion//Run下添加键值的方式来实现,另外也可以在RunServer下添加键值实现之:

……
//设定待添加的注册表的路径
LPCTSTR Rgspath="Software//Microsoft//Windows//CurrentVersion//Run" ;
……
//获取系统路径
GetSystemDirectory(SysPath,size);
GetModuleFileName(NULL,CurrentPath,size);
……
//把服务程序从当前位置拷贝到系统目录中
FileCurrentName = CurrentPath;
FileNewName = lstrcat(SysPath,"//System_Server.exe");
ret = CopyFile(FileCurrentName,FileNewName,TRUE);
……
//打开键值
ret=RegOpenKeyEx(HKEY_LOCAL_MACHINE,Rgspath,0,KEY_WRITE, &hKEY);
if(ret!=ERROR_SUCCESS)
{
  RegCloseKey(hKEY);
  return FALSE;
}
//设置键值
ret=RegSetValueEx(hKEY,"System_Server",NULL,type,
(const unsigned char*)FileNewName,size);
if(ret!=ERROR_SUCCESS)
{
  RegCloseKey(hKEY);
  return FALSE;
}
//关闭键值
RegCloseKey(hKEY);

  注册完之后就完成了自启动。下面进行本文的重点:对套接字进行编程,首先初始化Socket端口,并在初始化成功的前提下通过调用socket()创建一个套接字,然后调用bind()将该套接字和本地网络地址联系在一起,再调用listen()使套接字做好侦听的准备,并规定它的请求队列的长度。其中listen()函数主要用来建立一个socket套接字以侦听到来的联接,而且仅用于支持联接的 socket,即类型为 SOCK_STREAM 的 socket。该套接字被设为"被动"模式,负责响应到来的联接,并由进程将到来的联接排队挂起。该函数典型地用于需要同时有多个联接的服务器:如果一个联接请求到达且队列已满,客户端将收到一个 WSAECONNREFUSED 的错误。当没有可用的描述符时,listen() 将试图把函数合理地继续下去。它将接受联接直到队列为空。如果描述符变为可用,后来的对 listen() 或 accept() 调用将会把队列填充到当前或最近的累积数(the current or most recent "backlog’’),可能的话,继续侦听到来的联接。下面是这部分的主要代码:

……
wMajorVersion = MAJOR_VERSION;
wMinorVersion = MINOR_VERSION;
wVersionReqd = MAKEWORD(wMajorVersion,wMinorVersion);
……
Status = WSAStartup(wVersionReqd,&lpmyWSAData);
if (Status != 0)
return FALSE;
……
//创建Socket套接字
ServerSock = socket(AF_INET,SOCK_STREAM,0);
if (ServerSock==INVALID_SOCKET)
return FALSE;
dstserver_addr.sin_family = PF_INET;
dstserver_addr.sin_port = htons(7016);
dstserver_addr.sin_addr.s_addr = INADDR_ANY;

//BIND
Status = bind(ServerSock,(struct sockaddr far *)&dstserver_addr,sizeof(dstserver_addr));
if (Status != 0)
return FALSE;

//LISTEN
Status = listen(ServerSock,1);
if (Status != 0)
return FALSE;

  接下来需要调用accept()来接收连接。客户在建立套接字后就可调用connect()和服务器建立连接。其函数原形为:SOCKET PASCAL FAR accept ( SOCKET s, struct sockaddr FAR * addr, int FAR * addrlen );该例程从在 s 上挂起的联接队列中取出第一个联接,用和 s 相同的特性创建一个新的 socket 并返回新 socket 的句柄。如果队列中没有挂起的联接,并且 socket 也未标明是非阻塞的,则 accept() 阻塞调用者直到有一个联接。已经接受联接的 socket (accepted socket)不应用于接受更多的联接。参数 addr 是一个返回参数,填入的是通信层的联接实体地址。地址参数 addr 的严格格式由进行通信的地址族确定。addrlen 是一个返回参数值;该值在调用前包含 addr 指向的缓冲区空间长度;调用返回时包含返回地址的实际长度。

//ACCEPT
int len = sizeof(dstserver_addr);
NewSock = accept(ServerSock,(struct sockaddr far *)&dstserver_addr,&len);
if (NewSock < 0)
{
  closesocket(ServerSock);
  return FALSE;
}
//获取屏幕大小
SysWidth = GetSystemMetrics(SM_CXSCREEN);
SysHeight = GetSystemMetrics(SM_CYSCREEN);
……

  连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据。最后,待数据传送结束后,调用close()关闭套接字。下面的函数就负责将当前的屏幕状态,以数据的形式通过send函数发送给客户程序,以实现对远程服务器端的计算机的远程监视:

……
//Send Falg
FALG = US_FLAG;
send(NewSock,(char*)&FALG,sizeof(FALG)+1,MSG_OOB);
//Get Message
length = recv(NewSock,(char*)&iMsg,sizeof(iMsg)+1,0);
if (length < 0)
{
  //Close Sock
  closesocket(NewSock);
  closesocket(ServerSock);
  return FALSE;
}
//GetMessageData
if (iMsg < 4500)
{
  send(NewSock,(char*)&SysWidth,sizeof(SysWidth)+1,MSG_OOB);
  send(NewSock,(char*)&SysHeight,sizeof(SysHeight)+1,MSG_OOB);
}
switch(iMsg)
{
  case US_DESKTOPBIT: //发送当前屏幕图像
   SendDesktop();
   break;
  ……
}

  其中,SendDesktop()函数负责将屏幕保存成位图,然后再通过send()函数将其以数据的形式发送出去,这一部分牵扯较多的位图操作,比较繁琐,由于本文重点并不在此,仅作为一个功能函数将其关键性代码摘选如下:

void SendDesktop()
{
  ……
  //创建桌面设备环境句柄
  hdcmy = CreateDC("DISPLAY",NULL,NULL,NULL);
  hbufferdc = CreateCompatibleDC(hdcmy);
  //创建位图
  hBit = CreateCompatibleBitmap(hdcmy, BitWidth, BitHeight);
  hOldBitmap = (HBITMAP)SelectObject(hbufferdc, hBit);
  StretchBlt(hbufferdc, 0, 0, BitWidth, BitHeight,
  hdcmy, 0, 0,SysWidth,SysHeight, SRCCOPY);
  hBit = (HBITMAP)SelectObject(hbufferdc, hOldBitmap);
  ……
  //DDBtoDIB
  hPal = (HPALETTE) GetStockObject(DEFAULT_PALETTE );
  // 获取位图信息
  GetObject(bitmap,sizeof(bm),(LPSTR)&bm);
  //初始化位图信息头
  bi.biSize = sizeof(BITMAPINFOHEADER);
  bi.biWidth = bm.bmWidth;
  bi.biHeight = bm.bmHeight;
  bi.biPlanes = 1;
  //bi.biBitCount = bm.bmPlanes * bm.bmBitsPixel;
  bi.biBitCount = 4;
  bi.biCompression = BI_RGB;
  bi.biSizeImage = 0;
  bi.biXPelsPerMeter = 0;
  bi.biYPelsPerMeter = 0;
  bi.biClrUsed = 0;
  bi.biClrImportant = 0;
  ……
  lpbi = (LPBITMAPINFOHEADER)hDib;
  *lpbi = bi;
  GetDIBits(hdc, bitmap, 0L, (DWORD)bi.biHeight,(LPBYTE)NULL, (LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS );
  bi = *lpbi;
  if (bi.biSizeImage == 0)
  {
   bi.biSizeImage = ((((bi.biWidth * bi.biBitCount) + 31) & ~31) / 8)
   * bi.biHeight;
  }
  dwLen += bi.biSizeImage;
  if (handle = GlobalReAlloc(hDib, dwLen, GMEM_MOVEABLE))
   hDib = handle;
   ……
   lpbi = (LPBITMAPINFOHEADER)hDib;
   BOOL bgotbits = GetDIBits( hdc, bitmap0L, (DWORD)bi.biHeight,(LPBYTE)lpbi+ (bi.biSize + ncolors * sizeof(RGBQUAD)),(LPBITMAPINFO)lpbi, (DWORD)DIB_RGB_COLORS);
   SelectPalette(hdc,hPal,FALSE);
   ……
   send(NewSock,(char*)&bitSize,sizeof(bitSize)+1,MSG_OOB);
   recv(NewSock,(char*)&BitMsg,sizeof(BitMsg)+1,0);
   plmagePoint = (LPBYTE)hDib;
   for(WORD i=0;i    {
    send(NewSock,(char*)plmagePoint,sizeof(BYTE)*US_MAXSIZE,MSG_OOB);
    plmagePoint = plmagePoint + US_MAXSIZE;
    recv(NewSock,(char*)&BitMsg,sizeof(BitMsg)+1,0);
   }
   if (bitSize%US_MAXSIZE)
   {
    send(NewSock,(char*)plmagePoint,sizeof(BYTE)*GlobalSize(hDib)%US_MAXSIZE,MSG_OOB);
    recv(NewSock,(char*)&BitMsg,sizeof(BitMsg)+1,0);
   }
   ……
}

  客户机端程序设计实现

  相比而言,客户端程序的网络通讯部分的实现较为简单,只需创建socket套接字端口,并用connect()同服务器建立起连接后就可以用recv()和send()同服务器收发数据了。

  初始化Socket端口部分同服务器的实现部分类似,

……
wVersionrequested = MAKEWORD(2,0);
//启动套接字
WSAStartup(wVersionrequested,&wsaData);
……
SetTimer(hWnd,IDT_TIMER,US_TIME,NULL);

  在此,通过设置定时器来及时地把远程计算机的当前屏幕以位图数据的形式传到客户端,并显示在屏幕上,使维护人员能及时了解到远程计算机的工作状态。首先要用connect()先建立一个到对等端(peer)的联接。connect()函数主要用于创建到指定的外部关联的联接。如果 socket 尚未绑扎(unbound),则由系统为本地关联指定一个唯一值。注意如果名字结构的地址域(the address field of the name structure)为全 0,connect()将返回错误 WSAEADDRNOTAVAIL。

  对流式 sockets (SOCK_STREAM 类),将启动一个到使用名字(该 socket 名字空间的一个地址)的外部主机的活动联接。当调用成功完成时,该 socket 已准备好发送/接收数据。下面是定时器消息响应函数的部分主要代码:

……
clientSock = socket(AF_INET,SOCK_STREAM,0);
if (clientSock < 0)
return FALSE;
//建立连接
client.sin_family = PF_INET;
client.sin_port = htons(7016);
client.sin_addr.s_addr = inet_addr(client_address);
……
msgsock = connect(clientSock,(struct sockaddr*)&client,sizeof(client));
if (msgsock!=0)
  return FALSE;
……
//获取屏幕尺寸
GetWindowRect(hWnd,&rect);
BitWidth = rect.right - rect.left;
BitHeight = rect.bottom - rect.top;
recv(clientSock,(char*)&Flag,sizeof(Flag)+1,0);
if (Flag == US_FLAG)
{
  MouseEventFlag = false;
  //发送消息
  Msg = US_DESKTOPBIT;
  send(clientSock,(char*)&Msg,sizeof(Msg)+1,MSG_OOB);
  //Send Bit Height and Weidth
  send(clientSock,(char*)&BitWidth,sizeof(BitWidth)+1,MSG_OOB);
  send(clientSock,(char*)&BitHeight,sizeof(BitHeight)+1,MSG_OOB);
  //接收数据
  GetDesktopBit(hWnd);
  MouseEventFlag = true;
}
//关闭套接字,释放数据
closesocket(clientSock);

  这里的在从服务器接收到数据后通过调用GetDesktopBit()来完成数据的显示,使从远程计算机传来的数据能在本地客户机中再现:

……
//Get Bit Size
recv(clientSock,(char*)&bitSize,sizeof(bitSize)+1,0);
send(clientSock,(char*)&Flag,sizeof(Flag)+1,MSG_OOB);
//锁定内存
hDib = GlobalAlloc(GMEM_MOVEABLE,bitSize);
p = (LPBYTE)GlobalLock(hDib);
p2 = p;
for(WORD i=0;i<bitSize/US_MAXSIZE;i++)
{
  len = recv(clientSock,buf,US_MAXSIZE,0);
  CopyMemory(p2,buf,US_MAXSIZE);
  p2 = p2 + US_MAXSIZE;
  send(clientSock,(char*)&Flag,sizeof(Flag)+1,MSG_OOB);
}
if (bitSize%US_MAXSIZE)
{
  len = recv(clientSock,buf,bitSize%US_MAXSIZE,0);
  CopyMemory(p2,buf,len);
  p2 = p2 + bitSize%US_MAXSIZE;
  send(clientSock,(char*)&Flag,sizeof(Flag)+1,MSG_OOB);
}
p2 = p2 - bitSize;
……
hdc = GetDC(hWnd);
GetClientRect(hWnd,&rect);
//定义颜色
int color = (1<<((LPBITMAPINFOHEADER)p2)->biBitCount);
if(color>256)
color = 0;
//显示
StretchDIBits(hdc, 0, 0, rect.right,rect.bottom,0,0,
((LPBITMAPINFOHEADER)p)->biWidth,
((LPBITMAPINFOHEADER)p)->biHeight,
(LPBYTE)p+(sizeof(BITMAPINFOHEADER)+color*sizeof(RGBQUAD)),
(LPBITMAPINFO)p,DIB_RGB_COLORS, SRCCOPY);
……

  不论是服务器还是客户端,对于数据的传输都频繁地使用了recv和send函数。其中前者主要用于从一个 socket 接收数据、读取收到的数据。对流式套接字,将返回当前所有的尽可能多的数据,最长达到所提供的缓冲区的长度。如果该 socket 已经配置为线内(in-line)接收带外数据且有未读出的带外数据,则仅返回带外数据。应用程序可以使用 ioctlsocket() SIOCATMARK选项确定是否还有其它的带外数据待读。如果 socket 上没有到来的数据,则除非 socket 是非阻塞的,recv() 调用会等待数据到达。send()用于已联接的数据报或流式套接字,用来在一个 socket 上写出(write outgoing)数据。在这里需要特别指出的是:一个 send() 的成功完成并不能表明数据已被成功传递。如果保存(hold)待发送数据的传输系统中没有缓冲区空间可用,send() 将会阻塞,除非 socket 已设置为非阻塞 I/O 模式。

 

2008年于四川师范大学 计算机科学学院 小高

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值