Il modulo microsock
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. 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 un indirizzo (nome host e porta UDP) e una interfaccia di rete.
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 con sk_bindtodevice (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. 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?