= Il modulo microsock = == Trasmissione dati tra due socket == === Protocollo TCP === La trasmissione di dati tra due socket connessi con il protocollo TCP avviene in Netsukuku con l'uso dei metodi send, sendall e recv. Per la precisione quando viene chiamato un metodo remoto (vedi [[../ElencoRemotableFunctions|Chiamate a metodi remoti]]): * il TCPClient invia il messaggio con {{{sendall}}}; * il TCPServer riceve il messaggio con {{{recv}}} e esegue il metodo remotable; * l'esecuzione del metodo remotable produce una risposta; il TCPServer invia questa risposta con {{{send}}}; * il TCPClient riceve la risposta con {{{recv}}}. Quando si richiama il metodo {{{send}}} di un {{{microsock.dispatcher}}} questo memorizza il messaggio da inviare in coda al buffer nel suo membro {{{sendBuffer}}} e fa subito ritorno al chiamante, segnalando di aver trasmesso tutti i bytes richiesti. <
> La presenza di dati in questo buffer fa sì che il metodo {{{writable}}} restituisca {{{True}}}. Questo a sua volta, come detto sopra, fa sì che {{{asyncore.poll}}}, periodicamente e solo quando la trasmissione non risulterebbe bloccante, richiami il metodo {{{handle_write_event}}} che a sua volta richiama {{{handle_write}}} overridato nella classe {{{microsock.dispatcher}}}. <
> Il metodo {{{microsock.dispatcher.handle_write}}} richiama il metodo {{{asyncore.dispatcher.send}}} sulla classe base, non più quello overridato. Questo effetua l'invio di un certo numero di bytes senza bloccare il processo e restituisce il numero di bytes trasmessi. Il metodo {{{microsock.dispatcher.handle_write}}} di seguito toglie dal buffer i bytes già inviati e fa ritorno. Abbiamo detto che il metodo {{{microsock.dispatcher.send}}} fa subito ritorno segnalando la trasmissione di tutti i bytes richiesti. In realtà la trasmissione avviene in seguito. <
> Se si vuole essere sicuri che la trasmissione sia già avvenuta si chiama il metodo {{{microsock.dispatcher.sendall}}}. Questo metodo fa ritorno solo quando il buffer nel suo membro {{{sendBuffer}}} è completamente svuotato. <
> Per attendere questo evento effettua un loop in cui intervalla il test (poco oneroso) alla schedulazione degli altri microthread con {{{micro_block}}} e ad un {{{time.sleep}}} molto breve (vedi spiegazione in [[../ModuloXtime|modulo xtime]]). Per quanto riguarda la ricezione di dati, il metodo {{{readable}}} di un {{{microsock.dispatcher}}} si comporta in modo diverso a seconda che il socket sia di tipo UDP o TCP. <
> Per i socket di tipo UDP esso, ereditato tal quale dalla {{{asyncore.dispatcher}}}, restituisce sempre {{{True}}}. Invece per i socket di tipo TCP e connessi, esso restituisce {{{True}}} solo se qualche microthread è in attesa di ricevere dati (attributo {{{_receiving}}} valorizzato nel metodo {{{recv}}}). <
> Quando il metodo {{{readable}}} restituisce {{{True}}} (e inoltre ci sono dei dati disponibili per la lettura), {{{asyncore.poll}}} richiama il metodo {{{handle_read_event}}} che a sua volta richiama {{{handle_read}}} overridato nella classe {{{microsock.dispatcher}}}. <
> Il metodo {{{microsock.dispatcher.handle_read}}} richiama il metodo {{{recv}}} di {{{asyncore.dispatcher}}}, non quello overridato da {{{microsock.dispatcher}}}. Questo richiama il metodo {{{recv}}} sul socket reale che leggerà un massimo di {{{maxreceivebuf}}} bytes senza bloccarsi. Questi dati vengono inviati come messaggio sul Channel memorizzato sul membro {{{recvChannel}}}. <
> Siccome il Channel è stato istanziato con {{{micro_send = True}}} questa trasmissione sullo {{{stackless.channel}}} avviene in realtà su un altro microthread, lasciando così immediatamente libero di continuare il microthread che richiama periodicamente la {{{asyncore.poll}}}. <
> In questo modo si ottiene che i dati sono in attesa, senza bloccare gli altri microthread, di essere letti dallo pseudo socket. <
> Il metodo {{{recv}}} overridato nella classe {{{microsock.dispatcher}}} valorizza l'attributo {{{_receiving}}} detto sopra; di seguito sta in attesa di ricevere i dati dal Channel memorizzato sul membro {{{recvChannel}}}. Tutti i bytes che vi trova li mette nel buffer {{{readBufferString}}}. Poi ne restituisce al massimo quanti richiesti dal chiamante. <
> Per la precisione, prima di tutto il metodo guarda se nel buffer {{{readBufferString}}} ci sono già un numero di bytes da soddisfare del tutto il chiamante. Altrimenti si possono verificare 2 casi: * Ci sono nel Channel alcuni messaggi pronti per essere letti. In questo caso il metodo legge tutto quello che può dal Channel e accumula nel buffer. '''''TODO''': In realtà questo è il comportamento che si intenderebbe tenere, mi pare di capire dai commenti sul codice; ma non è implementato correttamente: ci vorrebbe un while invece di un if. Verificare e correggere.'' * Ci sono già alcuni bytes nel buffer (anche se non tanti quanti il massimo richiesto) e non ci sono altri messaggi pronti per essere letti nel Channel. In questo caso il metodo si prepara a restituire immediatamente quanto finora disponibile. * Ci sono 0 bytes nel buffer e non ci sono altri messaggi pronti per essere letti nel Channel. In questo caso il metodo deve bloccarsi in attesa di qualche byte. Lo fa mettendosi in ascolto sul Channel, quindi blocca il microthread ma non il resto dell'applicazione. Alla fine, da quello che ha a disposizione sul buffer, rimuove e restituisce al chiamante al massimo ''n'' bytes, quanti richiesti. === Protocollo UDP === La trasmissione di dati tra due socket connessi con il protocollo UDP in modalità broadcast avviene in Netsukuku con l'uso dei metodi sendto e recvfrom. Per la precisione quando viene chiamato un metodo remoto (vedi [[../ElencoRemotableFunctions|Chiamate a metodi remoti]]): * il {{{BcastClient}}} invia il messaggio in broadcast a tutta la LAN connessa alla sua scheda di rete (ogni {{{BcastClient}}} è associato ad una interfaccia di rete) con {{{sendto}}}; * il UDPServer riceve il messaggio con {{{recvfrom}}} e esegue il metodo remotable; * non sono previste risposte ai metodi remoti chiamati in broadcast. Il metodo {{{sendto}}} di un {{{microsock.dispatcher}}} riceve come parametri il messaggio da inviare e l'indirizzo a cui inviarlo. <
> La prima volta che un messaggio viene inviato ad un certo destinatario (che può essere l'indirizzo speciale "{{{}}}"), il metodo crea un Channel col costruttore {{{micro_send = True}}} associato a quel destinatario. <
> Poi memorizza messaggio, indirizzo destinatario, Channel e "numero di microthread in attesa sul Channel" nel suo membro {{{sendToBuffers}}} e si mette in attesa sul Channel, bloccando il microthread corrente ma non l'intero programma. <
> La valorizzazione del membro {{{sendToBuffers}}}, come avveniva per {{{sendBuffer}}} nella modalità TCP, fa in modo che, ogni volta che la trasmissione sul socket reale può avvenire senza bloccare, venga richiamato il metodo {{{handle_write}}} overridato nella classe {{{microsock.dispatcher}}}. <
> Questo metodo vede che deve inviare un certo numero di bytes ad un certo destinatario. Usa il metodo {{{sendto}}} sul socket reale. In base a quanti bytes è riuscito ad inviare aggiorna il numero di bytes restanti. Questi saranno presi in carico la prossima volta che il metodo {{{handle_write}}} viene richiamato. <
> Quando tutto il messaggio memorizzato in {{{sendToBuffers}}} per un certo destinatario è stato trasmesso viene inviato il numero totale di bytes al Channel associato in precedenza a questo destinatario. Quindi il microthread chiamante viene sbloccato ed è come se avesse inviato in un sol colpo tutto il messaggio. <
> Se altri microthread, usando il medesimo {{{BcastClient}}} comune a tutto il programma, tentassero di inviare messaggi allo stesso destinatario, questi verrebbero accodati perché il metodo {{{sendto}}} del {{{microsock.dispatcher}}}, nelle chiamate successive per lo stesso destinatario, usa la tupla memorizzata in precedenza nel suo membro {{{sendToBuffers}}}. Dall'altra parte, quando un socket UDP (quello creato dal {{{MicroUDPServer}}}) ha dei dati da leggere, il metodo {{{handle_read}}} richiamato dal microthread che esegue {{{ManageSockets}}} effettua la lettura con il metodo {{{recvfrom}}} sul socket reale e trasmette i bytes letti al Channel memorizzato nel membro {{{recvChannel}}}. <
> Siccome il Channel è stato istanziato con {{{micro_send = True}}} questa trasmissione sullo {{{stackless.channel}}} avviene in realtà su un altro microthread, lasciando così immediatamente libero di continuare il microthread che richiama periodicamente la {{{asyncore.poll}}}. <
> In questo modo si ottiene che i dati sono in attesa, senza bloccare gli altri microthread, di essere letti dallo pseudo socket. Il metodo {{{recvfrom}}} overridato nella classe {{{microsock.dispatcher}}} non fa altro che ricevere i dati dal Channel memorizzato sul membro {{{recvChannel}}} e restituirli al chiamante. <
> Siccome per il socket creato con un {{{BcastClient}}} questo metodo viene richiamato fin dall'inizio con il parametro {{{bytecount}}} a 8192, questo è il valore che sempre conterrà il suo membro {{{maxreceivebuf}}} utilizzato dal metodo {{{handle_read}}} per richiamare il metodo {{{recvfrom}}} sul socket reale. <
> Questo non implica che le letture fatte siano sempre complete. Del resto il protocollo UDP non garantisce tale completezza.