Newsgroup: it.comp.os.linux.development
Titolo: Server concorrente e ...disperazione!!

Ciao a tutti...
Sono praticamente disperato perchè non riesco a vedere una via d'uscita 
al mio problema... :(
Devo realizzare in *nix un server concorrente che gestisce connessioni 
da moduli "agent" e "client".
Gli *agent* sono in esecuzione, in una (sotto)rete, uno per host e 
recuperano le info sul sistema su cui girano mediante sysconf.
Inviano tali informazioni periodicamente al server (ogni 3 secondi) che 
le memorizza (in un qualche modo).
Se il server non riceve info da un agent dopo 6 secondi lo marca come 
disconnesso.
I *client* si connettono al server ed ottengono una lista di host 
connessi e disconnessi e posso richiedere le info scegliendo tali host 
tra quelli proposti.
Il tutto con socket TCP.
Ora...
L'agent l'ho fatto (credo) abb bene e non mi da problemi per il suo 
"piccolo" compito a cui deve assolvere...
Il client l'ho fatto minimale solo per prova...
Ma quello che mi sta facendo impazzire è il server :(
Mi spiego...
Ho fatto due socket: una per le connessioni degli agent, e l'altra per i 
client.
Tali socket sono "osservate" da una select() che quando necessario mi 
accetta la connessione che è pronta;
subito dopo la accept con una fork() creo un figlio che gestisce lo 
scambio dati...
Il problema che mi affligge è che siccome ho pensato di utilizzare un 
array di strutture per memorizzare le info sugli host, e dato che ogni 
figlio ha una copia della memoria del padre... come diavolo faccio a 
"propagare" le scritture che faccio sulla struttura dal(dai) figlio(i) 
al padre ?
Cioè, ho una struttura di questo tipo:
struct host_info {
	unsigned empty:1;
	unsigned connected:1;
	char name[30],
  	     date[26],
        	     info[300];
};
struct host_info host_array[MAXCONN];
(inizializzata da una apposita procedura)
Diciamo che arriva un agent che si vuole connettere, la select "chiama" 
ed io accetto e creo un figlio...
il padre prima di creare il figlio fa così:
do {
	i=i+1;
} while (host_array[i].empty==0);
 
/*marca occupato l'elemento della struttura*/
host_array[i].empty=0;
e poi forka;
Il figlio calcola l'ora corrente, recupera il nome (o ip) dell'host 
relativo all'agent che si è connesso, e memorizza le info del sistema 
relativo, ovvero:
else if (pid_agent==0) {
	close(sock_agent);
 
	strcpy(host_array[i].name, inet_ntoa(indirizzo_agent.sin_addr));
	alarm(6);
 
	while (1) {
 
		n=read(fd2, input, MAXLINE);
 
		if (n>0) {
			alarm(6);
 
			/*marca l'host come connesso*/
			host_array[i].connected=1;
 
			/*calcola l'ora attuale*/
			time(&ora);
			ctime_r(&ora, buffer);
 
			strcpy(host_array[i].date, buffer);
 
			/*memorizza le info*/			
			strcpy(host_array[i].info, input);
		}
		else if (n==0) {
			host_array[i].connected=0;
		}
		else {
			perror("\n read error on socket fd2");
			exit(EXIT_FAILURE);
		}
	}
exit(EXIT_SUCCESS); /*mai raggiunto*/
Visto che tutto quello che scrivo su host_array[i].xxx resta locale al 
figlio, come faccio per "trasmettere" queste informazioni al padre ??
Ho provato con:
-pipe: non funziona;
-memoria condivisa: un casino esagerato;
-socket locali: non funziona;
-socketpair(): non funziona.
Sono disperato... non vedo vie d'uscita... :(
Tranne *forse* quella di ripensare il server in chiave multi-thread...
...c'avrei anche provato a farne uno elementare... ma non capisco e non 
riesco a passare più argomenti alla start function (anche creando una 
struttura apposita è un casino, perchè gli dovrei passare un sacco di 
roba...)
Lanciatemi un salvagente, una scialuppa, un galleggiante...aiutoooo!!!!
P.S.: per il progetto "è vietato l'uso di code di messaggi etc."

 roba...)
perche' dovresti passarli per forza alla start function?
e' un nuovo thread e i parametri sono in veriabili da esso accessibili,
l'unica cosa che ti serve e' stare attento al locking.
ilSimo
-- 
now playing: 
Hooked to the circuits of a lifeless, cold machine
Lost in a world of artificial means
Ripped out of my body
I'll stand the rest of time
Am I perpetual ? - am I devine ?
                                Gammaray - Watcher In The Sky

 P.S.: per il progetto "è vietato l'uso di code di messaggi etc."
Per carita!!!
Ciao
Roberto.
-- 
questo articolo e` stato inviato via web dal servizio gratuito 
http://www.newsland.it/news
 segnala gli abusi ad 
ab...@newsland.it

 ma soprattutto: che vuol dire non funzionano?
Mi son messo con calma a ragionare un pò, riprovando con una pipe...
Ho messo il descrittore del lato lettura in un read_set per la select:
FD_SET(ls[0], &read_set);
e poi controllo con
if ( FD_ISSET(ls[0], &read_set) ) {
/.../
}
Funzionicchia... nel senso che funziona solo se c'è un singolo figlio 
(quindi solo se c'è un unico *agent* che si connette al *server*).
Diciamo che è abb ovvio, dato che la pipe è stata precedentemente 
"assegnata" ad un altro figlio...
Ovvero, faccio così:
nel padre:
	/.../
listen(sock_agent, MAXCONN);
listen(sock_client, MAXCONN);
 
if (pipe(ls) < 0) {
	perror("\n pipe error! ");
	exit(EXIT_FAILURE);
}
 
while (1) {
	/*per la select*/
	fd_set read_set;
	FD_ZERO(&read_set);
	FD_SET(sock_agent, &read_set);
	FD_SET(sock_client, &read_set);
	FD_SET(ls[0], &read_set);
	/.../
--
nel figlio:
	/.../
	close(ls[0]);
	write(ls[1], input , strlen(input));
	/.../
--
e dinuovo nel padre:
if ( FD_ISSET(ls[0], &read_set) ) {
	close(ls[1]);
	memset(tmp, 0, sizeof(char));
	num=read(ls[0], tmp, DIM);
	write(STDOUT_FILENO, tmp, num);
}
che, ripeto, funziona per un singolo figlio...
Come fare per rendere il tutto valido per più processi figli ?
Named pipes?
 >> Tranne *forse* quella di ripensare il server in chiave multi-thread...
 >> ...c'avrei anche provato a farne uno elementare... ma non capisco e non
 >> riesco a passare più argomenti alla start function (anche creando una
 >> struttura apposita è un casino, perchè gli dovrei passare un sacco di
 >> roba...)
 >
 > perche' dovresti passarli per forza alla start function?
 > e' un nuovo thread e i parametri sono in veriabili da esso accessibili,
 > l'unica cosa che ti serve e' stare attento al locking.
...scusa, ma se ad esempio una cosa del genere:
struct sockaddr_in broadcast;
/.../
broadcast.sin_family=AF_INET;
broadcast.sin_port=htons(4999);
broadcast.sin_addr.s_addr=htonl(INADDR_BROADCAST);
 
fd_udp=socket(AF_INET, SOCK_DGRAM, 0);
if (fd_udp<0) perror("\n UDP socket error "), exit(EXIT_FAILURE);
 
setsockopt(fd_udp, SOL_SOCKET, SO_BROADCAST, &flag, sizeof(flag) );
 
strcpy(buf, "ALIVE_RM");
n=sendto(fd_udp, buf, strlen(buf), 0, (struct sockaddr *) &broadcast, 
sizeof(broadcast));
if (n<0) perror("\n errore sendto "), exit(EXIT_FAILURE);
/.../
...la volessi far fare ad un thread che mi manda lui il messaggio in 
broadcast ...come farei ? cosi? :
/.../
if ((err=pthread_create(&tid, NULL, start, NULL)) != 0) {
	printf("errore: %s\n", strerror(err));
	exit(1);
}
/.../
void *start (void *arg) {
 
n=sendto(fd_udp, buf, strlen(buf), 0, (struct sockaddr *) &broadcast, 
sizeof(broadcast));
if (n<0) perror("\n errore sendto "), exit(EXIT_FAILURE);
 
}
... non funziona mica: al thread gli devo dire chi è *fd_udp*, *buf* e 
*broadcast*.
C'è qualcosa che mi sfugge?

 lo vedi?
Devo ammettere che l'idea mi aveva sfiorato...
E devo anche ammettere che la tua proposta è affascinante nella sua 
"semplicità" :)
Però, come ho scritto anche in risposta a ilSimo, ho provato meglio con 
una pipe... e diciamo che funzionicchia (nel senso che funziona solo per 
un processo figlio, non di più).
Quindi ora il problema è di come usare pipe con più processi 
concorrenti... ho pensato alle Named Pipes, che mi sembra di aver capito 
utilizzano un file "speciale" per far transitare le informazioni...
Devo approfondire la questione... sia sulla singola pipe che sulle named...
Cioè, dico io: se le pipe sono fatte per far comunicare due processi... 
come le uso per far comunicare X figli col processo padre ?

 come le uso per far comunicare X figli col processo padre ?
apri X pipe? :)
-- 
SF
Games are very educational. Scrabble teaches us vocabulary, Monopoly teaches 
us cash-flow management, and Dungeons & Dragons teaches us to loot dead bodies.

 apri X pipe? :)
ehm...magari... ma come ? :)

Alexandros ha scritto:


Purtroppo non ho con me il mio esempio in C, ma ti riassumo il metodo,
poi forse ti passa la voglia :)
Gia' usi select(2) e immagino che dei sets che passi ne tieni una copia,
perche select(2) al ritorno li modifica. Immagino una cosa simile:
fd_sed readfd, readfdtemp.
io di solito faccio cosi': faccio la manutenzione con FD_SET FD_CLR
del readfd, poi in readfdtemp = readfd e a select passo readfdtemp che
al ritorno da select sara' mutato. Allo scoprire la chiusura di fd o sd
aggiorno sempre e solo readfd e solo all' inizio del ciclo di select
lo copio in readfstemp (che sara' l' argomento si select).
Ora, visto che ho un set dove conservo tutti gli fd / sd aperti
fino a quel momento, non ho bisogno di variabili intere per conservare
gli fd o sd che siano: iterando FD_ISSET fino a MAXfd la stessa
variabile contatore (int i) quando FD_ISSET e' vera puo' essere usata
per Input Output. Per tenere compatti, in modo ordinale gli fd/sd
uso dup2 sul (MAXfd - 1) con quello che ogni volta viene chiuso e
decremento MAXfd. dup2 esegue la close del fd target e duplica l' fd.
a questo punto quello piu' grande ordinalmente puo essere chiuso.
Anziche' fare un array di pipe fino a MAXCONN, ad ogni chiamata a pipe
aggiorni MAXfd e il set readfd.
Spero di essere stato chiaro. Comunque ribadisco che non sono necessarie
altre variabili, a meno di non dovr distinguere porte in listen per cui
anziche IO si deve fare accept(2) .... il tal caso un pseudo if
if (fdsettato in setfdlisten) accept ...
else InputOutput


 ehm...magari... ma come ? :)
chiami pipe(2) diverse volte, una per ciascun figlio.


 C'è qualcosa che mi sfugge?
si, il come funziona lo scope delle variabili :D
puoi rendere le variabili globali (almeno all'interno di quel file), ed a
quel punto aggiri il problema della visibilita'.
oppure non ti resta altra soluzione che la struct con dentro quello che ti
serve (che poi ti torna utile se devi usare piu' socket)
ilSimo
-- 
now playing: 
It's a long way to the top
If you wanna rock'n'roll
                                AC/DC - It's A Long Way To The Top (If You
Wanna Rock'n'Roll)

 serve (che poi ti torna utile se devi usare piu' socket)
Alle variabili globali c'arrivo... :) il fatto è che siccome dovrei 
avere tre "tipi" di thread (uno per il broadcast udp, uno per le 
connessioni degli agent e uno per quelle dei client) praticamente dovrei 
rendere "globale" un bel pò di roba... e non è che è molto raccomandato 
ed elegante avere l'ira di Dio prima di "int main (... " :)

 poi forse ti passa la voglia :)
Mi piaci (we, non fraintendiamo :) ) con i tuoi interventi...
Ho letto quello che proponi, e ad essere sincero ad una prima lettura 
non c'ho capito molto... però mi intriga, devo approfondire...
Per adesso, credo di aver risolto con un array di pipe, giusto per 
contraddirti (cit. "Anziche' fare un array di pipe fino a MAXCONN...") :)
Scusate se non c'avevo pensato prima, ma in tutta sincerità non 
immaginavo si potessero trattare oggetti come gli array di pipe (oggetti 
ovviamente non in senso OOP :) ).
Anche se il tutto ora sembra proprio che funzioni bene, resta il fatto 
che non mi pare un granché elegante come soluzione...
Tanté, ho fatto così:
/.../
typedef int pipe_t[2];
pipe_t *pfds;
	/.../
pfds=(pipe_t *) malloc (MAXCONN*sizeof(pipe_t));
if (pfds==NULL) perror("\n malloc error "), exit(EXIT_FAILURE);
for (j=0; j<MAXCONN; j++) {
	if (pipe(pfds[j]) < 0) perror("\n pipe error! "), exit(EXIT_FAILURE);
}
while (1) {
	/.../
FD_ZERO(&read_set);
FD_SET(sock_agent, &read_set);
FD_SET(sock_client, &read_set);
 
for (j=0; j<MAXCONN; j++) {
	FD_SET(pfds[j][0], &read_set);
}
	/.../
poi nel figlio:
	/.../
close(pfds[i][0]);
write(pfds[i][1], input , strlen(input));
(la i viene incrementata in un altro punto di codice del padre, prima di 
fork)
	/../
e poi dinuovo nel padre:
	/.../
for (j=0; j<=i; j++) {
	if ( FD_ISSET(pfds[j][0], &read_set) ) {
		close(pfds[j][1]);
		num=read(pfds[j][0], tmp, DIM);
		write(STDOUT_FILENO, tmp, num);
	}
}
--
quello che si sembra meno efficiente è proprio quest' ultimo for...
cmq, funziona tutto :)
P.S.: per tutti: ...e non finisce quì :)  c'è ancora molto da fare per 
finire sto progetto... e vi terrò sicuramente "aggiornati"... :)

 finire sto progetto... e vi terrò sicuramente "aggiornati"... :)
Spero di averti messo sulla buona strada.
Alla prossima.


 ed elegante avere l'ira di Dio prima di "int main (... " :)
beh, e' un po' come il goto, in teoria e' un male, ma in pratica se esiste
e' perche' il alcuni casi e' utile.
il fatto e' che non puoi avere la botte piena e la moglie ubriaca: se devi
passare N parametri ad una funzione che accetta solo un puntatore, di
soluzioni non ne vedo, oltre a passare una struct con dentro quello che ti
serve. se poi pero' il problema e' che "e' un casino" non e' che ci si puo'
fare piu' di tanto
ilSimo
-- 
now playing: 
Do you know if Merlin did exist
Or Frodo wore the ring
Did Corum kill the gods
Or where's the wonderland
Which young Alice had seen
Or was it just a dream
I knew the answers
Now they're lost for me
                                Blind Guardian - Immaginations From The
Other Side