Newsgroup: it.comp.lang.delphi
Titolo: Stack Overflow - Indy9

Sto scrivendo un'applicazione Client-Server con Indy9.
Il Client si collega al Server, riceve un pacchetto di dati, si disconnette, 
elabora i dati, si riconnette e restituisce i risultati al server.
Il tutto gira bene, con ben sei clients di prova in rete che interagiscono 
con il Server.
Attualmente i pacchetti che il Server invia ai Clients sono circa 20.000 ed 
ogni client ne elabora circa 1 sesto.
Ma provando con un solo client, sia in locale che da remoto, ottengo sempre 
allo stesso numero di ciclo uno Stack Overflow (anche su una coppia di 
macchine diverse).
Questo mi fa pensare che se aumento il numero di pacchetti anche piů client 
restituiranno lo stesso errore.
Su Google ho trovato:
Every time you open a socket, it causes Winsock to assign a buffer. When the
socket is closed, the buffer is not returned to the system immediately.
I believe you can define the size of winsock buffer, but I think you could
look at cutting down the number of connection attempts you try to make,
or (it's simple - honest!) - as I do - stick a UDP server component - see
Indy components, on the server and a UDP client on your client application.
Send a connection request from UDP client every few seconds. When the UDP
server finally replies, THEN attempt to connect using the TCP socket.
Pensate che il problema sia davvero questo? Mi tocca usare un UDP solo per 
"prendere tempo"?
Sul Client, una volta letto lo stream, lo libero con Free,
e uso degli array per processare i dati
MyAr1: array of Cardinal,  // ma nel mio test contiene 4 byte
MyAr2: Array of Cardinal, // come sopra
MyAr3 array of array of byte // 4, 6
Ad ogni ciclo (connessione e disconnessione) li sovrascrivo e non memorizzo 
nulla, scrivo solo il pacchetto ricevuto tipo 1 2 3 4 in un memo.
Il problema potrebbe essere che i dati calcolati dal Client sono talmente 
pochi (per rendere il test piů rapido) che le connessioni - disconnessioni 
sono cosě ravvicinate da causare l'errore?
Cosa ne pensate?
Valter

sono così ravvicinate da causare l'errore?
Sicuramente devi evitare di connetterti e disconnetterti in
continuazione (te l'avevo già detto tempo fa).
In ogni caso, non sarebbe un errore di stack-overflow.

 In ogni caso, non sarebbe un errore di stack-overflow.
So che dovresti vedere il codice, ma se dipendesse proprio dalle connessioni 
troppo ravvicinate non c'è modo di liberare lo stack od il buffer del socket 
come ipotizzato nel post?

> In ogni caso, non sarebbe un errore di stack-overflow.
In effetti.
Evita ogni variabile locale, specie se di dimensioni sostenute.
Potresti anche avere beccato un bug di Indy...??
Hai idea di quante connessioni fai in un secondo, usi thread per ognuna, hai 
piu' connessioni attive contemporaneamente ed eventualmente quante.
Cmq e' un errore strano, specie con i socket, non mi e' mai successo. 
Eventualmente come Client prova a sostituire l'Indy col nativo di Delphi.
Ciao Giorgio 

Eventualmente come Client prova a sostituire l'Indy col nativo di Delphi.
I componenti Indy funzionano bene, se li si sa utilizzare...

Ho fatto diverse prove e sono perplesso.
Condizione > 1 Server - 1 Client completo di codice per elaborare i dati:
al 10.290° ciclo ottengo sempre StackOverflow
Condizione > 1 Server - 1 Client commentando il codice che analizza i dati
ma lasciando la trasmissione dei pacchetti
al 10.393 ciclo ottengo sempre Access Violation
Condizione > 1 Server - 2 Client con un massimo di pacchetti di 20.475
nessun errore
Ho quindi aumentato il numero di pacchetti nel test con i due client ed al
20.580° ciclo ecco l'errore.
E avrete notato: 20.580 / 2 = 10.290
La mia sensazione è che lo stack, nelle transazioni socket, si riempia senza
svuotarsi e
1) senza dati da elaborare il prg restituisce Access Violation dopo 10.393
cicli
2) ma se elaboriamo dei dati lo utilizziamo anche per questi ed ad un certo
punto questi non hanno più spazio e abbiamo un bel Stack Overflow.
Non so se mi sono spiegato bene.
Quindi ho preparato questa unit di prova dove non faccio assolutamente
niente se non connettermi / disconnettermi e anch'essa restituisce Access
Violation nei pressi di 10.380 cicli.
E' da notare che per ogni ciclo numerico si hanno 2 connessioni e 2
disconnessioni, quindi circa 41.000 operazioni sulle porte (ma le porte non
sono circa 65.000? Potrebbe centrare?)
Potete darci un'occhiata?
unit Unit1;
interface
uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, IdTCPConnection, IdTCPClient, IdBaseComponent, IdComponent,
  IdTCPServer, StdCtrls;
type
  TForm1 = class(TForm)
    IdTCPServer1: TIdTCPServer;
    IdTCPClient1: TIdTCPClient;
    Button1: TButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure IdTCPServer1Execute(AThread: TIdPeerThread);
    procedure IdTCPClient1Connected(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure CliAnalizzaDati;
  private
    { Private declarations }
    Count : integer;
    FirstConnect: boolean;
  public
    { Public declarations }
  end;
var
  Form1: TForm1;
implementation
{$R *.dfm}
// Sezione Server
procedure TForm1.FormCreate(Sender: TObject);
begin
  IdTCPServer1.Bindings.Add.Port := 9099;
  IdTCPServer1.Active := True;
  Count := 0;
  FirstConnect := true;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
  IdTCPServer1.Active := False;
end;
procedure TForm1.IdTCPServer1Execute(AThread: TIdPeerThread);
var
  Command: string;
begin
  Command := AThread.Connection.ReadLn;
  if Command = 'primaconnessione' then
    AThread.Connection.WriteLn('inviopacchetto')
      else
        if Command = 'mandodati' then
          // dati restituiti dal client
end;
// Sezione Client
procedure TForm1.Button1Click(Sender: TObject);
begin
IdTCPClient1.Host := '127.0.0.1';
IdTCPClient1.Port := 9099;
IdTCPClient1.Connect;
end;
procedure TForm1.IdTCPClient1Connected(Sender: TObject);
var
  S: string;
begin
  // con FirstConnect a true il client richiede il pacchetto
  // passando per il blocco in OnConnected,
  // a false salta questo blocco e torna alla procedura
  // CliAnalizzaDati e restituisce i dati elaborati
  // gli sleep(50) servono per prevenire errore di porta in uso
  if FirstConnect = true then
  begin
  // si prepara a ricevere il pacchetto
  IdTCPClient1.WriteLn('primaconnessione');
  S := IdTCPClient1.ReadLn;
  CliAnalizzaDati;
  // uscito dalla procedura che ha inviato i dati
  // si disconnette
  IdTCPClient1.Disconnect;
  sleep(50);
  // la prossima connessione richiede dati passando
  // per questo blocco
  FirstConnect := true;
  IdTCPClient1.Connect;
  sleep(50);
  end;
end;
procedure TForm1.CliAnalizzaDati;
begin
  // ha i dati da elaborare quindi si disconnette
  IdTCPClient1.Disconnect;
  sleep(50);
  // la boolean indica che alla prossima
  // connessione dovrà restituire i dati
  FirstConnect := false;
  // elabora i dati quindi richiede collegamento
  // passa per l'evento OnConnected ma con FiestConnect a false
  IdTCPClient1.Connect;
  sleep(50);
  IdTCPClient1.WriteLn('mandodati');
  inc(Count);
  label1.Caption := inttostr(Count);
  application.ProcessMessages;
end;
end.

  sleep(50);
Questi Sleep() non servono a nulla, devi controllare che il client si
sia effettivamente disconnesso basandoti sull'evento di disconnessione
(lo si è detto mille e più volte).
"Ovviamente", in nessuna parte del tuo codice ti sei preoccupato di
inserire una corretta gestione delle eccezioni...

  IdTCPClient1.Connect;
Con questa istruzione hai la ricorsione della IdTCPClient1Connected stessa, 
da cui l'errore di overflow dello stack!
Giorgio 

 (lo si è detto mille e più volte).
No in quanto l'evento di disconnessione e' chiamato dalla stessa 
IdTCPClient1.Disconnect, quindi terminata questa l'evento e' gia' stato 
gestito.
Giorgio 

gestito.
Non è così semplice.  Il tuo è un ragionamento che funziona nel caso
banale di un contesto mono-thread.  Nel caso in cui un TIdTCPClient
sia utilizzato da un thread secondario, infatti, non si può avere la
certezza che il thread sia disconnesso soltanto perché si è invocata
la Disconnect().  Si può esserne certi, invece, solo al momento
dell'invocazione della OnDisconnected.  E' meglio quindi adottare un
comportamento standard, valido peraltro in tutte le circostanze,
ovvero stabilire che un thread è disconnesso, quando e solo quando si
solleva l'evento di disconnessione.
In tutti i casi usare degli Sleep() non serve a nulla.

 inserire una corretta gestione delle eccezioni...
Prescindi un momento dall'OnDisconnect. Se togli gli sleep() ti restituisce
in breve address in use.
Mi sembrava di aver precisato che questa unit aveva il solo scopo di
riassumere la struttura fondamentale del programma (struttura peraltro
errata a detta di Giorgio causa ricorsione) e l'ho depurata di tutti i
controlli per renderla più snella. Lo scopo era solo quello di poter
esaminare agevolmente il codice "portante".
Avrai anche notato che non c'è traccia di Stream ma solo WriteLn e ReadLn.
Se commento tutto e l'errore continua a presentarsi il problema e nelle
quattro linee di codice rimanente.

 da cui l'errore di overflow dello stack!
Mi pareva un'operazione corretta (tanto che ad Andrea avevo escluso ci
fossero ricorsioni nel client, pensavo si riferisse al codice di calcolo).
A te è risultata immediatamente chiara la ragione dell'Overflow, a me un pò
meno...l'Overflow si verifica perchè il tutto accade all'interno di un solo
evento?
Hai idea di un'alternativa?
Ciao
Valter

in breve address in use.
Gli sleep() non servono, come già detto.  Sulla base di cosa, poi, hai
stabilito che 50 ms sono sufficienti a toglierti dai guai ?

evento?
L'overflow si verifica perché all'interno di un'OnConnected chiami una
Connect, che a sua volta invoca un OnConnected, che a sua volta invoca
la Connect, ecc... ecc... innescando un meccanismo infinito.

 stabilito che 50 ms sono sufficienti a toglierti dai guai ?
E' più giusto affermare che è una cattiva programmazione (ripeto: test), ma
non è vero che non servono. Prendi una form, mettici un TIdTCPServer ed un
TIdTCPClient, un TButton ed una TLabel ed incollaci la mia unit.
Lanciala e vedrai che ti darà Access Violation dopo 10.000 cicli circa.
Commenta gli sleep() e dopo circa 2.000 cicli ti restituirà Address Already
in Use
Se stai sotto a 50 ms si fermerà comunque.

non è vero che non servono. 
Valter, io non ho bisogno di fare dei test per affermare che qualcosa
non serve, quando lo affermo.  Se tu hai degli Access Violation dopo
10000 cicli, non risolvi la questione mettendoci delle pezze chiamate
"sleep", la rimandi solo a quando la mole di dati sarà più consistente
al punto da rompere questi "argini" che hai imposto.  

 al punto da rompere questi "argini" che hai imposto.
Gli sleep(), nelle unit originali, non ci sono.
Li ho usati nella unit postata solo per comprimere il codice. Lo scopo era
quello di verificare se anche riunendo server e client sulla stessa form e
tagliando tutto il codice di elaborazione dei dati l'errore si presentava
sempre dopo 10.000 cicli.
Presentandosi, in quelle quattro righe di codice doveva esserci la soluzione
che a quanto pare è la ricorsione nell'evento OnConnected.
Quando dico che servono, mi riferisco solo ed unicamente alla "demo" in
oggetto perchè senza metterli non si arriverebbe all'Access Violation ma
l'applicazione si fermerebbe prima per Address in use.
A prescinedere dall'uso già di per se improprio in questo test, capisco
perfettamente quello che vuoi dirmi e lo condivido pienamente.
Ma scusa, dalla tua risposta parrebbe che tu ritenga responsabili gli
sleep() e non la ricorsione dell'Access Violation.
Secondo te l'errore da cosa dipende?
Valter

Secondo te l'errore da cosa dipende?
In genere dipende da un cattivo uso dei puntatori...

 ha scritto nel messaggio 
news:jtaWg.18194$pp1....@tornado.fastwebnet.it...

L'alternativa e' semplice:
// Ti connetti
IdTCPClient1.Connect;
//Scambi dati ed esegui le operazioni
.........
// Ti Sconnetti
IdTCPClient1.Disconnect
In sostanza non e' necessario partire all'interno dell'evento OnConnect, 
cosi' come non devi neanche usare OnDisconnect in quanto le funzioni sono 
bloccanti,
quindi dopo il .Connect puoi tranquillamente partire con gli scambi dati. 
Dopo il disconnect in effetti un po' di respiro potrebbe essere necessario 
in quanto in
realta' la funzione nativa di chiusura del socket (interna a disconnect) non 
e' mai bloccante, ecco che uno sleep puo' non far male.
Se devi usare piu' volte il blocco precedente puoi farlo, come puoi anche 
metterlo in un ciclo for o while, vedi tu.
Ciao Giorgio 

 In sostanza non e' necessario partire all'interno dell'evento OnConnect,
[...]
Ora non ricordo il perchè ma ci ero finito quasi per coazione.
Pur non vedendolo successivamente come errore l'idea di partenza era
diversa, un pò come l'hai descritta tu.
Poi probabilmente mi sono perso "qualcosa" e non ho visto alternative alla
chiamata ricorsiva.
Riprendo da capo.
Grazie
Ciao
Valter

 In sostanza non e' necessario partire all'interno dell'evento OnConnect,
[...]
Ragazzi, è andata. Ho spostato il blocco dall'evento OnConnected e l'ho 
racchiuso in un While al momento senza fine.
Siamo a 15.000 transazioni...superata ampiamente la soglia dell'Overflow
era la chiamata ricorsiva
Grazie ad entrambi
Valter

e' mai bloccante, ecco che uno sleep puo' non far male
[/cit]
Forse mi sto impuntando su qualcosa che non si verificherà mai ma mi sono un 
pò intestardito e preferisco perdere del tempo e (se possibile) venirne a 
capo...non posso credere che sia impossibile gestire e quindi prevenire 
un'errore di Address Already in Use.
Risolto il problema della ricorsione l'applicazione sarebbe esente errori 
nella sua completezza...ma se commento il codice del client che processa i 
dati, le connessioni / disconnessioni sono troppo ravvicinate e sollevano 
l'errore di Address Already in Use.
Ho anche provato come precisato da Andrea a processare gli eventi passando 
per la OnDisconnect ma senza risultato.
Cosa ne pensate, è una battaglia persa?
Su Google ho trovato una miriade di messaggi simili al mio...un es:
...im using the indy components IdTCPServer and IdTCPClient but
ever so often i get an error message "Address already in use", this only
happens when the client is connecting and disconnecting frequently and
quickly in a loop...I have already set "ReuseSocket := rsTrue" and
"DisconnectSocket" instead of "Disconnect" at both the client and
server ends but still giving the error...
e di riflesso:
When a port is used for a connection and the connection is closed,
the port goes into a FD_WAIT state.  This FD_WAIT state is to provide
enough time for the connection be cleaned up with a packet exchange
(that's so a new connection will not wind up getting the old
connection's packets).  When a port is in this FD_WAIT state, you can
not connect with that port.
oppure:
When a socket is closed, the OS keeps the socket active in the background
for an extended period of time to make sure that all pending data has
received from the network.  Until the OS eventually releases the socket
completely, you cannot reuse the IP/Port that the socket was using.
Pare che le soluzioni siano diverse tra Indy9 e Indy10:
The only way to release the IP/Port faster is to use setsockopt() to enable
the SO_REUSEADDR attribute on the socket.  If you are using the latest Indy
10 snapshot, then TIdTCPClient has a ReuseSocket property available for
that.
If you are using an earlier build of Indy 10, then you can try using the
TIdIOHandlerSocket.OnSocketAllocated event to call setsockopt() manually.
In order to gain access to the event, you will have to assign a separate
TIdIOHandlerStack component to the TIdTCPClient instead of using the
implicit instance that TIdTCPClient creates for itself internally by
default.
Uno sleep() dopo la disconnessione parrebbe sufficiente ma se in un certo 
contesto fallisse?
Pensate sia meglio integrare il TIdIOHandlerSocket?
Oppure è possibile intercettare il messaggio di Address in Use, non 
visualizzarlo, e ritentare la connessione fino al suo aggancio?
Scusate la lunghezza del post ma ci tenevo ad evidenziare che il problema è 
diffuso
:-)
Valter

Termino questo thread riportando le mie conclusioni (per chi in futuro 
cercasse chiarimenti sull'argomento).
Quando una connessione viene chiusa questa rimane "agganciata" in background 
per un certo periodo di tempo (pare sia possibile cambiare il timeout in 
oggetto agendo sul registro o se i componenti Socket utilizzati prevedono 
questa opzione...forse è anche possibile intervenire con delle funzioni API, 
le sto cercando).
La porta utilizzata dalla connessione non è quindi riutilizzabile fino al 
rilascio del SO.
Se non si definiscono le porte da utilizzare, sequenzialmente vengono 
sfruttate, dopo la 1.024, fino alla 5.000 e non oltre.
Quindi se il loop di connessioni / disconnessioni è troppo veloce le porte 
si esauriscono perchè non fanno in tempo ad essere liberate e si solleva 
l'errore di Address Already in Use.
Purtroppo gestire al meglio gli eventi OnConnected e OnDisconnected del 
TIdTCPClient non è sufficiente in quanto in background le porte continuano 
ad essere occupate.
Partendo dal presupposto che difficilmente occorrerà utilizzare quasi 5.000 
porte in pochi minuti, attualmente non ho trovato di meglio che inserire uno 
sleep(50) dopo ogni disconnessione e questo permette alle porte di uscire 
sequenzialmente dal TimeOut e di essere riutilizzate. Considerando che 
questo lasso di tempo è sufficiente in locale a sostenere un loop senza fine 
di connessioni e disconnessioni senza codice aggiunto a maggior ragione 
dovrebbe bastare in una rete con tutti i suoi colli di bottiglia e 
soprattutto con del codice di elaborazione dati svolto tra una 
disconnessione e l'altra.
Quanto detto è solo il risultato delle mie ricerche, che potrebbero essere 
però incomplete ed in parte confutabili.
Ciao
Valter

 per la OnDisconnect ma senza risultato.
Hai palesemente frainteso quello che ho scritto.
La mia non era una soluzione per l'"Address already in use".
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 La mia non era una soluzione per l'"Address already in use".
E' possibile che sia accaduto, anche perchè nel Thread si sono affrontati 
diversi argomenti e discussioni su due errori diversi, ed è risultato un pò 
confusionario.
Comunque grazie a tutti i nostri colloqui ho risolto un problema e 
"compreso" la ragione dell'altro.
Valter