通过Socket套接字实现对计算机的远程监控

转载 2004年08月10日 14:08:00
一、引言 
 
在工程施工中经常遇到中心主控机房和工程现场相分离的情况,这就需要工程设计人员经常往返于中心机房与工程现场之间,有时甚至为了修改几个数据也要相关人员的现场操作才能解决。而且也不能很好的对工程现场进行实时的监测,这就为工程施工与系统的维护带来了极大的不便。现在局域网的技术已相当成熟,在中心机房和工程现场之间构建一个局域网也并不困难。所以我们可以在局域网的物理架构基础之上通过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()  

…… 
//创建桌面设备环境句柄 
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, bitmap, 0L, (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<bitSize/US_MAXSIZE;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 模式。 
 
小结:本文通过Socket流式套接字实现了对计算机的远程监控。随着计算机网络化的深入,计算机网络已渗透到各种传统行业中,计算机网络编程尤其是基于Windows Socket套接字的网络程序的编程日益显得重要。本文采用直接利用动态连接库wsock32.dll,以WinSock API对程序进行设计讲解,虽实现比较繁琐,但能地对程序的设计实现有很好的理解。随着经验的丰富,也可以采用VC++的MFC类库中提供的CAsyncSocket套接字类来实现Socket编程,比较方便。本程序在Windows 98下,由MicrosoftVisual C++ 6.0编译通过。 

首次在树莓派上同时实现SOCKET编程与串口编程

在上次终于实现了RFID与树莓派进行通讯后,今天尝试把从RFID传来的数据通过TCP/IP协议传输到MOXA进而传输到云端。        由于还没有很好的搞清楚两个MOXA3121与另外一个(忘了...
  • tango_future
  • tango_future
  • 2015年11月27日 00:07
  • 2595

socket连接两台电脑通信java

网上看的,测试成功,写下来 服务器端 import java.net.*; import java.io.*; public class Server { private ...
  • onepiecehuiyu
  • onepiecehuiyu
  • 2013年12月07日 19:22
  • 7591

Socket使用TCP/IP如何实现通信

什么是Socket? socket是进城间通讯的一种机制 Socket如何实现通讯 两个进程实现通讯,首先要找到对方,由于IP只能唯一标识计算机地址,所以还要加上TCP协议和端口号,因为TCP协...
  • sinat_29101179
  • sinat_29101179
  • 2015年07月12日 22:28
  • 1000

socket系列之什么是socket

1、什么是socket Socket是应用层与TCP/IP协议族通信的中间抽象层,它是一组接口,应用层通过调用这些接口实现发送和接收数据。一般这种抽象层由操作系统提供或者由JVM自己实现。使用sock...
  • wangyangzhizhou
  • wangyangzhizhou
  • 2014年11月22日 23:53
  • 2074

基于C# Winform的简易聊天程序[socket通信]

原理 启动服务端后,服务端通过持续监听客户端发来的请求,一旦监听到客户端传来的信息后,两端便可以互发信息了.服务端需要绑定一个IP,用于客户端在网络中寻找并建立连接.信息发送原理:将手动输入字符...
  • fsdad
  • fsdad
  • 2017年06月30日 13:10
  • 742

Unity(C#.net)网络通信问题解决(服务器开启失败,Socket下的“由于目标机器积极拒绝,无法连接”异常)

先给出问题的解决方案: 这种情况很可能是可能是服务器根本没有正常开启,没有启动监听。 首先对服务器工程进行调试,看看获取IPAddress的地方是不是出现了异常。 想进行Unity网络通信我们可以有...
  • u012999985
  • u012999985
  • 2016年03月26日 16:33
  • 7967

java-基本的Socket编程-实现服务器端和客户端通信

基本的Socket编程: 本实例介绍Socket编程的基本步骤。启动Socket服务后,再打开Socket刻画段,在输入框中输入消息,然后发送给服务器端,服务器端将收到的消息返回到客户端。关键技术:...
  • zlz18225318697
  • zlz18225318697
  • 2016年10月12日 17:48
  • 2582

计算机计算乘除法的原理

前言 虽然我们在编程语言中可以直接使用+-/,但是对某些要求不能用/的情况下,我们有必要了解一下计算机是怎样完成乘除法的。 首先,我们要明确一下计算机所能完成的最基本操作是:+(-)和左移右移。虽然I...
  • zdavb
  • zdavb
  • 2015年07月28日 17:24
  • 9864

[深入理解计算机系统] 计算机如何实现乘法与除法运算

乘法运算 (1)原码一位乘法 算法要点:(1)乘法通过加法和移位来实现。两个5位二进制数(最高位为符号位)相乘,共需要进行4次加法和4次移位。                 (2)部分积总是先加上被...
  • wangjianyu0115
  • wangjianyu0115
  • 2014年09月28日 11:36
  • 5173

计算机系统结构,组成和实现

计算机系统结构的定义:计算机系统结构是其所处环境中最高层次的概念,是对计算机系统中各个机器之间界面的划分和定义,以及对各级界面上,下的功能进行分配。也称为体系结构。计算机体系结构指的是计算机系统设计的...
  • hongbochen1223
  • hongbochen1223
  • 2015年05月17日 23:16
  • 2910
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:通过Socket套接字实现对计算机的远程监控
举报原因:
原因补充:

(最多只允许输入30个字)