Newsgroup: it.comp.lang.perl
Titolo: recv

Sono alle prese con un problema e non ne riesco a venirne a capo.
Lo script che allego si propone di catturare i messaggi inviati su protocollo HTTP con modalità POST ad un server per essere normalizzati e memorizzati su file che, a loro volta, sono archiviati ogni 15 minuti in formato zip.
Nell’eseguire i test ho utilizzato il simulatore benchmark di Apache, e  per simulare una particolare situazione di stress ho collegato il client al server, con cavo cross, riuscendo ad inviare e memorizzare/archiviare, in 12 ore, 47 milioni di record.
Nel successivo test, simulando questa volta le situazione di esercizio, con il client che invia le request al server mi sono accorto che, nel ciclo di “ascolto” quando con recv voglio valorizzare lo scalare $input il messaggio è incompleto. Nei vari tentativi di trovare una soluzione, provando ad inserire prima di recv l’istruzione sleep 1 i messaggi nello scalare sono risultati completi. Detta soluzione non è perseguibile in quanto ho la necessità di gestire la ricezione di request con picchi di 400 al secondo.
#!/usr/local/bin/perl -w
use strict;
use threads;
use threads::shared;
use Thread::Queue;
use IO::Socket::INET;
my $equal = "="; 
my $pipe = "|";
my $underscore = "_";
my $new_line = "\n";
my $txt = ".txt";
my $inputpathfile = "APS.ini";
#carico il file di ini in una record
open(APS_INI, "<$inputpathfile");
my @ini_record = <APS_INI>;
close APS_INI;
#parsing delle variabili di inizializzazione in un hash table
my %ini_hash;
#rimuovo il carattere \n
chomp(@ini_record);
foreach my $line (@ini_record) {
	#salto i commenti e le righe vuote
	next if (($line =~ m/^#/i) or ($line =~ /^$/));
	#salvo le variabili nell'hash
	my ($k,$v) = split(/=/,$line);
	$ini_hash{ "$k" } = $v;
}
my $pathfile = $ini_hash{'OUT_DIR'} . $ini_hash{'OUT_FILE'} . $txt;
#print "$pathfile\n";
my $fileno = -1;
share($fileno);
#coda per la gsetione dei messaggi provenienti da APS
my $append = Thread::Queue->new;
#processo che alla frequenza stabilita chiude il file dei messaggi APS temporaneo e lo salva compresso nella cartella di destinazione 
my $threads_rename_and_zip = threads->new(\&rename_and_zip) or die "Impossibile creare il thread di salvataggio $!\n";
#processo che estrae dalla coda dei messaggi APS il messaggio lo processa e lo salva in un file temporaneo
my $threads_aps_messages_handling = threads->new(\&aps_messages_handling) or die "Impossibile creare il thread di gestione messaggi APS $!\n";
#risposta da mandare ad APS
my $response = "HTTP/1.0 200 OK\r\n";
#socket su cui è in ascolto il server
my $socket = IO::Socket::INET->new (
						LocalHost => $ini_hash{'LocalHost'},
						LocalPort => $ini_hash{'LocalPort'},
						Proto => 'tcp',
						Listen => 500,
						Reuse => 1
						) or die "Impossibile aprire il socket $!\n";
#Messaggio di avvio programma
print "Parametri di configurazione:\n";
print "Indirizzo Server: $ini_hash{'LocalHost'}\n"."Porta: $ini_hash{'LocalPort'}\n";
print "Directory di destinazione file compressi: $ini_hash{'OUT_DIR'}\n\n";
print "Configurazione OK - Server in ascolto\n";
 
#processo principale che riceve da APS il messaggio lo accoda nella coda e risponde ad APS
my $input;
while (my $con = $socket->accept()) {
	$con->recv($input,1024);
	$append->enqueue($input);
	print $con $response;
	$con->close();
}
close $socket;
#funzione di gestione del messaggio da APS (eseguita da un thread)
sub aps_messages_handling {
	my $mess = $_[0];
	while (my $mess = $append->dequeue()) {
		my @campo_valore = split (/&/, $mess);
		my @valori;
		foreach my $campo (@campo_valore) {
			my ($n,$v) = split (/=/,$campo);
			$v =~ tr/+/ /;
			$v =~ s/%([\dA-Fa-f][\dA-Fa-f])/pack ("C", hex ($1))/eg;
			push (@valori, $v);
		}
		#Salva su file
		my $record = join('|', @valori);
		lock($fileno);
		open (FW, ">>&=$fileno");
		print FW $record.$pipe.$new_line;
		close (FW);
	}
}
#funzione di compressione e salvataggio (eseguita da un thread)
sub rename_and_zip {
	open (FH, ">$pathfile") or die "Non posso aprire il file";
	$fileno = fileno(FH);
	my $sleep_time = $ini_hash{'GRANULARITY'} - (time % $ini_hash{'GRANULARITY'});
	while (1) {
	    sleep ($sleep_time);
		lock ($fileno);
		close(FH);
		my ($Second, $Minute, $Hour, $Day, $Month, $Year, $WeekDay, $DayOfYear, $IsDST) = localtime(time);
		my $timestamp = sprintf ("%04d%02d%02d%02d%02d%02d",(1900 + $Year), ($Month + 1), $Day , $Hour , $Minute, $Second);
		my $newfilename = $ini_hash{'OUT_DIR'} . $ini_hash{'OUT_FILE'} . $underscore . $timestamp . $txt;
		rename($pathfile, $newfilename);
		open (FH, ">$pathfile") or die "Non posso aprire il file";
		$fileno = fileno(FH);
		system "gzip -c $newfilename >  $newfilename.gz";
		unlink $newfilename;
		$sleep_time = $ini_hash{'GRANULARITY'} - (time % $ini_hash{'GRANULARITY'});
	}
}   
Dopo ricerche in lungo e in largo non sono riuscito a trovare nulla che mi permetta di gestire la situazione appena descritta.
Ringrazindo fin da ora per l'attenzione e la disponibilità.
Beppe
-- 
Questo articolo e` stato inviato dal sito web 
http://www.nonsolonews.it

 $input il messaggio è incompleto.
la recv, come dice perldoc -f rect e' implementata in termini di
recvfrom(2)
che appunto, come read(2) in perl disponibile come sysread, si sbloccano
quando ci sono dati disponibili e ritornano il numero di bytes ricevuti.
Con nel mezzo TCP/IP, non e' detto che dati trasmessi insieme, non arrivino
frammentati. Nel tuo codice esegui una unica recv di 1024 bytes, ma non
e' detto che li riceverai tutti e anche se mandi messaggi inferiori non
e' detto che non siano necessarie 2 recv. Dovresti iterare recv fino alla
chiusura del socket remoto, ma piuttosto usa sysread, piu' appropriata e
semplice per socket.
Non ho potuto eseguire il codice ne esaminarlo attentamente, ma ti
suggerisco delle prove: se vuoi che il Perl faccia per te questa iterazione
puoi usare la readline del perl in modo slurp mettendo $/ = undef();
e usare al posto di $con->recv($input,1024); questo: $input=<$con>;
Credo possa funzionare! Prova tu. cosi' la lib perlio dovrebbe insistere
a leggere dal socket fino alla sua chiusura lato remoto.
Ti confido che e' una tecnica che non mi piace in generale. Non uso mai
readline in codice per networking. preferisco un ciclo, ma nel tuo caso,
visto che non mi sembra debba gestire piu' connessioni in modo non
bloccante, potrebbe essere sufficiente.
Ciao
Roberto.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 semplice per socket.
rettifico: sysread funziona con socket ottenuto con accept, ma non con
quella
roba restituita da IO::Socket::INET.
Anche la citazione di perldoc -f recv e' probabilmente inappropriata visto
che non e' detto che il modulo sia stato fatto usando la stessa
teconologia.
Il principio pero' rimane valido: devi levvere fino a EOF (praticamente
quando la read(2) o rect(2) restituisce 0.
La prova consigliata di lasciar fare a perlio mettendo a undef
il record separator rimane valida.
Ciao
Roberto


Personalmente non ho troppo tempo da investire nella lettura del tuo
codice. Sicuro che non riesci a restringere l'esempio a qualcosa di
meno corposo? Se anche gli altri sono nella mia condizione, temo ti
sia di ostacolo per ricevere una risposta esaustiva.
Restringendo il problema a quanto osservato da RobertoC, comunque,
direi che è arbitrario chiudere il file dopo una sola recv: dovresti
leggere finché ci sono dati, o finché non sei in grado di stabilire
che $input contiene tutto quel che vuoi. Uno sporco trucco potrebbe
consistere nel mandare *subito* la risposta, e poi effettuare le
letture in un ciclo fino alla condizione end-of-file, nella speranza
che la controparte chiuda il socket una volta ricevuta la risposta.
L'applicabilità è tutta da verificare.
Ciao,
   Flavio.

 semplice per socket.
No. Trattandosi di una richiesta HTTP, il client attenderà la risposta del
server. Perciò il server non può attendere la chiusura del socket.
Per implementare un server HTTP, bisogna... implementare il protocollo HTTP.
Se l'OP non vuole usare uno dei moduli appositi disponibili su CPAN, dovrà
per lo meno intercettare l'header "Content-Length" e tramite questo
determinare quanti byte dovrà leggere.
Max

 Max
Giustissino!!


Beppe ha scritto:

Scusate se lo faccio solo ora, grazie per le indicazioni date.
Controllando la lunghezza del messaggio sono riuscito a gestire correttamente la ricezione.
Per poter crescere ulteriormete, quali sono i moduli disponibili su CPAN che sottintende Roberto C.
Di nuovo grazie, alla prossima.
