优雅地关闭了Indy TCPServer移动应用程序Delphi XE8 [英] Connection Closed Gracefully Indy TCPServer Mobile App Delphi XE8
问题描述
我正在使用Indy TCPClient/TCPServer来验证移动设备的注册.这个过程非常简单,我在服务器端读取一个标识符,针对数据库控制文件对其进行验证,然后将响应发送回客户端.
I am using Indy TCPClient/TCPServer to verify registration of a mobile device. The process is fairly straight forward where I read an identifier on the Server Side, validate it against the database control file and send back a response to the client.
大多数情况下,一切似乎都能正常工作,但是在服务器端,我会定期收到EIDConnClosedGracefully异常.我似乎无法确切指出连接被不正确关闭的位置.实际上,似乎服务器在不应该执行readln时(关闭连接后)正在执行readln,我也不知道为什么.可能我没有正确同步.我在工具/选项/调试器选项"中将"Indy Silent Exceptions"设置为忽略,但我想知道引发该异常的原因.我可以执行4到5次注册功能,然后会引发异常,但这是非常不一致的.
Everything appears to work correctly for the most part but periodically I get the EIDConnClosedGracefully Exception on the Server Side. I can't seem to pinpoint exactly where the connection is being closed improperly. In fact, it appears that the Server is executing a readln when it is not supposed to (after the Connection is closed) and I do not know why. Possibly I am not synchronizing properly. I have the Indy Silent Exceptions set to ignore in my Tools/Options/Debugger Options but I would like to know what is throwing the exception. I can execute the registration function 4 or 5 times and then the exception will be thrown but it is very inconsistent.
任何建议将不胜感激.
以下是我的代码:
服务器
try
MIRec.RecType := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I')
or (MIRec.RecType = 'R') then
begin
// Verify the connecting device is registered
MIRec.Identifier := AContext.Connection.IOHandler.ReadLn;
qryMobileDevice.Close;
qryMobileDevice.Parameters.ParamByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.Open;
AContext.Connection.IOHandler.WriteLn(qryMobileDevice.FindField('ACTIVE').AsString);
MIRec.DeviceName := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I') then
LogEntry := 'A Connection Has Been Established With: ' + MIRec.DeviceName
else
begin
// Register the Device in STIKS
if qryMobileDevice.EOF then
begin
LogEntry := 'Registering: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
// If Record Does not exist Add to the Control File;
NextId := GetNextId('NEXT_MOBILE_DEVICE_ID');
qryMobileDevice.Insert;
qryMobileDevice.FieldByName('MOBILE_DEVICE_ID').Value := NextId;
qryMobileDevice.FieldByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.FieldByName('DESCRIPTION').Value := MiRec.Text;
qryMobileDevice.FieldByName('ACTIVE').Value := 'T';
qryMobileDevice.FieldByName('OPERATOR_SAVED').Value := 'From App';
qryMobileDevice.FieldByName('DATE_SAVED').Value := Now;
qryMobileDevice.Post;
end
else
begin
// Device has been Flagged and registration refused.
if qryMobileDevice.FindField('ACTIVE').AsString = 'T' then
LogEntry := '** Registration Successful: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now)
else
LogEntry := '** Registration Refused: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
end;
qryMobileDevice.Close;
end;
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add(LogEntry);
end);
end;
except
on e: exception do
begin
Memo1.Lines.Add('** An error occurred Receiving File ' + #13#10 + 'With a message: ' + E.Message);
end;
end;
客户
if MessageDlg('Register Device With Server?', TMsgDlgType.mtConfirmation, [TMsgDlgBtn.mbNo, TMsgDlgBtn.mbYes], 0) = mrNo then Exit;
try
IdTCPClient1.Connect;
try
MainForm.IdTCPClient1.IOHandler.WriteLn('R'); // Tell Server we are sending a Registration Record
Device := TUIDevice.Wrap(TUIDevice.OCClass.currentDevice);
IdTCPClient1.IOHandler.WriteLn(NSStrToStr(Device.identifierForVendor.UUIDString));
Registered := IdTCPClient1.IOHandler.ReadLn; // Get response from server
Authenticated := (Registered = 'T');
IdTCPClient1.IOHandler.WriteLn(NSStrToStr(Device.Name));
finally
IdTCPClient1.DisConnect;
if Registered <> 'T' then
MessageDlg('Registration Failed!', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], 0)
else
begin
Authenticated := True;
MessageDlg('Registration Has Completed Successfully!', TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], 0);
end;
end;
except
on e: exception do
begin
MessageDlg('** An error occurred Registering Device ' + #13#10 + 'With a message: ' + E.Message, TMsgDlgType.mtInformation, [TMsgDlgBtn.mbOK], 0);
end;
end;
我的日志中的输出.
A Client connected
** Registration Successful: ; 7FFC0274-AFB1-4E35-B8D9-F987B587804D; Wed. September 30/2015, 9:36:54 AM
A Client Disconnected
A Client connected
** Registration Successful: ; 7FFC0274-AFB1-4E35-B8D9-F987B587804D; Wed. September 30/2015, 9:37:00 AM
A Client Disconnected
A Client connected
** Registration Successful: ; 7FFC0274-AFB1-4E35-B8D9-F987B587804D; Wed. September 30/2015, 9:37:04 AM
** An error occurred Receiving File
With a message: Connection Closed Gracefully.
A Client Disconnected
推荐答案
服务器代码是否在OnConnect
或OnExecute
事件中?
Is the server code in the OnConnect
or OnExecute
event?
假设OnExecute
,这是一个 looped 事件,它将在连接的整个生命周期中连续循环.在每次迭代中,您将再次调用ReadLn
来从客户端读取下一个命令.如果客户端断开连接,则必须返回套接字以获取更多数据的下一次读取(在IOHandler.InputBuffer
用尽之后)将相应地引发异常.这是正常行为,以及Indy的设计运行方式.
Assuming OnExecute
, that is a looped event, it loops continuously for the lifetime of the connection. On each iteration, you would be calling ReadLn
again to read the next command from the client. If the client disconnects, the next read that has to go back to the socket for more data (after the IOHandler.InputBuffer
has been exhausted) will raise an exception accordingly. This is normal behavior, and how Indy is designed to operate.
真正的问题是您拥有一个异常处理程序,该处理程序无条件地将所有异常记录为错误,甚至是正常断开连接.而且,将错误消息添加到备忘录时,异常处理程序不会与UI线程同步,并且不会重新引发任何捕获的Indy异常,因此TIdTCPServer
可以根据需要进行处理(例如,停止OnExecute
循环并触发OnDisconnect
事件.
The real problem is that you have an exception handler that is unconditionally logging all exceptions as errors, even graceful disconnects. And your exception handler is not synchronizing with the UI thread when adding the error message to your memo, and it is not re-raising any caught Indy exception so TIdTCPServer
can handle it as needed (such as to stop the OnExecute
loop and trigger the OnDisconnect
event).
请尝试以下类似操作:
// if registration is only done once, this code should
// be in the OnConnect event instead...
procedure TMyForm.MyTCPServerExecute(AContext: TIdContext);
begin
try
MIRec.RecType := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I') or
(MIRec.RecType = 'R') then
begin
// Verify the connecting device is registered
MIRec.Identifier := AContext.Connection.IOHandler.ReadLn;
qryMobileDevice.Close;
qryMobileDevice.Parameters.ParamByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.Open;
AContext.Connection.IOHandler.WriteLn(qryMobileDevice.FindField('ACTIVE').AsString);
MIRec.DeviceName := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I') then
LogEntry := 'A Connection Has Been Established With: ' + MIRec.DeviceName
else
begin
// Register the Device in STIKS
if qryMobileDevice.EOF then
begin
LogEntry := 'Registering: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
// If Record Does not exist Add to the Control File;
NextId := GetNextId('NEXT_MOBILE_DEVICE_ID');
qryMobileDevice.Insert;
qryMobileDevice.FieldByName('MOBILE_DEVICE_ID').Value := NextId;
qryMobileDevice.FieldByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.FieldByName('DESCRIPTION').Value := MiRec.Text;
qryMobileDevice.FieldByName('ACTIVE').Value := 'T';
qryMobileDevice.FieldByName('OPERATOR_SAVED').Value := 'From App';
qryMobileDevice.FieldByName('DATE_SAVED').Value := Now;
qryMobileDevice.Post;
end
else
begin
// Device has been Flagged and registration refused.
if qryMobileDevice.FindField('ACTIVE').AsString = 'T' then
LogEntry := '** Registration Successful: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now)
else
LogEntry := '** Registration Refused: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
end;
qryMobileDevice.Close;
end;
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add(LogEntry);
end
);
end;
except
on E: Exception do
begin
if not (E is EIdSilentException) then
begin
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('** An error occurred Receiving File ' + #13#10 + 'With a message: ' + E.Message);
end
);
end;
// optionally remove the below 'if' to close the
// connection on any exception, including DB errors, etc...
if E is EIdException then
raise;
end;
end;
end;
另一方面,我建议完全放弃try/except
并改用服务器的OnException
事件.让任何异常关闭连接,然后在最后记录原因:
On the other hand, I would suggest getting rid of the try/except
altogether and use the server's OnException
event instead. Let any exception close the connection, and then just log why at the end:
procedure TMyForm.MyTCPServerExecute(AContext: TIdContext);
begin
MIRec.RecType := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I') or
(MIRec.RecType = 'R') then
begin
// Verify the connecting device is registered
MIRec.Identifier := AContext.Connection.IOHandler.ReadLn;
qryMobileDevice.Close;
qryMobileDevice.Parameters.ParamByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.Open;
AContext.Connection.IOHandler.WriteLn(qryMobileDevice.FindField('ACTIVE').AsString);
MIRec.DeviceName := AContext.Connection.IOHandler.ReadLn;
if (MIRec.RecType = 'I') then
LogEntry := 'A Connection Has Been Established With: ' + MIRec.DeviceName
else
begin
// Register the Device in STIKS
if qryMobileDevice.EOF then
begin
LogEntry := 'Registering: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
// If Record Does not exist Add to the Control File;
NextId := GetNextId('NEXT_MOBILE_DEVICE_ID');
qryMobileDevice.Insert;
qryMobileDevice.FieldByName('MOBILE_DEVICE_ID').Value := NextId;
qryMobileDevice.FieldByName('IDENTIFIER').Value := MIRec.Identifier;
qryMobileDevice.FieldByName('DESCRIPTION').Value := MiRec.Text;
qryMobileDevice.FieldByName('ACTIVE').Value := 'T';
qryMobileDevice.FieldByName('OPERATOR_SAVED').Value := 'From App';
qryMobileDevice.FieldByName('DATE_SAVED').Value := Now;
qryMobileDevice.Post;
end
else
begin
// Device has been Flagged and registration refused.
if qryMobileDevice.FindField('ACTIVE').AsString = 'T' then
LogEntry := '** Registration Successful: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now)
else
LogEntry := '** Registration Refused: ' + MiRec.Text + '; ' + MIRec.Identifier + '; ' + FormatDateTime('ddd. mmmm d/yyyy, h:mm:ss AM/PM', Now);
end;
qryMobileDevice.Close;
end;
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add(LogEntry);
end
);
end;
end;
procedure TMyForm.MyTCPServerException(AContext: TIdContext; AException: Exception);
begin
if not (AException is EIdSilentException) then
begin
TThread.Synchronize(nil,
procedure
begin
Memo1.Lines.Add('** An error occurred' + sLineBreak + 'With a message: ' + AException.Message);
end
);
end;
end;
顺便说一句,将TThread.Synchronize()
与TIdTCPServer
一起使用时必须非常小心.如果服务器事件处理程序调用Synchronize()
时主UI线程正忙于停用服务器,则UI线程与同步线程之间将发生死锁(主线程正在等待服务器完成停用,但服务器正在等待(该线程终止),但该线程正在等待UI线程完成停用服务器的操作).如您所示,对于简单的日志记录,我建议使用TThread.Queue()
来避免潜在的死锁.否则,请在辅助线程中停用服务器,以便UI线程可以自由继续处理Synchronize()
请求.
BTW, you have to be really careful when using TThread.Synchronize()
with TIdTCPServer
. If the main UI thread is busy deactivating the server when a server event handler calls Synchronize()
, a deadlock will occur between the UI thread and the synchronizing thread (the main thread is waiting for the server to finish deactivating, but the server is waiting for the thread to terminate, but the thread is waiting for the UI thread to finish deactivating the server). For simple logging as you have shown, I would suggest using TThread.Queue()
instead to avoid any deadlock potential. Or else deactivate the server in a worker thread so the UI thread is free to continue processing Synchronize()
requests.
这篇关于优雅地关闭了Indy TCPServer移动应用程序Delphi XE8的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!