Connection Closed Gracefully Indy TCPServer Mobile App Delphi XE8

独自空忆成欢 提交于 2019-12-08 02:06:19

问题


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.

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.

Any suggestions would be appreciated.

Following is my code:

Server

  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;

Client

  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;

Output from my log.

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

回答1:


Is the server code in the OnConnect or OnExecute event?

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.

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).

Try something more like this instead:

// 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;

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;

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.



来源:https://stackoverflow.com/questions/32870668/connection-closed-gracefully-indy-tcpserver-mobile-app-delphi-xe8

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!