TIdTcpServer 问题,请教达人 |
答題得分者是:careychen
|
zhweizw
一般會員 發表:7 回覆:16 積分:9 註冊:2008-01-12 發送簡訊給我 |
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 發送簡訊給我 |
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 begin TIdPeerThread(List.Items[I]).Stop; end; finally IdTCPServer1.Threads.UnlockList; end; IdTCPServer1.Active := False; end;
------
價值的展現,來自於你用哪一個角度來看待它!! |
zhweizw
一般會員 發表:7 回覆:16 積分:9 註冊:2008-01-12 發送簡訊給我 |
|
careychen
尊榮會員 發表:41 回覆:580 積分:959 註冊:2004-03-03 發送簡訊給我 |
哦,不好意思,如果您要用原本的程式碼的話..............那就不好意思,您的錯誤點就多了點,我一項一項跟您說
第一個錯誤: 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); self.Edit1.Text :=IntToStr(ThreadList.LockList.Count); 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 begin 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; self.IdTCPServer1.Active:=False; end;
------
價值的展現,來自於你用哪一個角度來看待它!! |
zhweizw
一般會員 發表:7 回覆:16 積分:9 註冊:2008-01-12 發送簡訊給我 |
非常感谢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 發送簡訊給我 |
由于我的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 發送簡訊給我 |
|
sdshw
一般會員 發表:8 回覆:11 積分:3 註冊:2008-03-24 發送簡訊給我 |
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 發送簡訊給我 |
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, 註解 無‧
|
本站聲明 |
1. 本論壇為無營利行為之開放平台,所有文章都是由網友自行張貼,如牽涉到法律糾紛一切與本站無關。 2. 假如網友發表之內容涉及侵權,而損及您的利益,請立即通知版主刪除。 3. 請勿批評中華民國元首及政府或批評各政黨,是藍是綠本站無權干涉,但這裡不是政治性論壇! |