我正在使用Indy TCPClient / TCPServer来验证移动设备的注册。这个过程非常简单,我在服务器端读取标识符,根据数据库控制文件对其进行验证,并将响应发送回客户端。
大部分内容似乎都能正常工作,但我会定期在服务器端获得EIDConnClosedGracefully异常。我似乎无法精确确定连接被错误关闭的位置。实际上,似乎服务器在不应该执行readln时(在Connection关闭之后)并且我不知道为什么。可能我没有正确同步。我在我的工具/选项/调试器选项中设置了忽略Indy Silent Exceptions但我想知道是什么引发了异常。我可以执行4或5次注册功能,然后抛出异常但是它非常不一致。
任何建议都将不胜感激。
以下是我的代码:
服务器
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
答案 0 :(得分:7)
服务器代码是OnConnect
还是OnExecute
事件?
假设OnExecute
,即循环事件,它会在连接的生命周期内连续循环。在每次迭代中,您将再次调用ReadLn
以从客户端读取下一个命令。如果客户端断开连接,则必须返回套接字以获取更多数据的下一次读取(在IOHandler.InputBuffer
已用尽之后)将相应地引发异常。这是正常行为,以及Indy如何运作。
真正的问题是你有一个异常处理程序,它无条件地将所有异常记录为错误,甚至是正常的断开连接。在向备忘录添加错误消息时,您的异常处理程序不会与UI线程同步,并且它不会重新引发任何捕获的Indy异常,因此TIdTCPServer
可以根据需要处理它(例如停止{{} 1}}循环并触发OnExecute
事件。)
尝试更像这样的东西:
OnDisconnect
另一方面,我建议完全删除// 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
顺便说一句,将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()
一起使用时,您必须非常小心。如果主UI线程忙于在服务器事件处理程序调用{{1}}时停用服务器,则UI线程和同步线程之间将发生死锁(主线程正在等待服务器完成停用,但服务器正在等待线程终止,但线程正在等待UI线程完成停用服务器)。对于您所显示的简单日志记录,我建议使用TIdTCPServer
来避免任何死锁。或者在工作线程中停用服务器,以便UI线程可以继续处理Synchronize()
个请求。