Differences between revisions 6 and 7
Revision 6 as of 2009-04-25 21:05:40
Size: 7138
Editor: lukisi
Comment:
Revision 7 as of 2009-04-25 21:06:55
Size: 7172
Editor: lukisi
Comment:
Deletions are marked like this. Additions are marked like this.
Line 56: Line 56:
Dal punto di vista del client, si crea un socket di tipo {{{SOCK_STREAM}}} e poi si richiama il metodo connect. Se vogliamo implementare un client con il protocollo TCP, dobbiamo creare un socket di tipo {{{SOCK_STREAM}}} e poi richiamare il metodo connect.

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) all'oggetto (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:

  1. viene effettivamente creato un vero socket (con stdsocket.socket(...)).

  2. viene creato anche un microsock.stacklesssocket. Questo nel suo costruttore fa queste operazioni:

    1. memorizza il vero socket.
    2. si crea e memorizza una istanza dell'oggetto microsock.dispatcher, che è una derivata di asyncore.dispatcher.

    3. microsock.dispatcher nel suo costruttore fa queste operazioni:

      1. memorizza il vero socket.
      2. la classe ha i membri connectChannel, acceptChannel e recvChannel.

      3. istanzia per recvChannel un Channel con micro_send = True.

      4. prepara una stringa buffer e una lista per i send (sendBuffer e sendToBuffers)

      5. prepara una stringa buffer e una lista per i recv (readBufferString e readBufferList)

      6. prepara una costante maxreceivebuf = 65536

      7. memorizza il vero socket nella mappa di asyncore (questo lo fa asyncore.dispatcher.__init__ => asyncore.dispatcher.set_socket => asyncore.dispatcher.add_channel)

      8. la sua rimozione dalla mappa avverrà con microsock.stacklesssocket.__del__ => microsock.dispatcher.close => asyncore.dispatcher.close => asyncore.dispatcher.del_channel

  3. 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 handle_connect (overridato rispetto al asyncore.dispatcher) invia un messaggio al Channel memorizzato nel membro connectChannel ...

con protocollo UDP

Trasmissione dati tra due socket

Netsukuku/ita/ModuloMicrosock (last edited 2009-05-04 15:51:42 by lukisi)