Newsgroup: it.comp.lang.delphi
Titolo: TIdTCPServer Indy9 - Gestione utenti

Ragazzi, mi occorrerebbe un vostro consiglio / chiarimento.
Utilizzo il CommandHandlers e mi avvalgo del componente HotLog v2.2 per 
loggare ogni transazione...
proprio verificando il log, e aumentando il numero di client connessi - poco 
meno di 30 - mi sono accorto di una bug che si verifica random:
il server crea ad ogni richiesta di un client un pacchetto dati e lo 
consegna al client stesso...dato che il server gestisce + utenti 
contemporaneamente si verifica, se una stessa parte di codice viene eseguita 
parallelamente, che talvolta assegna il medesimo pacchetto a due client e 
questo comporta anche incoerenze durante l'accesso al database, dove il 
server si aspetta di trovare qualcosa e non lo trova o duplica alcune 
voci...
utilizzo 3 CommandHandlers:
uno per la creazione e l'assegnazione dei pacchetti, ed i dati di consegna 
vengono memorizzati nel database
uno per la ricezione di termine elaborazione pacchetto da parte del client, 
ed il pacchetto elaborato viene eliminato di conseguenza dal database
uno per la ricezione di pacchetti positivi che vengono memorizzati come tali 
nel database.
I due ultimi Command non generano alcun problema anche se eseguiti 
contemporaneamente ad altri ma il primo deve per forza terminare la 
creazione del pacchetto ed assegnarla prima di gestire la richiesta di 
un'altro client.
Al momento, e pare funzionare, utilizzo una variabile booleana globale, 
dichiarata nella sezione Private.
Riporto il codice principale:
procedure TfrmMain.IdTCPServerRequestIndexCommand(ASender: TIdCommand);
var
   ...
begin
   RequestUser := ASender.Params.Strings[0];
   if not RequestOn then
   begin
      RequestOn := true; { blocco ogni altro accesso }
      { generazione pacchetti, assegnazione, accesso al db }
   end
      else
         begin
            HLog.AddStr('Tentativo di accesso simultaneo alla procedura')
            ...
            ASender.Thread.Connection.WriteLn('Busy');
            { il client interpreta la stringa }
            ...
            ASender.Thread.Connection.Disconnect;
            { il client tenta la connessione immediatamente dopo }
            ...
            exit;
         end;
   RequestOn := false; { accetta la prossima richiesta }
end;
Voi pensate che sia possibile che due client riescano a passare 
CONTEMPORANEAMENTE mentre RequestOn è false? Meglio usare un thread 
bloccante o così ho comunque la sicurezza che non si generino conflitti?
Grazie
Valter

      RequestOn := true; { blocco ogni altro accesso }
Non blocchi un bel niente così.  Due thread possono benissimo accedere
contemporaneamente alla variabile RequestOn e testare che è false,
quindi procedere indisturbati.  Devi usare degli oggetti di
sincronizzazione come le critical section, per gestire le corse
critiche.  Tu avrai un oggetto di tipo TCriticalSection, che
istanzierai all'inizio, poi il codice critico lo segnerai con
l'acquisizione e il rilascio della CS:
FMyCriticalSection.Aquire;
try
  ... qui ci va il tuo codice soggetto ad accesso serializzato
finally
  FMyCriticalSection.Release;
end;

 critiche.
[..]
Lo temevo!
Ma senti, questo è il procedimento giusto o sto cannando? Mi sembra troppo
facile...
uses
  ... SyncObjs;
nella sezione interface
type
  TRequestCriticalSection = class(TThread)
     { questa dichiarazione non mi serve!? }
  end;
nella sezione public
  FRequestCriticalSection: TCriticalSection;
  { mi pare che debba avere visibilità globale }
nel OnCreate del Form
  FRequestCriticalSection := TCriticalSection.Create;
ed infine nel CommandHandlers del server
procedure TfrmMain.IdTCPServerRequestIndexCommand(ASender: TIdCommand);
var
   ...
begin
   RequestUser := ASender.Params.Strings[0];
   FRequestCriticalSection.Acquire;
   try
      { tutto il codice deve essere serializzato }
   finally
      FRequestCriticalSection.Release;
   end;
end;
Mi sono aiutato con un tutorial reperito in rete...ci sono?
Da qualche test eseguito parrebbe ok ma una tua eventuale conferma sarebbe
gradita :-)
Se così fosse, una domanda: è possibile sapere se mentre il codice protetto
viene eseguito altri client tentano a loro volta di eseguirlo?
Ti ringrazio
Valter

 viene eseguito altri client tentano a loro volta di eseguirlo?
A che ti serve ?
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it


non č fondamentale, mi serviva solo per ulteriore controllo ma poi nella
versione finale questa verifica non servirŕ a nulla...
attualmente, anche se inaffidabile, con la variabile booleana globale
respingo il client che tenta un'accesso simultaneo e gli chiedo di ritentare
il collegamento...loggando tutte queste operazioni posso verificare se il
client respinto si ripresenta immediatamente dopo...
ma probabilmente con il TCriticalSection i client restano 'in coda' e
vengono serviti senza dubbio quando č il loro turno!?
Ho perň un dubbio:
supponendo per assurdo che un numero considerevole di client tentino tutti
insieme l'accesso - mettiamo 20 - respingendoli con un *busy* essi
ritenteranno il collegamento ad oltranza fino a quando riusciranno a
passare...
ma come si comporterebbero se vengono gestiti dal TCriticalSection?
La procedura di assegnazione pacchetti / scrittura db dura circa 1 secondo -
l'ultimo accederebbe dopo 20 secondi - non si potrebbe verificare un timeout
di connessione?
Lasciando il MaxConnection del Server a zero, mi conviene settare il
ListenQueue?
Ciao e grazie
Valter

 di connessione?
Non capisco che vuoi dire.



Neanch'io! :-)
Delete(question);
Ciao e grazie

 a conti fatti penso che resterò così...
Ti preoccupi ancora della compatibilità con Millenium e 98 ??


 Ti preoccupi ancora della compatibilità con Millenium e 98 ??
Ti ricorderai che ho un punto vendita di informatica ma soprattutto 
sopravvivo grazie all'assistenza...non so se è perchè siamo a Genova - 
leggendariamente ritenuta di manica stretta - ma non immagini quanti 98 e 
Millenium passano ancora sotto le mie mani...

 end;
Il Finally viene eseguito qualunque cosa accada nel codice dopo il Try.
In questo codice io richiamo alcune funzioni che processano un blocco 
Try..Except.
In ogni caso se la funzione non va a buon fine ritorna False e nel codice 
principale abbandono la procedura di assegnazione pacchetto ripristinando 
eventualmente contatori, dati database ecc..
Un breve esempio:
procedure TfrmMain.IdTCPServerRequestIndexCommand(ASender: TIdCommand);
var
   ...
begin
  FRequestCriticalSection.Acquire;
  try
    ...
    if not VerifyPack then
    begin
      HLog.AddStr('[VerifyPack] - Conferma abbandono processo;
      ...
      exit;
    end;
    ...
  finally
     FRequestCriticalSection.Release;
  end;
end;
Mi chiedevo se, una delle funzioni restituisce False e passa per Exit, il 
Finally del Try dell'Acquire viene sempre eseguito...?
ho simulato un'errore in una funzione ed il Release č stato processato...
puoi confermarmi che č corretto?
Grazie
Valter
 

 Finally del Try dell'Acquire viene sempre eseguito...?
Sì.
In ogni caso, come regola di buona programmazione, non dovresti mai
implementare uscite multiple da una funzione.  Non hai bisogno di Exit().
Vincola l'esecuzione del codice nel try al caso positivo.



Exit() non piace nemmeno a me ma non saprei come fare altrimenti...
se simulo un'eccezione nella funzione VerifyPack, lasciando ad esempio la 
tabella chiusa, il suo blocco Try..Except viene chiamato in causa e 
restituisce VerifyPack = False...ma il Try..Finally del codice principale, 
che è quello che ti riporto sotto, non riceve in qualche modo l'errore della 
funzione ma solo VerifyPack = False e senza exit non mi bypassa tutta la 
procedura jumpando immediatamente sul Finally...hai qualche suggerimento?


 Exit() non piace nemmeno a me ma non saprei come fare altrimenti...
Che vuol dire ? che problema c'è nel trasformare il codice seguente:
begin
  if b = 0 then Exit;
  a = a / b;
end;
in:
begin
  if b <> 0 then
    a = a / b;
end;
?



Perchč le funzioni e le condizioni di controllo prima di creare il pacchetto 
sono piů di una...
suggerisci di annidarle?
ponendo a = a / b { codice creazione pacchetti }
[BEGIN TCriticalSection]
{ eccezione sollevata durante la scansione del db per marcare i pacchetti
  a SCADUTI }
if b = 0 then exit; { se occorre ripristino i dati e resto in attesa di 
altro client }
{ eccezione sollevata durante la scansione del db per verificare se vi
  siano pacchetti scaduti nel qual caso non crea il nuovo ma assegna
  il vecchio }
if c = 0 then exit; { se occorre ripristino i dati e resto in attesa di 
altro client }
if ((c = 1) and (d = 0)) then
  begin
    a := a / b;
  end
    else
      begin
        { fine creazione pacchetti }
        d := 1;
      end;
else
if ((c = 1) and (d = 1)) then
begin
  { terminata la creazione dei pacchetti assegna quelli eventualmente
     scaduti ancora presenti nel db }
end;
[END TCriticalSection]
Ho riportato un riassunto della struttura di base ma effettuo altri 
controlli.
In questo contesto non so se posso applicare il tuo consiglio.
Valter

suggerisci di annidarle?
Oppure di usare una variabile di controllo del flusso:
goOn = (b<>0);
if goOn then ...
  goOn = ....
if goOn then ...
ecc... ecc...