alfredocentinaro.it https://www.alfredocentinaro.it/ Sito personale di Alfredo Centinaro, ingegnere informatico, insegnante, musicista. Programmazione, appunti, esercizi, sistemi e reti, tpsit, esami di stato, arduino Wed, 08 Apr 2026 09:51:44 +0000 it-IT hourly 1 https://wordpress.org/?v=6.9.4 https://www.alfredocentinaro.it/wp-content/uploads/2022/01/logo_alfredocentinaro-150x150.png alfredocentinaro.it https://www.alfredocentinaro.it/ 32 32 Area e perimetro del rettangolo in JavaFX https://www.alfredocentinaro.it/lezioni/java/javafx/area-e-perimetro-del-rettangolo-in-javafx/ Sun, 29 Mar 2026 10:19:59 +0000 https://www.alfredocentinaro.it/?p=8954 Proponiamo un esercizio semplice con il calcolo del perimetro ed area di un rettangolo per comprendere alcune dinamiche di JavaFx. Ce n’è una versione analoga qui con altri widget e scelte tecniche. Enum Operazioni Cominciamo col il banalissimo enum che sarà vivo dietro la tendina per la scelta dell’operazione. Classe applicazione Creando un progetto FX ... Leggi tutto

L'articolo Area e perimetro del rettangolo in JavaFX proviene da alfredocentinaro.it.

]]>
Proponiamo un esercizio semplice con il calcolo del perimetro ed area di un rettangolo per comprendere alcune dinamiche di JavaFx. Ce n’è una versione analoga qui con altri widget e scelte tecniche.

Enum Operazioni

Cominciamo col il banalissimo enum che sarà vivo dietro la tendina per la scelta dell’operazione.

package it.alfredocentinaro.calcoloareaperimetrofx;

public enum Operazione {
    AREA,
    PERIMETRO
}

Classe applicazione

Creando un progetto FX ci viene creata in automatico una classe Application che per comodità rinomino o meglio ne faccio il refactor. Nel codice non faccio molte modifiche. Mi assicuro che la scena sia della stesse dimensioni della finestra che creerò nello SceneBuilder, ad esempio 600×400. Modifico il titolo con “Gestione Rettangolo!”

package it.alfredocentinaro.calcoloareaperimetrofx;

import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class Application extends javafx.application.Application {
    @Override
    public void start(Stage stage) throws IOException {
        FXMLLoader fxmlLoader = new FXMLLoader(Application.class.getResource("hello-view.fxml"));
        Scene scene = new Scene(fxmlLoader.load(), 600, 400);
        stage.setTitle("Gestione Rettangolo!");
        stage.setScene(scene);
        stage.show();
    }
}

Launcher

Per i nostri esercizi è una classe con scarso interesse per eventuali modifiche. Ci servirà per fare il Run della nostra applicazione. E’ interessante che non abbia delle ereditarietà e che può essere usata per la creazione successiva di artefatti eseguibili.

package it.alfredocentinaro.calcoloareaperimetrofx;

public class Launcher {
    public static void main(String[] args) {
        javafx.application.Application.launch(Application.class, args);
    }
}

Lo SceneBuilder

Con lo SceneBuilder abbiamo provveduto a realizzare in modo asciutto l’interfaccia prevedendo come elemento base una VBox con annidate una Menubar e un Pane. Nel menù abbiamo modificato le voci esistenti per avere la funzione di chiusura e di lancio dell’alert con le informazioni del software. La disposizione poi delle Label, dei TextField, ComboBox e Button risulta abbastanza semplice. Non abbiamo impostato parametri particolari del layout se non nella label della descrizione in alto dove abbiamo cliccato la rotellina (molto piccola adire la verità) per sviluppare il testo come linea multipla e l’andata a capo. I vari campi sono stati impostati con una larghezza minima prescelta per rendere la grafica omogenea. Il Field del risultato è stato contrassegnato non editabile.

A ogni Field di input e i bottoni abbiamo associato un nome semplice e descrittivo nel tab di destra Code nel campo fx:id. Nello stesso tab per i bottoni e menu abbiamo inserito il nome della funzione da scatenare On Action, ovvero al click di quell’elemento.

Altra cosa fondamentale nel nostro SceneBuilder è collegare la classe controller. Il progetto IntelliJ in genere mette una classe di esempio HelloController che possiamo rinominare a piacere. Questa classe deve necessariamente essere impostata nel menu a sinistra al tab Controller dove compaiono Controller class e anche come riepilogo i campi a cui abbiamo assegnato un nome/id e che quindi potremo usare all’interno del codice che scriveremo. Se non c’è corrispondenza, semplicemente nessun click scatenerà azioni. Il dato può essere modificato a mano anche nel file fxml ma risulta più ostico. Salvate tutto.

Preleviamo ora il codice preconfezionato del controller col menu in alto View =>Show Sample Controlelr Skeleton. Questo è fondamentale, potete usare il flag full e il tastino copy per selezionare in automatico tutto. Torniamo al nostro codice in Intellij, nella classe controller, cancelliamo il codice di esempio e facciamo incolla con la selezione precedente.

Controller

SceneBuilder non ha la bacchetta magica ma già ci fornisce un valido contributo per cominciare a modificare il codice. Il controller è decisamente il punto critico del nostro codice.

Combobox operazione

Il primo elemento è il menù a tendina. Andiamo a cercare la riga dove viene dichiarato. Tra i tag andiamo ad inserire il tipo corrispondente all’enum creato, ovvero Operazione. Questo ci permetterà di gestire la tendina appunto come se fosse un enum.

private ComboBox<Operazione> operazione;  //IMPORTANTE: Qui va inserito l'enum!!!

L’altra operazione da fare per la nostra tendina è andare ad inserire le voci. Andiamo a cercare in fondo al controller il metodo void initialize() e aggiungiamo la riga per settare tutte le voci corrispondenti all’enum Operazione.

operazione.getItems().setAll(Operazione.values());

Bottone calcola

Sicuramente il bottone più significativo. Se abbiamo copiato lo skeleton corretto da SceneBuilder dovremmo avere già un metodo calcola. Lo andiamo a personalizzare con la logica molto banale:

  1. prelevo i valori dalla caselle latoa e latob
  2. converto i valori da testo a numero, double per essere più realistici
  3. vado a valutare la scelta della tendina
    • Se AREA calcolo l’aera e scrivo nella casella risultato
    • Se PERIMETRO procedo col perimetro e scrivo nella casella risultato
@FXML
    void calcola(ActionEvent event) {
        double a = Double.parseDouble(latoa.getText());
        double b = Double.parseDouble(latob.getText());

        Operazione scelta = operazione.getValue();


        if (scelta == Operazione.AREA)
        {
            double ris = a * b;
            if (ris >0)
                risultato.setText(String.valueOf(ris));
        }

        if (scelta == Operazione.PERIMETRO)
        {
            double ris = 2*(a + b);
            if (ris >0)
                risultato.setText(String.valueOf(ris));
        }
    }

Qui ammettiamo un po’ di pigrizia ma che può trasformarsi in un esercizio per l’alunno. Non abbiamo fatto controlli sui valori di input, se testuali, se vuoti ecc fornendo un feedback colorato e/o testuale sull’errore.

Bottone cancella

    @FXML
    void cancella(ActionEvent event) {
        latoa.setText("");
        latob.setText("");
        risultato.setText("");
    }

Menu Aiuto

Il metodo aiuto lo implementiamo in modo semplice. Apriamo un alert popup con qualche informazione. Non abbiamo previsto una intera finestrella ex novo come in altri esercizi su questo sito.

    void aiuto(ActionEvent event) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION); // Tipo di icona (Error, Information, Warning)
        alert.setTitle("Info");
        alert.setHeaderText("alfredocentinaro.it");
        alert.setContentText("Software realizzato dal prof. Alfredo Centinaro");

        alert.showAndWait(); // Blocca l'esecuzione finché l'utente non preme OK
    }

Menu Chiudi

    @FXML
    void chiudi(ActionEvent event) {
        System.exit(0);
    }

Listato completo

Il codice completo del nostro controller

package it.alfredocentinaro.calcoloareaperimetrofx;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.scene.control.*;

public class Controller {

    @FXML
    private ResourceBundle resources;

    @FXML
    private URL location;

    @FXML
    private Button btbcalcola;

    @FXML
    private Button btbcancella;

    @FXML
    private TextField latoa;

    @FXML
    private TextField latob;

    @FXML
    private MenuItem menaiuto;

    @FXML
    private MenuItem menchiudi;

    @FXML
    private ComboBox<Operazione> operazione;  //IMPORTANTE: Qui va inerito l'enum!!!

    @FXML
    private TextField risultato;

    @FXML
    void aiuto(ActionEvent event) {
        Alert alert = new Alert(Alert.AlertType.INFORMATION); // Tipo di icona (Error, Information, Warning)
        alert.setTitle("Info");
        alert.setHeaderText("alfredocentinaro.it");
        alert.setContentText("Software realizzato dal prof. Alfredo Centinaro");

        alert.showAndWait(); // Blocca l'esecuzione finché l'utente non preme OK
    }

    @FXML
    void calcola(ActionEvent event) {
        double a = Double.parseDouble(latoa.getText());
        double b = Double.parseDouble(latob.getText());

        Operazione scelta = operazione.getValue();

        //if (scelta == null)
        //{
        //    risultato.setText("Scegli un'operazione");
        //}

        if (scelta == Operazione.AREA)
        {
            double ris = a * b;
            if (ris >0)
                risultato.setText(String.valueOf(ris));
        }

        if (scelta == Operazione.PERIMETRO)
        {
            double ris = 2*(a + b);
            if (ris >0)
                risultato.setText(String.valueOf(ris));
        }
    }

    @FXML
    void cancella(ActionEvent event) {
        latoa.setText("");
        latob.setText("");
        risultato.setText("");
    }

    @FXML
    void chiudi(ActionEvent event) {
        System.exit(0);
    }

    @FXML
    void initialize() {
        assert btbcalcola != null : "fx:id=\"btbcalcola\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert btbcancella != null : "fx:id=\"btbcancella\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert latoa != null : "fx:id=\"latoa\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert latob != null : "fx:id=\"latob\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert menaiuto != null : "fx:id=\"menaiuto\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert menchiudi != null : "fx:id=\"menchiudi\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert operazione != null : "fx:id=\"operazione\" was not injected: check your FXML file 'hello-view.fxml'.";
        assert risultato != null : "fx:id=\"risultato\" was not injected: check your FXML file 'hello-view.fxml'.";

        operazione.getItems().setAll(Operazione.values());
    }

}

Ultima modifica 29 Marzo 2026

L'articolo Area e perimetro del rettangolo in JavaFX proviene da alfredocentinaro.it.

]]>
Protocollo NAT e PAT https://www.alfredocentinaro.it/lezioni/esami-di-stato/sistemi-reti-esami-di-stato/protocollo-nat-e-pat/ Sat, 21 Mar 2026 19:02:25 +0000 https://www.alfredocentinaro.it/?p=8938 NAT (Network Address Translation) Il NAT è una tecnica che permette di modificare gli indirizzi IP all’interno dei pacchetti IP in transito attraverso un router o un modem casalingo. Viene utilizzato principalmente per permettere a più dispositivi in una rete locale con indirizzi IP privati di accedere a Internet utilizzando un unico indirizzo IP pubblico. ... Leggi tutto

L'articolo Protocollo NAT e PAT proviene da alfredocentinaro.it.

]]>
NAT (Network Address Translation)

Il NAT è una tecnica che permette di modificare gli indirizzi IP all’interno dei pacchetti IP in transito attraverso un router o un modem casalingo. Viene utilizzato principalmente per permettere a più dispositivi in una rete locale con indirizzi IP privati di accedere a Internet utilizzando un unico indirizzo IP pubblico.

Il dispositivo di frontiera crea in modo statico una associazione 1:1 tra un IP privato e un IP pubblico. il tutto è registrato in una tabella sulla memoria del dispositivo. La sostituzione IP pubblico/privato è del tutto trasparente al client interno o al nodo di destinazione. Usato tipicamente per i server interni che devono essere raggiungibili dall’esterno.

PAT (Port Address Translation)

Il PAT è una variante del NAT (spesso confuso con lo stesso nome o chiamato NAT Overload). È la forma più comune di NAT utilizzata nelle reti domestiche e aziendali. Permette a centinaia di host interni di condividere un singolo indirizzo IP pubblico. Il meccanismo è simile al NAT. Per distinguere le diverse connessioni, il router/modem utilizza i numeri di porta TCP/UDP. Ogni sessione interna viene mappata sull’IP pubblico con una porta sorgente univoca. Ad essere sostituita quindi è la porta interna con quella esterna assegnata dal modem/router. Il pacchetto che arriva al server di destinazione quindi saranno noti IP pubblico/Porta Esterna come mittenti. A questi manderà indietro la risposta. Al router tornerà un sorgente con ip del server destinato all’ip pubblico del router e la porta PAT. Il router riconosce la porta è rinvia al client interno.

Dispositivo InternoIP Privato (Client)Porta InternaIP Pubblico (Router)Porta Esterna (PAT)IP Destinazione (Server)
PC Desktop192.168.1.1050012.22.33.4410001142.250.184.78
Smartphone192.168.1.1550012.22.33.4410002142.250.184.78
Laptop192.168.1.2088882.22.33.441000331.13.86.36

Vantaggi di NAT/PAT

Al lettore sono già chiari i vantaggi di queste tecnologie, spesso identificate con unico nome di NAT sui dispositivi casalinghi economici. Il risparmio degli indirizzi IPv4 è il vantaggio principale. Senza NAT, gli indirizzi IPv4 sarebbero esauriti da anni. Gli indirizzi IP pubblici hanno un costo che sosteniamo con gli abbonamenti o le ricariche, non potremmo spendere enormi quantità di soldi per far uscire dalla rete di casa decine di dispositivi tra telefoni, tv, stampanti, vari oggetti smart.

Meno evidente forse ma quello della sicurezza è un aspetto notevole. NAT/PAT nascondono la struttura della rete interna. Gli host esterni vedono solo l’IP pubblico del router, rendendo più difficile mappare la rete privata.

Limiti e Svantaggi

Qualche videogamer o nostalgici dell’emule già saranno balzati sulla sedia perché è anche vero che il NAT/PAT ha dei limiti, spesso non risolvibili. Il router deve processare ogni pacchetto per modificare l’header e ricalcolare il checksum, introducendo latenza al traffico. Questo si traduce in tempo di ping più elevato che nei videogame o applicazioni in tempo reale può creare problemi.

Molti protocolli (come il peer-to-peer o il VoIP) hanno difficoltà a funzionare “dietro” un NAT perché si aspettano una comunicazione diretta tra host. Alcuni in servizi utilizzano delle porte prefissate che non potrebbero essere sostituite a patto di non avere delle limitazioni nel traffico. In queste circostanze abilitare o meno alcune porte col Port Forwarding o l’UPnP per poter ricevere connessioni dall’esterno potrebbe essere problematico su modem a basso costo o disabilitati per scelta costruttiva di alcune compagnie telefoniche.

Ultima modifica 8 Aprile 2026

L'articolo Protocollo NAT e PAT proviene da alfredocentinaro.it.

]]>
Protocollo DNS https://www.alfredocentinaro.it/lezioni/sistemi-reti/protocollo-dns/ Mon, 16 Mar 2026 10:23:12 +0000 https://www.alfredocentinaro.it/?p=8931 Siamo solitamente abituati a navigare sul web ricordando i nomi dei siti web: gazzettadellosport.it, ansa.it ecc Niente di più semplice usuale ma in realtà le macchine in rete sappiamo bene avere solo identificativi con indirizzi IP. Ovvimente è impossibile per un utente normale ricordare gli indirizzi IP dei siti che visita giornalemente. Ecco perché in ... Leggi tutto

L'articolo Protocollo DNS proviene da alfredocentinaro.it.

]]>
Siamo solitamente abituati a navigare sul web ricordando i nomi dei siti web: gazzettadellosport.it, ansa.it ecc Niente di più semplice usuale ma in realtà le macchine in rete sappiamo bene avere solo identificativi con indirizzi IP. Ovvimente è impossibile per un utente normale ricordare gli indirizzi IP dei siti che visita giornalemente. Ecco perché in rete abbiamo tabelle dove è possibile associare corrispondeza tra ip e nome menmonico. E’ proprio la funzione del DNS. Il primo server DNS che incrociamo in rete è quello del nostro provider di telefonia e connettività internet (Tim, Fastweb, Iliad ecc). Mettono di default a disposizione dei loro clienti dei DNS che possono da subito rispondere alla richiesta del browser per ottenere l’indirizzo IP finale da utilizzare per cominciare la navigazione. Potrebbe capitare che alcuni siti web di scarso interesse o mai visitati dai clienti non sia incluso in tali tabelle. Allora i DNS locali in modo gerarchico provano a richiedere ad altri server DNS se possiedono la corrispondenza, ad esempio andando su un DNS nazionale che gestisce i domini .it ecc fino a giungere al server stesso che eventualemente ospita il sito in questione.

Cosa accade se un server o pc che ospita un sito web cambia IP? Bisogna reinviare una richiesat di aggiornamento delle tabelle, procedura non proprio snella.

DNS e cenura

Può capitare in alcuni paesi poco democratici o su eplicita richiesta politica, o delle autorità di polizia o di semplice interesse aziendale singolo, che alcuni DNS siano gestiti per non accogliere alcune corrispondente IP/Nome. Ovviamente quel sito non sarà raggiungibile via ricerca web o barra degli url ma dovrò conoscere eplicitamente il suo indirizzo IP, compito complessso per un utente medio. In alcuni casi non essere sulle tabelle DNS mondiali è una scelta dei siti del famoso deepweb.

Scelta dei DNS

I server DNS del vostro provider ISP non vi soddisfano? Possono essere cambiati tra le impostazioni della nostra sched di rete. Esistono DNS come quelle di google con ip 8.8.8.8 e 8.8.4.4, ma anche DNS di aziende private come Cloudfare 1.1.1.1 ritenuto tra i più veloci. Altri sono detti open perché non controllati direttamente da enti governativi o aziende con particolari interssi politici/economici come OpenDNS 208.67.222.222 o Quad9 9.9.9.9

Ultima modifica 16 Marzo 2026

L'articolo Protocollo DNS proviene da alfredocentinaro.it.

]]>
Protocollo ARP https://www.alfredocentinaro.it/lezioni/sistemi-reti/protocollo-arp/ Mon, 16 Mar 2026 10:00:18 +0000 https://www.alfredocentinaro.it/?p=8919 Il protocollo ARP si inserisce in una dinamica di rete in cui tutti i pc e i server ragionano con gli indirizzi IP. Il problema è che le reti locali come le LAN ragionano con i MAC address come gli switch che sono i dispositivi chiave di queste reti. Sulla busta del pacchetto IP che ... Leggi tutto

L'articolo Protocollo ARP proviene da alfredocentinaro.it.

]]>
Il protocollo ARP si inserisce in una dinamica di rete in cui tutti i pc e i server ragionano con gli indirizzi IP. Il problema è che le reti locali come le LAN ragionano con i MAC address come gli switch che sono i dispositivi chiave di queste reti.

Sulla busta del pacchetto IP che arriva ad un pc o router, un po’ come una lettera del postino, c’è scritto il nome del destinatario (Indirizzo IP), ma sulle cassette postali non ci sono i nomi, solo dei codici esadecimali di fabbrica (Indirizzo MAC). Per conoscere la corrispondenza tra IP e MAC allora un dispositivo dve fare una richiesta pubblica a tutta la rete per farsi rispondere dal pc che si vede coinvolto nella richiesta e fornisce il suo mac address.

  • ARP Request: È un messaggio in broadcast. Il PC “urla” a tutta la rete locale: “Chi ha l’IP 192.168.1.5? Dimmi il tuo MAC!”.
  • ARP Reply: È un messaggio unicast. Solo il proprietario di quell’IP risponde: “Sono io, il mio MAC è 00:AA:BB…”.

C’è anche il reverse ARP, che fa esattamente il contrario: da un mac vieen chiesto il corrispondente IP

La Tabella ARP (ARP Cache)

Per non urlare ogni volta nella rete locale, i computer e router salvano queste coppie (IP-MAC) in una tabella temporanea chiamata ARP Cache. Se devono inviare un altro pacchetto allo stesso IP, guardano prima lì. La tabella può essere facilmente visibile aprendo una console e digitando il comando arp -a

ARP Poisoning

L’ARP è un protocollo “fiducioso”: i computer accettano risposte ARP anche se non le hanno chieste. Qui entra in gioco l’hacker (Man-in-the-Middle). L’attacco è semplice da realizzare e anche statisticamente uno dei più realizzati nelle reti locali professionali o pubbliche.

Come funziona un attacco quindi?

  • Vittima A: Vuole parlare con un pc o col router della rete per andare su Internet.
  • Attaccante B: Vuole spiare il traffico.
  • Dispositivo C: un pc della rete o router ignaro di tutto
  1. L’attaccante invia un pacchetto ARP alla Vittima A dicendo: “Ehi, io sono il Router! Il mio MAC è [MAC dell’attaccante]”.
  2. Contemporaneamente, invia un pacchetto al Dispositivo C dicendo: “Ehi, io sono la Vittima A! Il mio MAC è [MAC dell’attaccante]”.
  3. Risultato: Entrambi aggiornano le loro tabelle con dati falsi.
  4. Ora, quando la Vittima A vuole inviare dati al dispositivo di rete o router, invia i dati all’attaccante pensando che sia quello corretto. L’attaccante legge tutto e poi inoltra i dati al vero router o il dispositivo vero per non farsi scoprire.

Ultima modifica 16 Marzo 2026

L'articolo Protocollo ARP proviene da alfredocentinaro.it.

]]>
I router https://www.alfredocentinaro.it/lezioni/esami-di-stato/sistemi-reti-esami-di-stato/i-router/ Sun, 22 Feb 2026 22:21:01 +0000 https://www.alfredocentinaro.it/?p=8877 Un router è un dispositivo di rete che connette diversi segmenti di rete (es. una LAN interna a Internet) e indirizza il traffico di dati tra di essi. Funziona a livello Layer 3 (Network Layer) del modello OSI, gestendo indirizzi IP nei vari pacchetti e le tabelle di routing. E’ facile confondersi sulla sua necessità ... Leggi tutto

L'articolo I router proviene da alfredocentinaro.it.

]]>

Un router è un dispositivo di rete che connette diversi segmenti di rete (es. una LAN interna a Internet) e indirizza il traffico di dati tra di essi. Funziona a livello Layer 3 (Network Layer) del modello OSI, gestendo indirizzi IP nei vari pacchetti e le tabelle di routing.

E’ facile confondersi sulla sua necessità a confronto con uno switch. Lo switch agisce a livello Layer 2 della rete, è capace di indirizzare solo i dispositivi locali che sono collegati con esso, considerabili vicini pochi centimetri o metri. Gli switch sappiamo posseggono poche porte, in genere non più di 24, e per poter collegare due pc, o un pc con un server, devono memorizzare gli indirizzi MAC dei dispositivi e richiedono un cavo dedicato per tutti i dispositivi, anche quelli che sarebbero distanti migliaia di km e ad alto traffico.

Il router entra in gioco perché può instradare gli indirizzi IP che per loro natura contengono informazioni più articolate del MAC address, essendo composti da parte rete/host, possono essere organizzati con delle mappe e dei percorsi a salti successivi che avvicinano i pacchetti alla destinazione.

SwitchRouter
5/24 porte ethernet/fibra5/10 porte differenti tecnologie
Costo basso (200/300€)Costo alto (300€/10000€)
Layer 2 (MAC Address)Layer 3 (IP Address)
Collega dispositivi nella stessa reteCollega diverse reti (es. LAN interna a Internet)
Non modifica i dati trasmessiModifica i dati per adattarli al formato della rete di destinazione
Esempio: Collega PC in ufficioEsempio: Collega una LAN interna a Internet
Un router della Cisco, leader per i dispositivi di rete

Modem casalinghi e router

Un errore terminologico molto fastidioso è quello di attribuire ai modem che comunemente usiamo in casa per le nostre connessioni ADSL o fibra il nome router. In realtà il modem fa il modem: converte il segnale di casa nostra in una tecnologia compatibile con la rete del nostro fornitore telefonico/internet. Non ha una funzione di routing vera a propria. Si è vero è un dispositivo multifunzione: contiene un access point per il wifi, uno switch in genere con 4/5 porte oltre che al modem stesso e tutta una genere di funzionalità di firewalling o QoS.

Parte hardware

CPU

Per quanto possa sembrare strano, il router ha praticamente le stesse caratteristiche hardware di un pc anche se non con le stesse necessità tecnologiche: un router ha bisogno di una cpu per processare i pacchetti in transito, ma non ha necessità di far girare un film o un videogame! Le cpu in genere sono modelli semplici come i processori Celeron o Pentium, le serie J o N di Intel per intenderci, con consumi ridotti e scarse competenze multimediali. In alcuni modelli, si ricorre anche alle cpu ARM, che solitamente sono molto efficienti e hanno un risparmi o energetico spinto (preferite quindi sui dispositivi mobili).

RAM

La RAM è presente senza prestazioni elevate, ne capienze enormi. Non è difficile trovare dispositivi con 1 GB, mentre 8 GB probabilmente sono il limite massimo ma su dispositivi che devono gestire carichi di traffico mostruosi ose il router ha anche funzionalità aggiuntive (es firewall).

Dischi

Sicuramente troviamo un hard disk per la gestione del sistema operativo, ma le capienze sono ridotte a poche manciate di Gigabyte.

Altre periferiche

Non sono necessarie scheda video, periferica audio o altri elementi collegabili con porte o slot. Se ci si deve collegare al router per le operazioni di configurazioe, lo si fa da terminali remoti attraverso una scheda ethernet o una porta seriale dedicata sui dispositivi più professionali. Le interfacce di configurazione sono ormai per lo più di tipo web quindi un piccolo server web eroga le varie pagine di configurazione ad un client connesso.

Schede di rete

Ci sono invece le interfacce di rete, in genere in numero esiguo, magari 4/5 che possono essere di tecnologie e prestazioni diverse, dall’ethernet a 10 Gb, alla fibra ottica, raramente troviamo interfacce wireless. Tutte sono però a prestazioni elevate e professionali rispetto alle schede integrate che troviamo nei nostri pc casalinghi, poco affidabili ad onor del vero. Non è difficile vedere schede di rete con dissipatori di calore per le prestazioni elevate.

Un router industriale

Schede TCAM/ASIC

Ci sono delle circuiterie/schede dedicate aggiuntive, le TCAM e ASIC, che sono delle tecnologie per la gestione ottimizzata dei flussi di dati operando in maniera elettronica per velocizzare in modo enorme il processamento dei pacchetti, lasciando libera la CPU di fare altre operazioni software. Le ASIC in particolare hanno prestazioni decisamente elevate rispetto ad un sistema di controllo software dei pacchetti. Le TCAM sono delle memorie molto più performanti delle ram poiché riescono con un ciclo di cpu a individuare una subnet mask o una ACL.

Sistema operativo

Come ogni simil pc, anche i router hanno un sistema operativo. Non dobbiamo aspettarci ovviamente Windows con le sue finestrelle colorate. Sono del tutto inutili su dispositivi così specifici. Il s.o. è ovviamente minimale e presenta nella maggior parte dei casi una console di comando stile dos/bash e/o una interfaccia web per determinate operazioni comuni. Non c’è modo ovviamente di installare software di produttività, giochi, app varie. Avere un sistema dedicato e specifico evita possibili bug o falle per attacchi hacker. Sui dispositivi Cisco è installato il celebre Ciscio IOS.

Tecniche di commutazione

I pacchetti elaborati dal router devono essere inoltrati da una porta di entrata ad una di uscita. Questa tecnica commuta quindi i pacchetti con due differenti modalità.

Store-and-Forward

È il metodo più affidabile. Il router riceve l’intero pacchetto, lo memorizza nel buffer, in genere la RAM o la TCAM e calcola un checksum (CRC), una sorta di “riassunto” o impronta del pacchetto.

  • Vantaggio: Zero errori. Se il pacchetto è corrotto, viene scartato subito.
  • Svantaggio: Latenza elevata, poiché il tempo di elaborazione dipende dalla lunghezza del pacchetto.

Cut-Through

Il router non immagazzina tutto il pacchetto. Inizia a trasmettere il pacchetto non appena legge l’indirizzo di destinazione (i primi byte del pacchetto stesso).

  • Vantaggio: Latenza bassissima.
  • Svantaggio: Propaga pacchetti corrotti, sprecando banda a valle.

Algoritmi di routing

Quello dell’instradamento è un problema molto complesso e ben trattato sui manuali universitari. Ci limitiamo ad indicare come esistano diverse strategie ed algoritmi che i router utilizzano per svolgere il loro compito di indirizzamento. Il primo sistema è il link statico. Questo implica che un collegamento al router successivo è impostato manualmente. In genere è il più veloce e preferito se risponde ai requisiti delle maschere di rete. Un link statico non si aggiorna da solo, deve essere esplicitamente un gestore di rete a rimuoverlo in caso in cui l’altra estremità fallisse o venisse giù. In questi collegamenti si imposta una distanza amministrativa, ovvero l’affidabilità del collegamento massima, in modo che venga preferita rispetto a collegamenti calcolati dai vari algoritmi basati su metriche come banda disponibile, livello di congestione, ed altri indicatori.

Algoritmo di Bellman-Ford (Distance-Vector)

Più semplice ma più lento ad aggiornarsi. E’ il principio fondante del sistema RIP/2. I router non conoscono l’intera topologia, ma solo ciò che dicono i vicini (“routing per sentito dire”). Funziona molto bene ma quando un link cade le tabelle di routing vanno ricalcolate. Usato nelle reti piccole.

Algoritmo di Dijkstra (Link-State)

Usato per trovare il cammino minimo in un grafo con pesi non negativi. Ogni router costruisce una mappa completa della rete (o la rigenera periodicamente). Si parte dal nodo sorgente, si esplorano i vicini e si aggiorna una lista di “distanze minime” provvisorie finché non si è certi del percorso più breve per ogni nodo. E’ la base dell’algoritmo OSPF (Open Shortest Path First), usato su aree di backbone più grandi.

Algoritmi tra AS

I precedenti RIP, OSPF sono classici algoritmi per piccolo reti, identificate come AS. Uno dei problemi forse più critici è come collegare gli AS. Un Autonomous System (AS) è un un insieme di indirizzi IP e reti gestitti da una singola compagnia spesso telefonica o altra azienda preposta. Qui gli algoritmi diventano meno interessanti dal punto di vista didattico. Citiamo EGP (Exterior Gateway Protocol).

Applicazioni aggiuntive

Spesso i router possono aggiungere una serie di funzionalità comode a cominciare dalla distribuzione di indirizzi IP per reti a valle attraverso il classico DHCP. Altri più sofisticati possono includere dei veri firewall a filtraggio di pacchetto o sistemi di Quality Of Service (QoS)

Ultima modifica 10 Marzo 2026

L'articolo I router proviene da alfredocentinaro.it.

]]>
Perché usare attributi privati e metodi di accesso nella OOP https://www.alfredocentinaro.it/lezioni/java/perche-usare-attributi-privati-e-metodi-di-accesso-nella-oop/ Thu, 15 Jan 2026 23:40:38 +0000 https://www.alfredocentinaro.it/?p=8787 Uno dei concetti più strani per chi approccia alla programmazione orientata agli oggetti OOP è quello dell’incapsulamento. Questo prevede la dichiarazione degli attributi privati e la conseguente modifica o reperimento del contenuto di tali attributi solo passando per i metodi di accesso set/get. Ma perché questa è una soluzione vincente? Non sarebbe meglio inserire attributi ... Leggi tutto

L'articolo Perché usare attributi privati e metodi di accesso nella OOP proviene da alfredocentinaro.it.

]]>
Uno dei concetti più strani per chi approccia alla programmazione orientata agli oggetti OOP è quello dell’incapsulamento. Questo prevede la dichiarazione degli attributi privati e la conseguente modifica o reperimento del contenuto di tali attributi solo passando per i metodi di accesso set/get. Ma perché questa è una soluzione vincente? Non sarebbe meglio inserire attributi pubblici e modificarli direttamente? Vediamo un esempio.

La classe Contocorrente

Proponiamo una classe semplicissima: il Contocorrente. La creiamo con pochi elementi essenziali: un id che identifica il conto corrente, il nome e cognome del correntista e un numero decimale che contiene la disponibilità, ovvero liquidità di denaro del conto che possiamo prelevare. Tutti gli attributi li impostiamo a public, quindi accessibili in modo diretto, ovvero possiamo assegnargli dei valori come fossero sostanzialmente variabili normali a cui siamo abituati. Non inseriamo metodi per semplicità e inseriamo solo un costruttore di default.

public class Contocorrente
{
    public String id;
    public String nomeCorrentista;
    public String cognomeCorrentista;
    public double saldo;

    public Contocorrente()
    {
        
    }
}

Creiamo il nostro programma main. Inizializziamo un oggetto Contocorrente attraverso il costruttore vuoto che abbiamo previsto. Se proviamo ad assegnare ai vari attributi dei valori, non abbiamo avvisi o errori. Gli attributi sono impostati pubblici, direttamente accessibili. Lanciamo il nostro programma main: non avremo nessun errore. Per id, nome e cognome non abbiamo particolari criticità, mentre il campo saldo vediamo che possiamo assegnare, aggiungere o sottrarre valori. In effetti l’idea pratica è che possiamo aprire il conto con un certo valore di soldi iniziale, versare sul nostro conto soldi o prelevare soldi sottraendo al totale. Qual è la criticità? Ad ogni operazione di prelievo dovrei controllare se c’è la disponibilità a sottrarre la quantità di denaro richiesta per impedire un prelievo non autorizzato che manderebbe “in rosso” il conto.

void main()
{
    Contocorrente conto = new Contocorrente();

    conto.id = "IT12A1234567890123456789012";
    conto.nomeCorrentista = "Alfredo";
    conto.cognomeCorrentista = "Centinaro";
    conto.saldo= 100.00;

    conto.saldo+= 200.00;
    conto.saldo-= 1000.00;

    IO.print(conto.saldo);
}

Continuiamo con la similitudine di vita reale. Vi immaginate ad uno sportello bancomat che io debba prima controllare il saldo e in base a quello prelevare solo una quantità disponibile di denaro? Vi immaginate quante persone con accesso diretto al prelievo sul saldo si disinteressano della quantità disponibile e ritirano deliberatamente una quantità di denaro non idonea? Consentire un accesso diretto ad un attributo, senza controlli, è pericoloso! Occorre una strategia che filtra l’accesso ad un attributo valutando se i valori che gli vengono assegnati è adeguato o provvedere ad opportune segnalazioni di errore.

Riscriviamo la nostra classe per impedire l’accesso diretto alle variabili. Dichiariamo gli attributi privati con la clausola “private”. Il costruttore invece rimane public, utilizzabile quindi sempre e comunque direttamente.

public class Contocorrente
{
    private String id;
    private String nomeCorrentista;
    private String cognomeCorrentista;
    private double saldo;

    public Contocorrente()
    {
        
    }
}

Lasciamo il main invariato con lo stesso codice. Con buona probabilità l’ide di sviluppo darà già un avviso che qualcosa non va con l’assegnazione diretta di valori ad attributi privati. La cosa sicuramente sarà comprovata se proviamo ad eseguire il codice del nostro main con un messaggio di errore “java: id has private access in Contocorrente” e così per tutti i campi che sto cercando di modificare indiscriminatamente in modo diretto.

Come modifichiamo allora i nostri campi? Abbiamo bisogno di metodi che possono essere invocati con opportuni parametri per modificare o reperire il valore degli attributi. Torniamo sulla nostra classe Contocorrente. Posizioniamo il cursore del mouse sotto il costruttore, premiamo il tasto destro del mouse, nella tendina clicchiamo la voce “Generete…”, selezioniamo la voce “Getter and Setter”. Nella finestrella che si apre selezioniamo con il tasto shift o ctrl tutti gli attributi che vengono mostrati. Avremo un codice automatico come il seguente.

I metodi di accesso set/get

public class Contocorrente
{
    private String id;
    private String nomeCorrentista;
    private String cognomeCorrentista;
    private double saldo;

    public Contocorrente()
    {

    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getNomeCorrentista() {
        return nomeCorrentista;
    }

    public void setNomeCorrentista(String nomeCorrentista) {
        this.nomeCorrentista = nomeCorrentista;
    }

    public String getCognomeCorrentista() {
        return cognomeCorrentista;
    }

    public void setCognomeCorrentista(String cognomeCorrentista) {
        this.cognomeCorrentista = cognomeCorrentista;
    }

    public double getSaldo() {
        return saldo;
    }

    public void setSaldo(double saldo) {
        this.saldo = saldo;
    }
}

Come potete vedere, abbiamo tutto un mondo di nuove funzioni set e get, dove set procede a fare assegnazioni su this.campo, su questo campo in questa classe potremmo quasi tradurre. Get invece restituisce il contenuto di questo attributo. Ogni attributo ha la sua coppia set/get pubblici, ovvero utilizzabili in modo diretto all’interno di un’altra classe o del main.

Qualcuno dirà: e cosa ci abbiamo guadagnato? E’ solo più codice da scrivere ed organizzare. Potenzialmente vero, ma concentriamoci sul metodo set dell’attributo saldo. Riscriviamolo in modo più interessante.

Con la versione attuale del set, il valore dell’attributo saldo viene sovrascritto a forza qualunque sia il suo valore. Se invece usiamo

    public void setSaldo(double saldo) {
        this.saldo += saldo;
    }

Ad un utente distratto sarà impossibile sovrascrivere ma solo aggiungere/versare o sottrarre/prelevare da saldo. Tanto che potremmo rinominare il parametro saldo passato alla funzione con “_soldi” più intuitivo. Ma ancora, proteggiamo il nostro saldo da prelievi che mandano in confusione il sistema. Ovvero voglio impedire di prelevare più di quanto è disponibile, mentre consento qualsiasi versamento.

    public void setSaldo(double _soldi) {
        //gestisco i prelievi non coperti
        if (_soldi < 0 && Math.abs(_soldi) <= this.saldo)
            this.saldo += _soldi;
        
        //se sto versando non faccio controlli particolari
        if (_soldi >0)
            this.saldo += _soldi;
    }

Ci siamo quasi, mettiamo un messaggio in modo tale che sia visibile l’esito dell’operazione se avvenuta con successo o meno per indisponibilità del saldo. Semplifichiamo un po’ anche se non proprio pulito per i puristi del codice e aggiungiamo messaggi forzando però l’uscita dopo l’operazione positiva.

 public void setSaldo(double _soldi) {
        //gestisco i prelievi non coperti
        if (_soldi < 0 && Math.abs(_soldi) <= this.saldo) {
            this.saldo += _soldi;
            IO.println("Operazione avvenuta con successo");
            return;
        }

        //se sto versando non faccio controlli particolari
        if (_soldi >0) {
            this.saldo += _soldi;
            IO.println("Operazione avvenuta con successo");
            return;
        }

        IO.println("Operazione fallita");
    }

e creiamo un main per testare i messaggi e le relative operazioni.

void main()
{
    Contocorrente conto = new Contocorrente();

    conto.setId("IT12A1234567890123456789012");
    conto.setNomeCorrentista("Alfredo");
    conto.setCognomeCorrentista("Centinaro");


    conto.setSaldo(100.00);
    conto.setSaldo(200.00);
    conto.setSaldo(-1000.00);

    conto.setSaldo(-50.00);

    IO.print("Saldo: " + conto.getSaldo());
}

L’operazione di aggiunta di 100 e 200 stampa messaggi positivi, mentre il prelievo di -1000 non passa, contrariamente al prelievo di 50 che invece è disponibile.

Possiamo ancora di più oltre che semplificare il controllo sulla disponibilità, possiamo anche potenziare il nostro metodo tornando un valore non void ma boolean, vero se possiamo eseguire l’operazione, falso se non è andato a buon fine il prelievo. Non ci resta che provare questa moltitudine di controlli nel nostro main. Usiamo una variabile boolean per gestire l’esito e stampare un messaggio a video descrittivo dell’esito.

void main()
{
    Contocorrente conto = new Contocorrente();
    boolean esito;

    conto.setId("IT12A1234567890123456789012");
    conto.setNomeCorrentista("Alfredo");
    conto.setCognomeCorrentista("Centinaro");


    esito = conto.setSaldo(100.00);
    if (esito)
        IO.println("Versamento 100.00 ok");

    esito = conto.setSaldo(200.00);
    if (esito)
        IO.println("Versamento 200.00 ok");

    esito = conto.setSaldo(-1000.00);
    if (esito)
        IO.println("Prelievo 10000 ok");
    else
        IO.println("Prelievo 10000 fallito");

    esito = conto.setSaldo(-50.00);
    if (esito)
        IO.println("Prelievo 50 ok");
    else
        IO.println("Prelievo 50 fallito");

    IO.print("Saldo: " + conto.getSaldo());
}

Ovviamente il prelievo di 1000 che non sono disponibili sul conto con 300 da messaggio errore mentre il prelievo di 50 su 300 va in porto.

Conclusioni

Abbiamo visto come i metodi di accesso set/get impediscano di modificare esplicitamente un attributo e consentano, dove necessario, un sistema complesso di controlli di valori non consentiti. Il tutto in modo trasparente all’utente che usa la classe nel main non abbia da fare controlli ulteriori espliciti. Questo è molto comodo: se la classe è ben documentata e generica potrà essere riusata in altri software vista la robustezza.

I dati sono sempre integri. con un congruo numero di controlli nessun utente sbadato può creare caos nei dati immessi.

Se volessi modificare il controllo con soglie o altre strategie, mi basta modificare una tantum il metodo, non tutti i punti dove viene valorizzato l’attributo. Questa è una grande cosa per la manutenzione del software quando sale la complessità.

Astrazione: se usi la classe nel tuo main, l’utente non deve conoscere le regole bancarie, quindi è a prova di errore!

Potete provare a modificare i metodi testuali controllando la validità dei valori inseriti e stampare un messaggio magari direttamente dentro il metodo set senza scomodare variabili booleane.

Ultima modifica 23 Gennaio 2026

L'articolo Perché usare attributi privati e metodi di accesso nella OOP proviene da alfredocentinaro.it.

]]>
Il livello di rete e gli indirizzi IP https://www.alfredocentinaro.it/lezioni/esami-di-stato/sistemi-reti-esami-di-stato/il-livello-di-rete-e-gli-indirizzi-ip/ Wed, 05 Nov 2025 16:32:41 +0000 https://www.alfredocentinaro.it/?p=8734 Il modello OSI resta un riferimento didattico fondamentale per comprendere come i dati viaggiano nelle reti. In questo articolo esploreremo in dettaglio il terzo livello, il livello di rete, con particolare attenzione al ruolo degli indirizzi IP, al motivo per cui il livello 2 (data link) non basta più da solo, e come l’evoluzione del ... Leggi tutto

L'articolo Il livello di rete e gli indirizzi IP proviene da alfredocentinaro.it.

]]>
Il modello OSI resta un riferimento didattico fondamentale per comprendere come i dati viaggiano nelle reti. In questo articolo esploreremo in dettaglio il terzo livello, il livello di rete, con particolare attenzione al ruolo degli indirizzi IP, al motivo per cui il livello 2 (data link) non basta più da solo, e come l’evoluzione del “classful” verso “classless” ha permesso di superare limiti importanti. Sono appunti ampiamente presenti nei libri di testo, qui cerchiamo una sintesi per gli alunni con allergia al libro.

Il livello MAC e i suoi limiti

Il secondo livello della pila ISO/OSI, denominato Data Link (o più sinteticamente livello MAC, approssimandolo con uno dei suoi due sottostrati), è molto potente perché ci permette di costruire reti locali LAN.

Il cuore pulsante di tali reti, come abbiamo visto, sono gli switch: dispositivi dotati di una memoria interna che registra i MAC address (indirizzi fisici univoci cablati sulle schede di rete Ethernet, WiFi e altre interfacce) per poter indirizzare le comunicazioni tra le postazioni che intendono dialogare.

Queste postazioni sono quindi collegate logicamente ma devono trovarsi anche geograficamente vicine nella stessa rete locale, che sia una scuola, una casa o un ufficio. Il motivo è semplice: i dispositivi sono collegati fisicamente allo switch e i cavi Ethernet o i ponti radio hanno limiti di distanza ben definiti (circa 100 metri per il cavo Ethernet Cat5e/6, alcune centinaia di metri per i ponti WiFi).

Ma allora, se volessi collegarmi a un server di Meta negli USA, o a un qualsiasi server italiano come ansa.it o gazzettadellosport.it, cosa dovrei fare con questa tecnologia?

Idealmente dovrei stendere un cavo dal server in questione fino al mio switch locale, in modo che questo registri il MAC address del server e lo renda accessibile alle postazioni collegate. Se rileggete l’ultima frase, troverete assurdo e impossibile pensare che un grande server, raggiunto anche solo da un migliaio di utenti al giorno, possa stendere fisicamente cavi verso ogni singolo utente o switch nel mondo.

La Soluzione: Il Livello di Rete

È evidente che occorre una tecnologia capace di:

  • Connettere non due singoli nodi, ma intere reti geograficamente distanti
  • Farlo mantenendo i vantaggi della commutazione di pacchetto
  • Superare i limiti fisici delle LAN

Ed è proprio qui che entra in gioco il livello 3 (Network) della pila ISO/OSI, con il protocollo IP e i dispositivi router, che permettono l’interconnessione di reti diverse attraverso Internet.

Gli indirizzi IP

Come posso quindi identificare un dispositivo, un pc, un server su una rete geografica? Occorre un principio simile a quello che permette di individuare una casa, un’abitazione ma con un linguaggio che sia più semplice per una macchina. L’indirizzo IP è perfetto, rappresenta quelli che sono via e numero civico di un indirizzo ma in formato numerico decisamente più gestibile da un calcolatore. Infatti ha questo formato

Quattro cifre separate da punti che vanno da 0.0.0.0 a 255.255.255.255 => es 192.168.1.103 oppure 158.47.255.118 ecc

Perché proprio da 0 a 255? In realtà se il numero non lo penso in decimale ma in binario le varie combinazioni le possiamo riscrivere con 00000000 a 11111111 ovvero 8 cifre dove 00000000 è davvero zero decimale mentre 11111111 è proprio 255.

Es. 74.125.43.99 => 01001010.01111101.00101011.01100011

Sono attualmente state introdotte due versioni la IPv4, ovvero quella appena elencata e la IPv6. La IPv4 si trova facilmente che ha 32 bit che possono valere 0 o 1 quindi 232 combinazione che equivalgono 4.294.967.296, 4 miliardi circa insomma. Non tantissimi se pensiamo a quanti dispositivi sono nelle nostre case (smartphone, tv, pc, tablet, altri dispositivi smart) nella solo porzione di mondo Europa e Usa, 700milioni e 400milioni circa. Il numero dell’IPv4 si è rivelato essere troppo limitato già negli anni ’90. La versione IPv6 di bit ne ha 128 e non è esattamente un numero semplice da scrivere 2128, con i suoi 38 zeri. Di certo un numero congruo con le esigenze odierne. 128 bit non sono espressi nel formato ad ottetti ma esadecimale in 8 gruppi di 16 bit.

Es. 2001:0db8:0000:0000:0000:8a2e:0370:7334 o eliminando gli zeri2001:0db8::8a2e:0370:7334

Gli ultimi 2 gruppi, sono 32 bit e spesso sarà possibile vederla non con due gruppi esadecimali ma proprio un indirizzo IPv4 per retrocompatibilità.

Es. ::ffff:192.168.32.1 o in forma estesa 0000:0000:0000:0000:0000:ffff:192.168.32.1

Classificazione Classful

Nel 1981 fu introdotta una classificazione arbitraria degli indirizzi IP, mantenuta ufficialmente in vita fino al 1993 quando fu abbandonata per evidenti limiti tecnici che vedremo dopo e il passaggio al classless (CIDR).. La classificazione prevedeva 5 classi, tre realmente di interesse, dove venivano assegnate due componenti: un certo numero di bit per indicare la rete di appartenenza, come se fosse un po’ la via di un certo indirizzo, e la parte host che identifica il dispositivo un po’ come fosse il numero civico che si affaccia alla via appena detta. E’ una classificazione “di ufficio”, non c’è molto da capire in realtà.

Limiti della Classful

Riproponiamo gli host per ogni classe:

ClassePrimi bitRange IPNetmask predefinitaN° retiHost per rete
A00.0.0.0 – 127.255.255.255255.0.0.0 (/8)128~16 milioni
B10128.0.0.0 – 191.255.255.255255.255.0.0 (/16)~16 mila~65 mila
C110192.0.0.0 – 223.255.255.255255.255.255.0 (/24)~2 milioni254

Questo implicava che se una scuola come la nostra con 300 pc vari avesse voluto indirizzi IP per ognuno, non sarebbe bastato un indirizzo di classe C. Ma per indirizzare 300 dispositivi, si sarebbe sprecato oltre 64000 indirizzi Un piccolo ufficio con una decina di PC certamente avrebbe scelto come adeguato un indirizzo di classe C ma anche qui una decina di indirizzi e oltre 240 sprecati. Questo sistema con classi \8 \16 \24 non prevedeva neanche la possibilità di ulteriormente segmentare, suddividere, una rete in sottoreti. Un disastro, a ridosso degli anni 80.

Incompatibilità con l’espansione di Internet

Il sistema classful funzionava bene negli anni ’80, ma con la crescita di Internet servivano reti di dimensione variabile, non solo “piccole, medie o grandi”. Serviva un modo per aggregare indirizzi (route summarization) e ridurre le dimensioni delle tabelle di routing dei vari router che andavano a diffondersi in modo crescente.

Tutto questo portò nel 1993 all’introduzione del CIDR (Classless Inter-Domain Routing), che supera tutti i limiti del classful permettendo:

  • subnet mask variabili (/8, /12, /19, /27, ecc.),
  • assegnazione più flessibile,
  • routing più efficiente
  • possibilità di creare sottoreti all’occorrenza

Oggi esiste ancora la necessità di riconoscere le classi ma perde il meccanismo bloccante.

Ultima modifica 15 Gennaio 2026

L'articolo Il livello di rete e gli indirizzi IP proviene da alfredocentinaro.it.

]]>
Server/Client socket TODO in Python https://www.alfredocentinaro.it/lezioni/sistemi-operativi/programmazione-python/server-client-socket-todo-in-python/ Wed, 22 Oct 2025 15:54:02 +0000 https://www.alfredocentinaro.it/?p=8641 Un classico esercizio che anche qui sul sito abbiamo visto in molti linguaggi e tecnologie. Questa volta lo affrontiamo in python utilizzando socket e dinamica client/server. Non teniamo conto di multi client con concorrenza ma vogliamo realizzare un client capace di inviare ad un server una sorta di lista delle cose da fare specificando un ... Leggi tutto

L'articolo Server/Client socket TODO in Python proviene da alfredocentinaro.it.

]]>
Un classico esercizio che anche qui sul sito abbiamo visto in molti linguaggi e tecnologie. Questa volta lo affrontiamo in python utilizzando socket e dinamica client/server. Non teniamo conto di multi client con concorrenza ma vogliamo realizzare un client capace di inviare ad un server una sorta di lista delle cose da fare specificando un testo/descrizione, una tipologia prefissata ed una durata numerica in ore. Il server è capace di archiviare la lista ed eseguire le operazioni di cancellazione, visualizzazione, individuare il task con durata maggiore, minore o una media dei valori archiviati.

La classe Task

Gli esercizi precedenti, come Server/Client prenotazione in python o Server/Client Ping Pong in Python erano piuttosto semplici. Al di la dello scambio di messaggi client/server i dati e comandi inviati erano semplici e le informazioni archiviate riconducibili a dati primitivi (un singolo testo, piuttosto che un valore intero per i posti). Qui facciamo un passetto in più perché per ogni dato gestito dal server, abbiamo bisogno di un contenitore più strutturato come una classe. Proponiamo quindo la classe Task che conterrà descrizione. tipologia, durata e un timestamp per annotare quando tale task è stato creato. Per semplicità abbiamo scritto i metodi set e un metodo per tornare una stringa completa di tutti i gli attributi di un oggetto Task.

from Tipologia import Tipologia
import time

class Task:
    def __init__(self, _descrizione: str = '', _tipologia: Tipologia = Tipologia.CASA, _durata: int = 0):
        self.descrizione = _descrizione
        self.tipologia = _tipologia
        self.durata = _durata
        self.timestamp = time.time()

    def setDescrizione(self, _descrizione: str) ->None:
        self.descrizione = _descrizione

    def setTipologia(self, _tipologia: Tipologia) ->None:
        self.descrizione = _tipologia   

    def setDurata(self, _durata: int) ->None:
        self.durata = _durata

    def toString(self) ->str:
        return self.descrizione + " " + self.tipologia + " " + str(self.durata) + " " + str(self.timestamp)

La tipologia poi la possiamo gestire come stringa semplice, oppure utilizzare un classico enum che in realtà in python si scrivi comunque con una particolare classe

from enum import Enum

class Tipologia(Enum):
    CASA = 1
    LAVORO = 2
    HOBBY = 3

Il server

A questo punto siamo pronti a scrivere il nostro server. Tra i suoi attributi spicca il dizionario che conterrà un indice numerico progressivo e il relativo oggetto Task.

Tra i metodi, definiamo un metodo toString() che altro non fa che restituire una stringa composta dall’ id numerico progressivo dell’elenco più le informazioni contenute all’interno del Task stesso

    def toString(self) -> str:
        stampa = ''
        for id,task in self.elenco.items():
            stampa += str(id) +" " +task.toString()
        return stampa

A differenza degli esercizi citati, per capire quale comando viene inviato dal client facciamo un ulteriore variazione possibile. Prelevo una sottostriga da sinistra di 4 caratteri, lunghezza massima del comando LIST e QUIT, mentre gli altri ADD, DEL, MIN MAX, AVG sono tutti lunghi tre caratteri. Se la sosttostringa presenta quindi uno spazio, si tratterà sicuramente di un comando da tre lettere e mi basta eliminare lo spazio vuoto.

 comando = messaggio[:4].strip() #prendo i primi 4 caratteri e al limite tolgo lo spazio vuoto se sono 3

Poi quindi procedo con il solito costrutto match per eseguire le richieste di routine e rispondere al client. Non commentiamo più di tanto le operazioni per individuare min, max e media avg perché consistono banalmente nei meccanismi base di scorrere l’intero dizionario, estrapolare il task e la sua durata per poter individuare o sommare per la richiesta.

import socket
from Task import Task

class ServerTask:
    def __init__(self, _host="127.0.0.1", _porta =5000):
        self.host = _host
        self.porta = _porta
        self.servizio = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
        self.id = 0  #id progressivo dei task
        self.elenco = {} #dizionario dove aggiungere i task
    

    def toString(self) -> str:
        stampa = ''
        for id,task in self.elenco.items():
            stampa += str(id) +" " +task.toString()
        return stampa
        
    def start(self) -> None:
        self.servizio.bind((self.host,self.porta))
        self.servizio.listen(1)
        print("Servizio attivo su ", self.host, self.porta)

        while True:
            connessione, indirizzo = self.servizio.accept()
            print("Connessione ricevuta da ", indirizzo)
            messaggio = connessione.recv(1024).decode()
            
            comando = messaggio[:4].strip() #prendo i primi 4 caratteri e al limite tolgo lo spazio vuoto se sono 3
            
            match comando:
                case "ADD":
                    comando, testo, tipologia, giorni = messaggio.split(" ",4)
                    self.id += 1
                    task = Task(testo,tipologia, int(giorni))
                    self.elenco[self.id] = task #aggiungo un nuovo task
                    risposta = "OK:" + str(self.id) + " ts="+ str(task.timestamp)
                    connessione.send(risposta.encode())

                case "DEL":
                    comando, id = messaggio.split(" ",2)
                    if self.elenco[id]: 
                        del self.elenco[id]
                        connessione.send("OK".encode())
                    else:
                        connessione.send("ERR non trovato".encode())

                case "LIST":
                    lista = self.toString()
                    connessione.send(lista.encode())

                case "MIN":
                    giorno_min = -1  #o metti un numero enorme o forzi il primo giro
                    for id,task in self.elenco.items():
                        if task.durata < giorno_min or giorno_min == -1:
                            giorno_min = task.durata
                    
                    connessione.send(str(giorno_min).encode())
                    
                case "MAX":
                    giorno_max= 0
                    for id,task in self.elenco.items():
                        if task.durata > giorno_max:
                            giorno_max = task.durata
                    
                    connessione.send(str(giorno_max).encode())

                case "AVG":
                    somma_giorni= 0
                    for id,task in self.elenco.items():
                        somma_giorni += task.durata

                    avg = somma_giorni / len(self.elenco)
                    
                    connessione.send(str(avg).encode())

                case "QUIT":
                    print("Connessione in chiusura...")
                    connessione.close()
                    exit()
                        
                case "_":  # restituisce un errore
                    pass


if __name__ == "__main__":
    server = ServerTask()
    server.start()

Ovviamenete è fortemente migliorabile, valutando opportunamente la correttezza dei dati inseriti, dell’enum.

Il client

In realtà il client assomiglia a tutti gli esercizi che abbiamo visto sulla gestione dei socket python. Si limita a chiedere all’utente una lista comando ed inviarla al server. Non facciamo particolari controlli di corretta e valida immissione del comando e dei suoi parametri. Tale funzionalità per il momento non la implementiamo se non in parte sul server.

import socket

class ClientTask:
    def __init__(self, _host = "127.0.0.1", _porta = 5000):
        self.host = _host
        self.porta = _porta
        self.servizio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

    def start(self) -> None:
        print("Comandi ADD/DEL/LIST/MIN/MAX/AVG/QUIT")
        print("es. ADD pippo CASA 1, DEL 4, LIST, QUIT")

        comando = ""
        while True and comando != "QUIT":
            self.servizio = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            self.servizio.connect((self.host,self.porta))
            comando = input(">")    
            self.servizio.send(comando.encode())
            risposta = self.servizio.recv(1024).decode()            
            print(risposta)
        
        self.servizio.close()
        print("Connesione chiusa")

if __name__ == "__main__":
    client = ClientTask()
    client.start()

Ultima modifica 15 Gennaio 2026

L'articolo Server/Client socket TODO in Python proviene da alfredocentinaro.it.

]]>
Concetti di programmazione multiprocesso https://www.alfredocentinaro.it/lezioni/sistemi-operativi/concetti-di-programmazione-multiprocesso/ Fri, 24 Oct 2025 06:55:26 +0000 https://www.alfredocentinaro.it/?p=8638 I processi Sui nostri pc siamo abituati ad installare diversi software o programmi. Questi rimangono sul disco fisso sopiti finche non decidiamo di eseguirli. Quando esegui un programma sul computer, questo non è più solo un file statico su disco, ma parte di esso o tutto viene spostato nella ram ovvero la memoria di lavoro: ... Leggi tutto

L'articolo Concetti di programmazione multiprocesso proviene da alfredocentinaro.it.

]]>
I processi

Sui nostri pc siamo abituati ad installare diversi software o programmi. Questi rimangono sul disco fisso sopiti finche non decidiamo di eseguirli. Quando esegui un programma sul computer, questo non è più solo un file statico su disco, ma parte di esso o tutto viene spostato nella ram ovvero la memoria di lavoro: diventa un processo.

Un processo è un’istanza di un programma in esecuzione. Il processo non è più statico ma si evolve, modificando le sue variabili, eseguendo le righe di codice con cui è stato programmato. Pensa a un programma (il codice) come una ricetta e al processo come lo chef che la sta eseguendo: ha gli ingredienti, gli utensili e sta attivamente cucinando.

Ogni processo necessita di risorse (CPU, memoria, file aperti, dispositivi vari come scheda video o la stampante) per operare. Chiaramenete la CPU è quella più importante e preziosa, che richiede uno scheduling accurato. (approfondisci qui Scheduling)

Il pcb, le memorie stack e heap

Ogni processo è gestito dal sistema operativo che tiene traccia delle sue informazioni vitali tramite il PCB (Process Control Block).

  • PCB (Process Control Block): Viene detto il descrittore del processo. È una struttura dati che contiene tutte le informazioni necessarie per gestire e controllare un processo. È come la carta d’identità del processo e include:
    • Stato del processo (in esecuzione, pronto, bloccato, ecc.).
    • Program Counter (l’indirizzo della prossima istruzione da eseguire).
    • Registri della CPU (per chi ha avuto la possibilità di studiare l’Assembly i 64-bit general-purpose registers (RAX, RBX, RCX, RDX, RSI, RDI, RSP, RBP).
    • Informazioni sulla gestione della memoria.
  • Memoria del Processo: La memoria allocata al processo è tipicamente divisa in sezioni:
    • Code/Text: Contiene il codice eseguibile del programma.
    • Data: Contiene le variabili globali e statiche.
    • Stack: Usato per gestire le chiamate a funzione (e i loro valori di ritorno) e per le variabili locali e i parametri di funzione. Cresce e decresce in modo automatico.
    • Heap: Usato per l’allocazione dinamica della memoria (es. con malloc o new nei linguaggi di programmazione ad oggetti). La memoria qui deve essere gestita esplicitamente dal programmatore altrimenti è facile creare errori di sistema.

Primi Programmi e Architettura Monocore

Nei primi computer, le CPU erano monocore (avevano un solo “cervello” o unità di calcolo). I programmi venivano eseguiti in modo sequenziale e top-down.

  • Programmazione Top-Down: Si parte da un problema generale (il top) e lo si scompone in sottoproblemi più piccoli e gestibili. Questo corrisponde spesso a suddividere il codice in funzioni o sottoprogrammi che vengono richiamati in sequenza.

In un sistema monocore, in ogni istante di tempo, solo un processo può essere effettivamente in esecuzione sulla CPU.

Multitasking e l’illusione del Parallelismo

Sebbene la CPU sia monocore, un utente può aprire contemporaneamente il browser, un editor di testo e un lettore musicale. Questo è reso possibile dal multitasking implementato dal sistema operativo.

  • Multitasking: È la capacità di un sistema operativo di eseguire più processi “contemporaneamente” (o almeno di darne l’illusione).
  • Illusione del Parallelismo: Il sistema operativo utilizza uno scheduler che interrompe un processo in esecuzione dopo un brevissimo intervallo di tempo (chiamato time slice o quanto), salva il suo stato (nel PCB) e carica lo stato di un altro processo (operazione chiamata context switch).
  • Questo cambio rapido e continuo tra i processi è così veloce che all’occhio umano sembra che stiano lavorando tutti nello stesso momento, creando l’illusione del parallelismo.

La Diffusione dei Multicore (Anni ’90/’00) 🚀

Con l’aumento della richiesta di potenza di calcolo e i limiti fisici nell’aumentare la velocità di un singolo core, l’industria si è mossa verso l’integrazione di più unità di calcolo nello stesso chip.

  • Multicore: Architettura in cui un singolo processore fisico contiene più core di elaborazione indipendenti (es. Dual-Core, Quad-Core, ecc.).

La diffusione di queste architetture, in particolare dagli anni 2000, ha reso possibile il vero parallelismo.

Multiprocesso: Fork, Join e Problemi

L’architettura multicore ha permesso di passare dal multitasking (falso parallelismo) al multiprocessing (vero parallelismo).

  • Multiprocesso: L’esecuzione di più processi contemporaneamente su core diversi.
  • Fork (Biforcazione): È una primitiva del sistema operativo che permette a un processo padre di creare una copia esatta di sé stesso (il processo figlio). Padre e figlio sono processi distinti che possono poi eseguire codice diverso in parallelo.
  • Join (Attesa): È la primitiva usata dal processo padre per attendere la terminazione di uno o più processi figli prima di continuare la propria esecuzione.

Problemi con il Multiprocesso

Sebbene il multiprocessing garantisca il parallelismo, ha degli svantaggi:

  1. Context Switch Costoso: Passare da un processo all’altro richiede il salvataggio e il caricamento di tutto il PCB e della mappa di memoria, che è un’operazione lenta per la CPU.
  2. Il fork di processi crea una copia in memoria del processo da padre a figlio. Questo implica un raddoppio nell’uso della memoria per contenere variabili e le varie istruzioni/righe di codice, nonché un sistema per eseguire solo parte di esse in base al fatto che sia in esecuzione il padre o il figlio.
  3. Condivisione Difficile: Per far comunicare due processi (padre e figlio) è necessario usare meccanismi specifici come pipe, socket o memoria condivisa, poiché per default la loro memoria è separata e protetta.

La Nascita dei Thread: L’Evoluzione Leggera

Per superare i problemi di lentezza e difficoltà di comunicazione del multiprocessing, è stato introdotto il concetto di thread.

  • Thread (o LWP – Lightweight Process): È un flusso di esecuzione all’interno di un processo. Il thread esegue solo una piccola parte del codice del processo, solitamente una funzione separata o separabile dal flusso principale.
  • Multithreading: È la capacità di un singolo processo di eseguire più thread contemporaneamente.

Vantaggi dei Thread

I thread sono la soluzione ideale per il parallelismo perché:

  1. Context Switch Veloce: Il cambio tra thread dello stesso processo è molto più veloce del cambio tra processi.
  2. Condivisione Facile: Tutti i thread all’interno di un processo condividono la stessa area di memoria (Code, Data, Heap) e i file aperti. Ogni thread ha però il proprio Stack e il proprio Program Counter.

In sintesi, un processo è come un condominio con le sue fondamenta (memoria condivisa), mentre i thread sono gli inquilini che vivono lì, ognuno con la propria pila di lavoro (Stack) ma che possono facilmente accedere alle aree comuni.

Ultima modifica 15 Gennaio 2026

L'articolo Concetti di programmazione multiprocesso proviene da alfredocentinaro.it.

]]>
Annotazioni di tipo in Python https://www.alfredocentinaro.it/lezioni/python/python-base/annotazioni-in-python/ Tue, 21 Oct 2025 22:18:01 +0000 https://www.alfredocentinaro.it/?p=8631 Python è un linguaggio molto facile da apprendere anche per i non informatici, grazie alla pulizia della sua sintassi, alla non tipizzazione dei dati e all’eliminazione di molti elementi ostici come punti e virgola, tipi di ritorno espliciti, ecc. In realtà, quando facciamo scripting rapido, queste caratteristiche sono molto desiderabili, ma se ci imbarchiamo in ... Leggi tutto

L'articolo Annotazioni di tipo in Python proviene da alfredocentinaro.it.

]]>
Python è un linguaggio molto facile da apprendere anche per i non informatici, grazie alla pulizia della sua sintassi, alla non tipizzazione dei dati e all’eliminazione di molti elementi ostici come punti e virgola, tipi di ritorno espliciti, ecc. In realtà, quando facciamo scripting rapido, queste caratteristiche sono molto desiderabili, ma se ci imbarchiamo in codice più complesso e strutturato, avere degli ausili per tenere traccia dei tipi di dato nei parametri potrebbe essere vincente. Per questi motivi, dalle versioni 3.5 e 3.6 sono state introdotte delle annotazioni di tipo (type hints), non obbligatorie, che non vengono utilizzate in fase di parsing/compilazione ma sono preziose per una lettura facilitata del codice. Vediamone alcune utili per il livello scolastico.


Ci sono anche alcuni plugin come Pylance di Visual Studio Code che leggono le annotazioni e danno suggerimenti non bloccanti utili allo sviluppatore. Pycharm, da IDE strutturato, è già compatibile.

Tipi di variabili

Il tipo di variabile sappiamo non essere obbligatorio, ma possiamo annotare il contenuto che verrà posto in fase di inizializzazione contestuale o semplicemente successiva. Attenzione alla seconda dicitura: se provo a stampare pluto mi darà comunque errore di variabile non inizializzata; l’annotazione non dà nessun valore predefinito.

pippo = 10   #classico
pippo: int = 10   #con annotazione

pluto: int   #errore

Ovviamente si possono usare i tipi floot, str, bool, meno usati bytes e Any.

Il tipo Any è particolarmente utile quando non sappiamo quale tipo di dato riceveremo, ma andrebbe usato con parsimonia perché vanifica i vantaggi delle annotazioni.

from typing import Any

dato_generico: Any = "potrebbe essere qualsiasi cosa"
dato_generico = 42  # nessun problema

Liste e dizionari

Anche su dati strutturati le annotazioni ci aiutano, almeno in fase di dichiarazione.

nomi: list[str] = ["pippo", "pluto"]    # lista di stringhe
eta: dict[str, int] = {"mario": 30}     # dizionario con chiavi string e valori int
numeri: list[int] = [1, 2, 3, 4, 5]     # lista di interi

Funzioni

Sicuramente le annotazioni più interessanti sono quelle del tipo di ritorno nelle funzioni. Python, sappiamo, può restituire uno o più valori semplicemente ponendo la parola return seguita da una o più variabili, di cui viene passata la copia. Nella definizione della funzione si riconosce solo la parola chiave def e i parametri; non è infatti necessario specificare di quale tipo (int, string, char, ecc.). Nello scripting veloce è comodo e desiderabile tale semplificazione, ma in un codice con classi e numerosi metodi/funzioni può risultare utile specificarlo.

def potenza(base: int, esponente: int = 2) -> int:
    return base ** esponente

def saluta(nome: str) -> str:
    return f"Ciao, {nome}!"

#funzioni senza ritorno
def stampa_messaggio(testo: str) -> None:
    print(testo)

Attenzione: Python può restituire anche più di un valore per funzione, ma non c’è un’annotazione diretta, quindi si usa il tipo tuple.

def coordinate() -> tuple[int, int]:
    return 10, 20

def dati_studente() -> tuple[str, int, float]:
    return "Mario", 18, 7.5  # nome, età, media

Interessante è la possibilità di un ritorno di tipo alternativo, magari condizionato da un’istruzione if. Occhio, potrebbe essere una pratica pericolosa in molti casi, da usare con cautela

def valore(x: str) -> int | None:
    if x.isdigit():
        return int(x)
    return None

Ultima modifica 14 Gennaio 2026

L'articolo Annotazioni di tipo in Python proviene da alfredocentinaro.it.

]]>