
网站导航
摘 要
体; font-size: 12pt">为提高机房使用效率,净化上机环境,杜绝个别用户使用机房内电脑做违规的事情,为机房管理员提供一个好的机房监控系统是必要的。作者在对机房微机管理过程进行了详细调查后,参考了一些网吧的微机管理系统,获得了机房监控系统的设计思路。机房管理系统应当能使管理员极其方便的获取某台微机的屏幕视图,锁定某台电脑的键盘和鼠标输入,并能发送消息给指定的电脑用户。
本论文从理论上分析了实现机房监控系统所需要的基础技术的原理,并以VC++6.0作为开发工具,在Win32平台下实现了一个机房监控系统。论文第二部分简述了TCP/IP协议以及Socket编程技术,第三部分讲述了系统的设计目标和通信协议,第四章则介绍了实现过程中使用的核心技术,包括屏幕截图、锁定屏幕以及服务器端和客户端的通信。通过本文的研究,为机房监控系统的设计和实现展示了一个完整的方案,具有一定参考价值。
关键词:机房监控系统;屏幕截图;锁定屏幕;Socket
The Design and Implementation of the Computer Room Monitor and Control System
Abstract
It is necessary to provide a computer room monitor and control system with good quality so that the users will obey the room rules and the room can work effectively in a steady environment. After analyzing the process of the computer room management and studying some Cyber-Cafe management system, the concept framework of the computer room monitor and control system was gained. At least, the monitor system must provide abilities to snap the screen of the computer specified by the manager, to disabled the input function of the keyboard and the mouse and to send messages to the computer users.
This article enumerated the technologies needed for the implementation of the monitor system and introduced in detail about the issue that how to build a Win32 executable system by using the VC++6.0 as the development tools. In this article, the 2nd chapter introduced the TCP/IP protocol and the Socket programming; the 3rd chapter described the design goals and the communication protocol; the 4th chapter covered the technology cores of the monitor and control system, which were screen snapping, screen locking and communication between server and client. This article advanced a complete solution for the design and implementation of the computer room monitor system and the solution will be reference for similar applying.
Key words: computer room monitor system; snap screen; lock screen; socket
目 录
论文总页数:31页
当今时代是一个信息种类多元化,信息量巨大化,传播速度急速化的时代。要积极面临这种现状,就要能快速的处理各种信息,从而引申出人们对信息处理工具的高度关注。而各种电子设备,尤其是电脑等硬件设备的出现,给人们的日常生活带来极大的变化。随着计算机网络的出现,更使得人们的信息处理方式发生了革命性的改变。毫无疑问,计算机以及由计算机组成的计算机网络已经成为当今时代最佳的信息处理工具。掌握这个工具对于科研和学习有着重要意义。
鉴于这种情况,现在许多学校都提供了微机室,让在校学生可以很好的学习计算机操作,并使用计算机辅助学习。对于同学们来说,这本来是个很好的机会,但有个别同学未能珍惜这个机会,他们没有好好利用这些微机进行学习和研究,而是在机房内打游戏或者闲聊等,甚至有人利用电脑进行违反法规的活动。这些同学不但浪费了自身的学习机会,同时也浪费了学校的宝贵资源,而且给周围的同学造成了很不好的影响。因此,如何对机房进行有效的管理成为了一个不容忽视的问题。
对于机房管理,一方面应该设立专门的管理员,拟定详细的机房使用规则;另一方面,也应该配备强大的管理软件,辅助管理员进行管理。因此,如何设计和实现机房监控系统成为了本次课题的研究问题。
文章在第二章介绍了一些基本的技术原理,涉及到TCP/IP协议、Socket技术、Win32编程等概念,这些都是实现机房监控系统的基础技术。在第三章,首先讨论了机房监控系统应该具备哪些功能,接着设计出了服务器端和客户端器的通信协议。在第四章,首先讲述了用户界面的实现,接着着重介绍了几个核心技术的实现,包括如何对某个特定客户端进行屏幕截图,如何锁定指定客户端的屏幕以及如何在服务器端和客户端的进行通信。
所谓协议是一套用技术术语描述某些事物应该如何做的规则。如果执行不同的协议(一方知道某事的概念,但另一方却不知道),那么他们会因各自的协议无法沟通而完不成有用的工作。在计算机网络领域中,为了完成一项任务,所涉及的两个(或多个)通信实体必须运行同样的协议。
Internet是 由众多的计算机网络交错连接形成的网际网,作为Internet成员的各种网络在通讯中分别执行自己的协议。所谓Internet协议是指在Internet的网络之间以及各成员网内部交换信息时要求遵循的协议。在Internet中,到处运行着协议,凡是涉及两个或多个远程实体的通信活动均由协议来支配。协议定义了两个或多个通信实体间所交换消息的格式与顺序,以及在发出和/或收到一个消息或者发生其他事件所采取的行动。TCP/IP是Internet网络上使用的通用协议。Internet依靠上千个网络和百万计的计算机,而 TCP/IP是把它们合在一起的粘结剂。
TCP/IP的目标是提供高速网络通信链路。TCP/IP的实际名字来源于两个最重要的协议:TCP协议与IP协议,其中传输控制协议TCP对应于开放系统互联0SI参考模型的传输层协议;网络连接协议IP则对应于OSI参考模型的网络层协议。所以TCP/IP分别是Internet在传输层和网络层执行的协议。
TCP协议是互联网传输层协议。TCP服务模型包括面向连接的服务和可靠的数据传输服务,调用TCP作为其传输协议的应用同时取得这两种服务。总体来说,TCP保证递送全部数据,但对递送速率和所经历的延迟不加保证。电子邮件、远程终端访问、Web和文件传送都使用TCP。这些应用选择TCP的主要原因在于TCP提供可靠的数据传输服务,能够保证所有数据最终到达其目的地。
TCP的传输实体一般为软件实现:一部分是用户进程,一部分是操作系统的部分核心(管理TCP字节流,实现与IP层的接口)。TCP的传输服务是通过在收发双方创建套接字Socket来实现的;Socket的地址是通过IP地址与端口号来标识的。每条TCP的传输连接用发送端套接字、接收端套接字来表示,是点到点的全双工通道(全双工——支持同时的双向传输)运行与TCP上的应用程序包括FTP、HTTP、SMTP和TELNET。
在Internet内部,从主机传送到主机的信息不是一个恒定的流,而是把数据分解成小包,即数据包。例如你要发一分很长的电子邮件(E-mail)给一位朋友,TCP就将该信息分成很多个数据包,每一个数据包用一序号和接收地址来标记。此外TCP还插入一些纠错信息。接着数据包被传过网络,即把它们传送给远程的主机这就是IP的工作。在另一端TCP接收数据并检查错误。如果有错误发生,TCP就可以要求重发信息。换句话说,IP的工作是把原始数据(数据包)从一端传送到另一端;TCP的工作就是管理这种流动并确保其数据是正确的。把数据分解成数据包有很多的好处。首先,它允许Internet让很多不同的用户在同一时间用同一通讯线路。因为这些数据包不必一起输送,所以通讯线路可以载着所有类型的数据包按它们自己的路径从一地到另一地。就如同一条高速公路上,各类汽车(即使它们开向不同的地方)都在公共道路上行驶。当数据包传输时,它们沿规定的路由从主机到主机,一直到它们到达最终目的地。这意味着Internet很具灵活性。如果一个特定的连接中断了,控制数据流动的计算机通常可以找到另一条路由。事实上,在单一数据传输中,多个数据包完全可能沿不同的路由传输。当条件改变时,网络可获得当时最好的连接。如当网络的某一部分过载,数据包可以改变路线去走那些比较空闲的线路。用数据包传输的另一个好处是,当某处出错,只须重新传送单个数据包,而不是整个信息。这样会大大加快Internet的传输总速度。这种灵活性产生很高的可靠性。Internet运行非常好,虽然所有数据包都必须通过很多计算机,但它可以用几秒钟就把一个文件从一主机传输到另一主机,尽管它们相距很远。
网际网络协议(IP)用于数据报交换网络互连系统种,是TCP/IP协议堆栈所采用的传输协议。IP协议的基本数据形态是数据元(datagram),一般都成为数据报,此协议提供了从来源主机到目的主机之间数据报的传输机制。IP数据报中携带这来源地址和目的地址,利用这些地址数据,可将数据报传输到目的主机。IP协议具有数据报分割以及重组机制,需要的时候可以将数据报作适当的分割以及重组,以便能够在网络中传输。
IP协议是网络层协议,它是一种不可靠的,无连接导向的协议,无论是端对端或者路由器对路由器,都不提供可靠的传输,也不会提供确认信息,除了报头校验和外,IP协议本身没有数据的错误控制机制,一旦数据发生错误或者未能完整的接收,都不能重传。甚至即使是只有一个字节发生错误或者没有收到,也必须将整个数据报都丢弃。虽然,IP协议提供了数据报的传输机制,但是并没有提供传输的可靠性、流量控制、顺序性服务机制,它所提供的只是最佳化的传输服务。
习惯上,人们把Internet的通讯协议笼统地称为TCP/IP协议,也有人把Internet称为TCP/IP网或TCP/IP Internet网。在这种意义下,Internet的TCP/IP协议可以说就是基于四种模型的协议:即应用层、传输层、网络层和网络访问层。其中网络访问层又分为网络接口层(链路层)和最基础的物理层。所以也可以说Internet的网络协议是基于五层模型的协议。
根据TCP/IP协议,互联网分为4层,加上最底层的硬件层一共是5层
|
应用层(第五层) |
|
传输层(第四层) |
|
网络层(第三层) |
|
链路层(第二层) |
|
物理层(第一层) |
图2-1 TCP/IP协议分层
1)物理层:
物理层的任务是把帧中的各位从一个节点移往下一个节点。这一层的协议也取决于链路,而进一步取决于链路真正的传输介质。
物理层提供了一个基本机制:对二进制数据(比特)进行编码(发送到物理介质)和解码(从物理介质接收),例如10Mbit/s(bit/s指比特每秒)以太网的曼彻斯特编码、光纤分布式数据接口(Fiber Distributed data Interface,简称FDDI)的4B/5B编码;物理层也负责通知第二层(链路层)何时访问介质。物理层以比特流的方式传送来自链路层的数据,而不理会数据的含义或格式;同样,它接收数据以后,不加分析直接传给链路层。
物理层也定义与介质的物理连接机制,但不是介质本身,按照参考模型的原理,实际的物理介质在物理层之下。
2)链路层:
链路层的任务是把完整的帧从一个网络组建移往某个紧邻的组件,负责管理数据格式。它定义了将数据组成正确帧的规程和在网络中传输帧的规程,帧是指一串数据,它是数据在网络中传输的单位。网络层在源端和目的端之间经由一系列分组交换机(路由器)路由分组。为了将一个分组从一个节点(主机或分组交换机)移往其路径上的下一个节点,网络层必须依赖由链路层提供的服务。链路层提供了对链路的管理,链路层提供的服务取决于应用在链路上的特定链路层协议。分组从源端到目的端一般需要经过多个链路,每个分组沿其路径到达不同链路是,可能有不同的链路层协议来处理。既是说,IP从不同的链路层协议获取的服务会不一样。
链路层负责监督相邻网络节点的信息流动,它使用检错或纠错技术来确保正确的传输:当链路检测到错误时,它请求重发,或是根据情况纠正。此外,链路层还要解决流量控制的问题。
3)网络层:
网络层提供越过多个网络的选路功能,为端点提供无连接的数据报访问,并定义端到端通过整个互联网网络的寻址功能。网络层负责寻址、打包以及从一台计算机通过一个或多个路由器到最终目标的包转发机制。网络层的核心协议有:IP、ARP/RARP、ICMP和IGMP。IP协议(Internet protocol,简称IP)是TCP/IP模型的核心,它是一个路由协议,负责IP寻址、路由选择、分段及包重组。
4)传输层:
传输层给应用提供在其客户端和服务器之间传输应用层消息的服务。即传输层负责为两个用户进程之间建立、管理和拆除可靠而又有效的端到端连接。传输层的核心协议是传输控制协议TCP(Transmission Control Protocol)和用户数据报协议UDP(User Datagram Protocol)。TCP给其应用提供面向连接的服务,该服务包括应用层消息往目的地有保证的递送以及流量控制(既发送端和接收端之间速度的匹配)。TCP还把长数据块分割成较小的段,并提供拥塞控制机制。这样当网络处于拥塞状态时,源端会抑制其发送速率。UDP协议给其应用提供无连接的服务,是一个几乎没有任何修饰的服务。
5)应用层:
应用层负责支持网络应用,直接与用户或应用程序通信,它给应用程序提供访问其他层服务的能力并定义应用程序用于交换数据的协议。应用层包括许多协议,例如支持Web的HTTP、支持文件传送的FTP、支持电子邮件的SMTP 协议都建立在这一层。
TCP/IP应用程序使用的应用层接口有Windows套接字(Winsock)、NetBIOS等。Windows套接字提供Windows下的标准应用编程接口;NetBIOS是工业标准接口,用于访问协议服务,如会话、数据报和名字解析等。
Internet 的核心层是网络层和传输层,相应的核心协议是IP协议和TCP协议。IP 协议的主要功能包括无连结数据报传送﹑数据报寻径以及差错处理三部分。IP协议的特点是点到点的,IP对等实体间的通信不经过中间机器,对等实体所在的机器位于同一物理网络,对等机器之间有直接的物理连接。IP层的主要功能是屏蔽下面物理层的差别,向上一层提供一致的数据格式。所有要传输的数据,被按照一定的格式分组封装层IP数据报,数据报单元通过寻径等机制进行传输,在接收方数据报进行重组,得到最初要传送的数据。由于IP协议是不可靠的数据传输协议,由于网络的拥塞而发生的数据丢失等情况是不可避免的,因此Internet 还必须有一定的控制重传机制,这就是差错与控制报文协议(ICMP)。
尽管计算机通过安装IP软件,从而保证了计算机之间可以发送和接收数据,但IP协议还不能解决数据分组在传输过程中可能出现的问题。因此,若要解决可能出现的问题,还需要TCP协议来提供可靠的并且无差错的通信服务。TCP协议被称作一种端对端协议。这是因为它为两台计算机之间的连接起了重要作用:当一台计算机需要与另一台远程计算机连接时,TCP协议会让它们建立一个连接、发送和接收数据以及终止连接。传输控制协议TCP协议利用重发技术和拥塞控制机制,向应用程序提供可靠的通信连接,使它能够自动适应网上的各种变化。即使在 Internet 暂时出现堵塞的情况下,TCP也能够保证通信的可靠。互联网是一个庞大的国际性网络,网路上的拥挤和空闲时间总是交替不定的,加上传送的距离也远近不同,所以传输数据所用时间也会变化不定。TCP协议具有自动调整"超时值"的功能,能很好地适应 Internet 上各种各样的变化,确保传输数值的正确。
IP协议只保证计算机能发送和接收分组数据,而TCP协议则可提供一个可靠的、可流控的、全双工的信息流传输服务。虽然IP和TCP这两个协议的功能不尽相同,也可以分开单独使用,但它们是在同一时期作为一个协议来设计的,并且在功能上也是互补的。只有两者的结合,才能保证 Internet 在复杂的环境下正常运行。凡是要连接到 Internet 的计算机,都必须同时安装和使用这两个协议,因此在实际中常把这两个协议统称作TCP/IP协议。
20世纪80年代初,美国政府的高级研究机构(ARPA)给加利福尼亚大学Berkeley分校提供资金,让他们在UNIX操作系统下实现TCP/IP协议。在这个项目中,研究人员为TCP/IP网络通信开发了API(应用程序接口)。这个API称为Socket接口。如今Socket接口是TCP/IP网络最为通用的API,也是在Internet上进行应用开发最为通用的API。
Internet是有不同种类的网络互连而成,实现不同网络及计算机之间交互操作的关键问题是由TCP/IP协议来解决的。TCP/IP协议的核心是传输层协议(TCP和UDP)、网络层协议(IP)和网络接口层,前两层在操作系统内核中实现。操作系统内核是不能直接为一般用户所感受到的,一般用户感受到的只有应用程序(包括系统应用程序),即各种应用程序构成了操作系统的用户视图。两者之间的接口是网络编程界面(程序员界面)。因此,TCP/IP网络环境下的应用程序也不是直接与TCP/IP核心打交道,而是与网络应用编程接口(套接字,socket)打交道,编程接口构成了核心协议的用户视图。
实际上,Socket在计算机中提供了一个通信端口,可以通过这个端口与任何一个具有Socket接口的计算机通信。应用程序在网络上传输,接收的信息都通过这个Socket接口来实现。在应用开发中,就像使用文件句柄一样,可以对Socket句柄进行读、写操作。可以把Socket看成是一根连接线,当在两台计算机之间建立了一个Socket之后,两台计算机就可以通过这根连接线来完成信息交互,所做的仅仅是向这根连接线里写入数据,再从其中读出数据,而不必担心会不会由信息丢失,Socket会负责照看好数据。
Socket用于表达两台机器之间的连接‘终端’。一个网络应用涉及两台(或两台以上)不同主机中跨网络彼此通信的两个进程。这两个进程通过经由各自的套接字(Socket)发送和接收消息彼此通信。对于一个给定的连接,每台机器上都有一个套接字。有点像我们打电话,用电缆把电话连接起来,中间的物理结构和具体实现过程我们不必了解,只要我们能进行通话即可。套接字是单台主机内应用层和传输层之间的接口。套接字也用于指代应用程序和网络之间的应用程序接口(application program interface,简称API),因为它又是用于构造互联网中的网络应用程序的编程接口。当我们进行编程时,需要用到Socket接口,Socket接口定义了许多函数或例程,我们可以直接调用它们实现网络连接、通信等功能。Socket接口为我们建立通信信道,我们可以通过这条通道来与一台或多台计算机进行连接。
套接字屏蔽了底层通信软件和具体操作系统的差异,使得任何两台安装了TCP协议软件和实现了套接字规范的计算机之间的通信成为可能。对于应用程序来说,它就像一个服务协议插座一样,各种不同的协议,对应不同的端口。
我们可以把套接字看作相应进程上的“门”:进程把消息发送到网络或从网络接收消息都得经过自身得套接字。当一个进程想给另一台主机中的另一个进程发送消息是,它就把该消息推出自家的门。该进程认定在这扇门的另一侧有一个传输设施会把这个消息传输到目的进程的门口。在服务器进程处于运行状态的情况下,客户进程就能够初始化一个到服务器的TCP连接了,这是通过在客户程序中创建一个套接字对象来完成的,当客户创建了它的套接字对象的时候,它详细说明了服务器进程的地址,也就是,服务器的IP地址和进程的端口号,一旦创建了套接字对象,客户端的TCP就发起一个三次握手,并建立一个和服务器的TCP连接,三次握手对客户和服务器程序来说是完全透明的。
可以提供给用户使用的套接字有两种,它们分别是数据报套接字和流式套接字:
1)流式套接字(SOCKET_STREAM)
提供了面向连接、双向可靠的数据流传输服务,数据无出错,无重复地发送且按发送顺序接收。内设流量控制,避免数据流超限;数据被看作是字节流,无长度限制。
流式套接字使用传输控制协议(TCP)。它提供了一种可靠的,面向连接的数据传输方式。通常基于连接的流式套接字是设计客户机/服务器应用程序时的标准。流式套接字主要用于大批量数据或者让数据按顺序无重复的到达目的地而使用
2)数据报式套接字(SOCKET_DGRAM)
提供了无连接服务。数据包以独立形式被发送,不提供无错保证,数据可能丢失或者重复,并且接收顺序无序。数据报套接字使用用户数据报协议(UDP)。实际使用中,同一个分组数据报可能不止一次的被发送,一般要等到接收方发回确认收到的消息才会停止发送。
一般有两种套接字规范:一种是针对UNIX系统的Berkely Socket规范;一种是占主流地位的Windows sockets(简称WinSock)规范,它是Microsoft 以Berkely Socket规范为范例开发的windows下的网络编程接口。
Windows socket规范定义并记录了如何使用API与Internet协议相连接,且所有的Windows socket实现都支持流式套接字和数据报套接字。应用程序调用Windows socket的API实现相互之间的通信。Windows socket又利用下层的网络通信协议功能和操作系统调用实现实际的通信工作。
本文在软件实现上使用的Socket函数正是Windows Socket API。下面介绍几个重要的API:
一) WSAStartup函数
int WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
使用Socket的程序在使用Socket之前必须调用WSAStartup函数。该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;操作系统利用第二个参数返回请求的Socket的版本信息。当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。该函数执行成功后返回0。
例:假如一个程序要使用2.1版本的Socket,那么程序代码如下
wVersionRequested = MAKEWORD( 2, 1 );
err = WSAStartup( wVersionRequested, &wsaData );
二) WSACleanup函数
int WSACleanup (void);
应用程序在完成对请求的Socket库的使用后,要调用WSACleanup函数来解除与Socket库的绑定并且释放Socket库所占用的系统资源。
三) socket函数
SOCKET socket( int af, int type, int protocol );
应用程序调用socket函数来创建一个能够进行网络通信的套接字。第一个参数指定应用程序使用的通信协议的协议族,对于TCP/IP协议族,该参数置PF_INET;第二个参数指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM;第三个参数指定应用程序所使用的通信协议。该函数如果调用成功就返回新创建的套接字的描述符,如果失败就返回INVALID_SOCKET。套接字描述符是一个整数类型的值。每个进程的进程空间里都有一个套接字描述符表,该表中存放着套接字描述符和套接字数据结构的对应关系。该表中有一个字段存放新创建的套接字的描述符,另一个字段存放套接字数据结构的地址,因此根据套接字描述符就可以找到其对应的套接字数据结构。每个进程在自己的进程空间里都有一个套接字描述符表但是套接字数据结构都是在操作系统的内核缓冲里。下面是一个创建流套接字的例子:
struct protoent *ppe;
ppe=getprotobyname("tcp");
SOCKET ListenSocket=socket(PF_INET,SOCK_STREAM,ppe->p_proto);
四) closesocket函数
int closesocket( SOCKET s );
closesocket函数用来关闭一个描述符为s套接字。由于每个进程中都有一个套接字描述符表,表中的每个套接字描述符都对应了一个位于操作系统缓冲区中的套接字数据结构,因此有可能有几个套接字描述符指向同一个套接字数据结构。套接字数据结构中专门有一个字段存放该结构的被引用次数,即有多少个套接字描述符指向该结构。当调用closesocket函数时,操作系统先检查套接字数据结构中的该字段的值,如果为1,就表明只有一个套接字描述符指向它,因此操作系统就先把s在套接字描述符表中对应的那条表项清除,并且释放s对应的套接字数据结构;如果该字段大于1,那么操作系统仅仅清除s在套接字描述符表中的对应表项,并且把s对应的套接字数据结构的引用次数减1。
closesocket函数如果执行成功就返回0,否则返回SOCKET_ERROR。
五) send函数
int send( SOCKET s, const char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。该函数的第一个参数指定发送端套接字描述符;第二个参数指明一个存放应用程序要发送数据的缓冲区;第三个参数指明实际要发送的数据的字节数;第四个参数一般置0。这里只描述同步Socket的send函数的执行流程。当调用该函数时,send先比较待发送数据的长度len和套接字s的发送缓冲区的长度,如果len大于s的发送缓冲区的长度,该函数返回SOCKET_ERROR;如果len小于或者等于s的发送缓冲区的长度,那么send先检查协议是否正在发送s的发送缓冲中的数据,如果是就等待协议把数据发送完,如果协议还没有开始发送s的发送缓冲中的数据或者s的发送缓冲中没有数据,那么send就比较s的发送缓冲区的剩余空间和len,如果len大于剩余空间大小send就一直等待协议把s的发送缓冲中的数据发送完,如果len小于剩余空间大小send就仅仅把buf中的数据copy到剩余空间里(注意并不是send把s的发送缓冲中的数据传到连接的另一端的,而是协议传的,send仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
六) recv函数
int recv( SOCKET s, char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;第三个参数指明buf的长度;第四个参数一般置0。这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
七) bind函数
int bind( SOCKET s, const struct sockaddr FAR *name, int namelen );
当创建了一个Socket以后,套接字数据结构中有一个默认的IP地址和默认的端口号。一个服务程序必须调用bind函数来给其绑定一个IP地址和一个特定的端口号。客户程序一般不必调用bind函数来为其Socket绑定IP地址和断口号。该函数的第一个参数指定待绑定的Socket描述符;第二个参数指定一个sockaddr结构,该结构是这样定义的:
struct sockaddr {
u_short sa_family;
char sa_data[14];
};
sa_family指定地址族,对于TCP/IP协议族的套接字,给其置AF_INET。当对TCP/IP协议族的套接字进行绑定时,我们通常使用另一个地址结构:
struct sockaddr_in {
short sin_family;
u_short sin_port;
struct in_addr sin_addr;
char sin_zero[8];
};
其中sin_family置AF_INET;sin_port指明端口号;sin_addr结构体中只有一个唯一的字段s_addr,表示IP地址,该字段是一个整数,一般用函数inet_addr()把字符串形式的IP地址转换成unsigned long型的整数值后再置给s_addr。有的服务器是多宿主机,至少有两个网卡,那么运行在这样的服务器上的服务程序在为其Socket绑定IP地址时可以把htonl(INADDR_ANY)置给s_addr,这样做的好处是不论哪个网段上的客户程序都能与该服务程序通信;如果只给运行在多宿主机上的服务程序的Socket绑定一个固定的IP地址,那么就只有与该IP地址处于同一个网段上的客户程序才能与该服务程序通信。我们用0来填充sin_zero数组,目的是让sockaddr_in结构的大小与sockaddr结构的大小一致。下面是一个bind函数调用的例子:
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(8888);
saddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(ListenSocket,(struct sockaddr *)&saddr,sizeof(saddr));
八) listen函数
int listen( SOCKET s, int backlog );
服务程序可以调用listen函数使其流套接字s处于监听状态。处于监听状态的流套接字s将维护一个客户连接请求队列,该队列最多容纳backlog个客户连接请求。假如该函数执行成功,则返回0;如果执行失败,则返回SOCKET_ERROR。
九) accept函数
SOCKET accept( SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen );
服务程序调用accept函数从处于监听状态的流套接字s的客户连接请求队列中取出排在最前的一个客户请求,并且创建一个新的套接字来与客户套接字创建连接通道,如果连接成功,就返回新创建的套接字的描述符,以后与客户套接字交换数据的是新创建的套接字;如果失败就返回INVALID_SOCKET。该函数的第一个参数指定处于监听状态的流套接字;操作系统利用第二个参数来返回新创建的套接字的地址结构;操作系统利用第三个参数来返回新创建的套接字的地址结构的长度。下面是一个调用accept的例子:
struct sockaddr_in ServerSocketAddr;
int addrlen;
addrlen=sizeof(ServerSocketAddr);
ServerSocket=accept(ListenSocket,(struct sockaddr *)&ServerSocketAddr,&addrlen);
十) connect函数
int connect( SOCKET s, const struct sockaddr FAR *name, int namelen );
客户程序调用connect函数来使客户Socket s与监听于name所指定的计算机的特定端口上的服务Socket进行连接。如果连接成功,connect返回0;如果失败则返回SOCKET_ERROR。下面是一个例子:
struct sockaddr_in daddr;
memset((void *)&daddr,0,sizeof(daddr));
daddr.sin_family=AF_INET;
daddr.sin_port=htons(8888);
daddr.sin_addr.s_addr=inet_addr("133.197.22.4");
connect(ClientSocket,(struct sockaddr *)&daddr,sizeof(daddr));
win32 是指Window都是32位的操作系统,例如98、XP、2000、2003等操作系统, Win32编程就是在这些系统下的编程,运用Window提供的API(Application Programming Interface)为Window编写应用程序。 win32编程可以用C来完成,主要是通过Windows的API来工作。然而更方便的是使用MFC,微软基础类(Microsoft Foundation Classes),实际上是微软提供的,用于在C++环境下编写应用程序的一个框架和引擎,VC++是Windows下开发人员使用的专业C++ SDK(SDK, Standard Software Develop Kit,专业软件开发平台),MFC就是挂在它之上的一个辅助软件开发包,MFC作为与VC++血肉相连的部分,可以大大方便程序员,简化程序员的工作。
MFC是Win API与C++的结合,API,即微软提供的Windows下应用程序的编程语言接口,是一种软件编程的规范,但不是一种程序开发语言本身,可以允许用户使用各种各样的第三方的编程语言来进行对Windows下应用程序的开发,使这些被开发出来的应用程序能在Windows下运行,比如VB, VC++, Java, Delphi编程语言函数本质上全部源于API,因此用它们开发出来的应用程序都能工作在Windows的消息机制和绘图里,遵守Windows作为一个操作系统的内部实现。上面说到MFC是微软对API函数的专用C++封装,这种结合一方面让用户使用微软的专业C++ SDK来进行Windows下应用程序的开发变得容易,因为MFC是对API的封装,微软做了大量的工作,隐藏了好多内节程序开发人员在Windows下用C++ & MFC编制软件时的大量内节,如应用程序实现消息的处理,设备环境绘图,这种结合是以方便为目的的,必定要付出一定代价,因此就造成了MFC对类封装中的一定程度的的冗余和迂回,但这是可以接收的。
本程序设计意图是为了更好的管理局域网内电脑的使用,对电脑的使用进行实时的监控,为局域网内部提供一个良好的运行环境。因此,该系统应该具有下列功能:
1)能随时查看某个主机的屏幕画面,即能进行屏幕截图。此功能是为了防止有人使用电脑做一些违法微机室管理条例的事情,在已经违反后,可以作为证据使其无法抵赖。
2)能随时锁定和解锁用户电脑。所谓“锁定”是指让用户无法进行键盘输入和鼠标操作,使电脑暂时“失灵”,该功能主要用于强迫用户下机。而解锁就是将已经锁定的电脑恢复正常。
3)能查看用户电脑中正在运行的进程,并关闭选中的进程。该功能可以用于设定某些程序无法执行(比如一些被禁止的游戏等),同时,也可以从一定程度上起到病毒防治作用。
4)能为用户设定使用时间。即控制用户的使用电脑的时间。
5)能向用户发送消息。管理员可以从服务器上直接发送一些消息到用户电脑上,比如对用户的违规行为进行警告,对时间快要用尽的用户进行提醒等。
6)能同时支持多个客户端。即一台服务器就能管理多个客户端,一般上限应不低于250台。
7)能方便的实现以上操作。要求程序界面友好,安装及维护简易可行。
按照前面提出的设计目标来看,该系统是一个典型的C/S系统,其架构图如下所示:
图3-1 系统框图
在该系统中,管理员通过一台服务器,对多个客户端进行控制。而这些控制的实现,都是通过向客户端发送消息来实现的。下面详细介绍一下这些控制协议。
1)用户登录。客户端输入服务器的ip和自身的用户名及密码,作为LOGIN命令的参数发送到服务器端。若用户名和密码正确,将返回OK,否则返回NO。
图3-2 用户登录
2)锁定和解锁。服务器端向客户端发送LOCK命令,客户端将尝试锁定屏幕,如果成功,将向服务器返回OK,否则返回NO;服务器端向客户端发送UNLOCK命令,客户端将尝试解除锁定屏幕,如果成功,将向服务器返回OK,否则返回NO。
图3-3 锁定和解锁
3)发送消息。服务器端通过发送命令MSG向客户端发送消息。消息的具体内容作为参数msg传递给客户端,客户端在收到后返回OK给服务器端。
图3-4 发送消息
4)屏幕截图。服务器端通过SNAP命令向客户端要求截图,客户端进行截图尝试,如失败,将返回NO;如果成功,将返回OK len命令,将图像数据大小从len参数中传递给服务器端,并接着传递所得图像的数据给服务器。
图3-5 屏幕截图
5)查看进程。服务器向客户端发送LIST命令,客户端尝试进行进程枚举,如果失败,将返回NO给服务器;如果成功,将发送OK len命令给服务器,len参数指名了进程信息的数据大小。接下来就将把进程信息发送到服务器端。
图3-6 查看进程
6)心跳包。所谓心跳包就是在客户端和服务器间定时通知对方自己状态的一个自己定义的命令字,按照一定的时间间隔发送,类似于心跳,所以叫做心跳包。通过使用心跳包,就能知道客户端和服务器端是否还相连。在本协议中,服务器将定时发送ALIVE命令给每个客户端,客户端在收到后回复OK给服务器端。
图3-6 心跳包
整个系统的运作流程如下面两图所示:
初始化,设置服务器
用户是否合法
向客户端发送请求并返回处理结果
接受主机的管理(如锁屏、截图等)
进行监听,接受连接登录
将新客户加入到管理集合中
结束
不合法
合法
返回处理结果
图3-7 服务器流程图
初始化,锁定屏幕
响应命令,进行相关操作(如截图,锁屏)
监听服务器命令
登录成功,解除锁定
接收用户命令
发送结果给服务器
结 束
服务器端在程序开始运行后,一方面启动了监听线程,不停的准备接收新的客户端的连接请求。如果客户端发送来的用户合法,那么将这个新的客户端添加到管理集合中。另一方面,服务器端随时接收管理员的管理操作,比如针对某个ip的电脑进行截图操作等。当用户选择终止程序时将退出。
客户端在程序运行后会首先锁定屏幕,只有当登录服务器成功后才会解除锁定。在登录成功后,客户端就不停的监听来自服务器的消息,并对消息进行响应(可能会在本地进行很多操作),然后把结果返回到服务器端。
如下图所示,用户界面为友好的Win32应用程序,在服务器程序界面左边停靠的工具条中,有一个树控件,该控件列出了系统中正在管理的客户端ip。选中其中的某个,就可以进行响应的操作,比如“截取屏幕”,那么就可以获取客户端的屏幕画面,并显示到用户界面中。
图4-1 服务器端运行效果图
在客户端,用户被要求填入服务器ip,用户名以及密码,然后登录。如下图所示:
图4-2 客户器端运行效果图
由于整个系统基本使用MFC开发,而且涉及到很多方面,代码量比较大,不可能一一介绍,下面选择几个较为重要的核心功能的实现进行介绍。
屏幕截图的基本功能是通过两个函数来实现的:Bmp2Stream和Screen2Bitmap。正如他们的名字所示,Screen2Bitmap的作用是将屏幕截图,并保存到一个位图中,然后返回这个位图的句柄;而Bmp2Stream则是为了网络传输作准备,它接收一个位图句柄,对这个位图进行编码,并拷贝到一个缓冲区里。下面是这两个函数的具体实现:
HBITMAP Screen2Bitmap()
{
HDC hscrdc, hmemdc;// 屏幕和内存设备描述表
HBITMAP hbitmap, holdbitmap;// 位图句柄
int nwidth, nheight;// 位图宽度和高度
int xscrn, yscrn;// 屏幕分辨率
//为屏幕创建设备描述表
hscrdc = CreateDC("display", NULL, NULL, NULL);
//为屏幕设备描述表创建兼容的内存设备描述表
hmemdc = CreateCompatibleDC(hscrdc);
// 获得屏幕分辨率
xscrn = GetDeviceCaps(hscrdc, HORZRES);
yscrn = GetDeviceCaps(hscrdc, VERTRES);
nwidth = xscrn;
nheight = yscrn;
// 创建一个与屏幕设备描述表兼容的位图
hbitmap = CreateCompatibleBitmap(hscrdc, nwidth, nheight);
// 把新位图选到内存设备描述表中
holdbitmap = (HBITMAP)SelectObject(hmemdc, hbitmap);
// 把屏幕设备描述表拷贝到内存设备描述表中
BitBlt(hmemdc, 0, 0, nwidth, nheight, hscrdc, 0, 0, SRCCOPY);
//得到屏幕位图的句柄
hbitmap = (HBITMAP)SelectObject(hmemdc, holdbitmap);
//清除
DeleteDC(hscrdc);
DeleteDC(hmemdc);
// 返回位图句柄
return hbitmap;
}
BOOL Bmp2Stream( HBITMAP& hBmp, BYTE* &bBuf, long &size,
DWORD type = CXIMAGE_FORMAT_JPG, int nWidth=0, int nHeight=0 )
{
if(hBmp != INVALID_HANDLE_VALUE)
{
CxImage img;
if(img.CreateFromHBITMAP(hBmp)) //位图句柄构建对象
{
if(nWidth <= 0)
{
nWidth = img.GetWidth();
}
if(nHeight <= 0)
{
nHeight = img.GetHeight();
}
if(!img.Resample(nWidth, nHeight)) //缩放位图
{
return FALSE;
}
if(img.Encode(bBuf, size, type)) //编码
return TRUE;
}
}
return FALSE;
}
补充一点,普通情况下,屏幕截图只能获得一幅位图,而这幅位图的大小一般都在1M以上,这不利于网络传输,因此,应该进行图像压缩,即图像格式转换。在本系统中,使用CxImage(CxImage类库是一个优秀的图像操作类库。它可以快捷地存取、显示、转换各种图像)这个开源项目提供的强大的库进行编码,将位图转化为Jpg格式再进行传输,这正是Bmp2Stream函数存在的目的。
屏幕的锁定功能可以通过多种方式实现,本系统使用的是Windows钩子(Hook)技术进行实现的。所谓钩子,钩子(Hook),是Windows消息处理机制的一个平台,应用程序可以在上面设置子程以监视指定窗口的某种消息,而且所监视的窗口可以是其他进程所创建的。当消息到达后,在目标窗口处理函数之前处理它。钩子机制允许应用程序截获处理window消息或特定事件。钩子实际上是一个处理消息的程序段,通过系统调用,把它挂入系统。每当特定的消息发出,在没有到达目的窗口前,钩子程序就先捕获该消息,亦即钩子函数先得到控制权。这时钩子函数即可以加工处理(改变)该消息,也可以不作处理而继续传递该消息,还可以强制结束消息的传递。
钩子有很多种,每一种类型的Hook可以使应用程序能够监视不同类型的系统消息处理机制。该系统只需要处理键盘和鼠标的输入,因此只需要在应用程序中安装WH_KEYBOARD Hook用来监视WM_KEYDOWN and WM_KEYUP消息,还有安装WH_MOUSE Hook监视从GetMessage 或者 PeekMessage 函数返回的鼠标消息,使用这个Hook监视输入到消息队列中的鼠标消息。按照Hook的技术规范,Hook API必须写到一个另一个dll中,而在主程序中调用这个dll中的Hook函数。
在本系统中,锁屏幕和解锁主要涉及到下面几个函数:
HWND gTargWnd; //客户端窗口句柄
#pragma data_seg ("shared")
static HHOOK g_hHookK=NULL;
static HHOOK g_hHookM=NULL;
#pragma data_seg ()
LRESULT CALLBACK MouseProc( int iCode, WPARAM wParam, LPARAM lParam )
{
/*
*判断是对哪个窗口进行鼠标输入,
*如果不是对客户端窗口进行输入,
*直接返回,从而达到屏蔽鼠标的作用
*/
if(::GetActiveWindow()!=gTargWnd)
return 1;
return CallNextHookEx(g_hHookM,iCode,wParam,lParam);
}
DLLEXPORT int CALLBACK InstallHOOK(HWND hWnd)
{
gTargWnd = hWnd; //客户端窗口句柄复制
//安装键盘和鼠标钩子
g_hHookK=SetWindowsHookEx(WH_KEYBOARD,KeyboardProc,g_hInst,0);
g_hHookM=SetWindowsHookEx(WH_MOUSE, MouseProc, g_hInst, 0);
if(g_hHookM && g_hHookK)
return TRUE;
else
return FALSE;
}
DLLEXPORT int CALLBACK UninstallHOOK()
{
//卸载键盘和鼠标钩子
if (UnhookWindowsHookEx(g_hHookK)==0||UnhookWindowsHookEx(g_hHookM))
return FALSE;
else
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int iCode, WPARAM wParam, LPARAM lParam )
{
/*
*判断是对哪个窗口进行键盘输入,
*如果不是对客户端窗口进行输入,
*直接返回,从而达到屏蔽键盘的作用
*/
if(::GetActiveWindow()!=gTargWnd)
return 1;
return CallNextHookEx(g_hHookK,iCode,wParam,lParam);
}
在分析以往一些机房监控系统的基础上,此设计采用Socket编程技术和钩子(Hook)技术设计方案。该方案采用C/S模式,采用VC++6.0作为开发工具,在Win32平台下实现了一个机房监控系统。系统能实现屏幕截图、锁定屏幕以及服务器端和客户端的通信等功能。经过测试后,该系统达到了预想中的效果,能够实现简易的功能。
本文的主要工作有:
1)介绍了机房监控的重要行,对CS模式、Socket、Hook及都进行了简单的介绍;
2) 阐述了Socket的基本实现原理;
3) 模拟了机房监控系统的开发需求,提出了比较详细的解决方案,阐明了系统的功能模块的划分及相互之间的关系,并详细描述了每个模块的实现过程或者实现方法;
该系统也有一些不尽人意的地方。需求分析并没有非常细致全面,软件的性能、健壮性、使用方便及美观等方面离真正的商业软件还有一定的差距;系统并没有考虑安全因素;另外,该系统并没有考虑与机房其他软件系统之间的关系,这些都有待今后的完善。
经过几个月来的设计,系统基本满足机房监控方面的需要。系统设计期间,学习到了很多课堂上没有的知识,增强了动手能力和解决实际问题的能力,提高了认识和学习新事物的能力,特别是增强了自己在程序设计方面的信心和动手实践能力,这些对于未来的工作无疑将是一笔宝贵的财富。
[1] Jeff Prosise. MFC Windows 程序设计 第二版[M]. 清华大学出版社,2007。
[2] 侯捷. 深入浅出MFC [M]. 华中科技大学出版社,2000。
[3] 汪令江 等. 奇思异想编程序VC篇[M]. 国防工业出版社,2004。
[4] Jeffrey Richer. Windows 核心编程[M]. 机械工业出版社,2000。
[5] 姚领田.精通MFC程序设计[M]. 人民邮电出版社,2006。
[6] John E.Swanke. VISUAL C++MFC扩展编程实例[M]. 北京机械工业出版社, 2000。
[7] 孙鑫 等 VC++深入详解[M]. 电子工业出版社,2006。