一、Delphi與Socket
電腦網路是由一系列網路通訊協定組成的,其中的核心協定是傳輸層的TCP/ip和UDP協定。 TCP是面向連線的,通訊雙方維持一條通路,好比目前的電話線,使用telnet登陸BBS,用的就是TCP協定;UDP是無連線的,通訊雙方都不保持對方的狀態,瀏覽器存取Internet時使用的HTTP協定就是基於UDP協定的。 TCP和UDP協定都非常複雜,尤其是TCP協議,為了確保網路傳輸的正確性和有效性,必須進行一系列複雜的糾錯和排序等處理。
Socket是建立在傳輸層協定(主要是TCP和UDP)上的一種套接字規範,最初是由美國加州Berkley大學提出,它定義兩台電腦間進行通訊的規範(也是一種程式設計規範),如果說兩台電腦是利用一個「通道「進行通信,那麼這個「通道「的兩端就是兩個套接字。套接字屏蔽了底層通訊軟體和特定作業系統的差異,使得任何兩台安裝了TCP協定軟體和實現了套接字規範的電腦之間的通訊成為可能。
微軟的Windows Socket規範(簡稱winsock)對Berkley的套接字規範進行了擴展,利用標準的Socket的方法,可以同任何平台上的Socket進行通信;利用其擴展,可以更有效地實現在Windows平台上計算機間的通訊。在Delphi中,其底層的Socket也應該是Windows的Socket。 Socket減輕了編寫電腦間通訊軟體的難度,但總的說來還是相當複雜的(這一點在後面具體會講到);InPRise在Delphi中對Windows Socket進行了有效的封裝,使得用戶可以很方便地編寫網路通訊程式。下面我們實例解讀在Delphi中如何利用Socket編寫通訊程式。
二、利用Delphi編寫Socket通訊程式。
下面是一個簡單的Socket通訊程序,其中客戶機和服務機是同一個程序,當客戶機(伺服器)在一個memo1中輸入一段文字然後敲入回車,該段文字就可以顯示在伺服器(客戶機)的memo2中,反之亦成立。具體步驟如下:
1.新建一個form,任意命名,不妨設之為chatForm;放上一個MainMenu(在Standard欄中),建立ListenItem、ConnectItem、Disconnect和Exit選單項目;在從Internet欄中選擇TServerSocket、TClientSocket加入到chatForm中,其中把TClientSocket的名字設為ClientSocket, port設為1025,預設的active為false;把TServerSocket的名字設為ServerSocket,port設為1025,預設的active是false,其他的不變;再放入兩個memo,一個命名為memo1,另外一個命名為memo2,其中把memo2的color設定為灰色,因為主要用來顯示對方的輸入。下面我們一邊寫程式一邊解釋原因。
2、雙擊ListemItem。寫入如下程式碼:
procedure TChatForm.ListenItemClick(Sender: TObject);
begin
ListenItem.Checked := not ListenItem.Checked;
if ListenItem.Checked then
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
end
else
begin
if ServerSocket.Active then
ServerSocket.Active := False;
end;
end;
程式段的說明如下:當使用者選擇ListemItem時,該ListenItem取反,如果選取的話,說明處於Listen狀態,讀者要了解的是:listen是Socket作為Server時一個專有的方法,如果處於listen,則ServerSocket設定為活動狀態;否則,取消listen,則關閉ServerSocket。實際上,只有使用者一開始選擇該選單項,表示該程式用作Server。反之,如果使用者選擇ConnectItem,則必然作為Client使用。
3、雙擊ConnectItem,敲入以下程式碼。
procedure TChatForm.ConnectItemClick(Sender: TObject);
begin
if ClientSocket.Active then ClientSocket.Active := False;
if InputQuery('Computer to connect to', 'Address Name:', Server) then
if Length(Server) > 0 then
with ClientSocket do
begin
Host := Server;
Active := True;
ListenItem.Checked := False;
end;
end;
這段程式的主要功能就是當使用者選擇ConnectItem選單項目時,設定應用程式為客戶機,彈出input框,讓使用者輸入伺服器的位址。這也就是我們不一開始固定ClientSocket的host的原因,讓使用者可以動態地連接不同的伺服器。讀者需要了解的是主機位址只是Socket作為客戶機時具有的屬性,Socket作為伺服器時「一般「不用位址,因為它是同本機綁定。
4.在memo1的keydown方法中寫入如下程式碼:
procedure TChatForm.Memo1KeyDown(Sender: TObject; var Key: Word;
Shift: TShiftState);
begin
if Key = VK_Return then
if IsServer then
ServerSocket.Socket.Connections[0].SendText(Memo1.Lines[Memo1.Lines.Count - 1])
else
ClientSocket.Socket.SendText(Memo1.Lines[Memo1.Lines.Count - 1]);
end;
該段程式碼的作用很明顯,就是開始發訊息了。其中如果是Server的話,它只向第一個客戶機發送訊息,由於一個伺服器可以連接多個客戶機,而同一客戶機的每一個連接都由一個Socket來維持,因此在ServerSocket.Socket.Connnections數組中儲存的就是同Client維持連線的Socket。在標準Socket中,伺服器方的Socket透過accept()方法的回傳值取得維持同客戶機連線的Socket,而發送、接受訊息的方法分別為send(sendto)和recv(recvfrom), Delphi對此進行了封裝。
5、其餘程式碼的簡介。
procedure TChatForm.ServerSocketAccept(Sender: TObject;
Socket: TCustomWinSocket);
begin
IsServer := True;
end;
ServerSocket的Accept方法,當客戶機第一次連線時完成,透過其參數可以認為,它是在標準的accept方法後執行的,因為有TCustomWinSocket這個參數類型,它應該是標準Server方Socket的回傳值。
procedure TChatForm.ClientSocketRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
procedure TChatForm.ServerSocketClientRead(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Add(Socket.ReceiveText);
end;
這兩段程式碼分別是伺服器方和客戶端在收到對方的訊息時,由Delphi觸發的,作用是在memo2中顯示收到的訊息。其中,ClientSocketRead中的Socket其實就是Socket本身,而在ServerSocketClientRead中的Socket其實是ServerSocket.Socket.Connection[]中的某個Socket。不過在Delphi中,對伺服器方的Socket進行了有效的封裝。
procedure TChatForm.ServerSocketClientConnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
Memo2.Lines.Clear;
end;
procedure TChatForm.ClientSocketDisconnect(Sender: TObject;
Socket: TCustomWinSocket);
begin
ListenItemClick(nil);
end;
這兩段比較簡單。其中ServerSocketClientConnect在ServerSocket收到一個新的連線時觸發。而ClientSocketDisconnect在ClientSocket發出Disconncet時觸發。
procedure TChatForm.Exit1Click(Sender: TObject);
begin
ServerSocket.Close;
ClientSocket.Close;
Close;
end;
procedure TChatForm.Disconnect1Click(Sender: TObject);
begin
ClientSocket.Active := False;
ServerSocket.Active := True;
end;
第一段為關閉應用程式。在標準Socket中,每個Socket在關閉時,必須呼叫closesocket()方法,否則系統不會釋放資源。而在ServerSockt.Close和ClientSocket.Close中,系統內部肯定呼叫了closesocket()方法。
三、標準Socket與Delphi中的Socket。
標準的Socket的應用程式框架如下:
Server方: Socket()[ 新建一個Socket]--Bind()[ 同伺服器位址邦定]--Listen() --Accept()--block wait--read()[接受訊息,在windows平台中,方法為send(TCP),或sendto(UDP)]-處理服務請求-Write()[傳送訊息,在windows平台中,方法為send(TCP),或為sendto(UDP)。
Client方相對簡單:Socket()--Connect()[透過一定的port連接特定的伺服器,這是與伺服器建立連線]--Write()--Read()。
Socket可以是基於TCP的,也可以是基於UDP,同時Socket甚至建立在其他的協議,例如IPX/SPX,DECNet等。新建一個Socket時,可以指定新建何類Socket。 Bind()用來同伺服器的位址邦定,如果一個主機只有一個IP位址,實際上邦定的作用就相對多餘了。 Listen()開始監聽網絡,Accept()用於接受連接,其回傳值是保持相同客戶端聯繫的Socket。
在Delphi中,對於Windows中的Socket進行了有效的封裝。在Delphi中,依其繼承關係,可以分層兩類:
一、TComponent--TAbstractSocket--TCustomSocket--TCustomServerSocket--TServerSocket
TComponent--TAbstractSocket--TCustomSocket--TClientSocket
二、直接從TObject繼承過來:
TObject--TCustomWinSocket--TServerWinSocket
TObject--TCustomWinSocket--TClientWinSocket
TObject--TCustomWinSocket--TServerClientWinSocket
可以看出第一類是建立在TCustomSocket基礎上,第二類是建立在TCustomWinSocket的基礎上。第一類建立在TComponet的基礎上,第二類直接建構在TObject基礎上。因此如果使用者非常熟悉Socket並且想要編寫控制台程式時,可以使用TCustomWinScoket類別。
同uses中可以看出,它們都在ScktComp.pas中實現,而在schtComp.pas中,則包含了winsock.pas文件,如果繼續深入winsock文件,在其中可以發現所有的Windows Socket的基本方法。
實際上,如果你了解了標準Socket的應用程式框架,對於使用Delphi編寫Socket應用程式也就得心應手了;這不是說你必須了解複雜的Socket中的標準函數,也沒有必要,因為Delphi已經為你做了很好的封裝了,這也正是Delphi的強勢所在,你只要了解那麼一點點的基本框架。
這是我對Delphi中的Socket應用的理解,不足之處希望大家指正。同時也樂於為大家解答Delphi中有關Socket的問題。