Size: 8246
Comment:
|
Size: 9989
Comment:
|
Deletions are marked like this. | Additions are marked like this. |
Line 5: | Line 5: |
La mappa di asyncore associa il fd (file descriptor, un numero intero) all'oggetto (che può essere un file, o un socket, ...). | La mappa di asyncore associa il fd (file descriptor, un numero intero) ad una istanza di {{{asyncore.dispatcher}}} che incapsula il vero oggetto sul quale vanno fatte le operazioni di I/O (che può essere un file, o un socket, ...). |
Line 70: | Line 70: |
Se vogliamo implementare un server con il protocollo UDP, dobbiamo creare un socket di tipo {{{SOCK_DGRAM}}}, associargli indirizzo e porta. <<BR>> '''''TODO''': Perché avviamo un UDPServer per ogni scheda se poi l'argomento {{{dev}}} non viene mai usato nella fase di attesa messaggi?'' <<BR>> Dopo di questo non ci sono altre operazioni da fare, quindi nessuna bloccante, prima di passare alla trasmissione/ricezione dati. |
|
Line 71: | Line 76: |
Se vogliamo implementare un client con il protocollo UDP, dobbiamo creare un socket di tipo {{{SOCK_DGRAM}}}, associare il socket ad una interfaccia di rete (supportato solo su linux) e poi richiamare il metodo {{{connect}}} dove al posto dell'indirizzo dell'host specifichiamo la stringa parola chiave {{{<broadcast>}}}. <<BR>> In questo modo otteniamo un socket pronto a comunicare in broadcast su tutta la LAN acceduta tramite quella interfaccia di rete. Per questo il programma Netsukuku si crea un socket UDP per ogni scheda di rete da gestire e li connette ed usa tutti per inviare messaggi ai suoi vicini (vedi la classe {{{BcastClient}}} nel [[../ModuloRPC|modulo RPC]]). <<BR>> Abbiamo già visto come il metodo {{{connect}}} di {{{microsock.dispatcher}}} finisce con il richiamare per prima cosa il metodo {{{socket.connect_ex}}} fornito dalla libreria di Python Stackless. Questo, quando si usa come indirizzo la parola chiave {{{<broadcast>}}} connette il socket e fa subito ritorno. <<BR>> '''''TODO''': Questa è una mia deduzione. La documentazione di Python non è così esplicita. E' corretta?'' |
|
Line 74: | Line 86: |
UDP | <<BR>> UDP il client invia con sendto, il server legge con recvfrom, non ci sono risposte. |
Il modulo microsock
Quando viene creato un socket usando il wrapper ntk.wrap.sock.Sock, viene registrato nella mappa di asyncore (un modulo standard di python stackless) e viene avviato un microthread nel modulo microsock.
La mappa di asyncore associa il fd (file descriptor, un numero intero) ad una istanza di asyncore.dispatcher che incapsula il vero oggetto sul quale vanno fatte le operazioni di I/O (che può essere un file, o un socket, ...).
Il microthread avviato esegue la funzione ManageSockets. Questa richiama asyncore.poll(0.05) ciclicamente (intervallando con micro_block per passare lo scheduler agli altri) fin quando esiste un socket (nel caso di netsukuku per sempre) nella mappa di asyncore.
Nota: La funzione asyncore.poll è bloccante. Il parametro passato è il numero di secondi dopo i quali cui si vuole cha la chiamata faccia ritorno.
Al suo interno la funzione richiama time.sleep oppure select.select, a seconda se ci sono o meno socket da controllare. In entrambi i casi queste funzioni usano chiamate di sistema che non impegnano la CPU, ma nemmeno permettono agli altri microthread di venire schedulati.
Per questo dobbiamo richiamarla con piccoli timeout intervallando con chiamate a micro_block. Vedi anche la trattazione dell'argomento nella pagina del modulo xtime.
Il metodo asyncore.poll, tramite il metodo select.select, scopre quali dei socket registrati nella sua mappa ha dei dati da inviare o da ricevere.
Vediamo quali passi segue la creazione e connessione di un socket, prima di analizzare quello che avviene quando si trasmette o riceve qualcosa.
Creazione di un socket
Quando si crea un socket all'interno del programma (con socket.socket(...)) avvengono queste operazioni:
viene effettivamente creato un vero socket (con stdsocket.socket(...)).
viene creato anche un microsock.stacklesssocket. Questo nel suo costruttore fa queste operazioni:
- memorizza il vero socket.
si crea e memorizza una istanza dell'oggetto microsock.dispatcher, che è una derivata di asyncore.dispatcher.
microsock.dispatcher nel suo costruttore fa queste operazioni:
- memorizza il vero socket.
la classe ha i membri connectChannel, acceptChannel e recvChannel.
istanzia per recvChannel un Channel con micro_send = True.
prepara una stringa buffer e una lista per i send (sendBuffer e sendToBuffers)
prepara una stringa buffer e una lista per i recv (readBufferString e readBufferList)
prepara una costante maxreceivebuf = 65536
memorizza il vero socket nella mappa di asyncore (questo lo fa asyncore.dispatcher.__init__ => asyncore.dispatcher.set_socket => asyncore.dispatcher.add_channel)
la sua rimozione dalla mappa avverrà con microsock.stacklesssocket.__del__ => microsock.dispatcher.close => asyncore.dispatcher.close => asyncore.dispatcher.del_channel
viene restituito l'oggetto microsock.stacklesssocket.
In seguito tutte le chiamate __getattr__ al finto socket (cioè all'oggetto stacklesssocket ritornato) vengono inoltrate al dispatcher (esempi di queste chiamate sono s.listen, s.bind, ...) mentre le chiamate __setattr__ vengono memorizzate localmente. ???
Connessione di una coppia di socket
con protocollo TCP
Se vogliamo implementare un server con il protocollo TCP, dobbiamo creare un socket di tipo SOCK_STREAM, associargli indirizzo e porta, richiamare il metodo listen e poi accept.
Quando si richiama il metodo listen di un microsock.dispatcher (ereditato dal asyncore.dispatcher) il dispatcher si memorizza lo stato accepting = True e poi esegue il vero metodo listen sul socket. Il metodo listen non è bloccante.
Quando si richiama il metodo accept di un microsock.dispatcher (overridato rispetto al asyncore.dispatcher) il dispatcher si istanzia un oggetto Channel con micro_send = True e lo memorizza sul membro acceptChannel. Su questo oggetto richiama il metodo recv.
Questo metodo a sua volta richiama il metodo receive di uno stackless.channel. Questo metodo blocca la tasklet, cioè il microthread attivo, e schedula gli altri microthread. Quello bloccato non sarà più ri-schedulato fino a che non ci saranno messaggi su quel canale.
Quando il socket reale riceve una richiesta di connessione, anche se non è stato chiamato in realtà il suo metodo accept, la funzione asyncore_poll (periodicamente richiamata in un microthread apposito, come detto sopra) se ne accorge come se il socket fosse in attesa di lettura. Viene quindi richiamato il metodo handle_accept, da overridare.
Il metodo handle_accept (overridato rispetto al asyncore.dispatcher) verifica che sul channel contenuto sul membro acceptChannel ci sia qualcuno effettivamente in ascolto, guardando l'attributo balance. Poi richiama il metodo asyncore.dispatcher.accept della classe base (non più quello overridato detto prima); questo richiama accept del socket, ma stavolta siamo sicuri che non si bloccherà. Restituirà un socket connesso e l'indirizzo del socket remoto.
Il metodo handle_accept poi incapsula il socket ricevuto in un oggetto stacklesssocket e lo invia al channel contenuto sul membro acceptChannel. Siccome il Channel è con micro_send = True, questo invio viene effettuato in un nuovo microthread. TODO: Questo è davvero necessario? Ci siamo prima accertati che il balance fosse negativo...!
Il microthread prima bloccato viene ora ri-schedulato e il metodo microsock.dispatcher.accept restituisce un stacklesssocket connesso.
Se vogliamo implementare un client con il protocollo TCP, dobbiamo creare un socket di tipo SOCK_STREAM e poi richiamare il metodo connect.
Quando si richiama il metodo connect di un microsock.dispatcher questo per prima cosa richiama il metodo base, cioè il metodo asyncore.dispatcher.connect.
Il metodo asyncore.dispatcher.connect utilizza il metodo socket.connect_ex fornito dalla libreria di Python Stackless. Questo avvia un tentativo di connessione ma la chiamata non si blocca. Invece restituisce un numero (errno) con il quale indica se il tentativo è andato a buon fine o se è in corso o se c'è stato un altro errore.
Se la connessione si completa, il metodo asyncore.dispatcher.connect richiama handle_connect_event, questo valorizza a True il suo membro connected e poi richiama il metodo handle_connect, da overridare. Il metodo microsock.dispatcher.connect in questo caso non ha bisogno di fare altro.
Se, invece, la connessione non si completa, il metodo asyncore.dispatcher.connect ritorna. Il metodo microsock.dispatcher.connect se ne accorge dal fatto che il membro connected è ancora a False. Quindi si prepara nel suo membro connectChannel un Channel con prefer_sender = True (vedi la classe Channel) e si mette in ascolto su quel canale. In questo modo il microthread si blocca in attesa della connessione, senza bloccare gli altri microthread.
Quando il sistema completa la connessione sul socket reale, la funzione asyncore_poll (periodicamente richiamata in un microthread apposito, come detto sopra) se ne accorge come se il socket fosse in attesa di lettura. Viene quindi richiamato il metodo handle_connect.
Il metodo handle_connect (overridato rispetto al asyncore.dispatcher) invia un messaggio al Channel memorizzato nel membro connectChannel così da ri-schedulare il microthread prima bloccato.
con protocollo UDP
Se vogliamo implementare un server con il protocollo UDP, dobbiamo creare un socket di tipo SOCK_DGRAM, associargli indirizzo e porta.
TODO: Perché avviamo un UDPServer per ogni scheda se poi l'argomento dev non viene mai usato nella fase di attesa messaggi?
Dopo di questo non ci sono altre operazioni da fare, quindi nessuna bloccante, prima di passare alla trasmissione/ricezione dati.
Se vogliamo implementare un client con il protocollo UDP, dobbiamo creare un socket di tipo SOCK_DGRAM, associare il socket ad una interfaccia di rete (supportato solo su linux) e poi richiamare il metodo connect dove al posto dell'indirizzo dell'host specifichiamo la stringa parola chiave <broadcast>.
In questo modo otteniamo un socket pronto a comunicare in broadcast su tutta la LAN acceduta tramite quella interfaccia di rete. Per questo il programma Netsukuku si crea un socket UDP per ogni scheda di rete da gestire e li connette ed usa tutti per inviare messaggi ai suoi vicini (vedi la classe BcastClient nel modulo RPC).
Abbiamo già visto come il metodo connect di microsock.dispatcher finisce con il richiamare per prima cosa il metodo socket.connect_ex fornito dalla libreria di Python Stackless. Questo, quando si usa come indirizzo la parola chiave <broadcast> connette il socket e fa subito ritorno.
TODO: Questa è una mia deduzione. La documentazione di Python non è così esplicita. E' corretta?
Trasmissione dati tra due socket
TCP il client invia con sendall, il server legge con varie recv, il server risponde con send, il client legge con varie recv.
UDP il client invia con sendto, il server legge con recvfrom, non ci sono risposte.