的所有人。 我正在开发基于Indy TCP控件的服务器/客户端程序。 现在,我遇到了一些不确定的问题.. 这只是关于异常破坏的连接...... 让我们假设网络状态意外中断,或者服务器应用程序异常终止,因此客户端无法再与服务器通信... 然后客户端将出现像“通过对等方重置连接”或“连接被拒绝...”之类的异常 在这些情况下,如何巧妙地处理这些异常? 我希望客户端在恢复服务器状态后自动再次连接并正常通信... 如果你有个好主意,请分享一下......
以下是我的代码。我使用了两个计时器控件。一种是发送活动并确认网络状态(5000ms)。如果网络状态正常,则此计时器已停止,另一个计时器已启用。第二个计时器是向服务器发送信息(1000毫秒)
如果在第二个计时器中,则发生异常,然后它被禁用,并且第一个计时器再次启用。
当发生“连接被拒绝”时,try except
块可以捕获它。
但是如果发生“通过对等方重置连接”,则try except
阻止无法捕获它。
{sendbuffer funtion}
function SendBuffer(AClient: TIdTCPClient; ABuffer: TBytes): Boolean; overload;
begin
try
Result := True;
try
AClient.IOHandler.Write(LongInt(Length(ABuffer)));
AClient.IOHandler.WriteBufferOpen;
AClient.IOHandler.Write(ABuffer, Length(ABuffer));
AClient.IOHandler.WriteBufferFlush;
finally
AClient.IOHandler.WriteBufferClose;
end;
except
Result := False;
end;
end;
{alive timer}
procedure TClientForm.Timer_StrAliveTimer(Sender: TObject);
var
infoStr : string;
begin
if not IdTCPClient_StrSend.Connected then
begin
try
if IdTCPClient_StrSend.IOHandler <> nil then
begin
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
IdTCPClient_StrSend.IOHandler.WriteBufferClear;
end;
IdTCPClient_StrSend.Connect;
except on E: Exception do
begin
SAOutMsg := 'connect fail : ' + E.ToString ;
Exit;
end;
end;
SAOutMsg := 'connect success : ';
if IdTCPClient_StrSend.Connected then
begin
IdTCPClient_StrSend.IOHandler.CheckForDisconnect(True, True);
IdTCPClient_StrSend.IOHandler.CheckForDataOnSource(100);
infoStr := MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME;
try
IdTCPClient_StrSend.IOHandler.WriteLn(infoStr, nil);
except on E: Exception do
begin
SAOutMsg := 'login info send fail : ';
Exit;
end;
end;
SAOutMsg := 'login info send success : ';
try
if IdTCPClient_StrSend.IOHandler.ReadLn() = 'OK' then
begin
Timer_StrAlive.Enabled := False;
Timer_Str.Enabled := True;
end;
except on E: Exception do
begin
SAOutMsg := 'login fail : ' + E.ToString ;
Exit;
end;
end;
SAOutMsg := 'login ok : ' ;
end;
end;
end;
{send part}
procedure TClientForm.Timer_StrTimer(Sender: TObject);
var
LBuffer: TBytes;
LClientRecord: TClientRecord;
begin
// IdTCPClient_StrSend.CheckForGracefulDisconnect(False);
if not IdTCPClient_StrSend.Connected then
begin
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end;
if IdTCPClient_StrSend.Connected then
begin
LClientRecord.data1 := str1;
LClientRecord.data2:= Trim(str2);
LClientRecord.data3 := Trim(str3);
LBuffer := MyRecordToByteArray(LClientRecord);
IdTCPClient_StrSend.IOHandler.CheckForDisconnect(True, True);
IdTCPClient_StrSend.IOHandler.CheckForDataOnSource(100);
if (SendBuffer(IdTCPClient_StrSend, LBuffer) = False) then
begin
SOutMsg := 'info send fail' ;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end
答案 0 :(得分:2)
在操作系统检测到丢失的连接(通常在内部超时时间已过去)之后执行套接字读/写操作之前,不会发生与连接丢失相关的异常,例如“通过对等方重置连接”,并且无效套接字连接。大多数Indy客户端组件不会自动执行此类操作,您必须告诉他们这样做(TIdTelnet
和TIdCmdTCPClient
是该规则的明显例外,因为它们运行内部读取线程)。因此,只需将套接字操作包装在try/except
块中,如果捕获Indy套接字异常(例如EIdSocketError
或后代),则可以调用Disconnect()
和Connect()
重新连接。
“拒绝连接”只能在调用Connect()
时发生。它通常意味着服务器已到达但当时无法接受连接,因为请求的IP /端口上没有侦听套接字,或者侦听套接字的待办事项中有太多未决连接(它也可能意味着防火墙)阻止连接)。再次,只需将Connect()
包裹在try/except
中即可处理错误,以便再次调用Connect()
。在这样做之前,您应该等待一个小的超时时间,以便让服务器有一段时间可能清除任何条件,使其首先拒绝连接(假设防火墙不是问题)。
Indy在很大程度上依赖于错误报告的异常,并且在较小程度上依赖于状态报告。因此,在使用Indy时,您通常需要使用try/except
处理程序。
更新:我发现代码中存在一些问题。 SendBuffer()
没有正确实现写缓冲。大多数Connected()
的来电以及CheckForDisconnect()
和CheckForDataOnSource()
的所有来电都是过度的,应该完全删除。唯一有意义的通话是每个计时器中第一次调用Connected()
。
尝试更像这样的事情:
{sendbuffer function}
function SendBuffer(AClient: TIdTCPClient; const ABuffer: TBytes): Boolean; overload;
begin
Result := False;
try
AClient.IOHandler.WriteBufferOpen;
try
AClient.IOHandler.Write(LongInt(Length(ABuffer)));
AClient.IOHandler.Write(ABuffer);
AClient.IOHandler.WriteBufferClose;
except
AClient.IOHandler.WriteBufferCancel;
raise;
end;
Result := True;
except
end;
end;
{alive timer}
procedure TClientForm.Timer_StrAliveTimer(Sender: TObject);
var
infoStr : string;
begin
if IdTCPClient_StrSend.Connected then Exit;
try
IdTCPClient_StrSend.Connect;
except
on E: Exception do
begin
SAOutMsg := 'connect fail : ' + E.ToString;
Exit;
end;
end;
try
SAOutMsg := 'connect success : ';
infoStr := MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME;
try
IdTCPClient_StrSend.IOHandler.WriteLn(infoStr);
except
on E: Exception do
begin
E.Message := 'login info send fail : ' + E.Message;
raise;
end;
end;
SAOutMsg := 'login info send success : ';
try
if IdTCPClient_StrSend.IOHandler.ReadLn() <> 'OK' then
raise Exception.Create('not OK');
except
on E: Exception do
begin
E.Message := 'login fail : ' + E.Message;
raise;
end;
end;
SAOutMsg := 'login ok : ' ;
except
on E: Exception do
begin
SAOutMsg := E.ToString;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Exit;
end;
end;
Timer_StrAlive.Enabled := False;
Timer_Str.Enabled := True;
end;
{send part}
procedure TClientForm.Timer_StrTimer(Sender: TObject);
var
LBuffer: TBytes;
LClientRecord: TClientRecord;
begin
if not IdTCPClient_StrSend.Connected then
begin
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end;
LClientRecord.data1 := str1;
LClientRecord.data2:= Trim(str2);
LClientRecord.data3 := Trim(str3);
LBuffer := MyRecordToByteArray(LClientRecord);
if not SendBuffer(IdTCPClient_StrSend, LBuffer) then
begin
SOutMsg := 'info send fail' ;
IdTCPClient_StrSend.Disconnect(False);
if IdTCPClient_StrSend.IOHandler <> nil then
IdTCPClient_StrSend.IOHandler.InputBuffer.Clear;
Timer_Str.Enabled := False;
Timer_StrAlive.Enabled := True;
Exit;
end
...
end;
现在,据说,在主UI线程中使用Indy定时器并不是使用Indy的最佳,甚至是最安全的方式。这种逻辑在工作线程中可以更好地工作,例如:
type
TStrSendThread = class(TThread)
private
FClient: TIdTCPClient;
...
protected
procedure Execute; override;
procedure DoTerminate; override;
public
constructor Create(AClient: TIdTCPClient); reintroduce;
end;
constructor TStrSendThread.Create(AClient: TIdTCPClient);
begin
inherited Create(False);
FClient := AClient;
end;
procedure TStrSendThread.Execute;
var
LBuffer: TIdBytes;
...
begin
while not Terminated do
begin
Sleep(ConnectInterval);
if Terminated then Exit;
try
FClient.Connect;
try
// report status to main thread as needed...
FClient.IOHandler.WriteLn(MY_MAC_ADDRESS+'|'+MY_COMPUTER_NAME);
if FClient.IOHandler.ReadLn() <> 'OK' then
raise Exception.Create('error message');
// report status to main thread as needed...
while not Terminated do
begin
Sleep(SendInterval);
if Terminated then Exit;
...
if not SendBuffer(FClient, LBuffer) then
raise Exception.Create('error message');
end;
finally
FClient.FDisconnect(False);
if FClient.IOHandler <> nil then
FClient.IOHandler.InputBuffer.Clear;
end;
except
on E: Exception do
begin
// report error to main thread as needed...
end;
end;
end;
end;
procedure TStrSendThread.DoTerminate;
begin
// report status to main thread as needed...
inherited;
end;
private
Thread: TStrSendThread;
...
// Timer_StrAliveTimer.Active := True;
if Thread = nil then
Thread := TStrSendThread.Create(IdTCPClient_StrSend);
...
// Timer_StrAliveTimer.Active := False;
if Thread <> nil then
begin
Thread.Terminate;
Thread.WaitFor;
FreeAndNil(Thread);
end;