Visual C++实现对计算机远程监控

Tags: vc
  摘要:本文讲述了利用Socket套接字进行网络编程的一般技术,并通过该技术实现了对计算机的远程监控。

  关键字:Socket套接字、服务器、客户端、远程监控

  引言

  在工程施工中经常遇到中心主控机房和工程现场相分离的情况,这就需要工程设计人员经常往返于中心机房与工程现场之间,有时甚至为了修改几个数据也要相关人员的现场操作才能解决。而且也不能很好的对工程现场进行实时的监测,这就为工程施工与系统的维护带来了极大的不便。现在局域网的技术已相当成熟,在中心机房和工程现场之间构建一个局域网也并不困难。所以我们可以在局域网的物理架构基础之上通过Socket套接字来实现计算机之间的通讯,使维护人员能在中心机房内足不出户就可以实时地监测、控制远在工程现场的计算机的工作状态。本文就对类似程序的实现方法进行简单的介绍。

  Socket网络程序的一般思路

  Windows Sockets 规范定义了一个基于 Microsoft Windows 的网络编程界面,它源于加里弗尼亚大学伯克利分校的伯克利软件发布(BSD)。它既包括熟悉的伯克利 Socket 风格的例程,也包括了一组 Windows 特有的扩展,使程序员可以利用Windows 原有的消息驱动机制进行网络方面的编程。而此类程序中最常用的一种模式就是客户/服务器模式。在这种框架中,客户应用程序向服务器应用程序请求服务。服务器应用程序一般在一个周知地址上侦听(listen)服务请求。就是说,直到一个客户向服务器发出联接请求之前,服务器进程进程是休眠的。收到请求时,服务器进程"醒来(wake up)",完成客户请求的相应的活动。

  套接字共有三种类型:流式套接字,数据报套接字以及原始套接字等。流式套接字定义了一种可靠的面向连接的服务,实现了无差错无重复的顺序数据传输;数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠;原始套接字则允许对低层协议如IP或ICMP等协议进行直接访问,主要用于对新的网络协议实现的测试等。无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与服务程序之间的相互作用。而面向连接服务器处理的请求往往比较复杂,不是一来一去的请求应答所能解决的,而且往往是并发服务器。

  本文所采用的就是面向连接的套接字,其工作过程如下:服务器首先启动,通过调用socket()建立一个套接字,然后调用bind()将该套接字和本地网络地址联系在一起,再调用listen()使套接字做好侦听的准备,并规定它的请求队列的长度,之后就调用accept()来接收连接。客户在建立套接字后就可调用connect()和服务器建立连接。连接一旦建立,客户机和服务器之间就可以通过调用read()和write()来发送和接收数据。最后,待数据传送结束后,双方调用close()关闭套接字。其主要的流程时序可以通过图1来表示:

服务器端程序设计实现

由于我们的目的是通过在位于中心机房的客户端来监控远程的服务器端,而根据前面介绍的面向连接套接字应用程序的工作方式,要求服务器必须先于客户端而运行。所以根据实际需要,我们应当让服务器程序能自启动。一般有三种方法:在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()
{

本文链接:http://www.4byte.cn/learning/67555.html



相关文章