Indy的idTcpServer如何判断连接已断开(300分)

  • 主题发起人 主题发起人 qince
  • 开始时间 开始时间
Q

qince

Unregistered / Unconfirmed
GUEST, unregistred user!
如何判断Indy的idTcpServer因客户端机器死机(或断电)造成的连接断开?
 
设置一个读写超时,超过了这个时间没有新的数据进来就是对方断开了
 
原码如下:
unit ServerFrmMainUnit;

interface

uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, IdTCPServer, IdThreadMgr, IdThreadMgrDefault, IdBaseComponent,
IdComponent;

type
TCommBlock = record // the Communication Block used in both parts (Server+Client)
Command,
MyUserName, // the sender of the message
Msg, // the message itself
ReceiverName: string[100]; // name of receiver
end;

type
PClient = ^TClient;
TClient = record // Object holding data of client (see events)
DNS : String[20]; { Hostname }
Connected, { Time of connect }
LastAction : TDateTime; { Time of last transaction }
Thread : Pointer; { Pointer to thread }
end;

TServerFrmMain = class(TForm)
Server: TIdTCPServer;
CBServerActive: TCheckBox;
Protocol: TMemo;
IdThreadMgrDefault1: TIdThreadMgrDefault;

procedure CBServerActiveClick(Sender: TObject);
procedure ServerConnect(AThread: TIdPeerThread);
procedure ServerExecute(AThread: TIdPeerThread);
procedure ServerDisconnect(AThread: TIdPeerThread);
procedure FormCreate(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);

private

public
end;

var
ServerFrmMain : TServerFrmMain;
Clients : TThreadList; // Holds the data of all clients

implementation

uses GlobalUnit;

{$R *.DFM}

procedure TServerFrmMain.CBServerActiveClick(Sender: TObject);
begin
Server.Active := CBServerActive.Checked;
end;

procedure TServerFrmMain.ServerConnect(AThread: TIdPeerThread);
var
NewClient: PClient;

begin
GetMem(NewClient, SizeOf(TClient));

NewClient.DNS := AThread.Connection.LocalName;
NewClient.Connected := Now;
NewClient.LastAction := NewClient.Connected;
NewClient.Thread :=AThread;

AThread.Data:=TObject(NewClient);

try
Clients.LockList.Add(NewClient);
finally
Clients.UnlockList;
end;

Protocol.Lines.Add(TimeToStr(Time)+' Connection from "'+NewClient.DNS+'"');
end;

procedure TServerFrmMain.ServerExecute(AThread: TIdPeerThread);
var
ActClient, RecClient: PClient;
CommBlock, NewCommBlock: TCommBlock;
RecThread: TIdPeerThread;
i: Integer;

begin
if not AThread.Terminated and AThread.Connection.Connected then
begin
AThread.Connection.ReadBuffer (CommBlock, SizeOf (CommBlock));
ActClient := PClient(AThread.Data);
ActClient.LastAction := Now; // update the time of last action

if (CommBlock.Command = 'MESSAGE') or (CommBlock.Command = 'DIALOG') then
begin // 'MESSAGE': A message was send - forward or broadcast it
// 'DIALOG': A dialog-window shall popup on the recipient's screen
// it's the same code for both commands...

if CommBlock.ReceiverName = '' then
begin // no recipient given - broadcast
Protocol.Lines.Add (TimeToStr(Time)+' Broadcasting '+CommBlock.Command+': "'+CommBlock.Msg+'"');
NewCommBlock := CommBlock; // nothing to change ;-))

with Clients.LockList do
try
for i := 0 to Count-1 do // iterate through client-list
begin
RecClient := Items; // get client-object
RecThread := RecClient.Thread; // get client-thread out of it
RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True); // send the stuff
end;
finally
Clients.UnlockList;
end;
end
else
begin // receiver given - search him and send it to him
NewCommBlock := CommBlock; // again: nothing to change ;-))
Protocol.Lines.Add(TimeToStr(Time)+' Sending '+CommBlock.Command+' to "'+CommBlock.ReceiverName+'": "'+CommBlock.Msg+'"');
with Clients.LockList do
try
for i := 0 to Count-1 do
begin
RecClient:=Items;
if RecClient.DNS=CommBlock.ReceiverName then // we don't have a login function so we have to use the DNS (Hostname)
begin
RecThread:=RecClient.Thread;
RecThread.Connection.WriteBuffer(NewCommBlock, SizeOf(NewCommBlock), True);
end;
end;
finally
Clients.UnlockList;
end;
end;
end
else
begin // unknown command given
Protocol.Lines.Add (TimeToStr(Time)+' Unknown command from "'+CommBlock.MyUserName+'": '+CommBlock.Command);
NewCommBlock.Command := 'DIALOG'; // the message should popup on the client's screen
NewCommBlock.MyUserName := '[Server]'; // the server's username
NewCommBlock.Msg := 'I don''t understand your command: "'+CommBlock.Command+'"'; // the message to show
NewCommBlock.ReceiverName := '[return-to-sender]'; // unnecessary

AThread.Connection.WriteBuffer (NewCommBlock, SizeOf (NewCommBlock), true); // and there it goes...
end;
end;
end;

procedure TServerFrmMain.ServerDisconnect(AThread: TIdPeerThread);
var
ActClient: PClient;

begin
ActClient := PClient(AThread.Data);
Protocol.Lines.Add (TimeToStr(Time)+' Disconnect from "'+ActClient^.DNS+'"');
try
Clients.LockList.Remove(ActClient);
finally
Clients.UnlockList;
end;
FreeMem(ActClient);
AThread.Data := nil;
end;

procedure TServerFrmMain.FormCreate(Sender: TObject);
begin
Clients := TThreadList.Create;
end;

procedure TServerFrmMain.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Server.Active := False;
Clients.Free;
end;

end.
 
AThread.Connection.Connected 不行吗?
 
如何判断Indy的idTcpServer因客户端机器死机(或断电)造成的连接断开,
AThread.Connection.Connected 好像不行吧
 
对,确实不能判断他是否断开,所以服务器必须一直在等待新的数据到来或者是在写数据
我认为要判断Client是否断开,写操作失败不能说明问题,只要读操作长时间没有收到
数据才能说明对方断开了
 
if Athread=nil then exit;
if Athread.Terminated then exit;
if Not AThread.Connection.Connected then exit;
就可以解决了
 
To StevenPeng:
可是因为idTcpServer是阻塞的,所以他会停在Read上,这时客户端机器死机,服务器
还是停在Read上,你的代码并没有解决问题,让他从Read中跳出来。
 
To 张无忌
因为idTcpServer是阻塞的, 如果设一个timeout,那么客户端没有断开时,如果很长
时间没有发送数据,idTcpServer就会认为它断开了。
 
to qince:
如果不这样,你根本没有办法判断是否断开,除非你要Client在空闲里发送
小数据包,证明自己还在线,这样看,还是用我的办法,如果长时间没收到
数据就断开。。。
 
To 张无忌
(如果不这样,你根本没有办法判断是否断开,除非你要Client在空闲里发送
小数据包,证明自己还在线,这样看,还是用我的办法,如果长时间没收到
数据就断开。。。)
可是我们的客户端已经写完代码了,要改很困难。我想Indy不会不提供判断客户端
非法断开的方法吧?
 
这个控件当然不会提供,因为这涉及到协议,一般要自己写的。。。,
 
To 张无忌
可是如果用API写阻塞方式,如果客户端死机,那么服务端会从READ中返回,且
READ返回0字节。这样就能判断客户端非法断开。
 
不啊,如果连接方死了,不一定发FIN数据包给你的,TCP不保证及时的断开通知,要自己写
 
To 张无忌
可以提供用Indy判断对方非法断开的编码思路吗?
 
我说过了的,就是读数据超时
 
还有一种办法,比如在聊天室里,因为你不停的把数据广播般发出去,这个时候就不用考虑异常
推出的问题,如果发送失败,可以考虑关掉套接字(只要不是有人刷屏),
 
To 张无忌
谢谢,还有其他方法吗?因为设置一个读写超时,超过了这个时间没有新的数据进来
服务端就会认为客户端非法断开。
 
基本上没其他好的办法了...
 
可不可以每隔几秒钟ping一下客户端,如果连续几次ping不通,就认为客户端死机,然
后处理断开连接?
 
后退
顶部