Newsgroup: it.comp.lang.delphi
Titolo: Indy9 - Sincronizzazione Thread

Ho un'applicazione Client-Server.
Il Client si collega al server inviando una stringa formattata con il 
proprio Username, il Server la riceve e restituisce una stringa che dovrei 
poi leggere sul Client.
Se lancio diverse sessioni del Client sullo stesso PC (ma anche da macchine 
remote) tutto bene.
Il problema nasce su un Client personalizzato che ho predisposto per 
verificare l'affidabilità del server con connessioni praticamente 
simultanee.
In breve ho inserito due TIdTCPClient sulla stessa form che inviano due 
Username diversi uno immediatamente dopo l'altro:
(purtroppo per comprendere il codice sarebbe necessario fare riferimento 
alle unit Client - Server postate da Andrea tempo addietro ma ci si può 
capire 
q.sa
 lo stesso)
procedure TClient.btnConnectAllClick(Sender: TObject);
begin
  // 1° client
  cli.Host := '127.0.0.1';
  cli.Port := 55555; // per dati in entrata
  cli.Connect();
  FClientThread := TClientThread.Create;
  FClientThread.Open('127.0.0.1' , 55556, True); // per dati in uscita
  // 2° client
  cli2.Host := '127.0.0.1';
  cli2.Port := 55555;
  cli2.Connect();
  FClientThread2 := TClientThread.Create;
  FClientThread2.Open('127.0.0.1', 55556, True);
end;
il server riceve e scrive nei memo test i dati corretti:
procedure TServer.SrvConnect(AThread: TIdPeerThread);
begin
  if AThread.Connection.Socket.Binding.Port = 55555 then
  begin
    memo1.Lines.Add('Utente connesso - IP: 
'+AThread.Connection.Socket.Binding.PeerIP);
    memo1.Lines.Add('Dati in entrata sulla porta: '+ 
inttostr(AThread.Connection.Socket.Binding.Port));
    memo1.Lines.Add('Porta remota: 
'+inttostr(AThread.Connection.Socket.Binding.PeerPort));
  end;
  if AThread.Connection.Socket.Binding.Port = 55556 then
  begin
    memo2.Lines.Add('Dati in uscita sulla porta: '+ 
inttostr(AThread.Connection.Socket.Binding.Port));
    memo2.Lines.Add('Porta remota: 
'+inttostr(AThread.Connection.Socket.Binding.PeerPort));
    FOutputThread := AThread; // dichiarato come: FOutputThread: 
TIdPeerThread;
  end;
end;
i due client rispondono uno dopo l'altro con l'evento OnConnected ed inviano 
il proprio Username
procedure TClient.CliConnected(Sender: TObject);
const
  Prefix = '@&';
var
  UserStr : string;
begin
  memo1.Lines.Add('Sono connesso sulla porta '+ 
inttostr(cli.Socket.Binding.Port));
  UserStr := prefix + EdUser.Text;
  cli.WriteLn(UserStr); // invia la stringa formattata contenente lo 
Username
end;
procedure TClient.Cli2Connected(Sender: TObject);
const
  Prefix = '@&';
var
  UserStr : string;
begin
  memo2.Lines.Add('Sono connesso sulla porta '+ 
inttostr(cli2.Socket.Binding.Port));
  UserStr := prefix + EdUser2.Text;
  cli2.WriteLn(UserStr);
end;
il server riceve nell' Execute e nei memo test e listbox mi visualizza gli 
utenti collegati con tutti i dati corretti
procedure TServer.SrvExecute(AThread: TIdPeerThread);
var
  ReceiveData, UserName: string;
begin
  ReceiveData := AThread.Connection.ReadLn;
  if copy(ReceiveData, 1, 2) = '@&' then
  begin
    Username := copy(ReceiveData, 3, length(ReceiveData)-1);
    memo1.Lines.Add(UserName);
    lbUsername.Items.Add(Username);
    AThread.Connection.Socket.SocksInfo.Username := username;
end;
if assigned(FOutputThread) and FOutputThread.Connection.Connected then
begin
  FOutputThread.Connection.WriteLn(Username); // restituisco al Client il 
suo Username
  FOutputThread := nil; // lo metto a NIL per reinizializzarlo subito dopo 
con la seguente connessione
end;
end;
Il problema è proprio nella restituzione dello Username che random sulla 
form del client non ne viene ricevuto nessuno, uno o entrambi (raramente).
procedure TClientThread.Execute;
var
  msg: string;
begin
  while not Terminated do
  begin
    if assigned(FIdTCPClient) and FIdTCPClient.Connected then
    begin
      msg := FIdTCPClient.ReadLn;
      form1.Memo1.Lines.Add(msg);
      form1.Memo1.Refresh;
    end;
    if assigned(FIdTCPClient2) and FIdTCPClient2.Connected then
    begin
      msg := FIdTCPClient2.ReadLn;
      form1.Memo2.Lines.Add(msg);
      form1.Memo2.Refresh;
    end;
end;
A prescindere dalla comprensione del codice mi/vi chiedevo:
quando mando una stringa al client 
*FOutputThread.Connection.WriteLn(Username);* come posso verificare 
l'avvenuta ricezione prima di inviarne una seconda (perchè pare si perdano 
random, se ritenete che il problema sia questo)?.
Grazie
Valter 

 random, se ritenete che il problema sia questo)?.
Non credo sia questo: il problema non si pone in quanto le funzioni Writeln 
sono bloccanti, cioe' ritornano solo quando la stringa e' stata inviata, 
essendo il componente Indy un socket sincrono.
In sostanza puoi porre piu' writeln di seguito senza alcun problema. 
Naturalmente al lato opposto dovranno esserci altrettanti readln.
Ciao Giorgio 

 inviata, essendo il componente Indy un socket sincrono.
Temevo / speravo in questa risposta...
non riesco a capire se sto chiedendo troppo o se, con un codice corretto, 
tutto dovrebbe funzionare regolarmente...propendo più per quest'ultima, 
d'altra parte se dovesse verificarsi un tentativo di collegamento da due 
client simultaneamente il server dovrebbe gestire le due richieste senza 
problemi ed inviare i dati ad entrambi con le giuste priorità...
dato che dai test il server vede e gestisce le due connessioni senza 
problemi l'intoppo dovrebbe essere o nell'invio dei dati di ritorno del 
server ai due client:
    if assigned(FOutputThread) and FOutputThread.Connection.Connected then
    begin
      FOutputThread.Connection.WriteLn(Username); // restituisco al Client 
il suo Username
      FOutputThread := nil;
    end;
 o nella ricezione dei due client stessi:
    if assigned(FIdTCPClient) and FIdTCPClient.Connected then
    begin
      msg := FIdTCPClient.ReadLn;
      form1.Memo1.Lines.Add(msg);
      form1.Memo1.Refresh;
    end;
    if assigned(FIdTCPClient2) and FIdTCPClient2.Connected then
    begin
      msg := FIdTCPClient2.ReadLn;
      form1.Memo2.Lines.Add(msg);
      form1.Memo2.Refresh;
    end;
è tutto il giorno che provo :-(

 server ai due client:
fai un debug passopasso, giorgio 

 fai un debug passopasso, giorgio
Già fatto. Senza toccare una riga di codice si comporta ogni volta in modo
diverso.
O arrivano entrambe le writeln, o solo una delle due, o si blocca durante il
debug.
Riavviando entrambe le applicazioni, la prima volta (ma non sempre) tutte e
due le writeln giungono a destinazione, se disconnetto e ci riprovo non c'è
più verso.
Ho però notato un dettaglio: anche se riavvio le applicazioni le porte
utilizzate dopo ogni disconnessione e riconnessione sono sempre
incrementali, come se non si resettassero alla chiusura dei programmi.
Inoltre, e questa è la cosa che mi fa impazzire!, mi porto i sorgenti da
casa al lavoro dal lavoro a casa, accendo il pc, li lancio e la primissima
volta arrivano entrambe le writeln. Dopodichè impazzisce tutto, anche se
riavvio il pc.
Sarà una combinazione?!

l'avvenuta ricezione
Il client, una volta inviate le informazioni può mettersi subito in
ascolto con un'operazione Read*() bloccante.  In alternativa, puoi
avere un thread client sempre in ascolto, come già detto, che si
limita a mandare i dati ricevuti a chi di dovere mediante messaggi.

    if assigned(FOutputThread) and FOutputThread.Connection.Connected then
NON DEVI USARE FOutputThread.

essendo il componente Indy un socket sincrono.
Non c'entra nulla il blocking socket con la scrittura sincrona.
L'operazione di scrittura è *ovviamente* sequenziale, lato client, ma
i dati viaggiano in asincrono, quindi non è affatto detto che, data la
scrittura A e la scrittura B, al server arrivino prima i dati di A e
poi quelli di B.

diverso.
Sei tu che stai sbagliando.  La logica di funzionamento dei socket e
in particolare di Indy è *MOLTO* semplice: lato server si scatena un
OnExecute per ogni connessione client.  Le letture sono bloccanti, nel
senso che una volta invocata Read*(), il sistema è in attesa sincrona
di dati: se ci sono li raccoglie e prosegue, se non ci sono attende.
Se segui una sequenza del tipo:
Lato client:
------------
Scrivi la dimensione dei dati (WriteCardinal)
Scrivi i dati (WriteStream)
Leggi la dimensione dei dati (ReadCardinal) 
Leggi i dati (ReadStream)
Lato server:
------------
OnExecute()
  Leggi la dimensione dei dati (ReadCardinal)
  Leggi i dati (ReadStream)
  Scrivi la dimensione dei dati (WriteCardinal)
  Scrivi i dati di risposta (WriteStream)
...tutto funziona a meraviglia...

Perche' non prendi spunto dal client/server che avevo postato, era per Indy 
10 ma cambia molto poco.
La stabilita', anche nel debugger e' garantita.
Ciao, Giorgio 

 Indy 10 ma cambia molto poco. [...]
Da una parte per non complicarmi ulteriormente le cose * cambia poco ma 
q.sa
 
cambia  :-) * e come avrai potuto constatare non sono una cima...
poi perchè sono abbastanza testardo e non vedo il motivo di non riuscire a 
modificare/adattare due unit che all'origine sono stabili e funzionali
V.

 limita a mandare i dati ricevuti a chi di dovere mediante messaggi.
Sto impazzendo! Non è quello che sto facendo (cercando di fare)?

 Sto impazzendo! Non è quello che sto facendo (cercando di fare)?
Sì, ma stai sbagliando.  Parti da un esempio più semplice: scrivi un
server che fa un binding su una sola porta (non due come in
quell'esempio che era fatto per uno specifico utilizzo), scrivi un
client che scrive e ascolta (senza il thread secondario).  Una volta
che questa "base" funziona, puoi spingerti nel modificare le cose.

scrivi un client che scrive e ascolta (senza il thread secondario)
ti riconosco una sacrosanta pazienza alquanto rara e non voglio abusare 
anche se temo di averlo già fatto (con ICS di Piette non avevo avuto nessun 
problema a scrivere un client-server)...
probabilmente ce l'ho sotto agli occhi ma non lo vedo:
quale accidenti è l'evento che ascolta nel client? mando con estrema 
facilità i dati al server, ma mi areno sul serio se devo leggere dati 
inviati dal server al client (sulla stessa porta)...col thread secondario ho 
un Execute e bene o male qualcosa arriva
dove mi sto perdendo? Cosa non vedo? 

 quale accidenti è l'evento che ascolta nel client?
dando un'occhiata all'unico esempio fornito con Indy che si adatti allo 
scopo (Chat) vedo che è stato utilizzato il TTimer per verificare dati in 
arrivo...a parte che non mi piace molto ma questo mi conferma che non sono 
disponibili eventi Execute nativi...o sbaglio?

 quale accidenti è l'evento che ascolta nel client?
Ti avevo già risposto in questo thread: non esiste.
Stai inquadrando male il problema: tu stai lavorando con connessioni
bloccanti.  Questo vuol dire che non hai necessità di un "evento"
asincrono che ti segnali i dati, perché la connessione la gestisci tu:
scrivi e poi ti metti subito in ricezione mediante una qualsiasi
Read*() bloccante.
La Read*() come già detto restituisce i dati se ci sono, altrimenti
ascolta.
Quello che stai sbagliando è partire da un mio esempio che era scritto
per un contesto specifico: in quel codice io ho ricondotto parte
dell'applicazione ad una logica asincrona perché il client potesse sia
inviare in qualsias momento che ricevere in qualsiasi momento.  Se i
dati possono arrivare dal server, in qualunque momento, allora hai
bisogno di un thread secondario a cui delegare il compito di stare
SEMPRE in ascolto, su una seconda connessione.  Questo thread
segnalerà al thread principale i dati mediante un messaggio, quando ci
sono.  Se invece ti basta che il client scriva e ascolti la risposta,
allora ti basta Write*() e Read*() sulla *STESSA* connessione.  Spero
di essere stato chiaro...

 Spero di essere stato chiaro...
talmente chiaro che ho capito!  :-)
ed il demo base ora funziona
uff, lentamenteeee, avvio le modifiche
GRAZIE
Valter

 ...tutto funziona a meraviglia...
E' vero, scrivendo una semplice applicazione come detto negli ultimi post ho
aperto 8 client contemporaneamente che scambiavano dati col server. Tutto ok
per un certo periodo di tempo, però.
Dopo poco meno di 8.000 transazioni globali tra gli 8 client ed il server
quest'ultimo è andato in Access Violation.
Il server era sotto Delphi, i client tutte applicazioni compilate.
Stavo chiedendo troppo su una macchina sola o pensi che non doveva succedere
e devo rivedere il codice prima di proseguire?
Ciao
Grazie

 Il server era sotto Delphi, i client tutte applicazioni compilate.
Dimentica quanto detto. Ho compilato anche il server ed ho levato delle
visualizzazioni dati nella TList. Siamo a 12.000 transazioni con 9 client e
non fa una piega