就好比,机房老师在自己计算机上演示,学生在自己的计算机上可以看到老师的桌面,不知道用软件是如何实现。

解决方案 »

  1.   

    用SOCKET实现啦。也就是类似刷屏机的原理,每隔几秒钟就去取一次教师电脑的桌面图像,这个不难实现的。找一些网络方面的书看看,应该会有介绍的。
    DELPHI:实现远程屏幕抓取  
    ---- 在网络管理中,有时需要通过监视远程计算机屏幕来了解网上微机的使用情况。
    虽然,市面上有很多软件可以实现该功能,有些甚至可以进行远程控制,但在使用上
    缺乏灵活性,如无法指定远程计算机屏幕区域的大小和位置,进而无法在一屏上同时
    监视多个屏幕。其实,可以用Delphi自行编制一个灵活的远程屏幕抓取工具,简述如下。 ---- 一、软硬件要求。 ---- Windows95/98对等网,用来监视的计算机(以下简称主控机)和被监视的计算机
    (以下简称受控机)都必须装有TCP/IP 协议,并正确配置。如没有网络,也可以在一台计
    算机上进行调试。 ---- 二、实现方法。 ---- 编制两个应用程序,一个为VClient.exe,装在受控机上,另一个为VServer.exe,
    装在主控机上。VServer.exe指定要监视的受控机的IP地址和将要在受控机屏幕上抓取区
    域的大小和位置,并发出屏幕抓取指令给VClient.exe,VClient.exe得到指令后,在受控
    机屏幕上选取指定区域,生成数据流,将其发回主控机,并在主控机上显示出抓取区域
    的BMP图象。由以上过程可以看出,该方法的关键有二:一是如何在受控机上进行屏幕抓
    取,二是如何通过TCP/IP协议在两台计算机中传输数据。 ---- UDP(User Datagram Protocol,意为用户报文协议)是Internet上广泛采用的通信
    协议之一。与TCP协议不同,它是一种非连接的传输协议,没有确认机制,可靠性不如
    TCP,但它的效率却比TCP高,用于远程屏幕监视还是比较适合的。同时,UDP控件不区
    分服务器端和客户端,只区分发送端和接收端,编程上较为简单,故选用UDP协议,使
    用Delphi 4.0提供的TNMUDP控件。 ---- 三、创建演示程序。 ---- 第一步,编制VClient.exe文件。新建Delphi工程,将默认窗体的Name属性设为
    “Client”。加入TNMUDP控件,Name属性设为“CUDP”;LocalPort属性设为“1111”
    ,让控件CUDP监视受控机的1111端口,当有数据发送到该口时,触发控件CUDP的
    OnDataReceived事件;RemotePort属性设为“2222”,当控件CUDP发送数据时,将数
    据发到主控机的2222口。 ---- 在implementation后面加入变量定义 const BufSize=2048;{ 发送每一笔数据的缓冲区大小 } 
    var 
    BmpStream:TMemoryStream; 
    LeftSize:Longint;{ 发送每一笔数据后剩余的字节数 } 为Client的OnCreate事件添加代码: 
    procedure TClient.FormCreate(Sender: TObject); 
    begin 
    BmpStream:=TMemoryStream.Create; 
    end; 为Client的OnDestroy事件添加代码: 
    procedure TClient.FormDestroy(Sender: TObject); 
    begin 
    BmpStream.Free; 
    end; 为控件CUDP的OnDataReceived事件添加代码: 
    procedure TClient.CUDPDataReceived(Sender: TComponent; 
    NumberBytes: Integer; FromIP: String); 
    var 
    CtrlCode:array[0..29] of char; 
    Buf:array[0..BufSize-1] of char; 
    TmpStr:string; 
    SendSize,LeftPos,TopPos,RightPos,BottomPos:integer; 
    begin 
    CUDP.ReadBuffer(CtrlCode,NumberBytes);{ 读取控制码 } 
    if CtrlCode[0]+CtrlCode[1]+CtrlCode[2]+CtrlCode[3]='show' then 
    begin { 控制码前4位为“show”表示主控机发出了抓屏指令 } 
    if BmpStream.Size=0 then { 没有数据可发,必须截屏生成数据 } 
    begin 
    TmpStr:=StrPas(CtrlCode); 
    TmpStr:=Copy(TmpStr,5,Length(TmpStr)-4); 
    LeftPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1)); 
    TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr) 
    -Pos(':',TmpStr)); 
    TopPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1)); 
    TmpStr:=Copy(TmpStr,Pos(':',TmpStr)+1,Length(TmpStr)- 
    Pos(':',TmpStr)); 
    RightPos:=StrToInt(Copy(TmpStr,1,Pos(':',TmpStr)-1)); 
    BottomPos:=StrToInt(Copy(TmpStr,Pos(':',TmpStr 
    )+1,Length(TmpStr)-Pos(':',TmpStr))); 
    ScreenCap(LeftPos,TopPos,RightPos,BottomPos); { 
    截取屏幕 } 
    end; 
    if LeftSize>BufSize then SendSize:=BufSize 
    else SendSize:=LeftSize; 
    BmpStream.ReadBuffer(Buf,SendSize); 
    LeftSize:=LeftSize-SendSize; 
    if LeftSize=0 then BmpStream.Clear;{ 清空流 } 
    CUDP.RemoteHost:=FromIP; { FromIP为主控机IP地址 } 
    CUDP.SendBuffer(Buf,SendSize); { 将数据发到主控机的2222口 } 
    end; 
    end; 其中ScreenCap是自定义函数,截取屏幕指定区域, 
    代码如下: 
    procedure TClient.ScreenCap(LeftPos,TopPos, 
    RightPos,BottomPos:integer); 
    var 
    RectWidth,RectHeight:integer; 
    SourceDC,DestDC,Bhandle:integer; 
    Bitmap:TBitmap; 
    begin 
    RectWidth:=RightPos-LeftPos; 
    RectHeight:=BottomPos-TopPos; 
    SourceDC:=CreateDC('DISPLAY','','',nil); 
    DestDC:=CreateCompatibleDC(SourceDC); 
    Bhandle:=CreateCompatibleBitmap(SourceDC, 
    RectWidth,RectHeight); 
    SelectObject(DestDC,Bhandle); 
    BitBlt(DestDC,0,0,RectWidth,RectHeight,SourceDC, 
    LeftPos,TopPos,SRCCOPY); 
    Bitmap:=TBitmap.Create; 
    Bitmap.Handle:=BHandle; 
    BitMap.SaveToStream(BmpStream); 
    BmpStream.Position:=0; 
    LeftSize:=BmpStream.Size; 
    Bitmap.Free; 
    DeleteDC(DestDC); 
    ReleaseDC(Bhandle,SourceDC); 
    end; 
    存为“C:VClientClnUnit.pas”和“C:VClientVClient.dpr”, 
    并编译。   ---- 第二步,编制VServer.exe文件。新建Delphi工程,将窗体的Name属性设为“Server”。加入TNMUDP控件,Name属性设为“SUDP”;LocalPort属性设为“2222”,让控件SUDP监视主控机的2222端口,当有数据发送到该口时,触发控件SUDP的OnDataReceived事件;RemotePort属性设为“1111”,当控件SUDP发送数据时,将数据发到受控机的1111口。加入控件Image1,Align属性设为“alClient”;加入控件Button1,Caption属性设为“截屏”;加入控件Label1,Caption属性设为“左:上:右:下”;加入控件Edit1,Text属性设为“0:0:100:100”;加入控件Label2,Caption属性设为“受控机IP地址”;加入控件Edit2,Text属性设为“127.0.0.1”; 在implementation后面加入变量定义 
    const BufSize=2048; 
    var 
    RsltStream,TmpStream:TMemoryStream; 为Server的OnCreate事件添加代码: 
    procedure TServer.FormCreate(Sender: TObject); 
    begin 
    RsltStream:=TMemoryStream.Create; 
    TmpStream:=TMemoryStream.Create; 
    end; 为Client的OnDestroy事件添加代码: 
    procedure TServer.FormDestroy(Sender: TObject); 
    begin 
    RsltStream.Free; 
    TmpStream.Free; 
    end; 为控件Button1的OnClick事件添加代码: 
    procedure TServer.Button1Click(Sender: TObject); 
    var ReqCode:array[0..29] of char;ReqCodeStr:string; 
    begin 
    ReqCodeStr:='show'+Edit1.Text; 
    StrpCopy(ReqCode,ReqCodeStr); 
    TmpStream.Clear; 
    RsltStream.Clear; 
    SUDP.RemoteHost:=Edit2.Text; 
    SUDP.SendBuffer(ReqCode,30); 
    end; 为控件SUDP的OnDataReceived事件添加代码: 
    procedure TServer.SUDPDataReceived(Sender: TComponent; 
    NumberBytes: Integer; FromIP: String); 
    var ReqCode:array[0..29] of char;ReqCodeStr:string; 
    begin 
    ReqCodeStr:='show'+Edit1.text; 
    StrpCopy(ReqCode,ReqCodeStr); 
    SUDP.ReadStream(TmpStream); 
    RsltStream.CopyFrom(TmpStream,NumberBytes); 
    if NumberBytes< BufSize then { 数据已读完 } 
    begin 
    RsltStream.Position:=0; 
    Image1.Picture.Bitmap.LoadFromStream(RsltStream); 
    TmpStream.Clear; 
    RsltStream.Clear; 
    end 
    else 
    begin 
    TmpStream.Clear; 
    ReqCode:='show'; 
    SUDP.RemoteHost:=Edit2.Text; 
    SUDP.SendBuffer(ReqCode,30); 
    end; 
    end; 存为“C:VServerSvrUnit.pas”和 
    “C:VServerVServer.dpr”,并编译。 
      ---- 四、测试。 ---- 1、本地机测试:在本地机同时运行Vserver.exe和VClient.exe,利用程序的默
    认设置,即可实现截屏。查看“控制面板”-“网络”-“TCP/IP”-“IP地址”,将
    程序的“客户IP地址”设为该地址 ,同样正常运行。 ---- 2、远程测试:选一台受控机,运行VClient.exe;另选一台主控机,运行
    VServer.exe,将“受控机IP地址”即Edit2的内容设为受控机的IP地址,“截屏”
    即可。以上简要介绍了远程屏幕抓取的实现方法,至于在主控机上一屏同时监视多
    个受控机,读者可自行完善。
      

  2.   

    我也同意: liangzisiyun(良子) 的
      

  3.   

    感谢,liangzisiyun(良子)全面的回答,不过我想那样的话数据量传播的很大,有什么压缩的技巧,因为老师是广播教学,下面的学生机可能有100台呢
      

  4.   

    利用流实现网络传输屏幕图像  大家应该见过很多网管程序,这类程序其中有一个功能就是监控远程电脑的屏幕。实际上,这也是利用流操作来实现的。下面我们给出一个例子,这个例子分两个程序,一个服务端,一个是客户端。程序编译后可以直接在单机、局部网或者互联网上使用。程序中已经给出相应注释。后面我们再来作具体分析。
      新建一个工程,在Internet面版上拖一个ServerSocket控件到窗口,该控件主要用于监听客户端,用来与客户端建立连接和通讯。设置好监听端口后调用方法Open或者Active:=True即开始工作。注意:跟前面的NMUDP不同,当Socket开始监听后就不能再改变它的端口,要改变的话必须先调用Close或设置Active为False,否则将会产生异常。另外,如果该端口已经打开的话,就不能再用这个端口了。所以程序运行尚未退出就不能再运行这个程序,否则也会产生异常,即弹出出错窗口。实际应用中可以通过判断程序是否已经运行,如果已经运行就退出的方法来避免出错。
      当客户端有数据传入,将触发ServerSocket1ClientRead事件,我们可以在这里对接收的数据进行处理。在本程序中,主要是接收客户端发送过来的字符信息并根据事先的约定来进行相应操作。
    程序全部代码如下:unit Unit1;{服务端程序}
    interface
    uses
    Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, JPEG,ExtCtrls, ScktComp;
    type
    TForm1 = class(TForm)
    ServerSocket1: TServerSocket;
    procedure ServerSocket1ClientRead(Sender: TObject;Socket: TCustomWinSocket);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    private
    procedure Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
    {自定义抓屏函数,DrawCur表示抓鼠标图像与否}
    { Private declarations }
    public
    { Public declarations }
    end;
    var
    Form1: TForm1;
    MyStream: TMemorystream;{内存流对象} 
    implementation
    {$R *.DFM}
    procedure TForm1.Cjt_GetScreen(var Mybmp: TBitmap; DrawCur: Boolean);
    var
    Cursorx, Cursory: integer;
    dc: hdc;
    Mycan: Tcanvas;
    R: TRect;
    DrawPos: TPoint;
    MyCursor: TIcon;
    hld: hwnd;
    Threadld: dword;
    mp: tpoint;
    pIconInfo: TIconInfo;
    begin
    Mybmp := Tbitmap.Create; {建立BMPMAP }
    Mycan := TCanvas.Create; {屏幕截取}
    dc := GetWindowDC(0);
    try
    Mycan.Handle := dc;
    R := Rect(0, 0, screen.Width, screen.Height);
    Mybmp.Width := R.Right;
    Mybmp.Height := R.Bottom;
    Mybmp.Canvas.CopyRect(R, Mycan, R);
    finally
    releaseDC(0, DC);
    end;
    Mycan.Handle := 0;
    Mycan.Free;
    if DrawCur then {画上鼠标图象}
    begin
    GetCursorPos(DrawPos);
    MyCursor := TIcon.Create;
    getcursorpos(mp);
    hld := WindowFromPoint(mp);
    Threadld := GetWindowThreadProcessId(hld, nil);
    AttachThreadInput(GetCurrentThreadId, Threadld, True);
    MyCursor.Handle := Getcursor();
    AttachThreadInput(GetCurrentThreadId, threadld, False);
    GetIconInfo(Mycursor.Handle, pIconInfo);
    cursorx := DrawPos.x - round(pIconInfo.xHotspot);
    cursory := DrawPos.y - round(pIconInfo.yHotspot);
    Mybmp.Canvas.Draw(cursorx, cursory, MyCursor); {画上鼠标}
    Mycursor.ReleaseHandle; {释放数组内存}
    MyCursor.Free; {释放鼠标指针}
    end; 
    end;
    procedure TForm1.FormCreate(Sender: TObject);
    begin
    ServerSocket1.Port := 3000; {端口}
    ServerSocket1.Open; {Socket开始侦听}
    end;
    procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
    if ServerSocket1.Active then ServerSocket1.Close; {关闭Socket}
    end;
    procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
    Socket: TCustomWinSocket);
    var
    S, S1: string;
    MyBmp: TBitmap;
    Myjpg: TJpegimage;
    begin
    S := Socket.ReceiveText;
    if S = 'cap' then {客户端发出抓屏幕指令}
    begin
    try
    MyStream := TMemorystream.Create;{建立内存流}
    MyBmp := TBitmap.Create;
    Myjpg := TJpegimage.Create;
    Cjt_GetScreen(MyBmp, True); {True表示抓鼠标图像}
    Myjpg.Assign(MyBmp); {将BMP图象转成JPG格式,便于在互联网上传输}
    Myjpg.CompressionQuality := 10; {JPG文件压缩百分比设置,数字越大图像月清晰,但数据也越大}
    Myjpg.SaveToStream(MyStream); {将JPG图象写入流中}
    Myjpg.free;
    MyStream.Position := 0;{注意:必须添加此句}
    s1 := inttostr(MyStream.size);{流的大小}
    Socket.sendtext(s1); {发送流大小}
    finally
    MyBmp.free;
    end;
    end;
    if s = 'ready' then {客户端已准备好接收图象}
    begin
    MyStream.Position := 0;
    Socket.SendStream(MyStream); {将流发送出去}
    end;
    end;
    end.  上面是服务端,下面我们来写客户端程序。新建一个工程,添加Socket控件ClientSocket、图像显示控件Image、一个 Panel 、一个Edit、两个 Button和一个状态栏控件StatusBar1。注意:把Edit1和两个 Button放在Panel1上面。ClientSocket的属性跟ServerSocket差不多,不过多了一个Address属性,表示要连接的服务端IP地址。填上IP地址后点“连接”将与服务端程序建立连接,如果成功就可以进行通讯了。点击“抓屏”将发送字符给服务端。因为程序用到了JPEG图像单元,所以要在Uses中添加Jpeg.
      

  5.   

    全部代码如下:
    unit Unit2{客户端};
    interface
    uses
    Windows,Messages,SysUtils,Classes,Graphics,Controls,Forms,Dialogs,StdCtrls,ScktComp,ExtCtrls,Jpeg, ComCtrls;
    type
    TForm1 = class(TForm)
    ClientSocket1: TClientSocket;
    Image1: TImage;
    StatusBar1: TStatusBar;
    Panel1: TPanel;
    Edit1: TEdit;
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure ClientSocket1Connect(Sender: TObject;
    Socket: TCustomWinSocket);
    procedure Button2Click(Sender: TObject);
    procedure ClientSocket1Error(Sender: TObject; Socket: TCustomWinSocket;
    ErrorEvent: TErrorEvent; var ErrorCode: Integer);
    procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket);
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure ClientSocket1Disconnect(Sender: TObject;
    Socket: TCustomWinSocket);
    private
    { Private declarations }
    public
    { Public declarations }
    end;
    var
    Form1: TForm1;
    MySize: Longint;
    MyStream: TMemorystream;{内存流对象}
    implementation
    {$R *.DFM}
    procedure TForm1.FormCreate(Sender: TObject);
    begin
    {-------- 下面为设置窗口控件的外观属性 ------------- }
    {注意:把Button1、Button2和Edit1放在Panel1上面}
    Edit1.Text := '127.0.0.1';
    Button1.Caption := '连接主机';
    Button2.Caption := '抓屏幕';
    Button2.Enabled := false;
    Panel1.Align := alTop;
    Image1.Align := alClient;
    Image1.Stretch := True;
    StatusBar1.Align:=alBottom;
    StatusBar1.SimplePanel := True;
    {----------------------------------------------- }
    MyStream := TMemorystream.Create; {建立内存流对象}
    MySize := 0; {初始化}
    end;
    procedure TForm1.Button1Click(Sender: TObject);
    begin
    if not ClientSocket1.Active then
    begin
    ClientSocket1.Address := Edit1.Text; {远程IP地址}
    ClientSocket1.Port := 3000; {Socket端口}
    ClientSocket1.Open; {建立连接}
    end;
    end;
    procedure TForm1.Button2Click(Sender: TObject);
    begin
    Clientsocket1.Socket.SendText('cap'); {发送指令通知服务端抓取屏幕图象}
    Button2.Enabled := False;
    end;
    procedure TForm1.ClientSocket1Connect(Sender: TObject;
    Socket: TCustomWinSocket);
    begin
    StatusBar1.SimpleText := '与主机' + ClientSocket1.Address + '成功建立连接!';
    Button2.Enabled := True;
    end;
    procedure TForm1.ClientSocket1Error(Sender: TObject;
    Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
    var ErrorCode: Integer);
    begin
    Errorcode := 0; {不弹出出错窗口}
    StatusBar1.SimpleText := '无法与主机' + ClientSocket1.Address + '建立连接!';
    end;
    procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
    Socket: TCustomWinSocket);
    begin
    StatusBar1.SimpleText := '与主机' + ClientSocket1.Address + '断开连接!';
    Button2.Enabled := False;
    end;
    procedure TForm1.ClientSocket1Read(Sender: TObject;
    Socket: TCustomWinSocket);
    var
    MyBuffer: array[0..10000] of byte; {设置接收缓冲区}
    MyReceviceLength: integer;
    S: string;
    MyBmp: TBitmap;
    MyJpg: TJpegimage;
    begin
    StatusBar1.SimpleText := '正在接收数据......';
    if MySize = 0 then {MySize为服务端发送的字节数,如果为0表示为尚未开始图象接收}
    begin
    S := Socket.ReceiveText;
    MySize := Strtoint(S); {设置需接收的字节数}
    Clientsocket1.Socket.SendText('ready'); {发指令通知服务端开始发送图象}
    end
    else
    begin {以下为图象数据接收部分}
    MyReceviceLength := socket.ReceiveLength; {读出包长度}
    StatusBar1.SimpleText := '正在接收数据,数据大小为:' + inttostr(MySize);
    Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {接收数据包并读入缓冲区内}
    MyStream.Write(MyBuffer, MyReceviceLength); {将数据写入流中}
    if MyStream.Size >= MySize then {如果流长度大于需接收的字节数,则接收完毕}
    begin
    MyStream.Position := 0;
    MyBmp := tbitmap.Create;
    MyJpg := tjpegimage.Create;
    try
    MyJpg.LoadFromStream(MyStream); {将流中的数据读至JPG图像对象中}
    MyBmp.Assign(MyJpg); {将JPG转为BMP}
    StatusBar1.SimpleText := '正在显示图像';
    Image1.Picture.Bitmap.Assign(MyBmp); {分配给image1元件 }
    finally {以下为清除工作 }
    MyBmp.free;
    MyJpg.free;
    Button2.Enabled := true;
    { Socket.SendText('cap');添加此句即可连续抓屏 }
    MyStream.Clear;
    MySize := 0;
    end;
    end;
    end;
    end;
    procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
    begin
    MyStream.Free; {释放内存流对象}
    if ClientSocket1.Active then ClientSocket1.Close; {关闭Socket连接}
    end;
    end.  程序原理:运行服务端开始侦听,再运行客户端,输入服务端IP地址建立连接,然后发一个字符通知服务端抓屏幕。服务端调用自定义函数Cjt_GetScreen抓取屏幕存为BMP,把BMP转换成JPG,把JPG写入内存流中,然后把流发送给客户端。客户端接收到流后做相反操作,将流转换为JPG再转换为BMP然后显示出来。
      注意:因为Socket的限制,不能一次发送过大的数据,只能分几次发。所以程序中服务端抓屏转换为流后先发送流的大小,通知客户端这个流共有多大,客户端根据这个数字大小来判断是否已经接收完流,如果接收完才转换并显示。
      这个程序跟前面的自制OICQ都是利用了内存流对象TMemoryStream。其实,这个流对象是程序设计中用得最普遍的,它可以提高I/O的读写能力,而且如果你要同时操作几个不同类型的流,互相交换数据的话,用它作“中间人”是最好不过的了。比如说你把一个流压缩或者解压缩,就先建立一个TMemoryStream对象,然后把别的数据拷贝进去,再执行相应操作就可以了。因为它是直接在内存中工作,所以效率是非常高的。有时侯甚至你感觉不到有任何的延迟。
      程序有待改进的地方:当然可以加一个压缩单元,发送前先压缩再发送。注意:这里也是有技巧的,就是直接把BMP压缩而不要转换成JPG再压。实验证明:上面程序一幅图像大小大概为40-50KB,如果用LAH压缩算法处理一下便只有8-12KB,这样传输起来就比较快。如果想更快的话,可以采用这样的方法:先抓第一幅图像发送,然后从第二幅开始只发跟前一幅不同区域的图像。外国有一个程序叫Remote Administrator,就是采用这样的方法。他们测试的数据如下:局部网一秒钟100-500幅,互联网上,在网速极低的情况下,一秒钟传输5-10幅。说这些题外话只想说明一个道理:想问题,特别是写程序,特别是看起来很复杂的程序,千万不要钻牛角尖,有时侯不妨换个角度来想。程序是死的,人才是活的。当然,这些只能靠经验的积累。但是一开始就养成好习惯是终身受益的!