适合读者:编程爱好者、黑客工具爱好者
前置知识:Borland C++ Builder 6.0基本使用方法
Socket:本文主要介绍了远程屏幕监视软件EagleEye的开发与设计过程。逐一介绍了比较正规的专业程序开发步骤:需求分析、功能设计与实现、软件测试运行。在功能设计中用程序流程图展现了程序执行的具体过程。在功能实现中详细的展现了系统的各个功能模块、所需的VCL类和自定义的相关类,给出了实现相应的功能的函数及代码,文章的最后还给出了本软件的主要功能源代码。我们推出这样的文章是想让大家真正了解到专业程序开发的流程和详细的步骤分析,或许这个文章就能让无数新手迈进专业程序开发的第一步呢?!
带你迈上专业软件开发第一步:远程屏幕监视软件的设计与实现
通过21天的编写,远程辅助类工具软件EagleEye 基本编写完毕,能够完成多窗口多种方式(可以选择截图或实时监视的方式)同时监视多台远程计算机,可以实现对本地计算机上显示的远程屏幕图像的实行存储,可以将图像拷贝到剪贴板,并且用户可以设定监视时间、监视方式,可以选择程序的运行方式(可以是正常模式,也可以使程序隐藏于后台运行)。完全实现了我最初的设想。心情不错,于是急忙拿出来和广大的黑防读者们一起共享,共同富裕嘛!
开发环境和功能分析
远程屏幕监视作为我自主编写的远程控制软件的一个模块,我将其独立分割出来发布测试,软件要求能实现分屏同时监视远程主机。大体的开发环境是:
操作系统:Windows XP SP1
开发所用计算机配置:512M内存+P42.4G CPU+800MHZ系统总线
环境:Borland C++ Builder 6.0+SUIStyle控件
至于功能需求分析,我们一点一点看,这或许是专业开发人员和普通自学程序员之间最大的差距,一起来学习、探讨一下吧!
1.为实现数据的网络传输,软件采用C/S(客户端/服务器)数据传输模式。从遵循计算机道德的角度出发,在该设计过程中,不打算编写具有木马特征的独立的服务端,而将软件集客户端(Client)和服务端(Server)于一体。软件基本定位是:远程辅助类工具软件。
Socket:咳……其实你可以花5分钟时间将客户端(Client)和服务端(Server)分离,并加入自启动的功能,如果够狠的话,再加入关联文件的功能,这不是一个截屏木马?
2.服务端(Server)实现:采用C++ Builder的Socket Server控件来实现服务端的数据传输功能。主要功能:监听本地计算机的指定端口,截获本机屏幕信息,拷贝屏幕区域到自定义的位图变量。
Socket:为了达到提高传输效率,服务端根据在连接过程中所获得的转换参数将位图转换成JPGE格式后存入缓存区,在与远程客户端连接的前提下,将缓存区数据流发送至客户端(Client)。
3.客户端(Client)实现:利用Winsock API函数来定义用于本软件网络数据传输的Sockets。功能实现:向远程主机服务端(Server)提出连接申请,并在此过程中将JPGE转换参数发送给服务端(Server)。在获得连接的前提下,不断接受远程服务端(Server)所发送至的包含远程主机屏幕信息的JPGE的数据流,并实时将所接受的图像在本软件的客户区域(Client Region)上重绘,从而实现在本地计算机实时监视远程计算机屏幕的目的。
详细功能设计
上面大体分析了功能和需要,下一步一起来看看如何进行详细的功能设计。
1.客户端(Client)
(1)客户端与服务端之间网络畅通状况的检测。编写Ping模块,此模块发送一个ICMP echo request(ICMP协议回显请求)至目标主机,如果获得回显,则向目标主机发送连接请求。
(2)客户端(Client)与服务端(Server)之间的数据传输。利用Winsock API函数来定义用于本软件数据传输的Sockets。具体过程为:连接远程主机->返回有效SOCKET(使用Connect_Server())->向SOCKET写字符串(使用Write_Socket())->向远程主机的指定端口发送字符串提供转换参数(使用SendMsg())->动态分配端口,并与SOCKET绑定->返回该SOCKET(使用BindSocket())->向远程主机的指定端口发送请求(使用SendStream())->从远程主机的指定端口接收数据流(使用RecvStream())。
(3)图像重绘:使用Image控件将从远程主机发送到的JPGE图像显示。
2.服务端(Server)
(1)服务端(Server)功能的实现。使用C++ Builder的Socket Server控件编写软件的服务端(Server)。监听本地计算机指定端口,接受由客户端发送至的相关参数,将参数传递给屏幕图像截取模块。
(2)屏幕图像截取与传输步骤:读取取得桌面的矩形区域范围GetWindowRect()->创建内存设备描述表从而定义位图变量GetDC()->拷贝屏幕的指定区域到位图BitBlt();->创建JPEG图象将位图转化为JPEG格式->保存JPEG图象信息至内存数据流Assign(),SavetoStream()->将图象信息数据流通过Sockets发送至客户端(由SendStream()实现)。
程序流程图
数据结构与算法
在此对实现主要功能的类和方法做出说明,对由IDE所生成的与可视化控件相关的方法在此不予详细说明。同时为配合新手学习,代码后附详细功能注释。
1.所用的VCL中现成的类
TBitmap(位图对象)类和TJPEGImage类。TBitmap(位图)是VCL中抽象图形类Graphics的一个对象,它可以用于在内存中创建和处理图像,也可以存储图像数据流。TBitmap包装了VCL中的位图操作,其属性有Palette、Height、Width和TransparentColor等;TJPEGImage封装了用于处理JPGE格式数据的Graphic类,它可以对以JPGE格式压缩的图像数据进行创建和读取。其属性有:Height、Palette、Performance、PixelFormat、Scale、Width等。
2.继承于VCL中的类
(1)线程类TRecvStreamThread,继承于TThread类:
class TRecvStreamThread : public TThrea
{
private:
protected:
void __fastcall Execute(); //
public:
__fastcall TRecvStreamThread(bool CreateSuspended);//接受数据的线程
bool __fastcall LoadImage(TImage *Image1);//显示图像
TImage *RemoteScreen; // 显示图象的对象指针
TStatusBar *StatusBar; // 显示状态信息的对象指针
AnsiString RemoteAddress; // 远程主机IP
int CL, CQ; // 色深和图象品质
};
(2)主窗口类TMainForm,继承于TForm类:
class TMainForm : public TForm
{
__published:
TMainMenu *MainMenu1;
TToolButton *ToolButton14;
……
……//可视化控件相关声明
void __fastcall HelpAbout1Execute(TObject *Sender);
void __fastcall FileExit1Execute(TObject *Sender);
void __fastcall ServerSocket1ClientRead(TObject *Sender,
TCustomWinSocket *Socket);
void __fastcall ServerSocket1ClientError(TObject *Sender,
TCustomWinSocket *Socket, TErrorEvent ErrorEvent,
int &ErrorCode);
void __fastcall FormCreate(TObject *Sender);
void __fastcall FileSaveAsItemClick(TObject *Sender);
void __fastcall HideForm(TObject *Sender);
……//处理可视化控件的相关方法
private:
void __fastcall CreateMDIChild(const AnsiString sAddress, int CL, int CQ);
public:
virtual __fastcall TMainForm(TComponent *Owner);
};
(3)子窗口类TMDIChild:
class TMDIChild : public TForm
{
__published:
……//可视化控件相关声明
void __fastcall FormClose(TObject *Sender, TCloseAction &Action);
void __fastcall Image1DblClick(TObject *Sender);
void __fastcall N3Click(TObject *Sender);
void __fastcall N2Click(TObject *Sender);
……//处理可视化控件的相关方法
private:
public:
virtual __fastcall TMDIChild(TComponent *Owner);
bool __fastcall LoadImage(void); // 载入图象
AnsiString RemoteAddress; // 远程主机IP
int CL, CQ; // 色深和图象品质
};
3.继承类中的相关方法:
(1)CaptureImage()——捕获当前屏幕并保存到Imagestream中。具体代码如下:
void CaptureImage(int options, int level, int cq, TMemoryStream* imgstream)
{
LONG width,height;
RECT capRect;
HDC DesktopDC;
switch (options) {
case CM_ENTIRESCREEN: // 捕获整个屏幕
// 取得桌面的矩形区域范围
GetWindowRect(GetDesktopWindow(),&capRect);
break;
case CM_ACTIVEWINDOW: // 捕获当前窗口
HWND ForegWin;
ForegWin = GetForegroundWindow(); // 取得当前窗口句柄
if (!ForegWin)
ForegWin = GetDesktopWindow();
GetWindowRect(ForegWin,&capRect); // 取得当前窗口的矩形区域范围
break;
}
DesktopDC = GetDC(GetDesktopWindow()); // 创建内存设备描述表
width = capRect.right - capRect.left;
height = capRect.bottom - capRect.top;
Graphics::TBitmap *bBitmap; // 定义位图变量
try {
bBitmap = new Graphics::TBitmap(); // 创建位图
bBitmap->Width=width;
bBitmap->Height=height;
if ((level>0)&&(level<8))
bBitmap->PixelFormat = TPixelFormat(level); // 设定色深
// 拷贝屏幕的指定区域到位图
BitBlt(bBitmap->Canvas->Handle,0,0,width,height,DesktopDC,
capRect.left,capRect.top,SRCCOPY);
if (cq>=0) {
TJPEGImage *jpeg;
try {
jpeg = new TJPEGImage; // 创建JPEG图象
jpeg->CompressionQuality = cq; // 设定图象品质
jpeg->Assign(bBitmap); // 将位图转化为JPEG格式
jpeg->SaveToStream(imgstream); // 保存JPEG图象信息
}
__finally {
delete jpeg; // 释放资源
}
}
else {
bBitmap->SaveToStream(imgstream); // 保存位图信息
}
}
__finally {
delete bBitmap; // 释放资源
}
}
(2)ServerSocket1ClientRead()——捕获并发送自己的屏幕图象。具体代码如下:
Void __fastcall TMainForm::ServerSocket1ClientRead(TObject *Sender,TCustomWinSocket *Socket)
{
AnsiString sRecvString = Socket->ReceiveText(); // 保存接收到的字符串
AnsiString sRemoteAddress = Socket->RemoteAddress; // 保存对方IP
int CL,CQ;
u_short port;
// 将接收到的字符串分解为接收端口、色深、品质3个参数
int pos = sRecvString.Pos("n");
// 接收端口
port = u_short(StrToIntDef(sRecvString.SubString(1,pos-1),0));
sRecvString = sRecvString.SubString(pos+1,sRecvString.Length()-pos);
pos = sRecvString.Pos("n");
// 色深
CL = StrToIntDef(sRecvString.SubString(1,pos-1),0);
sRecvString = sRecvString.SubString(pos+1,sRecvString.Length()-pos);
pos = sRecvString.Pos("n");
// 品质
CQ = StrToIntDef(sRecvString.SubString(1,pos-1),0);
if (port) {
TMemoryStream *ImageStream; // 定义数据流
try {
ImageStream = new TMemoryStream; // 分配内存
// 捕获当前屏幕并保存到ImageStream中
CaptureImage(CM_ENTIRESCREEN, CL, CQ, ImageStream);
// 发送ImageStream到接收端口
if (!SendStream(sRemoteAddress, port, ImageStream))
MessageBox(0,"发送数据流失败","EagleEye",MB_ICONERROR);
}
__finally {
delete ImageStream; // 释放资源
}
}
}
(3)LoadImage ()——接受服务端屏幕数据并在本地计算机上重绘图像。具体代码如下:
bool __fastcall TRecvStreamThread::LoadImage(TImage *Image1)
{
CPingReply reply;
CPing PingHost;
bool bRtn = false; // 函数返回值初始为FALSE
StatusBar->SimpleText = "正在连接主机...";
// 先PING主机,检测网络是否畅通
bool rtn = PingHost.Ping(RemoteAddress.c_str(),reply,64,4000,32);
if (rtn) {
u_short RecvPort=0;
TMemoryStream *Stream;
try{
for(int i=1;i<=(ConfigForm->times);i++) //进入循环,不断获得远程桌面图像
{ // 定义一个数据流并分配内存
Stream = new TMemoryStream;
TJPEGImage *jpeg; // 定义JPEG图象
try{
jpeg = new TJPEGImage; // 分配内存
int RecvSocket = BindSocket(&RecvPort); //动态分配接收端口
if (RecvSocket)
{ // 将接收端口和色深、图象品质合成一条命令,参数之间以'n'分隔
AnsiString Msg = IntToStr(RecvPort) + "n" +IntToStr(CL) + "n" +IntToStr(CQ) + "n";
Application->ProcessMessages(); // 处理系统消息
// 向远程主机发送命令
if (SendMsg(RemoteAddress,LISTENPORT,Msg))
{ // 开始接收图象到数据流中
if (RecvStream(RecvSocket,Stream))
{
StatusBar->SimpleText = "正在接收数据...";
// 从数据流中载入图象
jpeg->LoadFromStream(Stream); // 显示图象
Image1->Picture->Bitmap->Assign(jpeg); //MessageBeep(MB_OK);
// 发出提示声音,返回值为TRUE,表示成功
bRtn = true; }
else
MessageBox(0,"接收数据流失败","EagleEye",MB_ICONERROR); }
else
MessageBox(0,("无法与主机'"+ RemoteAddress +"'建立连接").c_str(),"EagleEye",MB_ICONERROR); }
else
MessageBox(0,"分配端口失败,无法继续接收数据","EagleEye",MB_ICONERROR);}
__finally {
delete jpeg; // 释放资源
}
RecvPort+=1;
}
}
__finally {
delete Stream; // 释放资源
}
}
else
MessageBox(0,("主机'"+RemoteAddress+"'没有响应").c_str(),"EagleEye",MB_ICONERROR);
return bRtn;
}
大体流程就是这样,中间因为篇幅的问题省略掉了Ping模块的编写,不过已经提供了全部的程序代码,大家可以自己看看。
后期程序测试
软件运行界面如图1所示:
图1
软件运行平台:Windows 98/2000/XP/2003
软件所实现功能:多窗口两种方式(可以选择截图或实时监视的方式)同时监
视多台远程计算机;用户可以设定监视时间、监视方式;用户可以实现对本地计算机上显示的远程屏幕图像的存储,可以将图像拷贝到剪贴板;用户可以选择程序的运行方式,可以是正常模式,也可以使程序隐藏于后台运行。
软件运行最低配置:由于条件限制没能在更低配置的机器测试,所以最低运行配置数据不准确。软件在P4赛扬1.6G,128M内存,133MHZ系统总线,操作系统为Windows 98的计算机上运行良好。
软件在100.0Mbps的局域网中对远程主机的实时监视图像传输速率可以234帧/min;在带宽为512kb/s的ADSL宽带用户的计算机上可以达到196帧/min;对带宽为56kb/s的拨号上网用户无法实现实时监视功能,只能使用截图方式对其监视。
结语
经过二十一天的设计和开发,远程屏幕监视软件EagleEye基本开发完毕。其功能基本符合需求,但由于设计时间太短,该软件还有许多不尽如人意的地方,比如出错处理不够周全,图像传输效率不是很高等一些方面问题,这些都有待进一步改善。
通过这软件设计,巩固了编程开发工具Borland C++ Builder 6.0的使用技能,它使用面向对象的开发技术,能够轻松开发出功能强大的应用程序。使用与Borland C++ Builder 6.0相配套的第三方面控件能够快速高效地制作美观的用户界面。使用其自带的相关控件可以快速、随意地制作出用户需要的各种形式的程序模块。
最后感谢《头老摺犯艺獯瓮陡宓幕帷T诖耍蚬笊绲墓愦蠖琳吲笥衙恰⒈嗉潜硎咀钪孕牡母行弧P形牟执伲缬胁蛔愦砦笾Γ肱乐附蹋篍mail:liuyit-123@163.com
Socket:如果软件在你的计算机无法编译,原因是你计算机上的C++Builder没有安装SUIpackL控件或控件的搜索路径与我的计算机的路径不同。安装后控件后修改EagleEye工程代码中的视图资源文件的路径为你计算机上SUIpack的搜索路径即可。
(文中涉及到的程序及源代码已收录到杂志配套光盘“杂志相关”栏目,按文章名查找即可)