Indy 10.5.8 for Delphi and Lazarus 修改版(2011)
阿新 • • 發佈:2018-08-02
troy lock 選擇 單用戶 tcp客戶端 現象 域名 在線 enter Indy 10.5.8 for Delphi and Lazarus 修改版(2011)
Internet Direct(Indy)是一組開放源代碼的Internet組件,涵蓋了幾乎所有流行的Internet協議。是由Chad Z. Hower領導的一群開發者構建的,Delphi 7中所帶的是Indy 9。在其的組件面板上,一共安裝有100多個Indy組件。使用這些組件你可以開發基於各種協議的TCP客戶和服務器應用程序,並處理相關的編碼和安全問題。可以通過前綴Id來識別Indy組件。
IdTcpServer/IdTcpClient IdTcpServer
uses IdContext //需要引用
屬性,方法:
IdTCPServer.Active :=True; //開啟服務器
IdTCPServer1.Bindings.Add.IP := ‘127.0.0.1‘;//綁定IP
IdTCPServer1.Bindings.Add.Port := 7956;//綁定端口
事件:
OnConnect : 客戶端連接成功觸發
OnDisConnect : 客戶端斷開觸發
OnExeCute : 收到客戶端數據觸發 例子
//像所有客戶斷發送數據
var
I: Integer;
Context: TIdContext;
begin
with IdTCPServer1.Contexts.LockList do
try
for I := 0 to Count - 1 do
begin
Context := TIdContext(Items[I]);
Context.Connection.IOHandler.Write(‘Hello,everybody!‘);
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end; //向某個客戶發送數據
var
I: Integer;
Context: TIdContext;
begin
with IdTCPServer1.Contexts.LockList do
try
for I := 0 to Count - 1 do
begin
Context := TIdContext(Items[I]);
if Context.Binding.PeerIP <> ‘192.168.10.90‘ then
continue;
Context.Connection.IOHandler.Write(‘Hello!‘);
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end; IdTcpClient
屬性,方法:
ConnectTimeOut:連接超時
Host:主機IP或域名
IPVersion:Ip版本 ipv4 or ipv6
Name:控件名
Port:主機端口
ReadTimeOut:讀取超時
IdTCPClient1.Connect; //連接服務端
IdTCPClient1.Disconnect;//端開連接
IdTCPClient1.Connected;//是否連接成功返回true 連接成功
IdTCPClient1.IOHandler.WriteLn(‘aa‘);// 向服務端發送數據 OnWork事件AWorkMode=wmRead 表示有收到數據 在DelPhi2007 中 使用Indy 的TCP連接教程(一) 首先 先說明下 為什麽要用 INDY10 最新的indy10可以基於win32上的程(Fiber) API.
什麽叫Fiber API呢,這裏是解釋:
纖程(Fiber) — 可以從 32 位版本的 Windows? 中使用的輕量級線程處理對象 — 在很多方案中都很有用。由於線程是寶貴資源,因此您有時不希望將整個 OS 線程專門用於執行簡單的任務。通過纖程,可以比線程更嚴密地控制任務的調度,因為是您而不是 OS 負責管理它們。由於它們具有較少的開銷,因此當您切換上下文時,它們還更加快速。此外,因為是由您控制纖程,所以對於它們而言,通常可以更容易地跟蹤同步問題。 不過這個特性,現在只有針對delphi7有用。
端口重疊可以讓你的服務器承擔更多的用戶。indy10值得一用。 indy10支持完成端口和纖程,性能有了巨大提升! ================================================================================ 我們先打開 DelPhi2007 工具吧! 首先 我們 做好一個簡單的客戶端 先新建一個窗口程序拖入一個TCP客戶端控件還有3個按鈕一個文本框 是 連接 斷開 和 發生 設置一下 IdTCPClient 控件的屬性 Host :127.0.0.1
Post:3000
下面我們來對連接按鈕做事件
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
IdTCPClient1.Connect;
ShowMessage(‘連接成功‘);
except
ShowMessage(‘連接失敗‘);
end;
end; 接著 我們來做一下服務端的程序
先新建一個窗口程序拖入一個TCP服務端控件兩個按鈕 以及一個 TMemo用來顯示信息
Bindings 0.0.0.0:3000
DefaultPort 3000 我們在“啟動服務” 按鈕上的事件 procedure TForm6.Button1Click(Sender: TObject);
begin
IdTCPServer1.Active:= true;
end; 啟動時 只要將其Active設置為 true 既啟動了服務而關閉則同樣設置為 False
接下來我們要對 IdTCPServer1 的 OnExecute 事件做處理! 選擇控件 EVENTS 欄雙擊OnExecute
在這裏代碼我們暫時這樣寫 procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
exit;
end; TIdContext 需要 uses IdContext 好 到這裏 運行下服務器 和客戶端 然後 啟動服務器 和 連接服務器
好已經可以連接得上了吧!
但是因為 我們在服務器監聽的部分退出了 所以 並沒有保持著連接
現在我們 修改一下 代碼吧 我們把OnExecute 代碼修改如下 procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
var
Swp:String;
begin
try
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Swp:=AContext.Connection.IOHandler.ReadLn();
Memo1.Lines.Add(Swp) ;
finally
end;
end; 我們對客戶端也修改一下
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
在運行測試一下
當按下連接按鈕後服務器上的文本框裏 加入了一行 ‘lianjie‘ 字符串而其再次點擊連接已經無效而剛剛每次點擊一次 都會提示一次連接成功 仔細看代碼就發現在連接的時候判斷了是否已經連接了如果已經保持連接了哪麽就不會在做下面的代碼!從而可知現在的連接已經是保持著的了!那好我們來發個信息看下是否真的可以連接了
在發送按鈕上的事件
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
好我們來測試一下 是不是連接以後真的可以向服務器發送數據了呢?
看到了吧!是不是可以發送數據了! 在Delphi 2007中使用Indy10的TCP連接的教程(系列二) 服務器怎麽樣區別數據到底是哪一個發送過來的呢,或者服務器如何對其回復數據呢!~ 先針對回復對應的客戶端發送過來的數據!已經客戶端接受並顯示服務器反饋回來的數據!
我們修改服務器上的OnExecute代碼如下! procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
var
Swp:String;
begin
try
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Swp:=AContext.Connection.IOHandler.ReadLn();
if(Swp<>‘‘)then
AContext.Connection.IOHandler.WriteLn(‘服務器已經收到您發來的信息:‘+Swp);
Memo1.Lines.Add(Swp) ;
finally
end;
end;
在客戶端裏我們加入一個TMemo用來接受服務器發來的數據信息!
然後我們在來看客戶端的代碼!
在連接和發送按鈕上的事件修改為
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
Str:=IdTCPClient1.IOHandler.ReadLn();
Memo1.Lines.Add(Str); ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end; procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
try
Str:=IdTCPClient1.IOHandler.ReadLn();
Memo1.Lines.Add(Str);
finally
end;
end;
我們編譯後打開多個客戶端進行測試 就會發現 對不同客戶端服務器會分別的響應並對其回復內容互不幹擾!
做到這裏大家也知道客戶端如果要發送一條數據才能相應的去讀取一條數據!可能有些人會想到利用定時器對數據進行定時讀取!~ 這樣也是一個辦法!但是在程序操作中由於數據太快而沒有及時讀取就會出現數據丟失掉了!那我們要用什麽方法才能很好的對數據進行準確讀取呢!在這裏我使用了線程!啟用一個線程利用一個死循環對數據進行讀取!一旦有數據就讀取出來並放在一個 StringList 裏供我們使用!
好我們一步步的來實現!
我們先來做一全局變量的定義新建一全局變量頁面 MainUnit.pas 我們先聲明兩個全局變量
代碼如下
unit MainUnit; interface
uses Classes,SyncObjs;
var
M_Lock : TCriticalSection;//臨界區,多線程同步問題。TCriticalSection
M_MsgList:TStringList;
implementation end.
然後我們在主程序的窗口創建事件裏創建這兩個對象
procedure TForm6.FormCreate(Sender: TObject);
begin
M_MsgList:=TStringList.Create;
M_Lock :=TCriticalSection.Create;
end;
接下來我們把這個頁面引用到程序中以及線程代碼中 線程頁面MyThread.pas代碼如下
unit MyThread; interface
uses Classes,SysUtils,Forms,Windows,Variants,idIOHandler,MainUnit;
type
TMainThread = class(TThread)
private
protected
procedure Foo;
procedure Execute;Override;
public
Constructor Create(Suspended:Boolean);
end; implementation
uses Client;
Constructor TMainThread.Create(Suspended:Boolean);//創建線程
Begin
inherited Create(Suspended);
FreeOnTerminate:=True;
End; procedure TMainThread.Foo;
var
Msg:string;
bool: boolean;
begin
bool:=true;
while bool do begin
try
Msg:= Form6.IdTCPClient1.IOHandler.ReadLn;
if(Msg=‘‘) then
bool:=false
else
begin
M_Lock.Enter;
M_MsgList.Add(Msg);
M_Lock.Leave;
end;
except
bool:=false;
end;
end;
end;
Procedure TMainThread.Execute;//線程啟動
begin
Foo;
End;
End.
線程做好了哪麽我們在程序裏進行使用線程吧!首先當然是要在程序中引用MyThread 啟動的代碼如下連接按鈕事件在連接的時候啟動線程
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
TMainThread.Create(false);
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
相應的我們把發送的讀取部分也去掉所有讀取全部交給線程去處理!
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
這裏線程讀取的內容我們全部都放入了StringList 是因為在我們操作界面時可能會出現訪問不安全的現象!因為在服務器發送過來的消息裏可能有一些是自己定義的執行的命令這些命令可能會直接操作主窗口的一些事件!而在線程裏直接操作某些控件是不安全的!所以我們還是先把所有數據放到StringList 裏!如果是其他的2進制你可以放入LIST 或者ObjectList裏!
好下一步就是要把StringList 裏的數據讀取出來並顯示在 Memo1 裏了!在這裏我是用一個定時器對StringList 進行檢查的!加入一個記時器設置時間為1毫秒!我們設置它活動的狀態就放在TCP客戶端控件的OnConnected事件裏!
Enabled False
Interval 1
procedure TForm6.IdTCPClient1Connected(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
停止活動哦事件放在TCP客戶端控件的OnDisconnected事件裏!
procedure TForm6.IdTCPClient1Disconnected(Sender: TObject);
begin
Timer1.Enabled:=false;
end;
然後我們在事件響應函數裏這樣做
procedure TForm6.Timer1Timer(Sender: TObject);
var
Msg:String;
begin
M_Lock.Enter;
while M_MsgList.Count > 0 do
begin
Msg:=‘‘;
Msg := M_MsgList[0];
M_MsgList.Delete(0);
if(Msg<>‘‘)then
Memo1.Lines.Add(Msg);
end;
M_Lock.Leave;
end;
我們再來運行下看一下效果吧!效果和剛剛的基本一樣!但是唯一不同的一點就在於!客戶端可以在任何一個時候接受來自服務器的數據!而非主動發送數據而只能單次獲取!而且使用了StringList 你完全可以在這裏安全的執行相應的事件或函數!不會對線程接受數據的操作有任何影響!
好到這裏客戶端既然能主動發送數據到服務器並且也能接受到服務器的反饋了!但是大家註意到沒有!如果服務器想對客戶端主動發送數據好像是不可以的!因為在服務端裏都是只有響應與其對話的那個客戶端的IdTCPServer1Execute事件裏才能有反應!也才能對這個用戶發送數據!
下面我們來做一下 服務端如何對所有用戶發送廣播信息!
在服務器上添加一按鈕 為廣播 以及一個文本輸入框!
在按鈕時間裏我們的代碼如下
procedure TForm6.Button3Click(Sender: TObject);
var
cList : TList;
Count : Integer;
Str:String;
begin
Str:=Edit1.Text;
try
cList := IdTCPServer1.Contexts.LockList;
for Count := 0 to cList.Count -1 do
begin
TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);
end;
finally
IdTCPServer1.Contexts.UnlockList; //一定要解鎖否則將會造成死鎖
end;
end;
好了我們編譯好客戶端 多開幾個來測試結果吧! 怎麽樣服務器可以主動給所有連接的用戶發送數據了吧!如果是按照我們之前的客戶端沒有使用隨時準備著接收那麽就不會接受到 服務器的廣播數據了或者接收到的數據不夠準確! 在Delphi 2007中使用Indy10的TCP連接教程(系列三) 做 服務端如何針對一個客戶進行主動發送信息!
首先 服務端 要針對某一個用戶進行發送信息那麽就意味著 沒一個客戶端必須擁有唯一標識身份的標誌!如 用戶名 用戶ID 等等!在這裏我們就使用用戶名吧!我們在客戶端連接的時候加上一用戶名 以便區別用戶!
我們在客戶端上加入一個文本輸入框命名為 UserName 在連接按鈕的代碼如下 procedure TForm6.ConetClick(Sender: TObject);
var
Str:String;
begin
Str:=UserName.Text;
if Str=‘‘ then
begin
ShowMessage(‘請輸入用戶名‘);
exit;
end; try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
TMainThread.Create(false);
IdTCPClient1.IOHandler.writeln(‘@User:‘+Str);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
在這裏我們在用戶名的前面加上 “@User:”是為了區別與其他客戶端發送到服務端的信息!您可以自己定義!
好那我們接著來看服務端的代碼吧!為了對用戶數據的管理方便我們先來定義一個 用戶類代碼我就直接貼出來了!
UserObj.pas
-----------------------
unit UserObj; interface
uses
Classes,
SyncObjs,
SysUtils,
IdContext;
type
TUserClass=class(TObject)
FUserName:String; //您還可以定義更多的數據以及方法
FContext: TIdContext; //這裏之所有要定義是可以在對象內發送信息 public
constructor create;
destructor Destroy; override;
procedure CheckMsg(AContext: TIdContext); //這裏是用於對象類處理信息 published
property UserName:string read FUserName write FUserName;
end; implementation
uses Server;
constructor TUserClass.create;
begin
inherited;
FUserName:=‘‘;
end; destructor TUserClass.Destroy;
begin
inherited;
end; procedure TUserClass.CheckMsg(AContext: TIdContext);
var
Msg,Key,Value : String;
Len:Longint;
begin
try
FContext := AContext;
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Msg:=AContext.Connection.IOHandler.ReadLn();
if(Msg<>‘‘) then
begin
if(Msg[1]=‘@‘) then //@表示命令
begin
Len:=Length(Msg);
if(Len>6) then
begin
Key:=Copy(Msg, 2, 4); //命令符號
if Key=‘User‘ then
begin
Value:=Copy(Msg, 7); //值
FUserName:=Value;
Form6.Memo1.Lines.Add(‘用戶:‘+FUserName+‘登陸服務器!‘) ;
end;
end;
end
else
Form6.Memo1.Lines.Add(FUserName+‘:‘+Msg);
end;
finally
end;
end;
end. ------------------------------------------------------------------
好我們來看下 服務器的程序是怎麽樣使用這個類來管理用戶數據的!
我們先引用UserObj 然後在IdTCPServer1控件的連接事件OnConnect上這樣做!
procedure TForm6.IdTCPServer1Connect(AContext: TIdContext);
begin
AContext.Data:=TUserClass.create;
end;
同樣我們在斷開連接的時候釋放掉這個對象
procedure TForm6.IdTCPServer1Disconnect(AContext: TIdContext);
begin
AContext.Data.Free;
AContext.Data := nil;
end;
接著我們就要把服務器的監聽事件交給我們的用戶對象去處理了!
我們把 IdTCPServer1控件的 OnExecute事件代碼改寫為如下:
procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
TUserClass(AContext.Data).CheckMsg(AContext);
end;
做到這裏我們來運行看一下效果!~ 客戶端先輸入用戶名 然後點擊 連接多個用戶進行連接後我們就發現 服務器上可以識別信息到底是誰發過來的了!接著要做服務器針對一個用戶發送信息了!
我們在服務端上添加一個指定發送信息的用戶名 文本輸入框!名稱也為 UserName 然後在添加一個單用戶發送按鈕
按鈕事件如下
procedure TForm6.Button4Click(Sender: TObject);
var
cList : TList;
Count : Integer;
Str,User:String;
begin
Str:=Edit1.Text;
User:=UserName.Text;
if(User=‘‘)then
begin
showmessage(‘請輸入要指定發送信息的用戶名!‘);
exit;
end; try
cList := IdTCPServer1.Contexts.LockList;
for Count := 0 to cList.Count -1 do
begin
if(TUserClass(TIdContext(cList[Count]).Data).UserName=User)then //轉為對象並判斷對象的用戶名
TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);
end;
finally
IdTCPServer1.Contexts.UnlockList; //一定要解鎖否則將會造成死鎖
end;
end;
收尾工作就是給服務器加上一個 啟動燈效果以及做一下簡單的握手退出!大概道理就是發送一個EXIT給服務器然後服務器退出後 客戶端再退出! 這樣做也是為了安全的退出連接!如果不做這一步好像也沒有什麽大問題~ 在測試中可能會有一些提示 說是連接還沒有結束就退出了主程序!這個問題我查閱了一些國外的文檔~上面說 “這個是DelPhi的正常提示!提示並不一定是報錯~~ 可以選擇編譯的時候忽略掉這個提示信息!~ 只要程序在獨立運行下沒有報錯了就行了!~”
OK 大功告成!~ 我們來測試一下我們是否真的可以指定用戶發送信息了!
IdTcpServer/IdTcpClient IdTcpServer
uses IdContext //需要引用
屬性,方法:
IdTCPServer.Active :=True; //開啟服務器
IdTCPServer1.Bindings.Add.IP := ‘127.0.0.1‘;//綁定IP
IdTCPServer1.Bindings.Add.Port := 7956;//綁定端口
事件:
OnConnect : 客戶端連接成功觸發
OnDisConnect : 客戶端斷開觸發
OnExeCute : 收到客戶端數據觸發 例子
//像所有客戶斷發送數據
var
I: Integer;
Context: TIdContext;
begin
with IdTCPServer1.Contexts.LockList do
try
for I := 0 to Count - 1 do
begin
Context := TIdContext(Items[I]);
Context.Connection.IOHandler.Write(‘Hello,everybody!‘);
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end; //向某個客戶發送數據
var
I: Integer;
Context: TIdContext;
begin
with IdTCPServer1.Contexts.LockList do
try
for I := 0 to Count - 1 do
begin
Context := TIdContext(Items[I]);
if Context.Binding.PeerIP <> ‘192.168.10.90‘ then
continue;
Context.Connection.IOHandler.Write(‘Hello!‘);
end;
finally
IdTCPServer1.Contexts.UnlockList;
end;
end; IdTcpClient
屬性,方法:
ConnectTimeOut:連接超時
Host:主機IP或域名
IPVersion:Ip版本 ipv4 or ipv6
Name:控件名
Port:主機端口
ReadTimeOut:讀取超時
IdTCPClient1.Connect; //連接服務端
IdTCPClient1.Disconnect;//端開連接
IdTCPClient1.Connected;//是否連接成功返回true 連接成功
IdTCPClient1.IOHandler.WriteLn(‘aa‘);// 向服務端發送數據 OnWork事件AWorkMode=wmRead 表示有收到數據 在DelPhi2007 中 使用Indy 的TCP連接教程(一) 首先 先說明下 為什麽要用 INDY10 最新的indy10可以基於win32上的程(Fiber) API.
什麽叫Fiber API呢,這裏是解釋:
纖程(Fiber) — 可以從 32 位版本的 Windows? 中使用的輕量級線程處理對象 — 在很多方案中都很有用。由於線程是寶貴資源,因此您有時不希望將整個 OS 線程專門用於執行簡單的任務。通過纖程,可以比線程更嚴密地控制任務的調度,因為是您而不是 OS 負責管理它們。由於它們具有較少的開銷,因此當您切換上下文時,它們還更加快速。此外,因為是由您控制纖程,所以對於它們而言,通常可以更容易地跟蹤同步問題。 不過這個特性,現在只有針對delphi7有用。
端口重疊可以讓你的服務器承擔更多的用戶。indy10值得一用。 indy10支持完成端口和纖程,性能有了巨大提升! ================================================================================ 我們先打開 DelPhi2007 工具吧! 首先 我們 做好一個簡單的客戶端 先新建一個窗口程序拖入一個TCP客戶端控件還有3個按鈕一個文本框 是 連接 斷開 和 發生 設置一下 IdTCPClient 控件的屬性 Host :127.0.0.1
Post:3000
下面我們來對連接按鈕做事件
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
IdTCPClient1.Connect;
ShowMessage(‘連接成功‘);
except
ShowMessage(‘連接失敗‘);
end;
end; 接著 我們來做一下服務端的程序
先新建一個窗口程序拖入一個TCP服務端控件兩個按鈕 以及一個 TMemo用來顯示信息
Bindings 0.0.0.0:3000
DefaultPort 3000 我們在“啟動服務” 按鈕上的事件 procedure TForm6.Button1Click(Sender: TObject);
begin
IdTCPServer1.Active:= true;
end; 啟動時 只要將其Active設置為 true 既啟動了服務而關閉則同樣設置為 False
接下來我們要對 IdTCPServer1 的 OnExecute 事件做處理! 選擇控件 EVENTS 欄雙擊OnExecute
在這裏代碼我們暫時這樣寫 procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
exit;
end; TIdContext 需要 uses IdContext 好 到這裏 運行下服務器 和客戶端 然後 啟動服務器 和 連接服務器
好已經可以連接得上了吧!
但是因為 我們在服務器監聽的部分退出了 所以 並沒有保持著連接
現在我們 修改一下 代碼吧 我們把OnExecute 代碼修改如下 procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
var
Swp:String;
begin
try
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Swp:=AContext.Connection.IOHandler.ReadLn();
Memo1.Lines.Add(Swp) ;
finally
end;
end; 我們對客戶端也修改一下
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
在運行測試一下
當按下連接按鈕後服務器上的文本框裏 加入了一行 ‘lianjie‘ 字符串而其再次點擊連接已經無效而剛剛每次點擊一次 都會提示一次連接成功 仔細看代碼就發現在連接的時候判斷了是否已經連接了如果已經保持連接了哪麽就不會在做下面的代碼!從而可知現在的連接已經是保持著的了!那好我們來發個信息看下是否真的可以連接了
在發送按鈕上的事件
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
好我們來測試一下 是不是連接以後真的可以向服務器發送數據了呢?
看到了吧!是不是可以發送數據了! 在Delphi 2007中使用Indy10的TCP連接的教程(系列二) 服務器怎麽樣區別數據到底是哪一個發送過來的呢,或者服務器如何對其回復數據呢!~ 先針對回復對應的客戶端發送過來的數據!已經客戶端接受並顯示服務器反饋回來的數據!
我們修改服務器上的OnExecute代碼如下! procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
var
Swp:String;
begin
try
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Swp:=AContext.Connection.IOHandler.ReadLn();
if(Swp<>‘‘)then
AContext.Connection.IOHandler.WriteLn(‘服務器已經收到您發來的信息:‘+Swp);
Memo1.Lines.Add(Swp) ;
finally
end;
end;
在客戶端裏我們加入一個TMemo用來接受服務器發來的數據信息!
然後我們在來看客戶端的代碼!
在連接和發送按鈕上的事件修改為
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
Str:=IdTCPClient1.IOHandler.ReadLn();
Memo1.Lines.Add(Str); ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end; procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
try
Str:=IdTCPClient1.IOHandler.ReadLn();
Memo1.Lines.Add(Str);
finally
end;
end;
我們編譯後打開多個客戶端進行測試 就會發現 對不同客戶端服務器會分別的響應並對其回復內容互不幹擾!
做到這裏大家也知道客戶端如果要發送一條數據才能相應的去讀取一條數據!可能有些人會想到利用定時器對數據進行定時讀取!~ 這樣也是一個辦法!但是在程序操作中由於數據太快而沒有及時讀取就會出現數據丟失掉了!那我們要用什麽方法才能很好的對數據進行準確讀取呢!在這裏我使用了線程!啟用一個線程利用一個死循環對數據進行讀取!一旦有數據就讀取出來並放在一個 StringList 裏供我們使用!
好我們一步步的來實現!
我們先來做一全局變量的定義新建一全局變量頁面 MainUnit.pas 我們先聲明兩個全局變量
代碼如下
unit MainUnit; interface
uses Classes,SyncObjs;
var
M_Lock : TCriticalSection;//臨界區,多線程同步問題。TCriticalSection
M_MsgList:TStringList;
implementation end.
然後我們在主程序的窗口創建事件裏創建這兩個對象
procedure TForm6.FormCreate(Sender: TObject);
begin
M_MsgList:=TStringList.Create;
M_Lock :=TCriticalSection.Create;
end;
接下來我們把這個頁面引用到程序中以及線程代碼中 線程頁面MyThread.pas代碼如下
unit MyThread; interface
uses Classes,SysUtils,Forms,Windows,Variants,idIOHandler,MainUnit;
type
TMainThread = class(TThread)
private
protected
procedure Foo;
procedure Execute;Override;
public
Constructor Create(Suspended:Boolean);
end; implementation
uses Client;
Constructor TMainThread.Create(Suspended:Boolean);//創建線程
Begin
inherited Create(Suspended);
FreeOnTerminate:=True;
End; procedure TMainThread.Foo;
var
Msg:string;
bool: boolean;
begin
bool:=true;
while bool do begin
try
Msg:= Form6.IdTCPClient1.IOHandler.ReadLn;
if(Msg=‘‘) then
bool:=false
else
begin
M_Lock.Enter;
M_MsgList.Add(Msg);
M_Lock.Leave;
end;
except
bool:=false;
end;
end;
end;
Procedure TMainThread.Execute;//線程啟動
begin
Foo;
End;
End.
線程做好了哪麽我們在程序裏進行使用線程吧!首先當然是要在程序中引用MyThread 啟動的代碼如下連接按鈕事件在連接的時候啟動線程
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
TMainThread.Create(false);
IdTCPClient1.IOHandler.writeln(‘lianjie‘);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
相應的我們把發送的讀取部分也去掉所有讀取全部交給線程去處理!
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
這裏線程讀取的內容我們全部都放入了StringList 是因為在我們操作界面時可能會出現訪問不安全的現象!因為在服務器發送過來的消息裏可能有一些是自己定義的執行的命令這些命令可能會直接操作主窗口的一些事件!而在線程裏直接操作某些控件是不安全的!所以我們還是先把所有數據放到StringList 裏!如果是其他的2進制你可以放入LIST 或者ObjectList裏!
好下一步就是要把StringList 裏的數據讀取出來並顯示在 Memo1 裏了!在這裏我是用一個定時器對StringList 進行檢查的!加入一個記時器設置時間為1毫秒!我們設置它活動的狀態就放在TCP客戶端控件的OnConnected事件裏!
Enabled False
Interval 1
procedure TForm6.IdTCPClient1Connected(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
停止活動哦事件放在TCP客戶端控件的OnDisconnected事件裏!
procedure TForm6.IdTCPClient1Disconnected(Sender: TObject);
begin
Timer1.Enabled:=false;
end;
然後我們在事件響應函數裏這樣做
procedure TForm6.Timer1Timer(Sender: TObject);
var
Msg:String;
begin
M_Lock.Enter;
while M_MsgList.Count > 0 do
begin
Msg:=‘‘;
Msg := M_MsgList[0];
M_MsgList.Delete(0);
if(Msg<>‘‘)then
Memo1.Lines.Add(Msg);
end;
M_Lock.Leave;
end;
我們再來運行下看一下效果吧!效果和剛剛的基本一樣!但是唯一不同的一點就在於!客戶端可以在任何一個時候接受來自服務器的數據!而非主動發送數據而只能單次獲取!而且使用了StringList 你完全可以在這裏安全的執行相應的事件或函數!不會對線程接受數據的操作有任何影響!
好到這裏客戶端既然能主動發送數據到服務器並且也能接受到服務器的反饋了!但是大家註意到沒有!如果服務器想對客戶端主動發送數據好像是不可以的!因為在服務端裏都是只有響應與其對話的那個客戶端的IdTCPServer1Execute事件裏才能有反應!也才能對這個用戶發送數據!
下面我們來做一下 服務端如何對所有用戶發送廣播信息!
在服務器上添加一按鈕 為廣播 以及一個文本輸入框!
在按鈕時間裏我們的代碼如下
procedure TForm6.Button3Click(Sender: TObject);
var
cList : TList;
Count : Integer;
Str:String;
begin
Str:=Edit1.Text;
try
cList := IdTCPServer1.Contexts.LockList;
for Count := 0 to cList.Count -1 do
begin
TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);
end;
finally
IdTCPServer1.Contexts.UnlockList; //一定要解鎖否則將會造成死鎖
end;
end;
好了我們編譯好客戶端 多開幾個來測試結果吧! 怎麽樣服務器可以主動給所有連接的用戶發送數據了吧!如果是按照我們之前的客戶端沒有使用隨時準備著接收那麽就不會接受到 服務器的廣播數據了或者接收到的數據不夠準確! 在Delphi 2007中使用Indy10的TCP連接教程(系列三) 做 服務端如何針對一個客戶進行主動發送信息!
首先 服務端 要針對某一個用戶進行發送信息那麽就意味著 沒一個客戶端必須擁有唯一標識身份的標誌!如 用戶名 用戶ID 等等!在這裏我們就使用用戶名吧!我們在客戶端連接的時候加上一用戶名 以便區別用戶!
我們在客戶端上加入一個文本輸入框命名為 UserName 在連接按鈕的代碼如下 procedure TForm6.ConetClick(Sender: TObject);
var
Str:String;
begin
Str:=UserName.Text;
if Str=‘‘ then
begin
ShowMessage(‘請輸入用戶名‘);
exit;
end; try
if not (IdTCPClient1.Connected) then
begin
IdTCPClient1.Connect;
TMainThread.Create(false);
IdTCPClient1.IOHandler.writeln(‘@User:‘+Str);
ShowMessage(‘連接成功‘);
end;
except
ShowMessage(‘連接失敗‘);
end;
end;
在這裏我們在用戶名的前面加上 “@User:”是為了區別與其他客戶端發送到服務端的信息!您可以自己定義!
好那我們接著來看服務端的代碼吧!為了對用戶數據的管理方便我們先來定義一個 用戶類代碼我就直接貼出來了!
UserObj.pas
-----------------------
unit UserObj; interface
uses
Classes,
SyncObjs,
SysUtils,
IdContext;
type
TUserClass=class(TObject)
FUserName:String; //您還可以定義更多的數據以及方法
FContext: TIdContext; //這裏之所有要定義是可以在對象內發送信息 public
constructor create;
destructor Destroy; override;
procedure CheckMsg(AContext: TIdContext); //這裏是用於對象類處理信息 published
property UserName:string read FUserName write FUserName;
end; implementation
uses Server;
constructor TUserClass.create;
begin
inherited;
FUserName:=‘‘;
end; destructor TUserClass.Destroy;
begin
inherited;
end; procedure TUserClass.CheckMsg(AContext: TIdContext);
var
Msg,Key,Value : String;
Len:Longint;
begin
try
FContext := AContext;
AContext.Connection.IOHandler.CheckForDisconnect(True, True);
Msg:=AContext.Connection.IOHandler.ReadLn();
if(Msg<>‘‘) then
begin
if(Msg[1]=‘@‘) then //@表示命令
begin
Len:=Length(Msg);
if(Len>6) then
begin
Key:=Copy(Msg, 2, 4); //命令符號
if Key=‘User‘ then
begin
Value:=Copy(Msg, 7); //值
FUserName:=Value;
Form6.Memo1.Lines.Add(‘用戶:‘+FUserName+‘登陸服務器!‘) ;
end;
end;
end
else
Form6.Memo1.Lines.Add(FUserName+‘:‘+Msg);
end;
finally
end;
end;
end. ------------------------------------------------------------------
好我們來看下 服務器的程序是怎麽樣使用這個類來管理用戶數據的!
我們先引用UserObj 然後在IdTCPServer1控件的連接事件OnConnect上這樣做!
procedure TForm6.IdTCPServer1Connect(AContext: TIdContext);
begin
AContext.Data:=TUserClass.create;
end;
同樣我們在斷開連接的時候釋放掉這個對象
procedure TForm6.IdTCPServer1Disconnect(AContext: TIdContext);
begin
AContext.Data.Free;
AContext.Data := nil;
end;
接著我們就要把服務器的監聽事件交給我們的用戶對象去處理了!
我們把 IdTCPServer1控件的 OnExecute事件代碼改寫為如下:
procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
TUserClass(AContext.Data).CheckMsg(AContext);
end;
做到這裏我們來運行看一下效果!~ 客戶端先輸入用戶名 然後點擊 連接多個用戶進行連接後我們就發現 服務器上可以識別信息到底是誰發過來的了!接著要做服務器針對一個用戶發送信息了!
我們在服務端上添加一個指定發送信息的用戶名 文本輸入框!名稱也為 UserName 然後在添加一個單用戶發送按鈕
按鈕事件如下
procedure TForm6.Button4Click(Sender: TObject);
var
cList : TList;
Count : Integer;
Str,User:String;
begin
Str:=Edit1.Text;
User:=UserName.Text;
if(User=‘‘)then
begin
showmessage(‘請輸入要指定發送信息的用戶名!‘);
exit;
end; try
cList := IdTCPServer1.Contexts.LockList;
for Count := 0 to cList.Count -1 do
begin
if(TUserClass(TIdContext(cList[Count]).Data).UserName=User)then //轉為對象並判斷對象的用戶名
TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);
end;
finally
IdTCPServer1.Contexts.UnlockList; //一定要解鎖否則將會造成死鎖
end;
end;
收尾工作就是給服務器加上一個 啟動燈效果以及做一下簡單的握手退出!大概道理就是發送一個EXIT給服務器然後服務器退出後 客戶端再退出! 這樣做也是為了安全的退出連接!如果不做這一步好像也沒有什麽大問題~ 在測試中可能會有一些提示 說是連接還沒有結束就退出了主程序!這個問題我查閱了一些國外的文檔~上面說 “這個是DelPhi的正常提示!提示並不一定是報錯~~ 可以選擇編譯的時候忽略掉這個提示信息!~ 只要程序在獨立運行下沒有報錯了就行了!~”
OK 大功告成!~ 我們來測試一下我們是否真的可以指定用戶發送信息了!
Indy 10.5.8 for Delphi and Lazarus 修改版(2011)