筆者在工作中遇到對局域網中各工作站與服務器之間進行Socket通信的問題。現在將本人總結出來的TServerSocket和TClientSocket兩個組件的基本用法寫出來,希望與您分享。
ClientSocket組件爲客戶端組件。它是通信的請求方,也就是說,它是主動地與服務器端建立連接。
ServerSocket組件爲服務器端組件。它是通信的響應方,也就是說,它的動作是監聽以及被動接受客戶端的連接請求,並對請求進行回覆。
ServerSocket組件可以同時接受一個或多個ClientSocket組件的連接請求,並與每個ClientSocket組件建立單獨的連接,進行單獨的通信。因此,一個服務器端可以爲多個客戶端服務。
本例包括一個服務器端程序和一個客戶端程序。客戶端程序可以放到多個計算機上運行,同時與服務器端進行連接通信。
本例的重點,一是演示客戶端與服務器端如何通信;二是當有多個客戶端同時連接到服務器端時,服務器端如何識別每個客戶端,並對請求給出相應的回覆。爲了保證一個客戶端斷開連接時不影響其它客戶端與服務器端的通信,同時保證服務器端能夠正確回覆客戶端的請求,在本例中聲明瞭一個記錄類型:
type client_record=record CHandle: integer; //客戶端套接字句柄 CSocket:TCustomWinSocket; //客戶端套接字 CName:string; //客戶端計算機名稱 CAddress:string; //客戶端計算機IP地址 CUsed: boolean; //客戶端聯機標誌 end; |
利用這個記錄類型數據保存客戶端的信息,同時保存當前客戶端的連接狀態。其中,CHandle保存客戶端套接字句柄,以便準確定位每個與服務器端保持連接的客戶端;Csocket保存客戶端套接字,通過它可以對客戶端進行回覆。Cused記錄當前客戶端是否與服務器端保持連接。
下面對組件ServerSocket和ClientSocket的屬性設置簡單說明。
ServerSocket的屬性:
· Port,是通信的端口,必須設置。在本例中設置爲1025;
· ServerTypt,服務器端讀寫信息類型,設置爲stNonBlocking表示異步讀寫信息,本例中採用這種方式。
· ThreadCacheSize,客戶端的最大連接數,就是服務器端最多允許多少客戶端同時連接。本例採用默認值10。
其它屬性採用默認設置即可。
ClientSocket的屬性:
· Port,是通信的端口,必須與服務器端的設置相同。在本例中設置爲1025;
· ClientType,客戶端讀寫信息類型,應該與服務器端的設置相同,爲stNonBlocking表示異步讀寫信息。
· Host,客戶端要連接的服務器的IP地址。必須設置,當然也可以在代碼中動態設置。
其它屬性採用默認設置即可。
程序源代碼:
· 服務器端源碼(uServerMain.pas):
unit uServerMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ScktComp, ToolWin, ComCtrls, ExtCtrls, StdCtrls, Buttons; const CMax=10; //客戶端最大連接數 type client_record=record CHandle: integer; //客戶端套接字句柄 CSocket:TCustomWinSocket; //客戶端套接字 CName:string; //客戶端計算機名稱 CAddress:string; //客戶端計算機IP地址 CUsed: boolean; //客戶端聯機標誌 end; type TfrmServerMain = class(TForm) ServerSocket: TServerSocket; ControlBar1: TControlBar; ToolBar1: TToolBar; tbConnect: TToolButton; tbClose: TToolButton; tbDisconnected: TToolButton; Edit1: TEdit; Memo1: TMemo; StatusBar: TStatusBar; procedure tbConnectClick(Sender: TObject); procedure tbDisconnectedClick(Sender: TObject); procedure ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); procedure ServerSocketListen(Sender: TObject; Socket: TCustomWinSocket); procedure ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket); procedure ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); procedure tbCloseClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure ServerSocketGetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); procedure ServerSocketClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); private { Private declarations } public { Public declarations } session: array[0..CMax] of client_record; //客戶端連接數組 Sessions: integer; //客戶端連接數 end; var frmServerMain: TfrmServerMain; implementation {$R *.DFM} //打開套接字連接,並使套接字進入監聽狀態 procedure TfrmServerMain.tbConnectClick(Sender: TObject); begin ServerSocket.Open ; end; //關閉套接字連接,不再監聽客戶端的請求 procedure TfrmServerMain.tbDisconnectedClick(Sender: TObject); begin ServerSocket.Close; StatusBar.Panels[0].Text :='服務器套接字連接已經關閉,無法接受客戶端的連接請求.'; end; //從客戶端讀取信息 procedure TfrmServerMain.ServerSocketClientRead(Sender: TObject; Socket: TCustomWinSocket); var i:integer; begin //將從客戶端讀取的信息添加到Memo1中 Memo1.Lines.Add(Socket.ReceiveText); for i:=0 to sessions do begin //取得匹配的客戶端 if session[i].CHandle = Socket.SocketHandle then begin session[i].CSocket.SendText('回覆客戶端'+session[i].CAddress+' ==> '+Edit1.Text); end; end; end; //服務器端套接字進入監聽狀態,以便監聽客戶端的連接 procedure TfrmServerMain.ServerSocketListen(Sender: TObject; Socket: TCustomWinSocket); begin StatusBar.Panels[0].Text :='等待客戶端連接...'; end; //當客戶端連接到服務器端以後 procedure TfrmServerMain.ServerSocketClientConnect(Sender: TObject; Socket: TCustomWinSocket); var i,j:integer; begin j:=-1; for i:=0 to sessions do begin //在原有的客戶端連接數組中有中斷的客戶端連接 if not session[i].CUsed then begin session[i].CHandle := Socket.SocketHandle ;//客戶端套接字句柄 session[i].CSocket := Socket; //客戶端套接字 session[i].CName := Socket.RemoteHost ; //客戶端計算機名稱 session[i].CAddress := Socket.RemoteAddress ;//客戶端計算機IP session[i].CUsed := True; //連接數組當前位置已經佔用 Break; end; j:=i; end; if j=sessions then begin inc(sessions); session[j].CHandle := Socket.SocketHandle ; session[j].CSocket := Socket; session[j].CName := Socket.RemoteHost ; session[j].CAddress := Socket.RemoteAddress ; session[j].CUsed := True; end; StatusBar.Panels[0].Text := '客戶端 '+Socket.RemoteHost + ' 已經連接'; end; //當客戶端斷開連接時 procedure TfrmServerMain.ServerSocketClientDisconnect(Sender: TObject; Socket: TCustomWinSocket); var i:integer; begin for i:=0 to sessions do begin if session[i].CHandle =Socket.SocketHandle then begin session[i].CHandle :=0; session[i].CUsed := False; Break; end; end; StatusBar.Panels[0].Text :='客戶端 '+Socket.RemoteHost + ' 已經斷開'; end; //關閉窗口 procedure TfrmServerMain.tbCloseClick(Sender: TObject); begin Close; end; procedure TfrmServerMain.FormCreate(Sender: TObject); begin sessions := 0; end; procedure TfrmServerMain.FormClose(Sender: TObject; var Action: TCloseAction); begin ServerSocket.Close ; end; //當客戶端正在與服務器端連接時 procedure TfrmServerMain.ServerSocketGetSocket(Sender: TObject; Socket: Integer; var ClientSocket: TServerClientWinSocket); begin StatusBar.Panels[0].Text :='客戶端正在連接...'; end; //客戶端發生錯誤 procedure TfrmServerMain.ServerSocketClientError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin StatusBar.Panels[0].Text :='客戶端'+Socket.RemoteHost +'發生錯誤!'; ErrorCode := 0; end; end. |
· 客戶端源碼(uClientMain.pas):
unit uClientMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ScktComp, ComCtrls, ToolWin, ExtCtrls, StdCtrls, Buttons; const SocketHost = '172.16.1.6'; //服務器端地址 type TfrmClientMain = class(TForm) ControlBar1: TControlBar; ToolBar1: TToolBar; tbConnected: TToolButton; tbSend: TToolButton; tbClose: TToolButton; tbDisconnected: TToolButton; ClientSocket: TClientSocket; Edit1: TEdit; Memo1: TMemo; StatusBar: TStatusBar; btnSend: TBitBtn; procedure tbConnectedClick(Sender: TObject); procedure tbDisconnectedClick(Sender: TObject); procedure ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket); procedure tbSendClick(Sender: TObject); procedure tbCloseClick(Sender: TObject); procedure FormShow(Sender: TObject); procedure ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket); procedure ClientSocketConnecting(Sender: TObject; Socket: TCustomWinSocket); procedure ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure ClientSocketError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); private { Private declarations } public { Public declarations } end; var frmClientMain: TfrmClientMain; implementation {$R *.DFM} //打開套接字連接 procedure TfrmClientMain.tbConnectedClick(Sender: TObject); begin ClientSocket.Open ; end; //關閉套接字連接 procedure TfrmClientMain.tbDisconnectedClick(Sender: TObject); begin ClientSocket.Close; end; //接受服務器端的回覆 procedure TfrmClientMain.ClientSocketRead(Sender: TObject; Socket: TCustomWinSocket); begin Memo1.Lines.Add(Socket.ReceiveText); end; //發送信息到服務器端 procedure TfrmClientMain.tbSendClick(Sender: TObject); begin ClientSocket.Socket.SendText(Edit1.Text); end; procedure TfrmClientMain.tbCloseClick(Sender: TObject); begin Close; end; //設置要連接的服務器端地址 procedure TfrmClientMain.FormShow(Sender: TObject); begin ClientSocket.Host := SocketHost; end; //已經連接到服務器端 procedure TfrmClientMain.ClientSocketConnect(Sender: TObject; Socket: TCustomWinSocket); begin tbSend.Enabled := True; tbDisconnected.Enabled :=True; btnSend.Enabled := True; StatusBar.Panels[0].Text := '已經連接到 '+ Socket.RemoteHost ; end; //正在連接到服務器端 procedure TfrmClientMain.ClientSocketConnecting(Sender: TObject; Socket: TCustomWinSocket); begin StatusBar.Panels[0].Text := '正在連接到服務器... ' ; end; //當斷開與服務器端的連接時發生 procedure TfrmClientMain.ClientSocketDisconnect(Sender: TObject; Socket: TCustomWinSocket); begin tbSend.Enabled := False; btnSend.Enabled := False; tbDisconnected.Enabled := False; StatusBar.Panels[0].Text := '已經斷開與 '+ Socket.RemoteHost +' 的連接'; end; procedure TfrmClientMain.FormClose(Sender: TObject; var Action: TCloseAction); begin ClientSocket.Close ; end; //當與服務器端的連接發生錯誤時 procedure TfrmClientMain.ClientSocketError(Sender: TObject; Socket: TCustomWinSocket; ErrorEvent: TErrorEvent; var ErrorCode: Integer); begin StatusBar.Panels[0].Text := '與服務器端的連接發生錯誤'; ErrorCode := 0; end; end. |
上述方法是比較簡單的實現方法,同時也是相對較容易理解的方法。通過這個方法,筆者成功實現了局域網內多個客戶端與服務器端進行Socket通信的功能,同時可以保證一個客戶端的連接、通信或是斷開都不影響其它客戶端的正常通信。
附錄:
服務器端窗體和客戶端窗體及組件的屬性設置參加相應的DFM文件。
uServerMain.pas對應的DFM文件(uServerMain.dfm) uClientMain.pas對應的DFM文件(uClientMain.dfm) object frmClientMain: TfrmClientMain |