codice:
import java.io.*;
import java.net.*;

class Serv {
  final static int    SECS = 1;
  final static int    PORT = 12345;

  public static void main (String args[]) {
    ServerSocket welcomeSocket = (ServerSocket)null;
    Socket       serviceSocket = (Socket)null;

    try {
      welcomeSocket = new ServerSocket(Serv.PORT);
      while (true) {
        serviceSocket = welcomeSocket.accept();//assegna una service socket ad ogni richiesta di connessione
        Fork serviceThread = new Fork(serviceSocket);
        serviceThread.start();
      }
    } catch(Exception e) { e.printStackTrace(); }
  }
}

class Fork extends Thread { //questo thread genera un server multithreading
  private Socket   sock;

  public Fork(Socket sock) { //
    this.sock = sock;
  }

  public void run() {
    try{          //probabilmente logs riguarda il loging dei comandi
      Logs         l = new Logs(this.sock);

      InputStream  i = this.sock.getInputStream(); //canale di input per le richieste dal client
      OutputStream o = this.sock.getOutputStream(); //can output

      Data d = new Data(i, l); // a questo thrad passiamo il canale di input, serve per mandare mess al client
      Comp c = new Comp(o, l); // a questo thread passiamo il can di output, gestisce la com di ritorno dal client
             //sia data che comp hanno in ingresso l,logs, perciò lasceranno traccia del loro operato in qualche modo
      d.comp = c; // è la maniera per far conoscere al thread d chi è il thread c
      
      
      c.data = d; //viceversa; i due thread dovranno probabilmente cooperare tra loro, qusta è la loro presentazione
   //crea la musica è li invia al client
      d.start();
      c.start();

    } catch (Exception e) { e.printStackTrace(); }
  }
}

class Comp extends Thread {//immette nel canale di output delle note ottenute da un calcolo casuale basato sul i valori
  OutputStream chan;   //di data, il canale di inputstream.
  Logs         logs;
  Data         data;

  public Comp(OutputStream chan, Logs logs) {
    this.chan = chan;
    this.logs = logs;
  }

  public void run() {
	  
    this.logs.log("Comp started");
    try {
      while (true) {
        while (this.data.isOn()) { // quando isOn + true
          int alt[] = this.data.getAlt();//getalt e getdut restituiscono un array di valori, per le alt e dur: il min e il max valore
          int dur[] = this.data.getDur();
          Note note = new Note(alt[0], alt[1], dur[0], dur[1]);
          note.xmit(this.chan); //stampa i valori delle note ottenute da random tra val min e max.In ingresso ha una variabile di output
          this.logs.log("Note " + Note.qty +
	                    " = ("+ note.alt + "," + note.dur + ")");
        }
        yield();// da CERCARE SUL MANUALE appartiene alla classe thread visto che siamo nella classe therad e non è estensione di nient'altro
      } //yield---> fa uscire il therad corrente dalla cpu(se un processo ne ha più bisogno lui cede il posto)
    } catch (Exception e) {
        this.logs.log("Exiting - Connection reset");
	this.stop();
    }
  }
}

class Note {
  static int qty = 0;
  int alt;
  int dur;

  public Note(int minalt, int maxalt, int mindur, int maxdur) {
    Note.qty++; // dopo incrementa la quantità, è una variabile static, perchè viene invocata da nota.blabla
    alt = minalt + (int)(Math.random()*(maxalt - minalt));
    dur = mindur + (int)(Math.random()*(maxdur - mindur));
  }
  
  public void xmit(OutputStream o) throws Exception {
      o.write((int)this.alt);
      o.write((int)this.dur);
      Thread.sleep(10*this.dur);
  }
}

class Data extends Thread {
  InputStream chan;
  Logs        logs;
  Comp        comp;

  int MINALT = 36;// il range delle altezze è di 4 ottave
  int MAXALT = 84;
  int MINDUR = 25;
  int MAXDUR = 75;
  boolean ON = false; //sembra essere un interruttore, inizialmente è spento

  private int minalt;//possono essere variabili temporanee cn cui modificare il valore degli omonimi
  private int maxalt;
  private int mindur;
  private int maxdur;

  public Data(InputStream chan, Logs logs) {
    this.chan = chan;
    this.logs = logs;
  }

  public void run() {
    this.logs.log("Data started");
    try {
      int b;  // il primo byte è un comando, il 2° pure,
      while ((b=this.chan.read()) != -1) {//fintantochè la read non legge -1, ovvero finchè è diverso da -1, che è l'end of file(del canale di input).
      this.logs.log("Command = " + b);
        switch (b) {// b è il valore che leggiamo dalla read
          case 0: setOff(); //se leggiamo ,dalla read, 0, facciamo un set off, spegnamo il dispositivo
	              this.logs.log("Stopped @ note: " + Note.qty);
                  break;
          case 1: setOn();//se leggiamo ,dalla read, 1, facciamo un set on, accendiamo il dispositivo
	              this.logs.log("Resumed @ note: " + Note.qty);
                  break;
          case 2: minalt = this.chan.read();//vengono innizializzate con i valori ricevuti, ma sono lo stesso valore per max e min?
                  maxalt = this.chan.read();
		          if (minalt <= maxalt) setAlt();//questa if serve per controllare che il client non invii un valore minimo + grande del corrispettivo val massimo
	              this.logs.log("Pitch   = [" + MINALT + "," + MAXALT + "]");
                  break;
          case 3: mindur = this.chan.read();
                  maxdur = this.chan.read();
		          if (mindur <= maxdur) setDur();
	              this.logs.log("Rythm   = [" + MINDUR + "," + MAXDUR + "]");
                  break;
        }
      }
    } catch (Exception e) {
        this.logs.log("Exiting - Connection reset");
	    setOn();
	    this.stop();
    }
  }

  synchronized void setOn() {
    ON = true; // set on manda in true l'interruttore
    notify();
  }

  synchronized void setOff() { //set of lo manda in false
    ON = false;
    notify();
  }

  synchronized boolean isOn() {
    while (!ON) try {wait();} catch (Exception e) {}; // quando on è falso lo manda in wait
    //quando l'interruttore è acceso, aspetta
    return ON;
  }

  synchronized void setAlt() {
    MINALT = minalt; // le variabili temporanee travasano il loro valore nei campi dell'oggetto di classe
    MAXALT = maxalt;
  }

  synchronized int[] getAlt() { //scrive in un array lungo 2 elem,da0 a 1, i valori MINAL E MAXALT, definiti nella classe data
    int alt[] = new int[2];
    alt[0] = MINALT; // minalt è il primo elemento dell'array
    alt[1] = MAXALT; //tra i 
    return alt;
  }

  synchronized void setDur() {
    MINDUR = mindur;
    MAXDUR = maxdur;
  }

  synchronized int[] getDur() {
    int dur[] = new int[2];
    dur[0] = MINDUR;
    dur[1] = MAXDUR;
    return dur;
  }
}

// La classe che segue serve solo per creare e gestire il file di log.
// L'unico metodo di cui e' utile conoscere il funzionamento e' "log".
class Logs {
  final static String PATH = "/home/gfp/Logs/ProMus/Logs-20-07-07/Tur1/";
  final static String LOGS = "logs";

  private Socket   sock; // è la socket che parla con il client
  DataOutputStream logs;

  public Logs(Socket sock) {
    this.sock = sock;
    InetAddress addr = this.sock.getInetAddress(); //prendiamo l'indirizzo del client
    int         port = this.sock.getPort();
    String      name = addr.getHostName();
    String      ipv4 = addr.getHostAddress();//prendiamo l'ip del client

    new File(Logs.PATH + ipv4).mkdir(); //creiamo una nuova directory con il nome passato come parametro di File(nome)
    try {
      this.logs =                    //java ha implementate 40classi che riguardano file, per i vari scopi cui possono servire.
        new DataOutputStream(
          new FileOutputStream( // poi lo trasforiamo in un file di outputstream
            new File(Logs.PATH + ipv4 , Logs.LOGS), true));
    } catch (FileNotFoundException e) {
	    System.out.println("No log file created: " + name + "[" + ipv4 + "]");
        e.printStackTrace();
	return;
    }
    log("****************************");
    log("Client Name = " + name);
    log("Client Addr = " + ipv4);
    log("Client Port = " + port);
  }

  synchronized void log(String mess) {
    String name = Thread.currentThread().getName(); //prende il nome del thread corrente e scrive un messaggio nel file di log
    try {
      this.logs.writeBytes(name + " - " + mess + "\n"); 
      //la trasformazione di questi file in tipo outputstrem è dovuta forse, dal fatto che per comodità usiamo il metodo writebyte
    } catch (IOException e) { e.printStackTrace(); }

  }
}