Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
IdTcpServer Kick Client (Disconnect) INVALID HANDLE Exception
#3
Project Explain
IoT Device <--------> TCP Server(Internet) <--------> PC Client
-----------------IOT SIDE-------------------------
IoT Device Connect to TCP Server (Add to DB and MemTables)(table online_client)
IoT Device Login and Ident yourself with ID(GSM No) Update DB and MemTables(table online_client)Client ID and times data
IoT Device Every 1min Send heartbeat Data to Server
------------------------------------------------------
-----------------PC CLIENT SIDE-----------------
PC Connect to TCP Server (Add to DB and MemTables)(table online_client)
PC Login and Ident yourself with ID(UserName) Update DB and MemTables(table online_client =>Client ID and times data) -- After add db and memtable(online_pair)
PC Request from IoT Device over the TCP Server. After IoT device Response to PC over TCP Server
------------------------------------------------------

IoT Device or PC can suddenly shut down.

In this case, it seems that there is still a connection in Server Contexts.List (List.Count=1). Actually, List.Count=0

I want to disconnect connections and remove FDMemTables that do not send or receive data in the last 5 minutes.

Debugger Exception Notification
---------------------------
Project LiftNetServer.exe raised exception class $C0000008 with message 'c0000008 INVALID_HANDLE'.
---------------------------

Code:
procedure TServer.spAddOnlineClient(pIP, pClientID: string; pPort: Integer; pIsLift: Boolean);
var
  connectDate: TDateTime;
  bool: string;
begin
  if dm.qryAddOnlineClient.Connection.Connected then
  begin
    try
      connectDate := Now;

      if pIsLift then bool := 'True' else bool := 'False';

      dm.qryAddOnlineClient.SQL.Text := 'SELECT spadd_online_client(' +
       QuotedStr(pIP) + '::text, ' +
       QuotedStr(pPort.ToString) + '::text, ' +
       QuotedStr(pClientID) + '::text, ' +
       bool + ', ' +
       QuotedStr(DateTimeToStr(connectDate)) + '::timestamp without time zone)';
      dm.qryAddOnlineClient.Open;
      if not dm.qryAddOnlineClient.Fields.Fields[0].IsNull then
        FIdDB := dm.qryAddOnlineClient.Fields.Fields[0].AsInteger
      else
        FIdDB := 0;

      //add record to memtable for fast work without db
      tblOnlineClient.Append;
      tblOnlineClient.FieldByName('id').AsInteger := FIdDB;
      tblOnlineClient.FieldByName('ip').AsString := pIP;
      tblOnlineClient.FieldByName('port').AsInteger := pPort;
      tblOnlineClient.FieldByName('client_id').AsString := pClientID;
      tblOnlineClient.FieldByName('is_lift').AsBoolean := pIsLift;
      tblOnlineClient.FieldByName('connect_date').AsDateTime := connectDate;
      tblOnlineClient.FieldByName('last_update').Value := Null;
      tblOnlineClient.Post;
    finally
      dm.qryAddOnlineClient.Close;
    end;
  end;
end;

procedure TServer.spDeleteOnlineClient(pIP: string; pPort: Integer);
begin
  if dm.qryDeleteOnlineClient.Connection.Connected then
  begin
    try
      //delete online_client and online_pair records
      dm.qryDeleteOnlineClient.SQL.Text := 'SELECT spdel_online_client(' +
       QuotedStr(pIP) + '::text, ' +
       QuotedStr(pPort.ToString) + '::text' + ')';
      dm.qryDeleteOnlineClient.Execute;


      //delete from memtable
      tblOnlineClient.Filter := ' ip=' + QuotedStr(pIP) + ' and port=' + QuotedStr(pPort.ToString);
      tblOnlineClient.Filtered := True;
      tblOnlineClient.IndexFieldNames := 'connect_date:D';
      tblOnlineClient.First;
      while not tblOnlineClient.Eof do begin
        tblOnlineClient.Delete;
        if tblOnlineClient.RecordCount > 0 then
          tblOnlineClient.Next;
      end;

      tblOnlinePair.Filter := ' (pc_ip='   + QuotedStr(pIP) + ' and pc_port=' +   QuotedStr(pPort.ToString) + ') ' +
                           ' or (lift_ip=' + QuotedStr(pIP) + ' and lift_port=' + QuotedStr(pPort.ToString) + ') ';
      tblOnlinePair.Filtered := True;
      tblOnlinePair.First;
      while not tblOnlinePair.Eof do begin
        tblOnlinePair.Delete;
        tblOnlinePair.Next;
      end;
    finally
      tblOnlineClient.Filter := '';
      tblOnlineClient.Filtered := False;
      tblOnlinePair.Filter := '';
      tblOnlinePair.Filtered := False;
      dm.qryDeleteOnlineClient.Close;
    end;
  end;
end;

procedure TServer.kickClient(AContext: IdContext.TIdContext);
begin
  if not Assigned(AContext) then
    Exit;

  writeToConsole('Disconnect:' + AContext.Binding.PeerIP + ':' + AContext.Binding.PeerPort.ToString, True);

  //update database and memtables
  spDeleteOnlineClient(AContext.Binding.PeerIP, AContext.Binding.PeerPort);
  //after disconnect client
  AContext.Connection.Disconnect;  //HERE IS THE PROBLEM after call this procedure
end;

//main loop
var
  Server: TServer;
begin
  Server := TServer.Create(cloud_port);
  try
    Server.IdTCPServer.Active := True;
    Server.writeToConsole('TCP Socket Server App Started', False);
    Server.writeToConsole('Listen Port ' + cloud_port, False);

    n1 := 0;
    Server.writeToConsole('Start...', False);

    while True do
    begin
      try
        Sleep(1);
        Inc(n1);

        //every 2 sec kick old client
        if n1 = 2000 then
        begin
          .......
          n1 := 0;

          Writeln(TimeToStr(Now) + ' Before : TCP Client=' + Server.IdTCPServer.Contexts.Count.ToString + ' - Table Count=' + Server.tblOnlineClient.RecordCount.ToString);
          Server.FLockList := Server.IdTCPServer.Contexts.LockList;
          try
            for n2 := Server.FLockList.Count-1 downto 0 do begin
              AContext := Server.FLockList[n2];

              if GetTickDiff(TConnection(AContext).LastSendRecv, Ticks) >= 300000 then  //Kill the zombie connection if you haven't received and sent data in the last 5 minutes
              begin
                Writeln(TimeToStr(Now) + ' : Kicked Old Client ID=' + TConnection(AContext).ClientID);
                Server.kickClient(AContext);
              end;
            end;
          finally
             Server.IdTCPServer.Contexts.UnlockList;
             Server.FLockList := nil;
          end;
        end;
      except
        on E: Exception do
          Writeln(E.ClassName, ': ', E.Message);
      end;
    end;
  finally
    Server.Destroy;
  end;
end.

procedure TServer.IdTCPServerConnect(AContext: TIdContext);
begin
  if Assigned(AContext) then
  begin
    if AContext.Binding.Port = IdTCPServer.Bindings.DefaultPort then
    begin
      //save client info to db at the first connection
      //Cant detect at the first connection IoT Device or PC. Have my hand only ip and port datas
      TConnection(AContext).LastSendRecv := Ticks;
      spAddOnlineClient(AContext.Binding.PeerIP, '', AContext.Binding.PeerPort, False); //add db and memtable
      writeToConsole('Welcome guest :) ' + AContext.Binding.PeerIP + ':' + AContext.Binding.PeerPort.ToString, False);
    end;
  end;
end;

procedure TServer.IdTCPServerDisconnect(AContext: TIdContext);
begin
  writeToConsole('Disconnect:' + AContext.Binding.PeerIP + ':' + AContext.Binding.PeerPort.ToString + ' Good Bye', False); // write to console screen
  spDeleteOnlineClient(AContext.Binding.PeerIP, AContext.Binding.PeerPort); //at the disconnect remove client from db and memtable
end;

procedure TServer.IdTCPServerExecute(AContext: TIdContext);
var
  RCIdByte: TIdBytes;

  vSessionPC, vSessionLift: TSession;
  vSessionPair: TSessionPair;
begin
  AContext.Connection.IOHandler.DefStringEncoding := IndyTextEncoding_UTF8;

  SetLength(RCIdByte, 0); //clean buffer

  AContext.Connection.IOHandler.ReadBytes(RCIdByte, -1); //read package

  FLockList := IdTCPServer.Contexts.LockList; //lock and make process
  try
    if Length(RCIdByte) > 0 then
    begin
      //prepare and validate package
      TTools.copyTIDArrayToArray(RCIdByte, RC);
      TTools.parseRCData2(RC, FDataPacket);

      if FDataPacket.IsKickedClient then
        kickClient(AContext) //kick client if data package is invalid
      else
      begin
        TConnection(AContext).LastSendRecv := Ticks; //update last action time at the receive or send data

        if FDataPacket.Direction = PC_TO_LIFT then  //dats came from PC to IoT Device
        begin
          if FDataPacket.Cmd = CMD_LOGIN_PC then
          begin
            vSessionLift := getOnlineClientData(FDataPacket.GSMNo, AContext.Binding.PeerIP, AContext.Binding.PeerPort);
            TConnection(AContext).ClientID := vSessionLift.ID;
            if (vSessionLift.IP = '') then
            begin
              TTools.preArrayData(TR, HEADER_LIFT_TO_PC, FDataPacket.GSMNo, FDataPacket.PcID, '', CMD_ERR_LIFT_IS_MISSING);
              sendData(TR, AContext.Binding.PeerIP, AContext.Binding.PeerPort.ToString);
              Exit;
            end;

            //Update DB and MemTable client_id and other infos
            spEditOnlineClient(AContext.Binding.PeerIP, FDataPacket.PcID, AContext.Binding.PeerPort, False);
            vSessionPC := getOnlineClientData(FDataPacket.PcID, AContext.Binding.PeerIP, AContext.Binding.PeerPort);
            //add online pair table and memtable
            spAddOnlinePair(vSessionLift.IP, vSessionLift.ID, vSessionLift.Port.ToInteger, vSessionLift.ConnectDate,
                            vSessionPC.IP, vSessionPC.ID, vSessionPC.Port.ToInteger, vSessionPC.ConnectDate);
          end
          else if FDataPacket.Cmd = CMD_DATA_PC_TO_LIFT then
          begin
            //two way comm. PC Request from the IoT Device
            vSessionPair := getOnlinePairDataByLiftID(AContext.Binding.PeerIP, FDataPacket.GSMNo, AContext.Binding.PeerPort);
            if (vSessionPair.LiftIP <> '') AND (vSessionPair.PcIP <> '') then //find pair and send data to pair
            begin
              spUpdateLastActtion(AContext.Binding.PeerIP, AContext.Binding.PeerPort); //update last_update, last action times
              sendData(RC, vSessionPair.LiftIP, vSessionPair.LiftPort); //send package to IoT device
            end;
          end;
        end
        else if FDataPacket.Direction = LIFT_TO_PC then
        begin
          if FDataPacket.Cmd = CMD_LOGIN_LIFT then
          begin
            FDataPacket.LiftSerialNo := TTools.getLiftSerialNo(FDataPacket);
            FDataPacket.Password := TTools.getLiftPassword(FDataPacket);

            //Update DB and MemTable client_id and other infos
            spEditOnlineClient(AContext.Binding.PeerIP, FDataPacket.GSMNo, AContext.Binding.PeerPort, True);
            //Update iot_device table and MemTables last_action time
            spAddOrEdtLift(AContext.Binding.PeerIP, AContext.Binding.PeerPort);
          end
          else if FDataPacket.Cmd = CMD_HB_LIFT then  //periodic(60sec) data heartbeat with some data
          begin
            spUpdateLiftHeartbeatData(AContext.Binding.PeerIP, AContext.Binding.PeerPort, False);
          end
          else if FDataPacket.Cmd = CMD_DATA_LIFT_TO_PC then
          begin
            //two way comm. IoT Device Response the PC
            vSessionPair := getOnlinePairDataByPcID(AContext.Binding.PeerIP, FDataPacket.PcID, AContext.Binding.PeerPort);
            if (vSessionPair.LiftIP <> '') AND (vSessionPair.PcIP <> '') then
            begin
              spUpdateLastActtion(AContext.Binding.PeerIP, AContext.Binding.PeerPort);
              sendData(RC, vSessionPair.PcIP, vSessionPair.PcPort);
            end;
          end;
        end;
      end;
    end;

  finally
    IdTCPServer.Contexts.UnlockList;
    FLockList := nil;
  end;
end;
Reply


Messages In This Thread
RE: IdTcpServer Kick Client (Disconnect) INVALID HANDLE Exception - by 3ddark - 06-18-2020, 06:14 AM

Forum Jump:


Users browsing this thread: 1 Guest(s)