線上訂房服務-台灣趴趴狗聯合訂房中心
發文 回覆 瀏覽次數:4616
推到 Plurk!
推到 Facebook!

TIdTcpServer 问题,请教达人

答題得分者是:careychen
zhweizw
一般會員


發表:7
回覆:16
積分:9
註冊:2008-01-12

發送簡訊給我
#1 引用回覆 回覆 發表時間:2008-11-23 02:48:54 IP:58.218.xxx.xxx 訂閱

Indy9 Bug 一直不断,现在使用TIdTcpServer又出现下面这样的错误:
当Client 连接到Server并且保持连接不断开,此时若直接退出Server会出现这样的错误:Terminate Thread TimeOut!
搜遍网上资料,似乎找到了一个很勉强的解决方法:
[code delphi]
Procedure TMainFrm.ShutDown;
var
List: TList;
I: Integer;
begin
List := IdTCPServer1.Threads.LockList;
try
StatusBar1.Panels[1].Text := 'Disconnecting...';
Application.ProcessMessages;
for I := 0 to List.Count - 1 do
begin
try
TIdPeerThread(List.Items[I]).Connection.Disconnect;
except
on E: Exception do
begin
TIdPeerThread(List.Items[I]).Stop;
end;
end;
end;
finally
IdTCPServer1.Threads.UnlockList;
end;
Sleep(IdTCPServer1.TerminateWaitTime);
IdTCPServer1.Active := False;
[/code]
意思是在结束Server进程前,先遍历Client列表,主动断开所有连接并且挂起主线程等到所有子线程结束。
这样似乎也可以勉强使用。
但是,为何我采取下面的方法处理,却得不到预期的目的呢?(使用下面的方法,仍旧出现Terminate Thread TimeOut!错误)---费解!

为了表达清楚,按顺序看代码:
1、定义一个记录体,在Client连接的时候保存一些必要信息

[code delphi]
TClientList = Record //客户端列表
ComputerName : string[20];
ComputerIP : string[15];
Thread : Pointer;
end;
pTClientList = ^TClientList;

var
MainFrm: TMainFrm;
ThreadList:TThreadList;
[/code]

2、主窗体创建时:

[code delphi]
procedure TMainFrm.FormCreate(Sender: TObject);
begin
//PostMessage(Application.Handle ,WM_SYSCOMMAND, SC_MINIMIZE,0);
ThreadList:=TThreadList.Create;
end;

[/code]
3、有Client连接时:

[code delphi]
procedure TMainFrm.IdTCPServer1Connect(AThread: TIdPeerThread);
var //连接时触发
pNewClient:pTClientList;
begin
GetMem(pNewClient,sizeof(TClientList)); //分配内存
pNewClient^.ComputerIP := AThread.Connection.Socket.Binding.PeerIP;
pNewClient^.ComputerName :=AThread.Connection.socket.LocalName;
pNewClient^.Thread := AThread;
AThread.Data :=TObject(pNewClient);
try
ThreadList.LockList.Add(pNewClient);//将每条Client信息保存
self.Edit1.Text :=IntToStr(ThreadList.LockList.Count);
finally
ThreadList.UnlockList;
end;
end;

[/code]
4、我想用下面的代码替代Procedure TMainFrm.ShutDown; 但是,不成功

[code delphi]
procedure TMainFrm.ShutDown2;
var
ActClient:pTClientList;
i:integer;
ActThread:TIdPeerThread;
begin
try
for i:=0 to ThreadList.LockList.Count -1 do
begin
ActClient:=ThreadList.LockList.Items[i];
ActThread:=ActClient.Thread;
try
ActThread.Connection.Disconnect;
except
on E:Exception do ActThread.Stop;
end;
end;
finally
ThreadList.UnlockList;
end;
Sleep(self.IdTCPServer1.TerminateWaitTime);
self.IdTCPServer1.Active:=False;
end;

[/code]
procedure TMainFrm.ShutDown2; 和procedure TMainFrm.ShutDown2 原理基本相同。
只是我用自己定义的记录体TClientList替代IdTcpServer.Threads 为什么结果却不同呢
careychen
尊榮會員


發表:41
回覆:580
積分:959
註冊:2004-03-03

發送簡訊給我
#2 引用回覆 回覆 發表時間:2008-11-23 15:35:29 IP:59.126.xxx.xxx 訂閱
HI, 您的問題其實很單純

其實原本的 Shutdown 函數就夠用了,您不必再創建一個 ThreadList 來放置連線資料的指標

所以:
1、請把相關有 ThreadList 的部份先刪除
2、您會出錯的地方在於 當 Disconnect 時,AThread.Data 還有指向您原本指定的 PNewClient 所導致的錯誤!

請注意一個觀念,自己 New 出來的東西,要自己 Free 掉

所以 Shutdown 函式中要加入下面紅色字的部份

Procedure TMainFrm.ShutDown;
var
List: TList;
I: Integer;
begin
List := IdTCPServer1.Threads.LockList;
try
StatusBar1.Panels[1].Text := 'Disconnecting...';
Application.ProcessMessages;
for I := 0 to List.Count - 1 do
begin
try
FreeMem(pTClientList(TIdPeerThread(List[I]).Data));
TIdPeerThread(List[I]).Data := nil;
TIdPeerThread(List.Items[I]).Connection.Disconnect;
except
on E: Exception do
begin
TIdPeerThread(List.Items[I]).Stop;
這邊也可以刪掉,您自己應該知道這邊其實都跑不到的!!
end;
end;
end;
finally
IdTCPServer1.Threads.UnlockList;
end;
Sleep(IdTCPServer1.TerminateWaitTime); // 這行可以刪掉
IdTCPServer1.Active := False;
end;
------
價值的展現,來自於你用哪一個角度來看待它!!
zhweizw
一般會員


發表:7
回覆:16
積分:9
註冊:2008-01-12

發送簡訊給我
#3 引用回覆 回覆 發表時間:2008-11-23 17:27:10 IP:221.229.xxx.xxx 訂閱
HI  careychen,
可能是我没有表达清楚我的意思。

我的意思是,ShutDown 去掉不用(这段代码是我在互联网上找到的 ),用shutDown2,为什么达不到ShutDown同样的作用呢
careychen
尊榮會員


發表:41
回覆:580
積分:959
註冊:2004-03-03

發送簡訊給我
#4 引用回覆 回覆 發表時間:2008-11-23 18:22:51 IP:59.126.xxx.xxx 訂閱
哦,不好意思,如果您要用原本的程式碼的話..............那就不好意思,您的錯誤點就多了點,我一項一項跟您說

第一個錯誤:
3、有Client连接时:

如果您同時用了兩次 LockList 而沒有相對數量的 unlockList ,那程式就會掛了!!
所以在下面的部份請加改紅色的部份

procedure TMainFrm.IdTCPServer1Connect(AThread: TIdPeerThread);
var //连接时触发
pNewClient:pTClientList;
List: TList;
begin
GetMem(pNewClient,sizeof(TClientList)); //分配内存
pNewClient^.ComputerIP := AThread.Connection.Socket.Binding.PeerIP;
pNewClient^.ComputerName :=AThread.Connection.socket.LocalName;
pNewClient^.Thread := AThread;
AThread.Data :=TObject(pNewClient);
try
List := ThreadList.LockList;
List.Add(pNewClient);
Self.Edit1.Text := IntToStr(List.Count);
ThreadList.LockList.Add(pNewClient);//将每条Client信息保存
self.Edit1.Text :=IntToStr(ThreadList.LockList.Count);
finally
ThreadList.UnlockList;
end;
end;


第二個錯誤:
當您在關閉您的 Client 時,沒有將該 AThread.Data 的指標 FreeMem 掉,這是上面就有回您的問題
也是為什麼會有 Terminate 的主因!!

第三個錯誤:
當您在關閉您的 Client 時,沒有將該 AThread 從 ThreadList 中移除,如果您是直接關閉程式的就還無所謂
但,如果只是先 Active 為 False ,之後是有需要再 Actvie 的話,那…第二次再 Active 起來時,程式就會開始出問題!

第四個警告:
如同第一次回您的,既然 IdTCPServer 中有自己在管理的 Threads ,如果您自己在弄一個變數 ThreadList 出來,
其實不是不行,但您就得【很辛苦的】 在該加的地方加上 ThreadList.Add ,在該移的地方加上 ThreadList.Delete
是有點多此一舉


以上合併第二、第三個錯誤,修正下面的 Shutdown2

procedure TMainFrm.ShutDown2;
var
ActClient:pTClientList;
i:integer;
ActThread:TIdPeerThread;
List: TList;
begin
try
List := ThreadList.LockList;
for i:= List.Count-1 downto 0 do
for i:=0 to threadlist.locklist.count-1 do
begin
ActClient:=ThreadList.LockList.Items[i];
ActThread:=ActClient.Thread;

ActThread := pTClientList(List[I])^.Thread;
try
FreeMem(pTClientList(ActThread.Data));
ActThread.Data := nil;
ActThread.Connection.Disconnect;
List.Delete(I);
except
on E:Exception do ActThread.Stop;
end;
end;
finally
ThreadList.UnlockList;
end;
Sleep(self.IdTCPServer1.TerminateWaitTime);
self.IdTCPServer1.Active:=False;
end;
------
價值的展現,來自於你用哪一個角度來看待它!!
zhweizw
一般會員


發表:7
回覆:16
積分:9
註冊:2008-01-12

發送簡訊給我
#5 引用回覆 回覆 發表時間:2008-11-23 20:12:49 IP:221.229.xxx.xxx 訂閱
非常感谢careychen耐心的解答!!!

由于我的IdTcpServer/Client不是很了解,所以我用了一个笨方法。其实我自己弄一个ThreadList 目的是,在Client连接时保存一些Client的附加信息,此后方便我定位某个Client。所以才有定义TClientList这个记录体。
我在想,如果不自定义一个记录体,Server在接受Client连接后自动把AThread加入到IdTcpServer1.Threads.LockList中,此后我如何定位某个Client呢。
是不是只能通过遍历IdTcpServer1.Threads.LockList.items,取得TIdTcpServer(IdTcpServer1.Threads.LockList.items[i]).Connection.Socket.PeerIp 来定位Client?
另外,在Client DisConnecte时,Server如何知道是哪条连线断开了,从而在Form.Memo1中清除掉对应的连线?

比如:
1、ClientA 发送数据给Server,要求Server将数据转发给ClientB。如何找到ClientB?
2、Form1.Memo1中有3条连线信息 :ClientA,ClientB,ClientC。如果ClientC主动断开连线,怎么定位这个Client联系,然后从Form1.Memo1中清除掉对应信息



careychen
尊榮會員


發表:41
回覆:580
積分:959
註冊:2004-03-03

發送簡訊給我
#6 引用回覆 回覆 發表時間:2008-11-23 23:24:02 IP:59.126.xxx.xxx 訂閱

由于我的IdTcpServer/Client不是很了解,所以我用了一个笨方法。其实我自己弄一个ThreadList 目的是,在Client连接时保存一些Client的附加信息,此后方便我定位某个Client。所以才有定义TClientList这个记录体。
我在想,如果不自定义一个记录体,Server在接受Client连接后自动把AThread加入到IdTcpServer1.Threads.LockList中,此后我如何定位某个Client呢。
是不是只能通过遍历IdTcpServer1.Threads.LockList.items,取得TIdTcpServer(IdTcpServer1.Threads.LockList.items[i]).Connection.Socket.PeerIp 来定位Client?
另外,在Client DisConnecte时,Server如何知道是哪条连线断开了,从而在Form.Memo1中清除掉对应的连线?

其實就原本上面的程式寫法來看,使用了 ThreadList 之後的用法,和使用 Threads 的用法並沒有不同,所以其實沒有更方便,反而是多維護了一個變數!!


比如:
1、ClientA 发送数据给Server,要求Server将数据转发给ClientB。如何找到ClientB?
// 就跑 For 迴圈而已,這個很快的,不必太擔心
2、Form1.Memo1中有3条连线信息 :ClientA,ClientB,ClientC。如果ClientC主动断开连线,怎么定位这个Client联系,然后从Form1.Memo1中清除掉对应信息
// 在 IdTCPServer 裡有個事件是 OnDisconnect ,而這個傳進來的 AThread 就是當時斷掉的那一條 Thread ,而在您的這裡,我倒建議改用 ListBox 來會更好操作

下面用一個簡單的 Sample ,您了解其中的函義後,就知道了
請在畫面上
拉一個 ListBox ,命名:lbClients
拉一個 Button ,命名:btnStart
拉一個 Button ,命名:btnShutdown
拉一個 IdTCPServer
拉一個 Label ,命名: lblStatus
[code delphi]
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, IdBaseComponent, IdComponent, IdTCPServer, StdCtrls, ComCtrls, IdThread;
type
// 新建一個 class
TMyIdPeerThread = class(TIdPeerThread)
ComputerName: String;
ComputerIP: String;
end;
TForm1 = class(TForm)
btnStart: TButton;
btnShutdown: TButton;
IdTCPServer1: TIdTCPServer;
Edit1: TEdit;
lbClients: TListBox;
lblStatus: TLabel;
procedure btnStartClick(Sender: TObject);
procedure IdTCPServer1Connect(AThread: TIdPeerThread);
procedure btnShutdownClick(Sender: TObject);
procedure IdTCPServer1Execute(AThread: TIdPeerThread);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
public
// 可以傳訊息給其他的 Client
function SendMsgToOne(const AReceiverComputer: String; const AMsg: String): Boolean;
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
function TForm1.SendMsgToOne(const AReceiverComputer: String; const AMsg: String): Boolean;
var List: TList;
AThread: TMyIdPeerThread;
I: Integer;
begin
Result := False;
List := IdTCPServer1.Threads.LockList;
try
try
for I := 0 to List.Count-1 do
begin
AThread := TMyIdPeerThread(List[I]);
if AThread.ComputerName = AReceiverComputer then
begin
AThread.Connection.WriteLn(AMsg);
break;
end;
end;
except
end;
finally
IdTCPServer1.Threads.UnlockList;
end;
end;
procedure TForm1.btnStartClick(Sender: TObject);
begin
IdTCPServer1.Active := true;
lblStatus.Caption := 'Wait to Connect...';
end;
procedure TForm1.IdTCPServer1Connect(AThread: TIdPeerThread);
begin
with TMyIdPeerThread(AThread) do
begin
ComputerIP := AThread.Connection.Socket.Binding.PeerIP;
ComputerName := AThread.Connection.socket.LocalName;
end;
// ListBox 可以顯示目前上線的主機
lbClients.Items.AddObject(TMyIdPeerThread(AThread).ComputerName, TObject(AThread));
end;
procedure TForm1.btnShutdownClick(Sender: TObject);
var
List: TList;
I: Integer;
begin
List := IdTCPServer1.Threads.LockList;
try
for I := List.Count - 1 downto 0 do
begin
try
lbClients.Items.Delete(lbClients.Items.IndexOfObject(TObject(List.Items[I])));
TIdPeerThread(List.Items[I]).Connection.Disconnect;
except
end;
end;
finally
IdTCPServer1.Threads.UnlockList;
end;
IdTCPServer1.Active := False;
lblStatus.Caption := 'Disconnected...';
end;
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var sTmp: STring;
begin
try
stmp := AThread.Connection.ReadLn;
except
end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
IdTCPServer1.ThreadClass := TMyIdPeerThread;
end;
end.
[/code]
------
價值的展現,來自於你用哪一個角度來看待它!!
zhweizw
一般會員


發表:7
回覆:16
積分:9
註冊:2008-01-12

發送簡訊給我
#7 引用回覆 回覆 發表時間:2008-11-24 09:49:20 IP:221.229.xxx.xxx 訂閱
Thank you very much!
sdshw
一般會員


發表:8
回覆:11
積分:3
註冊:2008-03-24

發送簡訊給我
#8 引用回覆 回覆 發表時間:2009-02-07 23:35:01 IP:119.162.xxx.xxx 訂閱
1. with TMyIdPeerThread(AThread) do
2. begin
3. ComputerIP := AThread.Connection.Socket.Binding.PeerIP;
4. ComputerName := AThread.Connection.socket.LocalName;
5. end;

上面的代码有何意义?
不建立TMyIdPeerThread类直接引用AThread.Connection.Socket.Binding.PeerIP; 或AThread.Connection.socket.LocalName;
是否也可以达到目的
careychen
尊榮會員


發表:41
回覆:580
積分:959
註冊:2004-03-03

發送簡訊給我
#9 引用回覆 回覆 發表時間:2009-02-08 16:52:26 IP:59.126.xxx.xxx 訂閱
HI, sdshw 這個例子是 for 原本的問題時來使用的,但其實就您現在的問題來看他的確是似乎多此一舉
但其實 TMyIdPeerThread 是可以擴展的,例如

[code delphi]
TMyIdPeerThread = class(TIdPeerThread)
ComputerName: String;
ComputerIP: String;
ConnectMemberID: Integer; // 連線的 MemberID
LastCommand: String; // 最後執行命令
OtherData: TSampleRecord; // Sample Record
end;
[/code]

那這樣用一般的 AThread 和使用擴展後的 TMyIdPeerThread 的 AThread 的資料就不一樣了
如果需要再多放一點資料在該 Thread 時,可以依自己的需求再繼續增加或修改

而且您可以看一下在上面一點的的 Code
[code delphi]
function TForm1.SendMsgToOne(const AReceiverComputer: String; const AMsg: String): Boolean;
var List: TList;
AThread: TMyIdPeerThread;
I: Integer;
begin
Result := False;
List := IdTCPServer1.Threads.LockList;
try
try
for I := 0 to List.Count-1 do
begin
AThread := TMyIdPeerThread(List[I]);
if AThread.ComputerName = AReceiverComputer then
// if AThread.Connection.Socket.LocalName = AReceiverComputer then
// 以您的問題的寫法來撰寫的話,程式碼就變長了,而且之後想拿 IP ,也得寫這麼長,其實我很懶的!!
begin
AThread.Connection.WriteLn(AMsg);
break;
end;
end;
except
end;
finally
IdTCPServer1.Threads.UnlockList;
end;
end; [/code]



===================引 用 sdshw 文 章===================
1. with TMyIdPeerThread(AThread) do
2. begin
3. ComputerIP := AThread.Connection.Socket.Binding.PeerIP;
4. ComputerName := AThread.Connection.socket.LocalName;
5. end;

上面的代码有何意义?
不建立TMyIdPeerThread类直接引用AThread.Connection.Socket.Binding.PeerIP; 或AThread.Connection.socket.LocalName;
是否也可以达到目的
------
價值的展現,來自於你用哪一個角度來看待它!!
編輯記錄
careychen 重新編輯於 2009-02-08 17:01:43, 註解 無‧
系統時間:2024-03-29 20:04:07
聯絡我們 | Delphi K.Top討論版
本站聲明
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。
2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。
3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇!