a亚洲精品_精品国产91乱码一区二区三区_亚洲精品在线免费观看视频_欧美日韩亚洲国产综合_久久久久久久久久久成人_在线区

首頁 > 編程 > Delphi > 正文

談Delphi編程中“流”的應(yīng)用

2019-11-18 18:41:43
字體:
供稿:網(wǎng)友
          談Delphi編程中“流”的應(yīng)用

                                                   陳經(jīng)韜


   什么是流?流,簡單來說就是建立在面向?qū)ο蠡A(chǔ)上的一種抽象的處理數(shù)據(jù)的工具。在流中,定義了一些處理數(shù)據(jù)的基本操作,如讀取數(shù)據(jù),寫入數(shù)據(jù)等,程序員是對流進(jìn)行所有操作的,而不用關(guān)心流的另一頭數(shù)據(jù)的真正流向。流不但可以處理文件,還可以處理動態(tài)內(nèi)存、網(wǎng)絡(luò)數(shù)據(jù)等多種數(shù)據(jù)形式。如果你對流的操作非常熟練,在程序中利用流的方便性,寫起程序會大大提高效率的。
  下面,筆者通過四個(gè)實(shí)例:EXE文件加密器、電子賀卡、自制OICQ和網(wǎng)絡(luò)屏幕傳輸來說明Delphi編程中“流”的利用。這些例子中的一些技巧曾經(jīng)是很多軟件的秘密而不公開的,現(xiàn)在大家可以無償?shù)闹苯右闷渲械拇a了。
  “萬丈高樓平地起”,在分析實(shí)例之前,我們先來了解一下流的基本概念和函數(shù),只有在理解了這些基本的東西后我們才能進(jìn)行下一步。請務(wù)必認(rèn)真領(lǐng)會這些基本方法。當(dāng)然,如果你對它們已經(jīng)很熟悉了,則可以跳過這一步。

一、Delphi中流的基本概念及函數(shù)聲明
在Delphi中,所有流對象的基類為TStream類,其中定義了所有流的共同屬性和方法。
TStream類中定義的屬性介紹如下:
1、Size:此屬性以字節(jié)返回流中數(shù)據(jù)大小。
2、Position:此屬性控制流中存取指針的位置。
Tstream中定義的虛方法有四個(gè):
1、Read:此方法實(shí)現(xiàn)將數(shù)據(jù)從流中讀出。函數(shù)原形為:
Function Read(var Buffer;Count:Longint):Longint;virtual;abstract;
參數(shù)Buffer為數(shù)據(jù)讀出時(shí)放置的緩沖區(qū),Count為需要讀出的數(shù)據(jù)的字節(jié)數(shù),該方法返回值為實(shí)際讀出的字節(jié)數(shù),它可以小于或等于Count中指定的值。
2、Write:此方法實(shí)現(xiàn)將數(shù)據(jù)寫入流中。函數(shù)原形為:
Function Write(var Buffer;Count:Longint):Longint;virtual;abstract;
參數(shù)Buffer為將要寫入流中的數(shù)據(jù)的緩沖區(qū),Count為數(shù)據(jù)的長度字節(jié)數(shù),該方法返回值為實(shí)際寫入流中的字節(jié)數(shù)。
3、Seek:此方法實(shí)現(xiàn)流中讀取指針的移動。函數(shù)原形為:
Function Seek(Offset:Longint;Origint:Word):Longint;virtual;abstract;
參數(shù)Offset為偏移字節(jié)數(shù),參數(shù)Origint指出Offset的實(shí)際意義,其可能的取值如下:
soFromBeginning:Offset為移動后指針距離數(shù)據(jù)開始的位置。此時(shí)Offset必須大于或者等于零。
soFromCurrent:Offset為移動后指針與當(dāng)前指針的相對位置。
soFromEnd:Offset為移動后指針距離數(shù)據(jù)結(jié)束的位置。此時(shí)Offset必須小于或者等于零。該方法返回值為移動后指針的位置。
4、Setsize:此方法實(shí)現(xiàn)改變數(shù)據(jù)的大小。函數(shù)原形為:
Function Setsize(NewSize:Longint);virtual;
另外,TStream類中還定義了幾個(gè)靜態(tài)方法:
1、ReadBuffer:此方法的作用是從流中當(dāng)前位置讀取數(shù)據(jù)。函數(shù)原形為:
PRocedure ReadBuffer(var Buffer;Count:Longint);
參數(shù)的定義跟上面的Read相同。注意:當(dāng)讀取的數(shù)據(jù)字節(jié)數(shù)與需要讀取的字節(jié)數(shù)不相同時(shí),將產(chǎn)生EReadError異常。
2、WriteBuffer:此方法的作用是在當(dāng)前位置向流寫入數(shù)據(jù)。函數(shù)原形為:
Procedure WriteBuffer(var Buffer;Count:Longint);
參數(shù)的定義跟上面的Write相同。注意:當(dāng)寫入的數(shù)據(jù)字節(jié)數(shù)與需要寫入的字節(jié)數(shù)不相同時(shí),將產(chǎn)生EWriteError異常。
3、CopyFrom:此方法的作用是從其它流中拷貝數(shù)據(jù)流。函數(shù)原形為:
Function CopyFrom(Source:TStream;Count:Longint):Longint;
參數(shù)Source為提供數(shù)據(jù)的流,Count為拷貝的數(shù)據(jù)字節(jié)數(shù)。當(dāng)Count大于0時(shí),CopyFrom從Source參數(shù)的當(dāng)前位置拷貝Count個(gè)字節(jié)的數(shù)據(jù);當(dāng)Count等于0時(shí),CopyFrom設(shè)置Source參數(shù)的Position屬性為0,然后拷貝Source的所有數(shù)據(jù);
TStream還有其它派生類,其中最常用的是TFileStream類。使用TFileStream類來存取文件,首先要建立一個(gè)實(shí)例。聲明如下:
constructor Create(const Filename:string;Mode:Word);
Filename為文件名(包括路徑),參數(shù)Mode為打開文件的方式,它包括文件的打開模式和共享模式,其可能的取值和意義如下:

打開模式:
fmCreate :用指定的文件名建立文件,如果文件已經(jīng)存在則打開它。
fmOpenRead :以只讀方式打開指定文件
fmOpenWrite :以只寫方式打開指定文件
fmOpenReadWrite:以寫寫方式打開指定文件
共享模式:
fmShareCompat :共享模式與FCBs兼容
fmShareExclusive:不允許別的程序以任何方式打開該文件
fmShareDenyWrite:不允許別的程序以寫方式打開該文件
fmShareDenyRead :不允許別的程序以讀方式打開該文件
fmShareDenyNone :別的程序可以以任何方式打開該文件

TStream還有一個(gè)派生類TMemoryStream,實(shí)際應(yīng)用中用的次數(shù)也非常頻繁。它叫內(nèi)存流,就是說在內(nèi)存中建立一個(gè)流對象。它的基本方法和函數(shù)跟上面是一樣的。
好了,有了上面的基礎(chǔ)后,我們就可以開始我們的編程之行了。
-----------------------------------------------------------------------
二、實(shí)際應(yīng)用之一:利用流制作EXE文件加密器、捆綁、自解壓文件及安裝程序

  我們先來說一下如何制作一個(gè)EXE文件加密器吧。 
  EXE文件加密器的原理:建立兩個(gè)文件,一個(gè)用來添加資源到另外一個(gè)EXE文件里面,稱為添加程序。另外一個(gè)被添加的EXE文件稱為頭文件。該程序的功能是把添加到自己里面的文件讀出來。Windows下的EXE文件結(jié)構(gòu)比較復(fù)雜,有的程序還有校驗(yàn)和,當(dāng)發(fā)現(xiàn)自己被改變后會認(rèn)為自己被病毒感染而拒絕執(zhí)行。所以我們把文件添加到自己的程序里面,這樣就不會改變原來的文件結(jié)構(gòu)了。我們先寫一個(gè)添加函數(shù),該函數(shù)的功能是把一個(gè)文件當(dāng)作一個(gè)流添加到另外一個(gè)文件的尾部。函數(shù)如下:

Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Target,Source:TFileStream;
MyFileSize:integer;
begin
try
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);
try
Target.Seek(0,soFromEnd);//往尾部添加資源
Target.CopyFrom(Source,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//計(jì)算資源大小,并寫入輔程尾部
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finally
Target.Free;
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
有了上面的基礎(chǔ),我們應(yīng)該很容易看得懂這個(gè)函數(shù)。其中參數(shù)SourceFile是要添加的文件,參數(shù)TargetFile是被添加到的目標(biāo)文件。比如說把a(bǔ).exe添加到b.exe里面可以:Cjt_AddtoFile('a.exe',b.exe');如果添加成功就返回True否則返回假。
根據(jù)上面的函數(shù)我們可以寫出相反的讀出函數(shù):
Function Cjt_LoadFromFile(SourceFile,TargetFile :string):Boolean;
var
Source:TFileStream;
Target:TMemoryStream;
MyFileSize:integer;
begin
try
Target:=TMemoryStream.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//讀出資源大小
Source.Seek(-MyFileSize,soFromEnd);//定位到資源位置
Target.CopyFrom(Source,MyFileSize-sizeof(MyFileSize));//取出資源
Target.SaveToFile(TargetFile);//存放到文件
finally
Target.Free;
Source.Free;
end;
except
Result:=false;
Exit;
end;
Result:=true;
end;
  其中參數(shù)SourceFile是已經(jīng)添加了文件的文件名稱,參數(shù)TargetFile是取出文件后保存的目標(biāo)文件名。比如說Cjt_LoadFromFile('b.exe','a.txt');在b.exe中取出文件保存為a.txt。如果取出成功就返回True否則返回假。
打開Delphi,新建一個(gè)工程,在窗口上放上一個(gè)Edit控件Edit1和兩個(gè)Button:Button1和Button2。Button的Caption屬性分別設(shè)置為“確定”和“取消”。在Button1的Click事件中寫代碼:
var S:string;
begin
S:=ChangeFileExt(application.ExeName,'.Cjt');
if Edit1.Text='790617' then 
begin
Cjt_LoadFromFile(Application.ExeName,S);
{取出文件保存在當(dāng)前路徑下并命名"原文件.Cjt"}
Winexec(pchar(S),SW_Show);{運(yùn)行"原文件.Cjt"}
Application.Terminate;{退出程序}
end
else 
Application.MessageBox('密碼不對,請重新輸入!','密碼錯(cuò)誤',MB_ICONERROR+MB_OK);
  編譯這個(gè)程序,并把EXE文件改名為head.exe。新建一個(gè)文本文件head.rc,內(nèi)容為: head exefile head.exe,然后把它們拷貝到Delphi的BIN目錄下,執(zhí)行Dos命令Brcc32.exe head.rc,將產(chǎn)生一個(gè)head.res的文件,這個(gè)文件就是我們要的資源文件,先留著。
  我們的頭文件已經(jīng)建立了,下面我們來建立添加程序。
  新建一個(gè)工程,放上以下控件:一個(gè)Edit,一個(gè)Opendialog,兩個(gè)Button1的Caption屬性分別設(shè)置為"選擇文件"和"加密"。在源程序中添加一句:{$R head.res}并把head.res文件拷貝到程序當(dāng)前目錄下。這樣一來就把剛才的head.exe跟程序一起編譯了。
  在Button1的Cilck事件里面寫下代碼:
if OpenDialog1.Execute then Edit1.Text:=OpenDialog1.FileName;
  在Button2的Cilck事件里面寫下代碼:
var S:String;
begin
S:=ExtractFilePath(Edit1.Text);
if ExtractRes('exefile','head',S+'head.exe') then
if Cjt_AddtoFile(Edit1.Text,S+'head.exe') then
if DeleteFile(Edit1.Text) then
if RenameFile(S+'head.exe',Edit1.Text) then
Application.MessageBox('文件加密成功!','信息',MB_ICONINFORMATION+MB_OK)
else
begin
if FileExists(S+'head.exe') then DeleteFile(S+'head.exe');
Application.MessageBox('文件加密失敗!','信息',MB_ICONINFORMATION+MB_OK)
end;
end;
其中ExtractRes為自定義函數(shù),它的作用是把head.exe從資源文件中取出來。
Function ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res : TResourceStream;
begin
try
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
try
Res.SavetoFile(ResNewName);
Result:=true;
finally
Res.Free;
end;
except
Result:=false;
end;
end;
   注意:我們上面的函數(shù)只不過是簡單的把一個(gè)文件添加到另一個(gè)文件的尾部。實(shí)際應(yīng)用中可以改成可以添加多個(gè)文件,只要根據(jù)實(shí)際大小和個(gè)數(shù)定義好偏移地址就可以了。比如說文件捆綁機(jī)就是把兩個(gè)或者多個(gè)程序添加到一個(gè)頭文件里面。那些自解壓程序和安裝程序的原理也是一樣的,不過多了壓縮而已。比如說我們可以引用一個(gè)LAH單元,把流壓縮后再添加,這樣文件就會變的很小。讀出來時(shí)先解壓就可以了。另外,文中EXE加密器的例子還有很多不完善的地方,比如說密碼固定為"790617",取出EXE運(yùn)行后應(yīng)該等它運(yùn)行完畢后刪除等等,讀者可以自行修改。

---------------------------------------------------------------------
三、實(shí)際應(yīng)用之二:利用流制作可執(zhí)行電子賀卡

  我們經(jīng)常看到一些電子賀卡之類的制作軟件,可以讓你自己選擇圖片,然后它會生成一個(gè)EXE可執(zhí)行文件給你。打開賀卡時(shí)就會一邊放音樂一邊顯示出圖片來。現(xiàn)在學(xué)了流操作之后,我們也可以做一個(gè)了。 
  添加圖片過程我們可以直接用前面的Cjt_AddtoFile,而現(xiàn)在要做的是如何把圖像讀出并顯示。我們用前面的Cjt_LoadFromFile先把圖片讀出來保存為文件再調(diào)入也是可以的,但是還有更簡單的方法,就是直接把文件流讀出來顯示,有了流這個(gè)利器,一切都變的簡單了。
  現(xiàn)在的圖片比較流行的是BMP格式和JPG格式。我們現(xiàn)在就針對這兩種圖片寫出讀取并顯示函數(shù)。

Function Cjt_BmpLoad(ImgBmp:TImage;SourceFile:String):Boolean;
var
Source:TFileStream;
MyFileSize:integer;
begin
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));//讀出資源
Source.Seek(-MyFileSize,soFromEnd);//定位到資源開始位置
ImgBmp.Picture.Bitmap.LoadFromStream(Source);
finally
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
  上面是讀出BMP圖片的,下面的是讀出JPG圖片的函數(shù),因?yàn)橐玫絁PG單元,所以要在程序中添加一句:uses jpeg。

Function Cjt_JpgLoad(JpgImg:Timage;SourceFile:String):Boolean;
var
Source:TFileStream;
MyFileSize:integer;
Myjpg: TJpegImage;
begin
try
Myjpg:= TJpegImage.Create;
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareDenyNone);
try
Source.Seek(-sizeof(MyFileSize),soFromEnd);
Source.ReadBuffer(MyFileSize,sizeof(MyFileSize));
Source.Seek(-MyFileSize,soFromEnd);
Myjpg.LoadFromStream(Source);
JpgImg.Picture.Bitmap.Assign(Myjpg);
finally
Source.Free;
Myjpg.free;
end;
except
Result:=false;
Exit;
end;
Result:=true;
end;
  有了這兩個(gè)函數(shù),我們就可以制作讀出程序了。下面我們以BMP圖片為例:
  運(yùn)行Delphi,新建一個(gè)工程,放上一個(gè)顯示圖像控件Image1。在窗口的Create事件中寫上一句就可以了:
Cjt_BmpLoad(Image1,Application.ExeName);
  這個(gè)就是頭文件了,然后我們用前面的方法生成一個(gè)head.res資源文件。
下面就可以開始制作我們的添加程序了。全部代碼如下:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, ExtDlgs;

type
TForm1 = class(TForm)
Edit1: TEdit;
Button1: TButton;
Button2: TButton;
OpenPictureDialog1: TOpenPictureDialog;
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
private
Function ExtractRes(ResType, ResName, ResNewName : String):boolean;
Function Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}
Function TForm1.ExtractRes(ResType, ResName, ResNewName : String):boolean;
var
Res : TResourceStream;
begin
try
Res := TResourceStream.Create(Hinstance, Resname, Pchar(ResType));
try
Res.SavetoFile(ResNewName);
Result:=true;
finally
Res.Free;
end;
except
Result:=false;
end;
end;
Function TForm1.Cjt_AddtoFile(SourceFile,TargetFile:string):Boolean;
var
Target,Source:TFileStream;
MyFileSize:integer;
begin
try
Source:=TFileStream.Create(SourceFile,fmOpenRead or fmShareExclusive);
Target:=TFileStream.Create(TargetFile,fmOpenWrite or fmShareExclusive);
try
Target.Seek(0,soFromEnd);//往尾部添加資源
Target.CopyFrom(Source,0);
MyFileSize:=Source.Size+Sizeof(MyFileSize);//計(jì)算資源大小,并寫入輔程尾部
Target.WriteBuffer(MyFileSize,sizeof(MyFileSize));
finally
Target.Free;
Source.Free;
end;
except
Result:=False;
Exit;
end;
Result:=True;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Caption:='Bmp2Exe演示程序.作者:陳經(jīng)韜';
Edit1.Text:='';
OpenPictureDialog1.DefaultExt := GraphicExtension(TBitmap);
OpenPictureDialog1.Filter := GraphicFilter(TBitmap);

Button1.Caption:='選擇BMP圖片';
Button2.Caption:='生成EXE';
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
if OpenPictureDialog1.Execute then
Edit1.Text:=OpenPictureDialog1.FileName;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
HeadTemp:String;
begin
if Not FileExists(Edit1.Text) then
begin
Application.MessageBox('BMP圖片文件不存在,請重新選擇!','信息',MB_ICONINFORMATION+MB_OK)
Exit;
end;
HeadTemp:=ChangeFileExt(Edit1.Text,'.exe');
if ExtractRes('exefile','head',HeadTemp) then
if Cjt_AddtoFile(Edit1.Text,HeadTemp) then
Application.MessageBox('EXE文件生成成功!','信息',MB_ICONINFORMATION+MB_OK)
else
begin
if FileExists(HeadTemp) then DeleteFile(HeadTemp);
Application.MessageBox('EXE文件生成失敗!','信息',MB_ICONINFORMATION+MB_OK)
end;
end;
end.
  怎么樣?很神奇吧:)把程序界面弄的漂亮點(diǎn),再添加一些功能,你會發(fā)現(xiàn)比起那些要注冊的軟件來也不會遜多少吧。
-----------------------------------------------------------------------
實(shí)際應(yīng)用之三:利用流制作自己的OICQ

  OICQ是深圳騰訊公司的一個(gè)網(wǎng)絡(luò)實(shí)時(shí)通訊軟件,在國內(nèi)擁有大量的用戶群。但OICQ必須連接上互聯(lián)網(wǎng)登陸到騰訊的服務(wù)器才能使用。所以我們可以自己寫一個(gè)在局部網(wǎng)里面使用。
  OICQ使用的是UDP協(xié)議,這是一種無連接協(xié)議,即通信雙方不用建立連接就可以發(fā)送信息,所以效率比較高。Delphi本身自帶的FastNEt公司的NMUDP控件就是一個(gè)UDP協(xié)議的用戶數(shù)據(jù)報(bào)控件。不過要注意的是如果你使用了這個(gè)控件必須退出程序才能關(guān)閉計(jì)算機(jī),因?yàn)門NMXXX控件有BUG。所有nm控件的基礎(chǔ) PowerSocket用到的ThreadTimer,用到一個(gè)隱藏的窗口(類為TmrWindowClass)處理有硬傷。
出問題的地方:
Psock::TThreadTimer::WndProc(var msg:TMessage)
if msg.message=WM_TIMER then
他自己處理
msg.result:=0
else
msg.result:=DefWindowProc(0,....)
end 
  問題就出在調(diào)用 DefWindowProc時(shí),傳輸?shù)腍WND參數(shù)居然是常數(shù)0,這樣實(shí)際上DefWindowProc是不能工作的,對任何輸入的消息的調(diào)用均返回0,包括WM_QUERYENDsession,所以不能退出windows。由于DefWindowProc的不正常調(diào)用,實(shí)際上除WM_TIMER,其他消息由DefWindowProc處理都是無效的。
解決的辦法是在 PSock.pas
在 TThreadTimer.Wndproc 內(nèi)
Result := DefWindowProc( 0, Msg, WPARAM, LPARAM );
改為:
Result := DefWindowProc( FWindowHandle, Msg, WPARAM, LPARAM );
  早期低版本的OICQ也有這個(gè)問題,如果不關(guān)閉OICQ的話,關(guān)閉計(jì)算機(jī)時(shí)屏幕閃了一下又返回了。
  好了,廢話少說,讓我們編寫我們的OICQ吧,這個(gè)實(shí)際上是Delphi自帶的例子而已:)
  新建一個(gè)工程,在FASTNET面版拖一個(gè)NMUDP控件到窗口,然后依次放上三個(gè)EDIT,名字分別為Editip、EditPort、EditMyTxt,三個(gè)按鈕BtSend、BtClear、BtSave,一個(gè)MEMOMemoReceive,一個(gè)SaveDialog和一個(gè)狀態(tài)條StatusBar1。當(dāng)用戶點(diǎn)擊BtSend時(shí),建立一個(gè)內(nèi)存流對象,把要發(fā)送的文字信息寫進(jìn)內(nèi)存流,然后NMUDP把流發(fā)送出去。當(dāng)NMUDP有數(shù)據(jù)接收時(shí),觸發(fā)它的DataReceived事件,我們在這里再把接收到的流轉(zhuǎn)換為字符信息,然后顯示出來。
  注意:所有的流對象建立后使用完畢后要記得釋放(Free),其實(shí)它的釋構(gòu)函數(shù)應(yīng)該為Destroy,但如果建立流失敗的話,用Destroy會產(chǎn)生異常,而用Free的話程序會先檢查有沒有成功建立了流,如果建立了才釋放,所以用Free比較安全。
  在這個(gè)程序中我們用到了NMUDP控件,它有幾個(gè)重要的屬性。RemoteHost表示遠(yuǎn)程電腦的IP或者計(jì)算機(jī)名,LocalPort是本地端口,主要監(jiān)聽有沒有數(shù)據(jù)傳入。而RemotePort是遠(yuǎn)程端口,發(fā)送數(shù)據(jù)時(shí)通過這個(gè)端口把數(shù)據(jù)發(fā)送出去。理解這些已經(jīng)可以看懂我們的程序了。

全部代碼如下:
unit Unit1;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ComCtrls,NMUDP;

type
TForm1 = class(TForm)
NMUDP1: TNMUDP;
EditIP: TEdit;
EditPort: TEdit;
EditMyTxt: TEdit;
MemoReceive: TMemo; 
BtSend: TButton;
BtClear: TButton;
BtSave: TButton;
StatusBar1: TStatusBar; 
SaveDialog1: TSaveDialog;
procedure BtSendClick(Sender: TObject);
procedure NMUDP1DataReceived(Sender: TComponent; NumberBytes: Integer;
FromIP: String; Port: Integer);
procedure NMUDP1InvalidHost(var handled: Boolean);
procedure NMUDP1DataSend(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure BtClearClick(Sender: TObject);
procedure BtSaveClick(Sender: TObject);
procedure EditMyTxtKeyPress(Sender: TObject; var Key: Char);
private
{ Private declarations }
public
{ Public declarations }
end;

var
Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.BtSendClick(Sender: TObject);
var
MyStream: TMemoryStream;
MySendTxt: String;
Iport,icode:integer;
Begin
Val(EditPort.Text,Iport,icode);
if icode<>0 then
begin
Application.MessageBox('端口必須為數(shù)字,請重新輸入!','信息',MB_ICONINFORMATION+MB_OK);
Exit;
end;
NMUDP1.RemoteHost := EditIP.Text; {遠(yuǎn)程主機(jī)}
NMUDP1.LocalPort:=Iport; {本地端口}
NMUDP1.RemotePort := Iport; {遠(yuǎn)程端口}
MySendTxt := EditMyTxt.Text;
MyStream := TMemoryStream.Create; {建立流}
try
MyStream.Write(MySendTxt[1], Length(EditMyTxt.Text));{寫數(shù)據(jù)}
NMUDP1.SendStream(MyStream); {發(fā)送流}
finally
MyStream.Free; {釋放流}
end;
end;


procedure TForm1.NMUDP1DataReceived(Sender: TComponent;
NumberBytes: Integer; FromIP: String; Port: Integer);
var
MyStream: TMemoryStream;
MyReciveTxt: String;
begin
MyStream := TMemoryStream.Create; {建立流}
try
NMUDP1.ReadStream(MyStream);{接收流}
SetLength(MyReciveTxt,NumberBytes);{NumberBytes為接收到的字節(jié)數(shù)}
MyStream.Read(MyReciveTxt[1],NumberBytes);{讀數(shù)據(jù)}
MemoReceive.Lines.Add('接收到來自主機(jī)'+FromIP+'的信息:'+MyReciveTxt);
finally
MyStream.Free; {釋放流}
end;
end;

procedure TForm1.NMUDP1InvalidHost(var handled: Boolean);
begin
Application.MessageBox('對方IP地址不正確,請重新輸入!','信息',MB_ICONINFORMATION+MB_OK);
end;

procedure TForm1.NMUDP1DataSend(Sender: TObject);
begin
StatusBar1.SimpleText:='信息成功發(fā)出!';
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
EditIP.Text:='127.0.0.1';
EditPort.Text:='8868';
BtSend.Caption:='發(fā)送';
BtClear.Caption:='清除聊天記錄';
BtSave.Caption:='保存聊天記錄';
MemoReceive.ScrollBars:=ssBoth;
MemoReceive.Clear;
EditMyTxt.Text:='在這里輸入信息,然后點(diǎn)擊發(fā)送.';

StatusBar1.SimplePanel:=true;
end;

procedure TForm1.BtClearClick(Sender: TObject);
begin
MemoReceive.Clear;
end;

procedure TForm1.BtSaveClick(Sender: TObject);
begin
if SaveDialog1.Execute then MemoReceive.Lines.SaveToFile(SaveDialog1.FileName);
end;

procedure TForm1.EditMyTxtKeyPress(Sender: TObject; var Key: Char);
begin
if Key=#13 then BtSend.Click;
end;
end.
  上面的程序跟OICQ相比當(dāng)然差之甚遠(yuǎn),因?yàn)镺ICQ利用的是Socket5通信方式。它上線時(shí)先從服務(wù)器取回好友信息和在線狀態(tài),發(fā)送超時(shí)還會將信息先保存在服務(wù)器,等對方下次上線后再發(fā)送然后把服務(wù)器的備份刪除。你可以根據(jù)前面學(xué)的概念來完善這個(gè)程序,比如說再添加一個(gè)NMUDP控件來管理在線狀態(tài),發(fā)送的信息先轉(zhuǎn)換成ASCII碼進(jìn)行與或運(yùn)行并加上一個(gè)頭信息,接收方接收信息后先判斷信息頭正確與否,如果正確才把信息解密顯示出來,這樣就提高了安全保密性。
  另外,UDP協(xié)議還有一個(gè)很大的好處就是可以廣播,就是說處于一個(gè)網(wǎng)段的都可以接收到信息而不必指定具體的IP地址。網(wǎng)段一般分A、B、C三類,
1~126.XXX.XXX.XXX (A類網(wǎng)) :廣播地址為XXX.255.255.255
128~191.XXX.XXX.XXX(B類網(wǎng)):廣播地址為XXX.XXX.255.255
192~254.XXX.XXX.XXX(C類網(wǎng)):廣播地址為XXX.XXX.XXX.255
  比如說三臺計(jì)算機(jī)192.168.0.1、192.168.0.10、192.168.0.18,發(fā)送信息時(shí)只要指定IP地址為192.168.0.255就可以實(shí)現(xiàn)廣播了。下面給出一個(gè)轉(zhuǎn)換IP為廣播IP的函數(shù),快拿去完善自己的OICQ吧^-^.

Function Trun_ip(S:string):string;
var s1,s2,s3,ss,sss,Head:string;
n,m:integer;
begin
sss:=S;
n:=pos('.',s);
s1:=copy(s,1,n);
m:=length(s1);
delete(s,1,m);
Head:=copy(s1,1,(length(s1)-1));
n:=pos('.',s);
s2:=copy(s,1,n);
m:=length(s2);
delete(s,1,m);
n:=pos('.',s);
s3:=copy(s,1,n);
m:=length(s3);
delete(s,1,m);
ss:=sss;
if strtoint(Head) in [1..126] then ss:=s1+'255.255.255'; //1~126.255.255.255 (A類網(wǎng))
if strtoint(Head) in [128..191] then ss:=s1+s2+'255.255';//128~191.XXX.255.255(B類網(wǎng))
if strtoint(Head) in [192..254] then ss:=s1+s2+s3+'255'; //192~254.XXX.XXX.255(C類網(wǎng))
Result:=ss; 
end;

-----------------------------------------------------------------------
五、實(shí)際應(yīng)用之四:利用流實(shí)現(xiàn)網(wǎng)絡(luò)傳輸屏幕圖像

  大家應(yīng)該見過很多網(wǎng)管程序,這類程序其中有一個(gè)功能就是監(jiān)控遠(yuǎn)程電腦的屏幕。實(shí)際上,這也是利用流操作來實(shí)現(xiàn)的。下面我們給出一個(gè)例子,這個(gè)例子分兩個(gè)程序,一個(gè)服務(wù)端,一個(gè)是客戶端。程序編譯后可以直接在單機(jī)、局部網(wǎng)或者互聯(lián)網(wǎng)上使用。程序中已經(jīng)給出相應(yīng)注釋。后面我們再來作具體分析。
  新建一個(gè)工程,在Internet面版上拖一個(gè)ServerSocket控件到窗口,該控件主要用于監(jiān)聽客戶端,用來與客戶端建立連接和通訊。設(shè)置好監(jiān)聽端口后調(diào)用方法Open或者Active:=True即開始工作。注意:跟前面的NMUDP不同,當(dāng)Socket開始監(jiān)聽后就不能再改變它的端口,要改變的話必須先調(diào)用Close或設(shè)置Active為False,否則將會產(chǎn)生異常。另外,如果該端口已經(jīng)打開的話,就不能再用這個(gè)端口了。所以程序運(yùn)行尚未退出就不能再運(yùn)行這個(gè)程序,否則也會產(chǎn)生異常,即彈出出錯(cuò)窗口。實(shí)際應(yīng)用中可以通過判斷程序是否已經(jīng)運(yùn)行,如果已經(jīng)運(yùn)行就退出的方法來避免出錯(cuò)。
  當(dāng)客戶端有數(shù)據(jù)傳入,將觸發(fā)ServerSocket1ClientRead事件,我們可以在這里對接收的數(shù)據(jù)進(jìn)行處理。在本程序中,主要是接收客戶端發(fā)送過來的字符信息并根據(jù)事先的約定來進(jìn)行相應(yīng)操作。
程序全部代碼如下:

unit Unit1;{服務(wù)端程序}
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);
{自定義抓屏函數(shù),DrawCur表示抓鼠標(biāo)圖像與否}
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
MyStream: TMemorystream;{內(nèi)存流對象} 
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 {畫上鼠標(biāo)圖象}
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); {畫上鼠標(biāo)}
DeleteObject(pIconInfo.hbmColor);{GetIconInfo 使用時(shí)創(chuàng)建了兩個(gè)bitmap對象. 需要手工釋放這兩個(gè)對象}
DeleteObject(pIconInfo.hbmMask);{否則,調(diào)用他后,他會創(chuàng)建一個(gè)bitmap,多次調(diào)用會產(chǎn)生多個(gè),直至資源耗盡}
Mycursor.ReleaseHandle; {釋放數(shù)組內(nèi)存}
MyCursor.Free; {釋放鼠標(biāo)指針}
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; {關(guān)閉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 {客戶端發(fā)出抓屏幕指令}
begin
try
MyStream := TMemorystream.Create;{建立內(nèi)存流}
MyBmp := TBitmap.Create;
Myjpg := TJpegimage.Create;
Cjt_GetScreen(MyBmp, True); {True表示抓鼠標(biāo)圖像}
Myjpg.Assign(MyBmp); {將BMP圖象轉(zhuǎn)成JPG格式,便于在互聯(lián)網(wǎng)上傳輸}
Myjpg.CompressionQuality := 10; {JPG文件壓縮百分比設(shè)置,數(shù)字越大圖像越清晰,但數(shù)據(jù)也越大}
Myjpg.SaveToStream(MyStream); {將JPG圖象寫入流中}
Myjpg.free;
MyStream.Position := 0;{注意:必須添加此句}
s1 := inttostr(MyStream.size);{流的大小}
Socket.sendtext(s1); {發(fā)送流大小}
finally
MyBmp.free;
end;
end;
if s = 'ready' then {客戶端已準(zhǔn)備好接收圖象}
begin
MyStream.Position := 0;
Socket.SendStream(MyStream); {將流發(fā)送出去}
end;
end;
end.

  上面是服務(wù)端,下面我們來寫客戶端程序。新建一個(gè)工程,添加Socket控件ClientSocket、圖像顯示控件Image、一個(gè) Panel 、一個(gè)Edit、兩個(gè) Button和一個(gè)狀態(tài)欄控件StatusBar1。注意:把Edit1和兩個(gè) Button放在Panel1上面。ClientSocket的屬性跟ServerSocket差不多,不過多了一個(gè)Address屬性,表示要連接的服務(wù)端IP地址。填上IP地址后點(diǎn)“連接”將與服務(wù)端程序建立連接,如果成功就可以進(jìn)行通訊了。點(diǎn)擊“抓屏”將發(fā)送字符給服務(wù)端。因?yàn)槌绦蛴玫搅薐PEG圖像單元,所以要在Uses中添加Jpeg.
全部代碼如下:
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;{內(nèi)存流對象}
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
begin
{-------- 下面為設(shè)置窗口控件的外觀屬性 ------------- }
{注意:把Button1、Button2和Edit1放在Panel1上面}
Edit1.Text := '127.0.0.1';
Button1.Caption := '連接主機(jī)';
Button2.Caption := '抓屏幕';
Button2.Enabled := false;
Panel1.Align := alTop;
Image1.Align := alClient;
Image1.Stretch := True;
StatusBar1.Align:=alBottom;
StatusBar1.SimplePanel := True;
{----------------------------------------------- }
MyStream := TMemorystream.Create; {建立內(nèi)存流對象}
MySize := 0; {初始化}
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if not ClientSocket1.Active then
begin
ClientSocket1.Address := Edit1.Text; {遠(yuǎn)程IP地址}
ClientSocket1.Port := 3000; {Socket端口}
ClientSocket1.Open; {建立連接}
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Clientsocket1.Socket.SendText('cap'); {發(fā)送指令通知服務(wù)端抓取屏幕圖象}
Button2.Enabled := False;
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText := '與主機(jī)' + ClientSocket1.Address + '成功建立連接!';
Button2.Enabled := True;
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
Errorcode := 0; {不彈出出錯(cuò)窗口}
StatusBar1.SimpleText := '無法與主機(jī)' + ClientSocket1.Address + '建立連接!';
end;
procedure TForm1.ClientSocket1Disconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
StatusBar1.SimpleText := '與主機(jī)' + ClientSocket1.Address + '斷開連接!';
Button2.Enabled := False;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
MyBuffer: array[0..10000] of byte; {設(shè)置接收緩沖區(qū)}
MyReceviceLength: integer;
S: string;
MyBmp: TBitmap;
MyJpg: TJpegimage;
begin
StatusBar1.SimpleText := '正在接收數(shù)據(jù)......';
if MySize = 0 then {MySize為服務(wù)端發(fā)送的字節(jié)數(shù),如果為0表示為尚未開始圖象接收}
begin
S := Socket.ReceiveText;
MySize := Strtoint(S); {設(shè)置需接收的字節(jié)數(shù)}
Clientsocket1.Socket.SendText('ready'); {發(fā)指令通知服務(wù)端開始發(fā)送圖象}
end
else
begin {以下為圖象數(shù)據(jù)接收部分}
MyReceviceLength := socket.ReceiveLength; {讀出包長度}
StatusBar1.SimpleText := '正在接收數(shù)據(jù),數(shù)據(jù)大小為:' + inttostr(MySize);
Socket.ReceiveBuf(MyBuffer, MyReceviceLength); {接收數(shù)據(jù)包并讀入緩沖區(qū)內(nèi)}
MyStream.Write(MyBuffer, MyReceviceLength); {將數(shù)據(jù)寫入流中}
if MyStream.Size >= MySize then {如果流長度大于需接收的字節(jié)數(shù),則接收完畢}
begin
MyStream.Position := 0;
MyBmp := tbitmap.Create;
MyJpg := tjpegimage.Create;
try
MyJpg.LoadFromStream(MyStream); {將流中的數(shù)據(jù)讀至JPG圖像對象中}
MyBmp.Assign(MyJpg); {將JPG轉(zhuǎn)為BMP}
StatusBar1.SimpleText := '正在顯示圖像';
Image1.Picture.Bitmap.Assign(MyBmp); {分配給image1元件 }
finally {以下為清除工作 }
MyBmp.free;
MyJpg.free;
Button2.Enabled := true;
{ Socket.SendText('cap');添加此句即可連續(xù)抓屏 }
MyStream.Clear;
MySize := 0;
end;
end;
end;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
MyStream.Free; {釋放內(nèi)存流對象}
if ClientSocket1.Active then ClientSocket1.Close; {關(guān)閉Socket連接}
end;
end.

  程序原理:運(yùn)行服務(wù)端開始偵聽,再運(yùn)行客戶端,輸入服務(wù)端IP地址建立連接,然后發(fā)一個(gè)字符通知服務(wù)端抓屏幕。服務(wù)端調(diào)用自定義函數(shù)Cjt_GetScreen抓取屏幕存為BMP,把BMP轉(zhuǎn)換成JPG,把JPG寫入內(nèi)存流中,然后把流發(fā)送給客戶端。客戶端接收到流后做相反操作,將流轉(zhuǎn)換為JPG再轉(zhuǎn)換為BMP然后顯示出來。
  注意:因?yàn)镾ocket的限制,不能一次發(fā)送過大的數(shù)據(jù),只能分幾次發(fā)。所以程序中服務(wù)端抓屏轉(zhuǎn)換為流后先發(fā)送流的大小,通知客戶端這個(gè)流共有多大,客戶端根據(jù)這個(gè)數(shù)字大小來判斷是否已經(jīng)接收完流,如果接收完才轉(zhuǎn)換并顯示。
  這個(gè)程序跟前面的自制OICQ都是利用了內(nèi)存流對象TMemoryStream。其實(shí),這個(gè)流對象是程序設(shè)計(jì)中用得最普遍的,它可以提高I/O的讀寫能力,而且如果你要同時(shí)操作幾個(gè)不同類型的流,互相交換數(shù)據(jù)的話,用它作“中間人”是最好不過的了。比如說你把一個(gè)流壓縮或者解壓縮,就先建立一個(gè)TMemoryStream對象,然后把別的數(shù)據(jù)拷貝進(jìn)去,再執(zhí)行相應(yīng)操作就可以了。因?yàn)樗侵苯釉趦?nèi)存中工作,所以效率是非常高的。有時(shí)侯甚至你感覺不到有任何的延遲。
  程序有待改進(jìn)的地方:當(dāng)然可以加一個(gè)壓縮單元,發(fā)送前先壓縮再發(fā)送。注意:這里也是有技巧的,就是直接把BMP壓縮而不要轉(zhuǎn)換成JPG再壓。實(shí)驗(yàn)證明:上面程序一幅圖像大小大概為40-50KB,如果用LAH壓縮算法處理一下便只有8-12KB,這樣傳輸起來就比較快。如果想更快的話,可以采用這樣的方法:先抓第一幅圖像發(fā)送,然后從第二幅開始只發(fā)跟前一幅不同區(qū)域的圖像。外國有一個(gè)程序叫Remote Administrator,就是采用這樣的方法。他們測試的數(shù)據(jù)如下:局部網(wǎng)一秒鐘100-500幅,互聯(lián)網(wǎng)上,在網(wǎng)速極低的情況下,一秒鐘傳輸5-10幅。說這些題外話只想說明一個(gè)道理:想問題,特別是寫程序,特別是看起來很復(fù)雜的程序,千萬不要鉆牛角尖,有時(shí)侯不妨換個(gè)角度來想。程序是死的,人才是活的。當(dāng)然,這些只能靠經(jīng)驗(yàn)的積累。但是一開始就養(yǎng)成好習(xí)慣是終身受益的!

上一篇:如何用Delphi制作錄音程序

下一篇:關(guān)于Delphi大Bug的更詳細(xì)的例子

發(fā)表評論 共有條評論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
學(xué)習(xí)交流
熱門圖片

新聞熱點(diǎn)

疑難解答

圖片精選

網(wǎng)友關(guān)注

主站蜘蛛池模板: 91麻豆精品久久久久蜜臀 | 国产乱人伦av在线a 日韩电影中文字幕 | 人人鲁人人莫一区二区三区 | 午夜免费视频 | 亚洲第一网站 | 欧美日韩福利 | 国产精品久久久久久久久久 | 日韩视频中文字幕在线观看 | 国产亚洲精品v | 日韩一区二区精品视频 | 国产一区精品 | 日韩在线视频一区 | 久久激情网站 | 91中文字幕 | 九九免费视频 | 亚洲精品久久久久久一区二区 | 热re99久久精品国产99热 | 亚洲天堂久久 | 亚洲天天做| 日韩美女一区二区三区 | 国产一区二区视频在线观看 | 久久久美女| 国产精品观看 | 中文字幕在线视频网站 | 日韩综合网 | 六月丁香在线观看 | 99免费观看视频 | 99精品国产一区二区 | 极品美女国产精品免费一区 | 午夜窝窝 | 久久精品二区 | 一级一级国产片 | 欧美精品网站 | 色爽av | 天堂综合网久久 | 狠狠狠色丁香婷婷综合久久五月 | 色五月情 | 资源av | 女人高潮特级毛片 | 国产精品国产成人国产三级 | 嫩草视频在线观看免费 |