Connection Closed Gracey Indy TCPServer移动应用程序Delphi XE8

Leo*_* M. 2 delphi mobile exception indy10 delphi-xe8

我使用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;
Run Code Online (Sandbox Code Playgroud)

客户

  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;
Run Code Online (Sandbox Code Playgroud)

从我的日志输出.

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
Run Code Online (Sandbox Code Playgroud)

Rem*_*eau 7

OnConnectOnExecute事件中的服务器代码?

假设OnExecute,这是一个循环事件,它会在连接的生命周期内连续循环.在每次迭代中,您将ReadLn再次调用以从客户端读取下一个命令.如果客户端断开连接,则必须返回到套接字以获取更多数据的下一次读取(在IOHandler.InputBuffer耗尽之后)将相应地引发异常.这是正常行为,以及Indy如何运作.

真正的问题是你有一个异常处理程序,它无条件地将所有异常记录为错误,甚至是正常的断开连接.并且在将错误消息添加到备忘录时,您的异常处理程序不会与UI线程同步,并且它不会重新引发任何捕获的Indy异常,因此TIdTCPServer可以根据需要处理它(例如停止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;
Run Code Online (Sandbox Code Playgroud)

另一方面,我建议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;
Run Code Online (Sandbox Code Playgroud)

顺便说一句,你在使用的时候要非常小心TThread.Synchronize()使用TIdTCPServer.如果主UI线程忙于在服务器事件处理程序调用时停用服务器Synchronize(),则UI线程和同步线程之间将发生死锁(主线程正在等待服务器完成停用,但服务器正在等待线程终止,但线程正在等待UI线程完成停用服务器).对于您已经显示的简单日志记录,我建议使用它TThread.Queue()来避免任何死锁潜力.或者在工作线程中停用服务器,以便UI线程可以继续处理Synchronize()请求.