[摘要] 編寫過Winsock應用程式的程式設計師都知道,編寫Winsock應用程式絕不是一件輕而易舉的事,您不得直接與複雜的Winsock中的Api打交道,幸運的是,Delphi4中的Tclientsocket 和Tserversocket封裝了Windows中有關的Api,大為簡化了對Winsock的訪問,使得我們能夠非常輕易的編寫Winsock應用程式。本文透過一個讀取區域網路內另一台電腦螢幕的範例,來介紹如何用Delphi編寫Winsock應用程式。
在單位做過網管的人都可能有這樣的經歷,透過電話「遙控」指導別人操作是一件多麼心煩的事,而且我又是一個懶人,不想天天為一點小事從樓頂跑到樓下,怎麼辦呢?編一個讀取另一台電腦螢幕的程式怎麼樣?不就直觀多了。在區域網路內進行通信,最好的選擇當然是用Winsock,編寫過Winsock應用程式的程式設計師都知道,編寫Winsock應用程式絕不是一件輕而易舉的事,您不得直接與複雜的Winsock中的Api打交道,幸運的是,Delphi4中的Tclientsocket 和Tserversocket封裝了Windows中有關的Api,大為簡化了對Winsock的訪問,使得我們能夠非常輕易的編寫Winsock應用程式。儘管如此,最好還是對Winsock有一些了解,在這裡我就不再贅述,您可以找些書自己看看。
透過網路傳輸數據,您至少需要一對Socket,其中一個在客戶端,另一個在服務端,一旦客戶端與服務端的socket建立起連接,就可以相互通信了,用Socket建立連接是建立在Tcp/ ip基礎上的,同時也支援ipx/spx等相關協定。在Delphi中分別用Tclientsocket 和Tserversocket來操縱客戶端與服務端Socket的連線與通訊。要說明的是,這兩個元件用來管理伺服器與客戶的連接,本身並不是Socket對象,操縱Socket物件的是TcustomwinSocket,如Tclientwinsocket、 Tserverclientwinsocket、Tserverwinsocket。
一、Tclientsocket元件:
把一個Tclientsocket加到Form上,應用程式就變成一個Tcp/ip客戶,就可以用Tclientsocket來操縱客戶端的Socket物件。
若要建立與伺服器的連接,應先指定要連接的伺服器。指定伺服器有兩種方式,一種是設定Host屬性指定伺服器的主機名,如http://www.inPRise.com或區域網路中的機器名,這種方式直觀,但要進行網域名稱解析,速度會稍慢一些;另一種方法是設定Adress屬性指定主機的ip位址,如130.0.1.1。這兩種方法是等價的,但如果同時設定了Host和Adress,Delphi將只使用Host屬性。
然後要指定連接伺服器的連接埠號,這裡也有兩種方式,一是設定Service使用預設連接埠號,一是直接設定Port連接埠號,在1024以下的連接埠號碼中,很多都已經分配出去了,如FTP的連接埠為20和21,SMTP的連接埠是25,WEB伺服器的連接埠為80等,為防止無意間的衝突,建議在編制自已的應用程式時,最好將Port設為1024以上。如果同時設定了Service和port,Delphi將使用Service預設的連接埠。
指定好伺服器和連接埠號碼後,呼叫open方法或將Active屬性設為True,客戶端的Socket就會向服務端的Socket提出連接請求,如果此時服務端處於監聽狀態,就會自動接受請求建立連接,建立連線時,會觸發其Onconnet事件。需要斷開連線時,只需要呼叫close方法或將Active屬性設為False,此時會觸發ondisconnet事件。
二、Tserversocket元件:
就像Tclientsocket一樣,建造一個伺服器,只需要將一個Tserversock元件放在Form即可。
服務端的socket物件管理起來較為複雜。當伺服器處於監聽狀態,此時服務端的socket物件用Tserversocket來操縱;當客戶提出請求,伺服器回應請求並建立了連接,此時用Tserverclientwinsocket來操縱服務端Socket與客戶端的Socket的連接。
要使伺服器處於監聽狀態,必須先指定連接埠號,當然,應該和客戶端的連接埠號碼相同。然後呼叫open方法或Active屬性設為True。
三、通信:
一旦建立起客戶與伺服器的連線後,就可以進行相互間的通訊了。 Delphi為Tserversocket和Tclientsocket提供了幾個通訊用的方法,用sendtext發送本本訊息,用sendstream發送流,用SendBuf發送指定長度的資料。
要注意的是,由於windows預設緩衝區大小為4K,所以當發送長於4K的訊息時,例如,從服務端向客戶端發送的一個二進位流,在服務端只需要用Socket.SendStream()就行了,而在客戶端就不一樣,它將多次觸發onread事件,而Delphi又沒有定義如“onreadend”之類的事件,因此,必須在接收時程式設計師自己對資料進行組裝。本文採取的方法是先將流長度發給客戶端,然後發送流,客戶端將接收的資料寫進一個流中,當流長度等於服務端發回的長度時,就表示客戶端已接收完畢了。對服務端來說,做為sendstream參數的流,會為Socket 所“擁有”,Socket物件結束時,它也將結束,而不要自己去釋放它,否則,會觸發一個異常。
同樣,當發送的文字小於4K,例如在客戶端程式中進行如下呼叫時
clientsocket1.Socket.SendText('gets');
clientsocket1.Socket.SendText('gets');
clientsocket1.Socket.SendText('gets');
服務端接收時會出現getsgets之類的現象,這可能是因為當緩衝區內的資料還未發送完時,又將新的文字放入緩衝區,電腦把它也當成同一批資料進行處理的緣故。為避免這個現象的發生,在程序內可採取一來一回「拋球」式的做法:
客戶端服務端
clientsocket1.Socket.SendText('data1') socket.ReceiveText;
socket.sendtext('ok');
socket.receivetext;
clientsocket1.Socket.SendText(' data2')
socket.ReceiveText;
socket.sendtext('end');
socket.receivetext;
在另一台電腦上執行服務端程式後,在您的客戶端程式上文字方塊內輸入該電腦名,接“連接”,按“取圖”,如何,對方電腦的螢幕一覽無餘了。以下是程式的全部原始碼,本程式在NT4.0、Win95、Win98、區域網路內運作通過,當然,windows必須得裝tcp/ip協議,而且必須有動態分配的或指定的ip位址。
如果您覺邊看邊「指揮」還是比較麻煩,您還可以對image1上的鍵盤、滑鼠事件進行分析,然後發給服務端,服務端接收後,再進行同樣的操作,這樣您就可以不用麻煩操作員了。利用Delphi的Tclientsocket 和Tserversocket,還可以完成諸如文件複製、網上聊天、ICQ等應用程序的開發,實現起來都很簡單,你可以自由的發揮出你的想像力,編寫出更富魅力的程序來。
客戶端程式:
unit cmain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp, StdCtrls, ExtCtrls,jpeg;
type
TForm1 = class(TForm)
Panel1: TPanel;
ScrollBox1: TScrollBox;
Image1: TImage;
Button1: TButton;
Edit1: TEdit;
Button2: TButton;
ClientSocket1: TClientSocket;
Label1: TLabel;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
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);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
c:longint;
m:tmemorystream;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
begin
try
clientsocket1.Close;
clientsocket1.Host:=edit1.text;
clientsocket1.Open; //連線服務端
except
showmessage(edit1.text+#13#10+'未開機或未安裝服務程式');
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
clientsocket1.Socket.SendText('gets'); //發送申請,通知服務端需要螢幕圖象
end;
procedure TForm1.ClientSocket1Connect(Sender: TObject;
Socket: TCustomWinSocket);
begin
caption:='連接到'+edit1.text;
end;
procedure TForm1.ClientSocket1Error(Sender: TObject;
Socket: TCustomWinSocket; ErrorEvent: TErrorEvent;
var ErrorCode: Integer);
begin
caption:='連線'+edit1.text+'失敗';
showmessage(edit1.text+#13#10+'未開機或未安裝服務程式');
errorcode:=0;
end;
procedure TForm1.ClientSocket1Read(Sender: TObject;
Socket: TCustomWinSocket);
var
buffer:array [0..10000] of byte; //設定接收緩衝區
len:integer;
ll:string;
b:tbitmap;
j:tjpegimage;
begin
if c=0 then //C為服務端發送的位元組數,如果為0表示為尚未開始圖象接收
begin
ll:=socket.ReceiveText;
c:=strtoint(ll); //設定需接收的位元組數
clientsocket1.Socket.SendText('okok'); //通知服務端開始傳送圖象
end else
begin //以下為圖象資料接收部分
len:=socket.ReceiveLength; //讀出套件長度
socket.ReceiveBuf(buffer,len); //接收資料包並讀入緩衝區內
m.Write(buffer,len); //追加入流M中
if m.Size>=c then //若流長度大於需接收的位元組數,則接收完畢
begin
m.Position:=0;
b:=tbitmap.Create;
j:=tjpegimage.Create;
try
j.LoadFromStream(m); //將流M中的資料讀至JPG影像物件J中
b.Assign(j); //將JPG轉為BMP
Image1.Picture.Bitmap.Assign(b); //指派給image1元件
finally //以下為清除工作
b.free;
j.free;
clientsocket1.Active:=false;
clientsocket1.Active:=true;
m.Clear;
c:=0;
end;
end;
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
m:=tmemorystream.Create;
end;
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
m.free;
ClientSocket1.Close;
end;
end.
服務端程式:
unit smain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ScktComp,jpeg;
type
TForm1 = class(TForm)
ServerSocket1: TServerSocket;
procedure ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
m1:tmemorystream;
implementation
{$R *.DFM}
procedure TForm1.ServerSocket1ClientRead(Sender: TObject;
Socket: TCustomWinSocket);
var
s,s1:string;
desk:tcanvas;
bitmap:tbitmap;
jpg:tjpegimage;
begin
s:=socket.ReceiveText;
if s='gets' then //客戶端發出申請
begin
bitmap:=tbitmap.Create;
jpg:=tjpegimage.Create;
desk:=tcanvas.Create; //以下程式碼為取得目前螢幕圖象
desk.Handle:=getdc(hwnd_desktop);
m1:=tmemorystream.Create; //初始化流m1,在用sendstream(m1)發送流後,
//它將保留到socket對話結束,
//不能用手排free掉,否則會觸發異常
with bitmap do
begin
width:=screen.Width;
height:=screen.Height;
canvas.CopyRect(canvas.cliprect,desk,desk.cliprect);
end;
jpg.Assign(bitmap); //將圖象轉換成JPG格式
jpg.SaveToStream(m1); //將JPG圖象寫入流中
jpg.free;
m1.Position:=0;
s1:=inttostr(m1.size);
Socket.sendtext(s1); //傳送圖像大小
end;
if s='okok' then //客戶端已準備好接收圖象
begin
m1.Position:=0;
Socket.SendStream(m1); //傳送JPG圖象
end;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
ServerSocket1.open;
end;
end.