-------------[La funzione fork() che cos'è e a che serve??]---------- Ci tengo a dire, che le info. contenute in questa guida sono il risultato della fusione tra le mie conoscenze e le varie guide che si trovano in giro. Disclaimer: Non mi ritengo assolutamente responsabile di eventuali danni contro voi stessi e contro terzi.Io scrivo solo per informare!!!!! By:Evilcry E-Mail:evilcry@virgilio.it Salve a tutti, oggi parleremo della funzione fork(),di cosa sia e di come possa essere usata La funzione fork() serve per duplicare un processo o più specificatamente crea una gerarchia dei processi. #include pid_t fork(void); Questa funzione restituirà: [1] -1 in caso di errore. Se invece l'esecuzione va a buon fine ritornerà 0, questo però nel processo figlio ed un valore maggiore di zero "pid process identifier" nel processo babbo :). [2] Fork() è chiamato nel processo padre o parent, ed il valore restituito si trova sia nel in processo parent sia nel proc. figlio. [3] Quando a un processo figlio occorre il PID del padre ,utilizza la funzione getppid(),mentre getpid() restitusce l' ID del processo figlio. [4] Quando descrittori di file ,e quindi i socket :),sono aperti dal processo padre prima della chiamata a fork, sono condivisi dal figlio in i/o. [5] Inoltre quando un processo (padre o figlio) è chiuso tramite close(), la connessione o stream di dati è "gestito" dal processo rimasto. [6] Per ultimo, ma non meno importante anzi!!, la possibilità che replichi un processo server, per gestire in parallelo le connessioni che s'instaurano (la mia preferita :P).Ed è proprio di quest' ultima caratteristica che vi parlerò. **********************{[(Server TCP concorrente)]}************************* Spero che tutti sappiate come funziona a grandi linee un server,comunque per sicurezza vi ho fatto questa semplice tabella: 1) Attendi una connessione 2) Accetta la connessione 3) Duplica il processo... uno continuera' nella gestione della connessione (punto 4) e l'altro ripartira' dal punto 1 4) Analizza le richieste del client 5) Elabora le richieste 6) Invia i risultati al client 7) Se ci sono altre richieste vai al punto 4 8) Termina il processo La forma più semplice di server non fa che aspettare una richiesta da parte di un client su una determinata porta TCP, ma può accettare solo una connessione per volta.In quelli più evoluti come i "Web server",una volta che ricevono una richiesta da parte di un client,utilizzano la funzione fork() auto duplicandosi (si parla sempre di processi!)in modo da poter,accettare simultaneamente più connessioni sulla stessa porta.Se osserviamo cosa succede a livello dei socket vediamo che: + Il server chiama le accept passando come argomento il socket listening cioè in ascolto,e dopo chiama fork(). + Il processo padre chiude il connected socket,e ripete l' accept ed il socket listening. + Il descrittore del connected socket,dopo aver restituito l' accept resta sempre come processo figlio,disponibile all' I/O con la connessione. Il processo figlio si occupa della connessione instaurata,mentre quello padre attende nuove connessioni.Il processo figlio termina la connessione attraverso close(),inviando cioè una sequenza di FIN. Praticamente si ha questo: pid_t pid;int listenfd,connfd; listenfd=socket(AF_INET,SOCK_STREAM,0); bind (listenfd,(struct sockaddr*)&Local,sizeof(Local)); listen(listenfd,10); for(;;) { connfd=accept(listenfd,(struct sockaddr*)&Cli,&len); /* Ecco qui la chiamata a fork () */ pid=fork(); if (pid !=0) close(connfd); /* Processo padre */ else { /* Processo Figlio */ close(listenfd); Usa_una_nuova_connessione_indicata_da_newsockfd(connfd); close(connfd); exit(0); } } Adesso analizziamo cosa succede al livello delle porte,vedendo più in dettaglio il listato precedente.Se abbiamo un' applicazione server che gira su un host in cui si trovano svariate interfacce di rete,che attende una connessione sulla porta 6001 da parte di client che vi accedono attraverso queste interfacce. --------------------------------------------------------------------- listenfd = socket (AF_INET, SOCK_STREAM, 0); /* collega il socket ad un indirizzo IP locale e una porta TCP locale */ memset ( &Local, 0, sizeof(Local) ); Local.sin_family = AF_INET; Local.sin_addr.s_addr = htonl(INADDR_ANY); /* wildcard :=) */ Local.sin_port = htons(6001); bind ( listenfd, (struct sockaddr*) &Local, sizeof(Local)); /* accetta max 100 richieste simultanee di inizio conness., da adesso */ listen(listenfd, 100 ); /* accetta la prima conness. creando un nuovo socket per la conness. */ for( ; ; ){ connfd = accept(listenfd, (struct sockaddr*) &Cli, &len); pid = fork(); /* Eccolooooooo :=). */ if ( pid !=0 ) /* processo padre */ close ( connfd ); else { /* processo figlio */ close ( listenfd ); /* chiuso il listening socket */ il figlio usa il connected socket () close ( connfd ); exit(0); } } -------------------[I/O Multiplexing]--------------------------- Esistono vari modi di comunicare, ad esempio: duplex(due utenti comunicano contemporaneamente ) Half-duplex(uno per volta),quello che a noi c'interessa di più il multiplex in pratica più di due utenti interagiscono tra loro contemporaneamente. Applichiamo questo concetto allora ... Un' applicazione,potrebbe doversi connettere nello stesso momento a vari tipi di input e di output Su unix e quindi su linux :=) esistono vari tipi di I/O: I/O Bloccante. I/O Non bloccante. I/O Multiplexing. I/O Guidato da Signal. I/O asincrono (Poco usato). Analizzando ora un modello di I/O standard,noteremo che quando è effettuata una richiesta I/O tramite una semplice chiamata read() o write () (definite primitive), il controllo non è restituito al chiamante se non dopo che è finita l' operazione di input o di output, ovvero la funzione read () ha letto da un buffer di dati dal kernel o la write () ha scritto in un buffer del kernel.Questa che vi ho spiegato è la modalita standard cioè bloccante, ed ora analizzeremo i sui lati negativi :O| -Quando si chiama la funzione read (), su un descrittore di file o socket, e i dati non sono già accodati alla coda (perdonate l' italiano :=) ) del kernel,l'applicazione rimane bloccata fino a che i dati non sono disponibili, impossibilitando la lettura la lettura di dati pronti sugli altri descrittori o socket. Ciò vale anche per write (). -Un problema simile è causato dalla funzione accept () dei nostri amati socket :=), che restituisce il controllo solo quando una richiesta di inizio connessione è disponibile, ovvero quando è andata a buon fine cioè in coda. ------------------------[I/O multiplexing part two]----------- Che cosa ne pensate se potessimo "dire" al kernel di avvisarci quando in un sistema di I/O, avviene una condizione di disponibilità di I/O...o più precisamente quando: -I dati sono pronti alla lettura in una coda di kernel,e si accede tramite la funzione read () che restituirà subito il controllo al "richiedente" dei dati. -o una coda di output del kernel si svuota e si appresta ad accedere dei dati passati da write (). -o quando si verifica un errore in I/O e read () o write () restituiscono -1. -oppure quando un listening socket è pronto a fornire un connected socket,che dobbiamo intendere come una risposta ad un'accept() poichè ha ricevuto una richiesta di connessione da parte di un client. Per ora abbiamo finito :=) spero sia stato interessante,la prossima volta vi parlerò della funzione select () legata alla funzione fork (),con qualche simpatica implementazione :DDD +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Saluti a: [Evil],Quequero,AndreaGeddon,e4m,Ntoskrnl,_1/2Matto,Slash,Quake2,Pincopall,Bigalex.