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;