Scuola Politecnica e delle Scienze di Base Corso di Laurea in Ingegneria Informatica Elaborato finale in PROGRAMMAZIONE I Programmazione di applicazioni ClientServer in un linguaggio interpretato: Python Anno Accademico 2015/2016 Candidato: Mickael Di Carluccio matr. N46000541 Indice Introduzione 1 Python III 1 1.1 Caratteristiche Fondamentali . . . . . . . . . . . . . . . . . . . . . . 2 1.2 Utilizzi tipici . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 1.3 Primo approccio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5 1.4 Costrutti di Controllo . . . . . . . . . . . . . . . . . . . . . . . . . . 7 1.5 Ciclo: For e While . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.6 Strutture Dati . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 1.7 File . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 2 Programmazione Distribuita 21 2.1 Applicazioni Client-Server . . . . . . . . . . . . . . . . . . . . . . . 23 2.2 Architettura Client-Server . . . . . . . . . . . . . . . . . . . . . . . 25 2.3 Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 2.4 Tipi di Socket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28 3 Programmazione in Python di un’applicazione Client-Server 30 3.1 Primo Esempio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 31 3.2 Trasferimento di File . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Propongo di considerare questa domanda: "Le macchine sono in grado di pensare?" —Alan Turing Introduzione Python è un linguaggio di programmazione dinamico orientato agli oggetti utilizzabile per molti tipi di sviluppo software. Offre un forte supporto all’integrazione con altri linguaggi e programmi, è fornito di una estesa libreria standard e può essere imparato in pochi giorni. Molti programmatori Python possono confermare un sostanziale aumento di produttività e ritengono che il linguaggio incoraggi allo sviluppo di codice di qualità e manutenibilità superiori. Python gira sui più comuni sistemi operativi come Windows, Linux/Unix, Mac OS. In questo elaborato si descrivono le caratteristiche fondamentali del linguaggio e si illustrano i fondamenti della programmazione in Python di applicazioni client e server comunicanti attraverso i protocolli TCP/IP. Figura 1: Python III Capitolo 1 Python Python è un linguaggio di programmazione ad alto livello, rilasciato pubblicamente per la prima volta nel 1991 dal suo creatore Guido van Rossum, programmatore olandese attualmente operativo in Dropbox. Deriva il suo nome dalla commedia Monty Python’s Flying Circus dei celebri Monty Python, in onda sulla BBC nel corso degli anni 70. Attualmente, lo sviluppo di Python (grazie e soprattutto all’enorme e dinamica comunità internazionale di sviluppatori) viene gestito dall’organizzazione no-profit Python Software Foundation. Python supporta diversi paradigmi di programmazione, come quello object-oriented (con supporto all’ereditarietà multipla), quello imperativo e quello funzionale, ed offre una tipizzazione dinamica forte. È fornito di una libreria built-in estremamente ricca, che unitamente alla gestione automatica della memoria e a robusti costrutti per la gestione delle eccezioni fa di Python uno dei linguaggi più ricchi e comodi da usare. Comodo, ma anche semplice da usare e imparare. Python, nelle intenzioni di Guido van Rossum, è nato per essere un linguaggio immediatamente intuibile. La sua sintassi è pulita e snella così come i suoi costrutti, decisamente chiari e non ambigui. I blocchi logici vengono costruiti semplicemente allineando le righe allo stesso modo, incrementando la leggibilità e l’uniformità del codice anche se vi lavorano diversi autori. Python è un linguaggio pseudocompilato: un interprete si occupa di analizzare il codice sorgente (semplici file testuali con estensione .py) e, se sin1 Capitolo 1. Python tatticamente corretto, di eseguirlo. In Python, non esiste una fase di compilazione separata (come avviene in C, per esempio) che generi un file eseguibile partendo dal sorgente. L’esser pseudointerpretato rende Python un linguaggio portabile. Una volta scritto un sorgente, esso può essere interpretato ed eseguito sulla gran parte delle piattaforme attualmente utilizzate, siano esse di casa Apple (Mac) che PC (Microsoft Windows e GNU/Linux). Semplicemente, basta la presenza della versione corretta dell’interprete. Infine, Python è free software: non solo il download dell’interprete per la propria piattaforma, così come l’uso di Python nelle proprie applicazioni, è completamente gratuito; ma oltre a questo Python può essere liberamente modificato e così ridistribuito, secondo le regole di una licenza pienamente open-source. Queste caratteristiche hanno fatto di Python il protagonista di un enorme diffusione in tutto il mondo, e anche in Italia, negli ultimi anni. Questo perché garantisce lo sviluppo rapido (e divertente) di applicazioni di qualsiasi complessità in tutti i contesti: dal desktop al web, passando dallo sviluppo di videogiochi e dallo scripting di sistema. 1.1 Caratteristiche Fondamentali 1. È free Python è completamente gratuito ed è possibile usarlo e distribuirlo senza restrizioni di copyright. Spesso alla parola “free” è associato il concetto di “software non supportato”, ma questo non è assolutamente vero nel caso di python: è sufficiente consultare periodicamente il sito ufficiale per rendersene conto. 2. È orientato agli oggetti Python è un linguaggio orientato agli oggetti, supporta nozioni avanzate di polimorfismo, ereditarietà, operatori di overloading il tutto con una semplice sintassi. 2 Capitolo 1. Python 3. È portabile Python è un linguaggio portabile sviluppato in ANSI C. È possibile usarlo su diverse piattaforme come: Unix, Linux, Windows, DOS, Macintosh, Sistemi Real Time, OS/2, cellulari Nokia e Android, questo perché è interpretato, quindi lo stesso codice può essere eseguito su qualsiasi piattaforma purché abbia l’interprete Python installato. 4. È facile da usare Chi ha fatto esperienza di programmazione con altri linguaggi non troverà Python complesso da imparare. Si tratta infatti di un linguaggio di alto livello con una sintassi dalle regole piuttosto semplici. 5. È ricco di librerie La dotazione standard offre numerose librerie alle quali si aggiungono moduli di terze parti che crescono continuamente. In Internet si trova materiale relativo a HTML, PDF, XML, formati grafici, CGI e perfino interi Web Server. 6. È performante Python è un linguaggio interpretato. In questo caso “interpretato” non è sinonimo di lento, infatti python “compila” il proprio codice in un bytecode molto efficiente. Questo permette di raggiungere prestazioni vicine ai linguaggi in codice nativo. Inoltre python implementa molte strutture dati e funzioni come componente intrinseca del linguaggio. Queste strutture sono dette “built-in types and tools” e sono state sviluppate con accurata efficienza. 7. Gestisce la memoria automaticamente Come in Java, in Python esiste il meccanismo di “garbage collection“, che permette di liberare il programmatore dall’ansia di allocazione selvaggia della memoria. 3 Capitolo 1. Python 8. È integrabile ad altri linguaggi Python può essere integrato ad altri linguaggi come .NET con IronPython o python per .NET, Java con Jython 1.2 Utilizzi tipici Python è un linguaggio come detto poc’anzi, molto potente ed infatti sono davvero tanti gli ambiti in cui è usato: 1. Accesso ai Database: Python definisce una modalità standard di accesso ai database, la cosiddetta DB-API [1] attualmente alla v2.0. E’ possibile collegarsi con qualsiasi database esistente, tra cui: -Oracle -MySQL -SQLite 2. Applicazioni desktop: Python include nella libreria standard i binding alla libreria di sviluppo TkInter [2], ma se desiderate interfacciarvi con altri toolkit grafici, non c’è che l’imbarazzo della scelta. Tra le più diffuse segnaliamo: -wxPython (fusione della libreria C++ wxWidgets con Python) -PyQt (tutorial) 3. Giochi e grafica 3D: Python è ampiamente usato per lo sviluppo di giochi sia a livello commerciale che "hobbistico". Tra i principali strumenti ricordiamo: -PyGame -PyKyra 4 Capitolo 1. Python 4. Sviluppo software: Esistono innumerevoli aspetti legati allo sviluppo software, che possono aiutare ogni sviluppatore a diventare un programmatore migliore. Anche Python non si sottrae a questo compito e fornisce alcuni strumenti a dir poco vitali: -Trac è uno dei progetti migliori nel suo genere. Si tratta di un wiki che integra un sistema di bugtracking altamente configurabile -Buildbot è un framework studiato per costruire e controllare lo sviluppo software attraverso test e definendo processi di sviluppo controllati -Roundup è un semplice, ma efficace, sistema di tracciamento estremamente personalizzabile. 1.3 Primo approccio Scrivere codici in Python risulta davvero semplice e non ha bisogno di un compiler. Si tratta di un linguaggio interpretato, e questo significa che il programma può essere eseguito appena saranno finite le modifiche apportate al file. Questo rende la revisione, la modifica e la risoluzione dei problemi di un programma molto più rapida rispetto agli altri linguaggi. Come ogni programmatore sa, il primo approccio a qualsiasi linguaggio di programmazione è la verifica dell’output a schermo del messaggio "Hello, World!". "Print" è una delle funzioni di base di Python, ed è usata per visualizzare informazioni nel terminale durante un programma. Al contrario di molti altri linguaggi (es. C, C++), non serve indicare la fine di una linea con ";". Non servirà neppure usare parentesi graffe () per indicare dei blocchi. Basterà indentare il testo per indicare la sua inclusione in un blocco. Una volta che le righe di codice sono finite, basterà salvare il file specificando l’estensione del file ".py" . Assicurarsi di salvare il file in un punto di facile accesso, perché si dovrà raggiungere il suo percorso dal prompt dei comandi. 5 Capitolo 1. Python Come avviene l’esecuzione del programma? Basterà aprire il Prompt dei Comandi o il Terminale e raggiungere il percorso dove è stato salvato il file. Eseguire, poi, il file digitando "Nome_Programma".py. Nel caso in esame, per un primo programma in Python, si è scelto di salvare il file con la denominazione "hello.py". —-Codice 1 print (" Hello , World ! ") HelloWorld.py —–Stampa 1 2 3 4 Microsoft Windows [ Versione 6.1.7601] Copyright ( c ) 2009 Microsoft Corporation . Tutti i diritti riservati . C :\ Users \ Mickael Di Carluccio > D :\ Desktop \ Tesi \ hello . py Hello , World ! Come si può notare dalla Stampa, per poter avere accesso alla Directory in cui risiede il file di interesse ".py" è necessario conoscere esattamente dove è stato salvato il file e fare un’operazione di "change directory" con l’uso dello slash tra una cartella e una sottocartella. 6 Capitolo 1. Python 1.4 Costrutti di Controllo Si veda un esempio di programma con flusso di controllo dove c’è un input da tastiera, e una verifica dell’età con i costrutti: if, elif, else ed una stampa sul terminale del messaggio in base all’età inserita. Codice 1 2 3 4 5 6 7 anni = int ( input (" Inserisci la tua eta ’: ")) if anni <= 12: print (" Che bello essere un bambino ! ") elif anni in range (13 , 26): print (" Sei ancora un ragazzo ! ") else : print (" Chiamami Signore ") —Stampa 1 Microsoft Windows [ Versione 6.1.7601] Copyright ( c ) 2009 2 Microsoft Corporation . Tutti i diritti riservati . 3 C :\ Users \ Mickael Di Carluccio > D :\ Desktop \ Tesi \ Control . py 4 Inserisci la tua eta ’: 5 Sei ancora un ragazzo ! 26 Vediamo meglio come viene interpretata la logica usata dai costrutti di controllo if, elif, else. -if : nell’esempio di cui sopra "se gli anni inseriti da tastiera sono un numero intero inferiore o uguale a 12" potranno essere eseguite le istruzioni al suo interno. N.B. Python non usa le parentesi graffe, bensì i due punti. -elif : Abbreviazione di elseif è usata per indicare una seconda condizione da controllare qualora la condizione specificata nel costrutto if non rispetti i parametri specificati. N.B. E’ stata usata anche la parola chiave range per indicare quali sono gli estremi specificati. else: è il costrutto che prende in esame i valori che non rientrano nelle condizioni 7 Capitolo 1. Python dell’if e dell’elif. Nel caso sopra citato qualora il valore di input inserito sia superiore a 26, verrà mandata a video la stampa "Chiamami Signore". 1.5 Ciclo: For e While In programmazione, ci sono molti programmi più elementari che usano la ricorsione per eseguire una ripetizione. Questa ripetizione è più comunemente chiamata iterazione. Dato che l’iterazione è così comune, Python fornisce vari sistemi per renderla più semplice da implementare. Il primo sistema è l’istruzione while. Ecco un esempio, ContoAllaRovescia, viene riscritto usando l’istruzione while: Codice 1 2 3 4 5 def ContoAllaRovescia ( n ): while n > 0: print n n = n -1 print (" Partenza !") La chiamata ricorsiva è stata rimossa e quindi questa funzione ora non è più ricorsiva. Si può leggere il programma con l’istruzione while come fosse scritto in un linguaggio naturale: "Finchè (while) n è più grande di 0 stampa il valore di n e poi diminuiscilo di 1. Quando arrivi a 0 stampa la stringa "Partenza!"." In modo più formale ecco il flusso di esecuzione di un’istruzione while: 1. Valuta la condizione controllando se essa è vera (1) o falsa (0). 2. Se la condizione è falsa esci dal ciclo while e continua l’esecuzione dalla prima istruzione che lo segue. 3. Se la condizione è vera esegui tutte le istruzioni nel corpo del while e torna al passo 1. 8 Capitolo 1. Python Il corpo del ciclo while consiste di tutte le istruzioni che seguono l’intestazione e che hanno la stessa indentazione. Questo tipo di flusso è chiamato ciclo o loop. Nota che se la condizione è falsa al primo controllo, le istruzioni del corpo non sono mai eseguite. Il corpo del ciclo dovrebbe cambiare il valore di una o più variabili così che la condizione possa prima o poi diventare falsa e far così terminare il ciclo. In caso contrario il ciclo si ripeterebbe all’infinito, determinando un ciclo infinito. Nel caso di ContoAllaRovescia possiamo essere certi che il ciclo è destinato a terminare visto che n è finito ed il suo valore diventa via via più piccolo fino a diventare pari a zero. Il ciclo for è un altro dei cicli di iterazione messi a disposizione dal linguaggio Python, si tratta in sostanza di un costrutto, comune anche ad altri linguaggi, che consente di ripetere un’operazione un certo numero di volte, più tecnicamente "iterare una sequenza o un oggetto", fino alla soddisfazione di una determinata condizione (implicita) che terminerà il ciclo. Per chiarire tale dinamica sarà possibile proporre un semplice esempio basato sull’impiego delle liste, queste ultime sono un tipo di dato supportato da Python che verrà analizzato nel prossimo paragrafo, per il momento basti sapere che una lista permette di gestire un insieme di valori di diversa natura (stringhe, interi, decimali) delimitati da parentesi quadre e separati tramite una virgola. Il codice proposto di seguito permetterà di ciclare e visualizzare in output tutti gli elementi presenti in una lista definita dallo sviluppatore. ––FIBONACCI 1 2 3 4 5 6 # definizione della lista fibonacci = [1 ,1 ,2 ,3 ,5 ,8 ,13 ,21 ,34 ,55 ,89 ,144] # iterazione dei valori in lista for val in fibonacci : # stampa dei valori iterati print ( val ) Nell’esempio mostrato la condizione da soddisfare per la terminazione del ciclo sarà quindi quella di stampare fino all’ultimo valore presente nella lista passata 9 Capitolo 1. Python come argomento, nel nostro caso "fibonacci". Un fattore sintattico molto importante da tenere a mente riguarda il fatto che nella digitazione di un ciclo for l’engine di Python si aspetta che l’istruzione associata al ciclo venga indentata. Vedremo nel paragrafo File anche l’istruzione break che è l’istruzione che permette al ciclo di terminare anzitempo. Ossia, qualora si verificasse la condizione desiderata, dopo le istruzione eseguite all’interno del ciclo While, break termina il ciclo passando alla prima istruzione fuori dal ciclo. 1.6 Strutture Dati Vediamo quali sono le strutture dati più usate come Liste, Pile, Code. 1.6.1 Liste Una lista[4] è una serie ordinata di valori, ognuno identicato da un indice. I valori che fanno parte della lista sono chiamati elementi. Le liste sono simili alle stringhe essendo insiemi ordinati di caratteri, fatta eccezione per il fatto che gli elementi di una lista possono essere di tipo qualsiasi. Liste e stringhe (e altri tipi di dati che si comportano da insiemi ordinati) sono chiamate sequenze. Ci sono parecchi modi di creare una lista nuova, e quello più semplice è racchiudere i suoi elementi tra parentesi quadrate ([]): 1. [10, 20, 30, 40] 2. ["Pippo", "Pluto", "Paperino"] Il primo esempio è una lista di quattro interi, il secondo una lista di tre stringhe. Gli elementi di una stessa lista non devono necessariamente essere tutti dello stesso tipo. Questa lista, infatti, contiene una stringa, un numero in virgola mobile, un intero 10 Capitolo 1. Python ed un’altra lista: ["ciao", 2.0, 5, [10, 20]] Una lista all’interno di un’altra lista è detta lista annidata. Le liste che contengono numeri interi consecutivi sono così comuni che Python fornisce un modo semplice per crearle: 1 2 range (1 ,5) [1 , 2 , 3 , 4] La funzione range prende due argomenti e ritorna una lista che contiene tutti gli interi a partire dal primo (incluso) fino al secondo (escluso). Ci sono altre due forme per range. Con un solo argomento crea una lista a partire da 0: 1 2 range (10) [0 , 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9] Se è presente un terzo argomento questo specifica l’intervallo tra valori successivi, chiamato passo. Questo esempio mostra come ottenere una stringa dei numeri dispari tra 1 e 10: 1 2 range (1 , 10 , 2) [1 , 3 , 5 , 7 , 9] Infine esiste una lista speciale che non contiene alcun elemento: è chiamata lista vuota ed è indicata da [ ]. 1.6.2 Classi ed oggetti Le classi sono tipi composti definiti dall’utente. Vediamo nello specifico cosa significa e quali migliorie comporta. Creiamo dunque, il tipo Punto. Considerando il concetto matematico di punto nelle due dimensioni, il punto è definito da una coppia di numeri (le coordinate). In notazione matematica le coordinate dei punti sono spesso scritte tra parentesi con una virgola posta a separare 11 Capitolo 1. Python i due valori. Per esempio (0, 0) rappresenta l’origine e (x, y) il punto che si trova x unità a destra e y unità in alto rispetto all’origine. Un modo naturale di rappresentare un punto in Python è una coppia di numeri in virgola mobile e la questione che ci rimane da definire è in che modo raggruppare questa coppia di valori in un oggetto composto: un sistema veloce anche se poco elegante sarebbe l’uso di una tupla, anche se possiamo fare di meglio. Un modo alternativo è quello di definire un nuovo tipo composto chiamato classe. Una definizione di classe ha questa sintassi: 1 2 class Punto : pass Le definizioni di classe possono essere poste in qualsiasi punto di un programma ma solitamente per questioni di leggibilità sono poste all’inizio, subito sotto le istruzioni import. Le regole di sintassi per la definizione di una classe sono le stesse degli altri tipi composti: la definizione dell’esempio crea una nuova classe chiamata Punto. L’istruzione pass non ha effetti: è stata usata per il solo fatto che la definizione prevede un corpo che deve ancora essere scritto. Creando la classe Punto abbiamo anche creato un nuovo tipo di dato chiamato con lo stesso nome. I membri di questo tipo sono detti istanze del tipo o oggetti. La creazione di una nuova istanza è detta istanziazione: solo al momento dell’istanziazione parte della memoria è riservata per depositare il valore dell’oggetto. Per creare un oggetto di tipo Punto viene chiamata una funzione chiamata Punto: 1 P1 = Punto () Alla variabile P1 è assegnato il riferimento ad un nuovo oggetto Punto. Una funzione come Punto, che crea nuovi oggetti e riserva quindi della memoria per depositarne i valori, è detta costruttore. Possiamo aggiungere un nuovo dato ad un’istanza usando la notazione punto: 12 Capitolo 1. Python Figura 1.1: Assegnazione attributi alla classe Punto P1 . x = 3.0 P1 . y = 4.0 1 2 In questo caso stiamo selezionando una voce da un’istanza e queste voci che fanno parte dell’istanza sono dette attributi. Questo diagramma di stato mostra il risultato delle assegnazioni: La variabile P1 si riferisce ad un oggetto Punto che contiene due attributi ed ogni attributo (una coordinata) si riferisce ad un numero in virgola mobile. Possiamo leggere il valore di un attributo con la stessa sintassi: print P1 . y 1 2 4.0 x = P1 . x print x 3 4 5 3.0 L’espressione P1.x significa "vai all’oggetto puntato da P1 e ottieni il valore del suo attributo x". In questo caso assegniamo il valore ad una variabile chiamata x: non c’è conflitto tra la variabile locale x e l’attributo x di P1: lo scopo della notazione punto è proprio quello di identificare la variabile cui ci si riferisce evitando le ambiguità. 13 Capitolo 1. Python 1.6.3 Pile In questo paragrafo verrà esaminata la pila, un tipo di dato astratto molto comune. Una pila è una collezione e cioè una struttura di dati che contiene elementi multipli. Un TDA (Tipo Dato Astratto) è definito dalle operazioni che possono essere effettuate su di esso e che sono chiamate interfaccia. L’interfaccia per una pila consiste di queste operazioni: __init__: Inizializza un pila vuota. Push: Aggiunge un elemento alla pila. Pop: Rimuove e ritorna un elemento dalla pila. L’elemento tornato è sempre l’ultimo inserito. EVuota: Controlla se la pila è vuota. Una pila è spesso chiamata struttura di dati LIFO ("last in/first out", ultimo inserito, primo fuori) perché l’ultimo elemento inserito in ordine di tempo è il primo ad essere rimosso: un esempio è una serie di piatti da cucina sovrapposti, ai quali aggiungiamo ogni ulteriore piatto appoggiandolo sopra agli altri, ed è proprio dall’alto che ne preleviamo uno quando ci serve. Le operazioni che Python fornisce per le liste sono simili a quelle definite per la nostra pila. Il codice utilizzato è chiamato implementazione del TDA Pila. Più in generale un’implementazione è un insieme di metodi che soddisfano la sintassi e la semantica dell’interfaccia richiesta. —–Classe Pila 1 2 3 4 5 6 7 8 9 class Pila : def __init__ ( self ): self . Elementi = [] def Push ( self , Elemento ) : self . Elementi . append ( Elemento ) def Pop ( self ): return self . Elementi . pop () def EVuota ( self ): return ( self . Elementi == []) 14 Capitolo 1. Python L’oggetto Pila contiene un attributo chiamato Elementi che è la lista di oggetti contenuta nella pila. Il metodo __init__ inizializza Elementi come lista vuota. Push inserisce un nuovo elemento nella pila aggiungendolo a Elementi. Pop esegue l’operazione inversa, rimuovendo e ritornando l’ultimo elemento inserito nella pila. Per controllare se la pila è vuota EVuota confronta Elementi con una lista vuota e ritorna vero/falso. Un’implementazione di questo tipo in cui i metodi sono solo una semplice invocazione di metodi già esistenti viene detta maschera. 1.6.4 Code Questo paragrafo presenta due tipi di dati astratti (TDA): la Coda e la Coda con priorità. Nella vita reale un esempio di coda può essere la linea di clienti in attesa di un servizio di qualche tipo. Nella maggior parte dei casi il primo cliente della fila è quello che sarà servito per primo, anche se ci possono essere delle eccezioni. Ad esempio, all’aeroporto ai clienti il cui volo sta per partire può essere concesso di passare davanti a tutti, indipendentemente dalla loro posizione nella fila o anche al supermercato un cliente può scambiare per cortesia il suo posto con qualcuno che deve pagare solo pochi prodotti. La regola che determina chi sarà il prossimo ad essere servito si chiama politica di accodamento. Quella più semplice è la FIFO ("First in, first out") dove il primo che arriva è il primo ad essere servito. La politica di accodamento più generale è l’accodamento con priorità dove a ciascun cliente è assegnata una priorità ed il cliente con la massima priorità viene servito per primo indipendentemente dall’ordine di arrivo. Diciamo che questa politica di accodamento è la più generale perché la priorità può essere basata su qualsiasi fattore: l’orario di partenza dell’aereo, la quantità di prodotti da pagare ad una cassa o anche la gravità dello stato di un paziente al 15 Capitolo 1. Python pronto soccorso. I tipi di dati astratti Coda e Coda con priorità condividono lo stesso insieme di operazioni. La differenza sta soltanto nella loro semantica: una Coda usa la politica FIFO, mentre la Coda con priorità, come suggerisce il nome stesso, usa la politica di accodamento con, appunto, priorità. Una prima implementazione del TDA Coda a cui guarderemo è chiamata coda linkata perché è composta di oggetti Nodo linkati. Ecco una definizione della classe: —–Classe Coda 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Coda : def __init__ ( self ): self . Lunghezza = 0 self . Testa = None def EVuota ( self ): return ( self . Lunghezza == 0) def Inserimento ( self , Contenuto ): NodoAggiunto = Nodo ( Contenuto ) NodoAggiunto . ProssimoNodo = None if self . Testa == None : # se la lista e ’ vuota il nodo e ’ il primo self . Testa = Nodo else : # trova l ’ ultimo nodo della lista Ultimo = self . Testa while Ultimo . ProssimoNodo : Ultimo = Ultimo . ProssimoNodo # aggiunge il nuovo nodo Ultimo . ProssimoNodo = NodoAggiunto self . Lunghezza = self . Lunghezza + 1 def Rimozione ( self ): Contenuto = self . Testa . Contenuto self . Testa = self . Testa . ProssimoNodo self . Lunghezza = self . Lunghezza - 1 return Contenuto Il TDA Coda con priorità ha la stessa interfaccia del TDA Coda ma una semantica diversa. L’interfaccia è sempre: __init__ : Inizializza una nuova coda vuota. Inserimento: Aggiungi un elemento alla coda. Rimozione: Rimuovi un elemento dalla coda. L’elemento da rimuovere e ritornare è quello con la priorità più alta. EVuota: Controlla se la coda è vuota. 16 Capitolo 1. Python La differenza di semantica è che l’elemento da rimuovere non è necessariamente il primo inserito in coda, ma quello che ha la priorità più alta. Cosa siano le priorità e come siano implementate sono fatti non specificati dall’implementazione, dato che questo dipende dal genere di elementi che compongono la coda. Per esempio se gli elementi nella coda sono delle stringhe potremmo estrarle in ordine alfabetico. Se sono punteggi del bowling dal più alto al più basso, e viceversa nel caso del golf. In ogni caso possiamo rimuovere l’elemento con la priorità più alta da una coda soltanto se i suoi elementi sono confrontabili tra di loro. Questa è un’implementazione di una coda con priorità che usa una lista Python come attributo per contenere gli elementi della coda: —–Classe Coda con priorità 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class CodaConPriorita : def __init__ ( self ): self . Elementi = [] def EVuota ( self ): return self . Elementi == [] def Inserimento ( self , Elemento ): self . Elementi . append ( Elemento ) def Rimozione ( self ): Indice = 0 for i in range (1 , len ( self . Elementi )): if self . Elementi [ i ] > self . Elementi [ Indice ]: Indice = i Elemento = self . Elementi [ Indice ] self . Elementi [ Indice : Indice +1] = [ ] return Elemento I metodi __init__ , EVuota e Inserimento sono tutte maschere delle operazioni su liste. L’unico metodo "interessante" è Rimozione: All’inizio di ogni iterazione Indice contiene l’indice dell’elemento con priorità massima. Ad ogni ciclo viene confrontato questo elemento con l’i-esimo elemento della lista: se il nuovo elemento ha priorità maggiore, il valore di Indice diventa i. Quando il ciclo for è stato completato Indice è l’indice dell’elemento con priorità massima. Questo elemento è rimosso dalla lista e ritornato. 17 Capitolo 1. Python 1.7 File Quando un programma è in esecuzione i suoi dati sono in memoria; nel momento in cui il programma termina o il computer viene spento tutti i dati in memoria vengono irrimediabilmente persi. Per conservare i dati devi quindi memorizzarli in un file, solitamente memorizzato su hard disk, una memoria flash (pen drive o schede di memoria) o CD-ROM. Lavorando con un gran numero di file è logico cercare di organizzarli: questo viene fatto inserendoli in cartelle (dette anche "folder" o "directory"). Ogni file all’interno di una cartella è identificato da un nome unico. Leggendo e scrivendo file i programmi possono scambiare informazioni e anche generare documenti stampabili usando il formato PDF o altri formati simili. Lavorare con i file è molto simile a leggere un libro: per usarli li devi prima aprire e quando hai finito li chiudi. Mentre il libro è aperto lo puoi leggere o puoi scrivere una nota sulle sue pagine, sapendo in ogni momento dove ti trovi al suo interno. La maggior parte delle volte leggerai il libro in ordine, ma nulla ti vieta di saltare a determinate pagine facendo uso dell’indice. Questa metafora può essere applicata ai file. Per aprire un file devi specificarne il nome e l’uso che intendi farne (lettura o scrittura). L’apertura del file crea un oggetto file: nell’esempio che segue useremo la variabile f per riferirci all’oggetto file appena creato. 1 2 3 f = open (" test . dat " ," w ") print f < open file ’ test . dat ’ , mode ’w ’ at fe820 > La funzione open prende due argomenti: il primo è il nome del file ed il secondo il suo "modo". Il modo "w" significa che stiamo aprendo il file in scrittura ("write"). Nel caso non dovesse esistere un file chiamato test.dat l’apertura in scrittura farà in modo di crearlo vuoto. Nel caso dovesse già esistere, la vecchia copia verrà rimpiazzata da quella nuova e definitivamente persa. 18 Capitolo 1. Python Quando stampiamo l’oggetto file possiamo leggere il nome del file aperto, il modo e la posizione dell’oggetto in memoria. Per inserire dati nel file invochiamo il metodo write: 1 2 --- f . write (" Adesso ") --- f . write (" chiudi il file ") La chiusura del file avvisa il sistema che abbiamo concluso la scrittura e rende il file disponibile alla lettura: 1 --- f . close () Solo dopo aver chiuso il file possiamo riaprirlo in lettura e leggerne il contenuto. Questa volta l’argomento di modo è "r": 1 --- f = open (" test . dat " ," r ") Se cerchiamo di aprire un file che non esiste otteniamo un errore: 1 2 --- f = open (" test . cat " ," r ") IOError : [ Errno 2] No such file or directory : ’ test . cat ’ Il metodo read legge dati da un file. Senza argomenti legge l’intero contenuto del file: 1 2 3 --- Testo = f . read () --- print Testo Adesso chiudi il file "read" accetta anche un argomento che specifica quanti caratteri leggere: 1 2 3 --- f = open (" test . dat " ," r ") --- print f . read (5) Adess Se non ci sono caratteri sufficienti nel file, read ritorna quelli effettivamente disponibili. Quando abbiamo raggiunto la fine del file read ritorna una stringa vuota: 19 Capitolo 1. Python 1 2 3 4 --- print f . read (1000006) o chiudi il file --- print f . read () --La funzione che segue, copia un file leggendo e scrivendo fino a 50 caratteri per volta. Il primo argomento è il nome del file originale, il secondo quello della copia: 1 2 3 4 5 6 7 8 9 10 11 def CopiaFile ( Originale , Copia ): f1 = open ( Originale , " r ") f2 = open ( Copia , " w ") while 1: Testo = f1 . read (50) if Testo == "": break f2 . write ( Testo ) f1 . close () f2 . close () return L’istruzione break come già accennata nel paragrafo "Ciclo: For e While" interrompe immediatamente il loop saltando alla prima istruzione che lo segue (in questo caso f1.close()). Il ciclo while dell’esempio è apparentemente infinito dato che la sua condizione ha valore 1 ed è quindi sempre vera. L’unico modo per uscire da questo ciclo è di eseguire un break che viene invocato quando Testo è una stringa vuota e cioè quando abbiamo raggiunto la fine del file in lettura. 20 Capitolo 2 Programmazione Distribuita Nell’ambito delle applicazioni informatiche si è soliti distinguere tra: 1. Architetture locali: tutti i componenti sono sulla stessa macchina. 2. Architetture distribuite: le applicazioni e i componenti possono risiedere su nodi diversi messi in comunicazione da una rete. I vantaggi della seconda alternativa consistono principalmente nella possibilità di uso concorrente dei programmi, nella centralizzazione dei dati, nella distribuzione del carico elaborativo, il tutto al prezzo di una maggiore complessità specialmente riguardo alla comunicazione fra i vari compenenti. Le applicazioni distribuite si possono classificare in base al «grado» di distribuzione: 1. Applicazioni client-server: sono presenti solo due livelli e le operazioni sono svolte quasi interamente sul servente. Come esempio possiamo citare i classici siti Web statici o dinamici; gli strumenti per la realizzazione di tale tipo di applicazioni sono i socket di rete, la cui programmazione è possibile in vari linguaggi, tra cui C, C++, Java, Python. 21 Capitolo 2. Programmazione Distribuita Figura 2.1: Modello Logico Client-Server 2. Applicazioni multi-livello: si ha un numero maggiore di livelli, allo scopo, soprattutto, di alleggerire il carico elaborativo dei serventi. Quelle che vengono suddivise infatti sono le funzionalità del lato servente, lasciando sostanzialmente invariate le caratteristiche della parte cliente che ha il compito di ospitare l’interfaccia dell’applicazione. Un esempio di tale tipo di architettura è quello del modello three-tier avente una struttura suddivisa in tre strati o livelli: (a) front-end o presentation tier o interfaccia; (b) middle tier o logica applicativa; (c) back-end o data tier o gestione dati persistenti. Questa nomenclatura è tipica delle applicazioni Web; più in generale si può fare riferimento ad una suddivisione in tre livelli, applicabile a qualsiasi applicazione software, che è la seguente: (a) PL (Presentation Layer): è la parte di visualizzazione dei dati (moduli, «controlli» di input, ecc.) necessari per l’interfaccia utente; (b) BLL (Business Logic Layer): è la parte principale dell’applicazione, che definisce le varie entità e le loro relazioni indipendentemente dalle modalità di presentazione all’utente e di salvataggio negli archivi; 22 Capitolo 2. Programmazione Distribuita (c) DAL (Data Access Layer): contiene tutto il necessario alla gestione dei dati persistenti (sostanzialmente sistemi di gestione di basi di dati). Il numero di livelli può essere anche maggiore di tre, allo scopo di suddividere ulteriormente i compiti dei vari strati; in questo caso si parla di applicazioni multi-tier o n-tier. 2.1 Applicazioni Client-Server Il termine "sistema client-server" (letteralmente cliente-serviente) indica un’architettura di rete nella quale genericamente un computer client o terminale client si connette ad un server per la fruizione di un certo servizio, quale ad esempio la condivisione di una certa risorsa hardware/software con altri client, appoggiandosi alla sottostante architettura protocollare. Più semplicemente, i sistemi client/server sono un’evoluzione dei sistemi basati sulla condivisione semplice delle risorse: la presenza di un server permette ad un certo numero di client di condividerne le risorse, lasciando che sia il server a gestire gli accessi alle risorse per evitare conflitti di utilizzazione tipici dei primi sistemi informatici. 2.1.1 Client Il software client [3] in genere è di limitata complessità, limitandosi normalmente ad operare come interfaccia verso il server. In generale nel campo informatico il termine client indica una componente che accede ai servizi o alle risorse di un’altra componente, detta server. In questo contesto si può quindi parlare di client riferendosi all’hardware o al software. Un computer collegato ad un server tramite rete locale o geografica, ed al quale richiede uno o più servizi, utilizzando uno o più protocolli di rete è un esempio di 23 Capitolo 2. Programmazione Distribuita client hardware. Un programma di posta elettronica è un esempio di client software. Sono sempre di più i software, come il web, l’e-mail, i database, che sono divisi in una parte client (residente ed in esecuzione sul pc client) ed una parte server (residente ed in esecuzione sul server). Il termine client indica anche il software usato sul computer client per accedere alle funzionalità offerte dal server. Ad esempio, nel web il software client è il web browser, e parla con un server web attraverso il protocollo HTTP; per l’e-mail il client è detto in gergo mail user agent o MUA (ad esempio, Outlook, Mozilla Thunderbird, Eudora, ...), e parla con il server (Mail Transfer Agent o MTA) attraverso i protocolli SMTP e POP o IMAP; il client per la consultazione o la modifica del database (spesso costituito da librerie software utilizzate da un’applicazione) parla con il DBMS, che gestisce il database e risponde alle interrogazioni del client. 2.1.2 Server l software server, oltre alla gestione logica del sistema, deve implementare tutte le tecniche di gestione degli accessi, allocazione e rilascio delle risorse, condivisione e sicurezza dei dati o delle risorse. Ad esempio un server di posta elettronica è paragonabile ad un qualunque ufficio postale. Gli utilizzatori per accedere via client alla loro cassetta di posta elettronica devono esser stati autorizzati. In modo analogo un utente deve possedere la chiave della cassetta sita presso un ufficio postale dalla quale vuole prelevare la corrispondenza. 2.1.3 Interazione Client-Server Quando un computer client si connette direttamente ad un sistema di database o ad una server application standard, questa viene chiamata 2-tier architecture (architettura a 2 livelli). 24 Capitolo 2. Programmazione Distribuita Recentemente, è più usuale per computer client, chiamati thin client che non incorporano business logic, ma solo elementi di interfaccia, connettersi ad una server application che implementa una business logic nella quale transitivamente (ossia successivamente) comunica con il database del server, il quale memorizza i dati utilizzati dall’applicazione. Tale architettura è chiamata 3-tier architecture (architettura a 3 livelli). In generale architetture ad n-livelli possono impiegare un certo numero di servizi distinti, comprese relazioni transitive tra application server che implementano differenti funzioni di business logic, ognuna delle quali può impiegare o meno un sistema di database condiviso o distinto. 2.2 Architettura Client-Server Vantaggi dell’architettura client-server 1. Il carico computazionale viene ripartito tra due piattaforme in genere distinte. Ognuna di esse può essere ottimizzata per i compiti a cui è destinata: gestione di basi di dati, gestione di documenti, calcolo numerico, gestione di strumentazione, interfaccia utente. 2. L’architettura si presta in modo naturale alla condivisione delle risorse: le risorse gestite dal server sono accessibili in teoria da qualunque nodo di Internet; se il server è realizzato in modo concorrente, l’accesso può avvenire da più nodi simultaneamente. 3. Client e server possono: (a) essere sviluppati su piattaforme (cioè sistemi operativi) diversi; tutti i moderni sistemi operativi supportano infatti il protocollo TCP/IP; 25 Capitolo 2. Programmazione Distribuita (b) essere sviluppati con linguaggi di programmazione diversi; tutti i moderni linguaggi di programmazione supportano infatti il protocollo TCP/IP; (c) essere sviluppati da programmatori diversi, creando così una modalità naturale di ripartizione del lavoro in un team. Svantaggi dell’architettura client-server 1. Maggior complessità del sistema. 2. Dipendenza da una connessione di rete efficace. 3. Procedura di sviluppo un po’ più complicata. 2.2.1 Architettura client-server TCP/IP La connessione TCP/IP stabilisce un collegamento punto-punto tra due applicazioni. Gli estremi di questo collegamento sono contrassegnati da un indirizzo IP, che identifica la workstation e da un numero di porta, che rende possibile la coesistenza, sulla stessa workstation, di più connessioni, facenti capo ad applicazioni indipendenti. Le applicazioni che utilizzano il protocollo TCP/IP in un contesto client-server non solo devono conoscere ciascuna l’indirizzo IP e il numero di porta dell’altra, ma devono anche condividere il protocollo dell’applicazione, che pertanto deve essere stato preventivamente definito. Una volta stabilita la connessione e il protocollo cui scambiare dati su di essa, il sottostante protocollo TCP/IP si incarica di far arrivare questi dati, suddivisi in pacchetti, da un estremo all’altro del collegamento. In particolare, il protocollo TCP si occupa di assemblare e disassemblare i pacchetti e di gestire l’handshaking che garantisce l’affidabilità della connnessione, mentre il protocollo IP si occupa del trasporto dei singoli pacchetti e della scelta del miglior instradamento degli stessi lungo la rete. Questo meccanismo è alla base della robustezza del protocollo 26 Capitolo 2. Programmazione Distribuita TCP/IP nel suo insieme, che a sua volta rappresenta una delle motivazioni dello sviluppo del protocollo stesso in ambito militare (ARPAnet). Le varie applicazioni standard esistenti (navigazione Web, trasferimento file, posta elettronica e molte altre) utilizzano protocolli di applicazione standardizzati (http, ftp, pop3, imap, smtp etc.). Ogni applicazione client-server specifica deve invece definire ed applicare il proprio protocollo di applicazione proprietario. Questo può prevedere lo scambio di dati in blocchi di dimensioni fisse (è la soluzione più semplice). 2.3 Socket Un socket[5] è oggetto software che permette l’invio e la ricezione di dati, tra host remoti (tramite una rete) o tra processi locali (Inter-Process Communication). Data una qualsiasi piattaforma, è probabile che ci siano altre forme di IPC più veloci, ma per la comunicazione tra piattaforme diverse i socket sono quasi una scelta obbligata. Furono inventati a Berkeley come parte dello Unix BSD. Si diffusero assai rapidamente con Internet. Per buone ragioni la combinazione dei socket con INET rende la comunicazione con macchine di qualunque tipo sparse qua e là per il mondo incredibilmente facile (almeno se comparata con gli altri sistemi). Più precisamente, il concetto di socket si basa sul modello Input/Output su file di Unix, quindi sulle operazioni di open, read, write e close; l’utilizzo, infatti, avviene secondo le stesse modalità, aggiungendo i parametri utili alla comunicazione, quali indirizzi, numeri di porta e protocolli. Socket locali e remoti in comunicazione, formano una coppia (pair), composta da indirizzo e porta di client e server. Solitamente i sistemi operativi forniscono delle API per permettere alle applicazioni di controllare e utilizzare i socket di rete. 27 Capitolo 2. Programmazione Distribuita I tipi di protocolli utilizzati dal socket, ne definiscono la famiglia (o dominio). Possiamo distinguere, ad esempio, due importanti famiglie: 1. AF_INET : comunicazione tra host remoti, tramite Internet; 2. AF_UNIX : comunicazione tra processi locali, su macchine Unix. (Questa famiglia è anche chiamata Unix Domain Socket). 2.4 Tipi di Socket All’interno della famiglia possiamo distinguere il tipo di socket, a seconda della modalità di connessione. Abbiamo: 1. Stream socket: orientati alla connessione (connection-oriented), basati su protocolli affidabili come TCP o SCTP; 2. Datagram socket: non orientati alla connessione (connectionless), basati sul protocollo veloce ma inaffidabile UDP; 3. Raw socket (raw IP): il livello di trasporto viene bypassato, e l’header è accessibile al livello applicativo. 2.4.1 Stream Socket Vedremo più nel particolare solo questa tipologia di Socket. Essendo basati su protocolli a livello di trasporto come TCP, garantiscono una comunicazione affidabile, full-duplex, orientata alla connessione, e con un flusso di byte di lunghezza variabile. La comunicazione mediante questo socket, si compone di queste fasi: 1. – Creazione dei socket Client e server creano i loro rispettivi socket, e il server lo pone in ascolto su una porta. 28 Capitolo 2. Programmazione Distribuita Dato che il server può creare più connessioni con client diversi (ma anche con lo stesso), ha bisogno di una coda per gestire le varie richieste. 2. – Richiesta di connessione Il client effettua una richiesta di connessione verso il server. Da notare che possiamo avere due numeri di porta diversi, perchè una potrebbe essere dedicata solo al traffico in uscita, l’altra solo in entrata; questo dipende dalla configurazione dell’host. In sostanza, non è detto che la porta locale del client coincida con quella remota del server. Il server riceve la richiesta e, nel caso in cui sia accettata, viene creata una nuova connessione. 3. – Comunicazione Ora client e server comunicano attraverso un canale virtuale, tra il socket del primo, ed uno nuovo del server, creato appositamente per il flusso dei dati di questa connessione: data socket. In fede di quanto accennato nella prima fase, il server crea il data socket perchè il primo serve esclusivamente alla gestione delle richieste. È possibile, quindi, che ci siano molti client a comunicare con il server, ciascuno verso il data socket creato dal server appositamente per loro. 4. – Chiusura della connessione Essendo il TCP un protocollo orientato alla connessione, quando non si ha più la necessità di comunicare, il client lo comunica al server, che ne deistanzia il data socket. La connessione viene così chiusa. 29 Capitolo 3 Programmazione in Python di un’applicazione Client-Server Questo capitolo tratterà in modo dettagliato le applicazioni client-server in Python, linguaggio di programmazione analizzato nel capitolo 1. Utilizzeremo un protocollo di comunicazione TCP/IP tra Client e Server. Vedremo prima un’applicazione che tratterà la comunicazione semplice tra un client ed un server con uno scambio di messaggi e successivamente, in maniera più dettagliata l’invio di un file da parte del server, scelto dal client. 30 Capitolo 3. Programmazione in Python di un’applicazione Client-Server 3.1 Primo Esempio Vediamo un primo esempio in cui il server si mette in ascolta su una porta predefinita ed in seguito ad una connessione TCP/IP con il client, lo stesso client invia la data e l’ora relativa alla connessione effettuata. Server.py 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 # server . py import socket import time # create a socket object serversocket = socket . socket ( socket . AF_INET , socket . SOCK_STREAM ) # get local machine name host = socket . gethostname () port = 9999 # bind to the port serversocket . bind (( host , port )) # queue up to 5 requests serversocket . listen (5) while 1: # establish a connection clientsocket , addr = serversocket . accept () print (" Connessione con [ addr ] ,[ port ] % s " % str ( addr )) currentTime = time . ctime ( time . time ()) + "\ r \ n " clientsocket . send ( currentTime . encode ( ’ ascii ’)) clientsocket . close () 1. socket.socket(): Crea un nuovo socket utilizzando il dato indirizzo, il tipo di socket ed il numero di protocollo. 2. socket.bind(address): Associa il socket all’indirizzo. 3. socket.listen(backlog): Ascolta le connessioni fatte al socket. L’argomento("backlog") specifica il numero massimo di connessioni in coda che saranno almeno 0; il massimo valore dipende dal sistema (di solito è 5), il minimo valore è sempre 0. 4. socket.accept(): Il valore di ritorno è una coppia (conn, address) dove conn è un nuovo oggetto socket che serve a mandare e ricevere dati, e address è l’indirizzo legato al socket. 31 Capitolo 3. Programmazione in Python di un’applicazione Client-Server Una volta accettato, un nuovo socket viene creato ed avrà un proprio indentificativo. Questo nuovo socket è utilizzato unicamente con questo particolare client. 5. socket.send(bytes[, flags]): Invia dati al socket. Il socket deve essere connesso al socket. Ritorna il numero di bytes inviati. 6. socket.close(): Indica la chiusura del socket. Tutte le operazioni successive sul socket falliranno.I socket sono automaticamente chiusi quando vengono rifiutati, ma è sempre raccomandato chiuderli con l’operazione close(). Client.py 1 2 3 4 5 6 7 8 9 10 11 12 13 # client . py import socket # create a socket object s = socket . socket ( socket . AF_INET , socket . SOCK_STREAM ) # get local machine name host = socket . gethostname () port = 9999 # connection to hostname on the port . s . connect (( host , port )) # Receive no more than 1024 bytes tm = s . recv (1024) s . close () print (" Time connection server : % s " % tm . decode ( ’ ascii ’)) Output 1 2 $ python server . py & Connessione con [ addr ] ,[ port ] ( ’127.0.0.1 ’ , 54597) 3 4 5 $ python client . py Time connection server is Wed Apr 20 16:32:15 2016 32 Capitolo 3. Programmazione in Python di un’applicazione Client-Server 3.2 Trasferimento di File Si veda un esempio di applicazione Client-Server con l’utilizzo dei file[6]. —Server.py 1 # server . py 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 import socket port = 60000 s = socket . socket () host = socket . gethostname () s . bind (( host , port )) s . listen (15) print ( ’ Server listening .... ’) while True : conn , addr = s . accept () print ( ’ Got connection from ’ , addr ) data = conn . recv (1024) print ( ’ Server received ’ , repr ( data . decode ())) filename = ’ mytext . txt ’ f = open ( filename , ’ rb ’) l = f . read (1024) while ( l ): conn . send ( l ) print ( ’ Sent ’, repr ( l . decode ())) l = f . read (1024) f . close () print ( ’ Done sending ’) conn . send ( ’ - > Thank you for connecting ’. encode ()) conn . close () —Output Server 1 2 3 Microsoft Windows [ Versione 6.1.7601] Copyright ( c ) 2009 Microsoft Corporation . Tutti i diritti riservati . 4 5 6 7 8 9 10 C :\ Users \ Mickael Di Carluccio >" C :\ server . py " Server listening .... Got connection from ( ’192.168.1.11 ’ , 62793) Server received ’ Hello Server ! ’ Sent ’ ciao a tutti ’ Done sending 33 Capitolo 3. Programmazione in Python di un’applicazione Client-Server –-Client.py 1 # client . py 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 import socket s = socket . socket () host = socket . gethostname () port = 60000 s . connect (( host , port )) s . send ( ’ Hello Server ! ’. encode ()) with open ( ’ received . txt ’ , ’wb ’) as f : print ( ’ file opened ’) while True : print ( ’ receiving data ... ’) data = s . recv (1024) if not data : break print ( ’ Data = > ’, data . decode ()) # write data to a file f . write ( data ) f . close () print ( ’ Successfully get the file ’) s . close () print ( ’ connection closed ’) —Output Client 1 2 3 Microsoft Windows [ Versione 6.1.7601] Copyright ( c ) 2009 Microsoft Corporation . Tutti i diritti riservati . 4 5 6 7 8 9 10 11 12 13 C :\ Users \ Mickael Di Carluccio >" C :\ client . py " file opened receiving data ... Data = > ciao a tutti receiving data ... Data = > -> Thank you for connecting receiving data ... Successfully get the file connection closed 14 15 C :\ Users \ Mickael Di Carluccio > 34 Conclusioni In questo elaborato abbiamo esaminato il modo con cui è possibile programmare un’applicazione Client/Server in linguaggio Python. L’obiettivo prefissato prevedeva la programmazione in linguaggio Python, di un’applicazione Client/Server in grado di aprire un file, copiare il contenuto al suo interno e di scriverlo in un nuovo file, salvandolo. Dapprima, però, abbiamo discusso delle potenzialità che questo linguaggio interpretato ci fornisce, descrivendo i suoi vantaggi e svantaggi, di come vengono dichiarate le strutture dati e di come, proprio per il conseguimento dell’obiettivo, di come vengono trattati i file. Essendo un’applicazione Client/Server, abbiamo discusso anche di quelle che sono le applicazioni distribuite, descrivendone struttura e architettura. Ma non solo. Abbiamo discusso di come è stato possibile, far comunicare un applicativo Client, con un Server, secondo i vari protocolli di rete, come il TCP/IP e l’UDP. La tesi tratta argomenti davvero interessanti soprattutto per chi, come me, si è affacciato per la prima volta al mondo Python, restandone davvero sorpreso e meravigliato. Tra i numerosi vantaggi, quelli che più mi hanno colpito sono: non prevedono parenti graffe, ma si basa solo sulla corretta indentazione delle righe di codice, ma anche e soprattutto dal fatto che non esiste un vero e proprio compilatore del Python, infatti, con un semplice editor di testo è possibile scrivere righe di codice per poi verificarle con un Prompt dei comandi. Quest’ultima valutazione, considerata da me un vantaggio, fa sì che in qualunque 35 Capitolo 3. Programmazione in Python di un’applicazione Client-Server momento ed in qualunque luogo, in presenza di un pc, è possibile "buttare giù" righe di codice. Tra le società più importanti che usano Python abbiamo: 1. Google (molti componenti del motore di ricerca) 2. Industrial Light+Magic (Star Wars Episode II...) 3. Infoseek (search engine) 4. Yahoo! Groups (mailing list manager) 5. Nasa (vari utilizzi, tra cui strumenti per il pre-mission planning dello Space Shuttle) 6. IBM (test automatici di strumenti internet) 7. Blender 3D (scripting language). Ciò fa capire la potenza di Python che viene scelto da numerose aziende a discapito di tanti altri linguaggi di programmazione. 36 Ringraziamenti Ringrazio per aver reso possibile il conseguimento della mia laurea, innanzitutto i miei genitori, che mi hanno sempre incitato ed esortato a non mollare quando le cose non andavano per il verso giusto. Ringrazio poi, i miei colleghi, Filippo Masi e Francesco Apuzzo per aver condiviso insieme il percorso di studi che ci ha accompagnati fino al termine degli esami, ringrazio Francesco soprattutto per il tempo dedicatomi, nonostante ne avesse poco lui stesso. Ringrazio i miei compagni di vita, Davide D’Ambrosio, Andrea Esposito, Ernesto Borruto e tutti gli amici della palestra che hanno seguito il mio cammino verso la laurea. 37 Bibliografia [1] http://www.python.it/doc/articoli/PyDbAPI2_0.html [2] http://linuxdidattica.org/docs/altre_scuole/msm_p/txs_01.html#id6 [3] https://it.wikipedia.org/wiki/Sistema_client/server [4] "Pensare da informatico - Imparare con Python" Allen Downey, Jeffrey Elkner, Chris Meyers [5] http://fortyzone.it/socket-cosa-sono/ [6] http://www.bogotobogo.com/python/python_network_programming_server_client.php 38