优雅地关闭了Indy TCPServer移动应用程序Delphi XE8 [英] Connection Closed Gracefully Indy TCPServer Mobile App Delphi XE8

查看:115
本文介绍了优雅地关闭了Indy TCPServer移动应用程序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

推荐答案

服务器代码是否在OnConnectOnExecute事件中?

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆