[轉]在DelPhi2007 中 使用Indy 的TCP連接教程(一)2009年07月18日 星期六 00:57首先 先說(shuō)明下 為什么要用 INDY10
最新的indy10可以基于win32上的纖程(Fiber) API.
什么叫Fiber API呢,這里是解釋:
纖程(Fiber) — 可以從 32 位版本的 Windows? 中使用的輕量級線(xiàn)程處理對象 — 在很多方案中都很有用。由于線(xiàn)程是寶貴資源,因此您有時(shí)不希望將整個(gè) OS 線(xiàn)程專(zhuān)門(mén)用于執行簡(jiǎn)單的任務(wù)。通過(guò)纖程,可以比線(xiàn)程更嚴密地控制任務(wù)的調度,因為是您而不是 OS 負責管理它們。由于它們具有較少的開(kāi)銷(xiāo),因此當您切換上下文時(shí),它們還更加快速。此外,因為是由您控制纖程,所以對于它們而言,通??梢愿菀椎馗櫷絾?wèn)題。 不過(guò)這個(gè)特性,現在只有針對delphi7有用。
端口重疊可以讓你的服務(wù)器承擔更多的用戶(hù)。indy10值得一用。
indy10支持完成端口和纖程,性能有了巨大提升!
================================================================================
DelPhi2007 中 Indy 升級到了10 而其代碼的操作方式也改了很多!
很多網(wǎng)友 包括本人 也是在網(wǎng)絡(luò )上找了很多資料 都沒(méi)有一個(gè)很滿(mǎn)意的答案! 沒(méi)辦法 也得自己親自調試 親自寫(xiě)了!
經(jīng)過(guò) 好一段時(shí)間的摸索!現那出來(lái)分享!如果有什么使用錯了的地方也希望大家給予修正!
先來(lái)侃侃客戶(hù)端程序! 因為這個(gè)程序畢竟真的很難找到資料了 所以為了大家很好的使用上這個(gè)控件 我在這里是一邊做 一邊填代碼了!
我們先打開(kāi) DelPhi2007 工具吧!
首先 我們 做好一個(gè)簡(jiǎn)單的客戶(hù)端
先新建一個(gè)窗口程序 拖入一個(gè)TCP客戶(hù)端控件 還有3個(gè)按鈕 一個(gè)文本框 是 連接 斷開(kāi) 和 發(fā)生
設置一下 IdTCPClient 控件的屬性
Host :127.0.0.1
Post:3000
下面 我們來(lái)對連接按鈕做事件
procedure TForm6.ConetClick(Sender: TObject);
begin
try
if not (IdTCPClient1.Connected) then
IdTCPClient1.Connect;
ShowMessage('連接成功');
except
ShowMessage('連接失敗');
end;
end;
接著(zhù) 我們來(lái)做一下服務(wù)端的程序
先新建一個(gè)窗口程序 拖入一個(gè)TCP服務(wù)端控件 兩個(gè)按鈕 以及一個(gè) TMemo用來(lái)顯示信息
Bindings 0.0.0.0:3000
DefaultPort 3000
我們在“啟動(dòng)服務(wù)” 按鈕上 的事件
procedure TForm6.Button1Click(Sender: TObject);
begin
IdTCPServer1.Active:= true;
end;
啟動(dòng)時(shí) 只要將其Active設置為 true 既啟動(dòng)了服務(wù) 而關(guān)閉則同樣設置為 False
接下來(lái) 我們要對 IdTCPServer1 的 OnExecute 事件做處理! 選擇控件 EVENTS 欄 雙擊OnExecute
在這里 代碼我們暫時(shí)這樣寫(xiě)
procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
exit;
end;
TIdContext 需要 uses IdContext
好 到這里 運行下服務(wù)器 和 客戶(hù)端 然后 啟動(dòng)服務(wù)器 和 連接服務(wù)器
好 已經(jīng)可以連接得上了吧!
但是 因為 我們在服務(wù)器監聽(tīng)的部分退出了 所以 并沒(méi)有保持著(zhù)連接
現在 我們 修改一下 代碼吧 我們把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;
我們對客戶(hù)端也修改一下
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;
在運行測試一下
當按下連接按鈕后 服務(wù)器上的文本框里 加入了一行 'lianjie' 字符串 而其再次點(diǎn)擊連接已經(jīng)無(wú)效 而剛剛每次點(diǎn)擊一次 都會(huì )提示一次連接成功 仔細看代碼就發(fā)現 在連接的時(shí)候判斷了是否已經(jīng)連接了 如果已經(jīng)保持連接了哪么就不會(huì )在做下面的代碼!從而可知現在的連接已經(jīng)是保持著(zhù)的了! 那好我們來(lái)發(fā)個(gè)信息看下是否真的可以連接了
在發(fā)送按鈕上的事件
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
好 我們來(lái)測試一下 是不是連接以后真的可以向服務(wù)器發(fā)送數據了呢?
看到了吧! 是不是可以發(fā)送數據了!
暈 機子升級 提示我自動(dòng)關(guān)機了!
好吧今天就先講到這里! 明天繼續講 服務(wù)器回復數據后 而客戶(hù)端怎樣接受來(lái)自服務(wù)端的數據!
-----------------------------------
在Delphi 2007中使用Indy10的TCP連接的教程(系列二)[轉]2009年07月18日 星期六 00:58好我們接著(zhù)昨天的來(lái)講!
我們昨天已經(jīng)做到了 客戶(hù)端能正常的連接服務(wù)器 并且向服務(wù)器發(fā)送內容了!大家可以多開(kāi)幾個(gè)客戶(hù)端然后連接服務(wù)器!并發(fā)送內容! 在這里可能你會(huì )問(wèn)到!服務(wù)器怎么樣區別數據到底是哪一個(gè)發(fā)送過(guò)來(lái)的呢,或者服務(wù)器如何對其回復數據呢!~ 那么!我們現在就先針對回復對應的客戶(hù)端發(fā)送過(guò)來(lái)的數據!已經(jīng)客戶(hù)端接受并顯示服務(wù)器反饋回來(lái)的數據!
我們修改服務(wù)器上的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('服務(wù)器已經(jīng)收到您發(fā)來(lái)的信息:'+Swp);
Memo1.Lines.Add(Swp) ;
finally
end;
end;
在客戶(hù)端里我們加入一個(gè)TMemo用來(lái)接受服務(wù)器發(fā)來(lái)的數據信息!
然后我們在來(lái)看客戶(hù)端的代碼!
在連接和發(fā)送按鈕上的事件修改為
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;
我們編譯后 打開(kāi)多個(gè)客戶(hù)端進(jìn)行測試 就會(huì )發(fā)現 對不同客戶(hù)端 服務(wù)器會(huì )分別的響應并對其回復內容 互不干擾!
做到這里 大家也知道客戶(hù)端如果要發(fā)送一條數據才能相應的去讀取一條數據! 可能有些人會(huì )想到利用定時(shí)器對數據進(jìn)行定時(shí)讀??!~ 這樣也是一個(gè)辦法!但是在程序操作中 由于數據太快而沒(méi)有及時(shí)讀取 就會(huì )出現數據丟失掉了!那我們要用什么方法才能很好的對數據進(jìn)行準確讀取呢!在這里我使用了線(xiàn)程!啟用一個(gè)線(xiàn)程利用一個(gè)死循環(huán)對數據進(jìn)行讀??!一旦有數據就讀取出來(lái) 并放在一個(gè) StringList 里 供我們使用!
好我們一步步的來(lái)實(shí)現!
我們先來(lái)做一全局變量的定義 新建一全局變量頁(yè)面 MainUnit.pas
我們先聲明兩個(gè)全局變量
代碼如下
unit MainUnit;
interface
uses Classes,SyncObjs;
var
M_Lock : TCriticalSection;
M_MsgList:TStringList;
implementation
end.
然后我們在主程序的窗口創(chuàng )建事件里創(chuàng )建這兩個(gè)對象
procedure TForm6.FormCreate(Sender: TObject);
begin
M_MsgList:=TStringList.Create;
M_Lock :=TCriticalSection.Create;
end;
接下來(lái)我們把這個(gè)頁(yè)面引用到程序中 以及線(xiàn)程代碼中 線(xiàn)程頁(yè)面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);//創(chuàng )建線(xiàn)程
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;//線(xiàn)程啟動(dòng)
begin
Foo;
End;
End.
線(xiàn)程做好了 哪么我們在程序里進(jìn)行使用線(xiàn)程吧!首先當然是要在程序中引用MyThread 啟動(dòng)的代碼如下連接按鈕事件 在連接的時(shí)候啟動(dòng)線(xiàn)程
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;
相應的我們把發(fā)送的讀取部分也去掉 所有讀取全部交給線(xiàn)程去處理!
procedure TForm6.SendClick(Sender: TObject);
var
Str:String;
begin
Str:=Edit1.Text;
if(IdTCPClient1.Connected) then
IdTCPClient1.IOHandler.writeln(Str);
end;
這里 線(xiàn)程讀取的內容我們全部都放入了StringList 是因為 在我們操作界面時(shí)可能會(huì )出現 訪(fǎng)問(wèn)不安全的現象!因為在服務(wù)器發(fā)送過(guò)來(lái)的消息里可能有一些是自己定義的執行的命令 這些命令可能會(huì )直接操作主窗口的一些事件!而在線(xiàn)程里直接操作某些控件是不安全的!所以我們還是先把所有數據放到StringList 里!如果是其他的2進(jìn)制 你可以放入LIST 或者ObjectList里!
好 下一步就是要把StringList 里的數據讀取出來(lái) 并顯示在 Memo1 里了!在這里我是用一個(gè)定時(shí)器對StringList 進(jìn)行檢查的! 加入一個(gè)記時(shí)器 設置時(shí)間為1毫秒!我們設置它活動(dòng)的狀態(tài)就放在TCP客戶(hù)端控件的OnConnected事件里!
Enabled False
Interval 1
procedure TForm6.IdTCPClient1Connected(Sender: TObject);
begin
Timer1.Enabled:=true;
end;
停止活動(dòng)哦事件放在TCP客戶(hù)端控件的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;
我們再來(lái)運行下 看一下效果吧! 效果和剛剛的基本一樣! 但是唯一不同的一點(diǎn)就在于!客戶(hù)端可以在任何一個(gè)時(shí)候接受來(lái)自服務(wù)器的數據!而非主動(dòng)發(fā)送數據而只能單次獲??!而且使用了StringList 你完全可以在這里安全的執行相應的事件或函數!不會(huì )對線(xiàn)程接受數據的操作有任何影響!
好 到這里 客戶(hù)端既然能主動(dòng)發(fā)送數據到服務(wù)器 并且也能接受到服務(wù)器的反饋了!但是大家注意到?jīng)]有!如果服務(wù)器想對客戶(hù)端主動(dòng)發(fā)送數據好像是不可以的! 因為在服務(wù)端里 都是只有響應與其對話(huà)的那個(gè)客戶(hù)端的IdTCPServer1Execute事件里才能有反應!也才能對這個(gè)用戶(hù)發(fā)送數據!
下面 我們來(lái)做一下 服務(wù)端如何對所有用戶(hù)發(fā)送廣播信息!
在服務(wù)器上 添加一按鈕 為廣播 以及一個(gè)文本輸入框!
在按鈕時(shí)間里 我們的代碼如下
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; //一定要解鎖 否則將會(huì )造成死鎖
end;
end;
好了 我們編譯好客戶(hù)端 多開(kāi)幾個(gè)來(lái)測試結果吧! 怎么樣 服務(wù)器可以主動(dòng)給所有連接的用戶(hù)發(fā)送數據了吧! 如果是按照我們之前的客戶(hù)端沒(méi)有使用隨時(shí)準備著(zhù)接收那么 就不會(huì )接受到 服務(wù)器的廣播數據了 或者接收到的數據不夠準確!
好了! 今天就先講到這里! 明天繼續講 服務(wù)器怎樣針對一個(gè)指定的客戶(hù)發(fā)送數據!
------------------
在Delphi 2007中使用Indy10的TCP連接教程(系列三)[轉]2009年07月18日 星期六 00:59前兩天已經(jīng)介紹了 Indy10 的基本通訊!而且也實(shí)現了 客戶(hù)端的發(fā)送和接收數據!~ 以及服務(wù)端的廣播信息! 現在我們就接著(zhù)來(lái)做 服務(wù)端如何針對一個(gè)客戶(hù)進(jìn)行主動(dòng)發(fā)送信息!
首先 服務(wù)端 要針對某一個(gè)用戶(hù)進(jìn)行發(fā)送信息 那么就意味著(zhù) 沒(méi)一個(gè)客戶(hù)端必須擁有唯一標識身份的標志! 如 用戶(hù)名 用戶(hù)ID 等等!在這里我們就使用用戶(hù)名吧! 我們在客戶(hù)端連接的時(shí)候加上一用戶(hù)名 以便區別用戶(hù)!
我們在客戶(hù)端上加入一個(gè)文本輸入框 命名為 UserName 在連接按鈕的代碼如下
procedure TForm6.ConetClick(Sender: TObject);
var
Str:String;
begin
Str:=UserName.Text;
if Str='' then
begin
ShowMessage('請輸入用戶(hù)名');
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;
在這里我們在用戶(hù)名的前面加上
“@User:”是為了區別與其他客戶(hù)端發(fā)送到服務(wù)端的信息!您可以自己定義!
好 那我們接著(zhù)來(lái)看服務(wù)端的代碼吧!為了對用戶(hù)數據的管理方便 我們先來(lái)定義一個(gè) 用戶(hù)類(lèi) 代碼我就直接貼出來(lái)了!
UserObj.pas
-----------------------
unit UserObj;
interface
uses
Classes,
SyncObjs,
SysUtils,
IdContext;
type
TUserClass=class(TObject)
FUserName:String; //您還可以定義更多的數據 以及方法
FContext: TIdContext; //這里之所有要定義 是可以在對象內發(fā)送信息
public
constructor create;
destructor Destroy; override;
procedure CheckMsg(AContext: TIdContext); //這里是用于對象類(lèi)處理信息
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('用戶(hù):'+FUserName+'登陸服務(wù)器!') ;
end;
end;
end
else
Form6.Memo1.Lines.Add(FUserName+':'+Msg);
end;
finally
end;
end;
end.
------------------------------------------------------------------
好 我們來(lái)看下 服務(wù)器的程序是怎么樣使用這個(gè)類(lèi)來(lái)管理用戶(hù)數據的!
我們先 引用UserObj 然后在IdTCPServer1控件的 連接事件OnConnect上這樣做!
procedure TForm6.IdTCPServer1Connect(AContext: TIdContext);
begin
AContext.Data:=TUserClass.create;
end;
同樣 我們在斷開(kāi)連接的時(shí)候釋放掉這個(gè)對象
procedure TForm6.IdTCPServer1Disconnect(AContext: TIdContext);
begin
AContext.Data.Free;
AContext.Data := nil;
end;
接著(zhù) 我們就要把服務(wù)器的監聽(tīng)事件交給我們的用戶(hù)對象去處理了!
我們把 IdTCPServer1控件的 OnExecute事件代碼改寫(xiě)為如下:
procedure TForm6.IdTCPServer1Execute(AContext: TIdContext);
begin
TUserClass(AContext.Data).CheckMsg(AContext);
end;
做到這里 我們來(lái)運行看一下效果!~ 客戶(hù)端 先輸入用戶(hù)名 然后點(diǎn)擊 連接 多個(gè)用戶(hù)進(jìn)行連接后 我們就發(fā)現 服務(wù)器上可以識別信息到底是誰(shuí)發(fā)過(guò)來(lái)的了! 接著(zhù)要做 服務(wù)器針對一個(gè)用戶(hù)發(fā)送信息了!
我們在服務(wù)端上添加一個(gè) 指定發(fā)送信息的用戶(hù)名 文本輸入框!名稱(chēng)也為 UserName 然后 在添加一個(gè)單用戶(hù)發(fā)送按鈕
按鈕事件如下
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('請輸入要指定發(fā)送信息的用戶(hù)名!');
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 //轉為對象并判斷對象的用戶(hù)名
TIdContext(cList[Count]).Connection.IOHandler.WriteLn(Str);
end;
finally
IdTCPServer1.Contexts.UnlockList; //一定要解鎖 否則將會(huì )造成死鎖
end;
end;
收尾工作 就是給服務(wù)器加上一個(gè) 啟動(dòng)燈效果 以及做一下簡(jiǎn)單的 握手退出!大概道理就是發(fā)送一個(gè)EXIT給服務(wù)器 然后服務(wù)器退出后 客戶(hù)端再退出! 這樣做 也是為了安全的退出連接! 如果不做這一步好像也沒(méi)有什么大問(wèn)題~ 在測試中 可能會(huì )有一些提示 說(shuō)是連接還沒(méi)有結束就退出了主程序! 這個(gè)問(wèn)題我查閱了一些國外的文檔~上面說(shuō) “這個(gè)是DelPhi的正常提示! 提示并不一定是報錯~~ 可以選擇編譯的時(shí)候忽略掉這個(gè)提示信息!~ 只要程序在獨立運行下沒(méi)有報錯了 就行了!~”
OK 大功告成!~ 我們來(lái)測試一下我們是否真的可以指定用戶(hù)發(fā)送信息了!