{ "title": "Flertrådede servere", "exercisesLink": "opgaver/opgaver.htm" }
  Hvis en server skal kunne håndtere flere samtidige klienter, er en mulig løsning at gøre den flertrådet. Man laver en tråd for hver klient, og denne tråd skal udelukkende tage sig af den pågældende klient.
 

 

1. Grundlæggende opbygning

  Lad os se hvilke objekter vi skal have i spil:
Figur 1:
Manager med tre workers
Alle er tråde ClientManager'en er den overordnede, der delegerer varetagelsen af de enkelte klienter videre til hver deres ClientWorker. De er alle selvstændige tråde, og vi har derfor hele fire tråde i figuren ovenfor.
  Det er ClientManager, der har ServerSocket og som afventer klienterne i et accept-kald. Når ClientManager'en modtager en forbindelse, laver den en instans af ClientMinion og giver forbindelsen videre til denne. Herefter vender den tilbage til et nyt accept-kald.
  Lad os se kildeteksten til klassen ClientManager:
Kildetekst 1:
Manager
            import java.net.*;
            import java.io.*;
            import java.util.LinkedList;
            
            class ClientManager extends Thread {
              private LinkedList workers;
              private int port;
              private boolean stop;
              
              public ClientManager( int port ) {
                this.port = port;
                this.stop = false;
                
                workers = new LinkedList();
              }
              
              public void run() {
                ServerSocket server;
                Socket connection;
                
                try {
                  server = new ServerSocket( port, 100 );
                  server.setSoTimeout( 100 );
                  System.out.println( "[ClientManager] Server online" );
                  
                  while ( !stop )
                    try {
                      connection = server.accept();
                    
                      ClientWorker worker = new ClientWorker( this, connection );
                      workers.add( worker );
                      worker.start();
                    }
                    catch ( SocketTimeoutException e ) {
                      // ignore timeout
                    }
                  
                  System.out.println( "[ClientManager] Server offline" );
                }
                catch ( IOException e ) {
                  System.out.println( "[ClientManager] I/O error" );
                }
              }
              
              public void shutdown() {
                stop = true;
              }
            }
          
Lytter på port Konstruktoren modtager port-nummeret, og efter tråden er sat i gang, sætter run-metoden sig til at lytte efter indgående forbindelser på denne port. Den gør det ved at lave en ServerSocket på porten og sætte sig til at vente i det blokerende accept-kald.
  Når der kommer en indgående forbindelse giver ClientManager'en, den derved fremkomne instans af Socket, videre til en ny instans af ClientWorker. Vi giver samtidig den ny ClientWorker en reference til dens ClientManager, da det kan være nyttigt, at en worker kender sin manager (det er dog ikke noget vi anvender i dette eksempel).
Stoppe Manageren opretholder en liste over sine workers. Det giver den bla. mulighed for, at kunne bede alle sine workers om at lukke deres forbindelser og terminere; hvis manageren f.eks. selv får besked om at lukke ned (terminering af workers er ikke implementeret i vores eksempel). I forbindelse med at stoppe, indfører vi en variabel: stop, der fungerer som sentinel på run-metodens while-løkke.
Timeout Det blokerende accept-kald er ikke hensigtmæssig i denne sammenhæng, da manageren kun vil kunne stoppe umiddelbart efter en klient opretter en forbindelse. Da accept-kaldet ikke kan blive interrupted, må vi gøre noget andet. Det er muligt at sætte en timeout, så accept-kaldet kaster en exception efter et vist antal millisekunder; hvis der ikke har været en indgående forbindelse. Som udgangspunkt er det ikke sat noget timeout, men ved at anvende metoden:
void setSoTimeout( int ms )
  kan vi sætte en timeout. I vores eksempel har vi sat en timeout hver 1/10 sekund. Anfører man 0 som parameter til denne metode, slår man timeouts fra.
   
  Lad os dernæst se klassen ClientWorker:
Kildetekst 2:
Worker
            import java.net.*;
            import java.io.*;
            
            class ClientWorker extends Thread {
              private Socket connection;
              private ClientManager manager;
              
              public ClientWorker( ClientManager manager, Socket connection ) {
                this.manager = manager;
                this.connection = connection;
              }
              
              public void run() {
                System.out.println( "[ClientWorker] New connection from: " +
                                    connection.getInetAddress().getHostAddress() );
              
                try {
                  BufferedWriter writer = new BufferedWriter(
                                            new OutputStreamWriter(
                                              connection.getOutputStream() ) );
                
                  sendLine( writer, "You are connected" );
                  sendLine( writer, "to the server" );
                  sendLine( writer, "<end>" );
            
                  writer.close();
                  System.out.println( "[ClientWorker] Connection closed" );
                }
                catch ( IOException e ) {
                  System.out.println( "[ClientWorker] I/O error" );
                }
              }
              
              private void sendLine( BufferedWriter writer, String line )
                throws IOException
              {
                System.out.println( "[ClientWorker] sending line: " + line );
                
                writer.write( line );
                writer.newLine();
                writer.flush();
                
                Sleeper.nap();
              }
            }
          
Streams Konstruktoren modtager de to referencer vi så ovenfor; hvoraf den sidste anvendes i run-metoden.
  Som man kan se har vi vendt situationen om i forhold til kapitlet: "Det grundlæggende", da det nu er serveren der sender tekst til klienten. Derfor har vi også behov for en ny klient, selvom det ikke har nogen indvirkning på klienten at serveren er blevet flertrådet:
Kildetekst 3:
Klient
            import java.net.*;
            import java.io.*;
            
            public class SimpleClient extends Thread {
              
              public void run() {
                try {
                  Sleeper.nap(); // være sikker på at serveren er online
                  
                  Socket connection = new Socket( "127.0.0.1", 6010 );
                  System.out.println( "[Client] Connection open" );
                
                  BufferedReader reader = new BufferedReader(
                                            new InputStreamReader(
                                              connection.getInputStream() ) );
                  
                  String line = "";
                  while ( !line.equals( "<end>" ) ) {
                    line = reader.readLine();
                    
                    System.out.println( "[Client] received line: " + line );
                  }
                  
                  reader.close();
                  System.out.println( "[Client] Connection closed" );
                }
                catch ( IOException e ) {
                  System.out.println( "[Client] Connection failed" );
                }
              }
            }
          
  Klienten indholder ikke noget nævneværdigt nyt.
   
  Til slut har vi testanvendelsen:
Kildetekst 4:
Starter server, og klient der opretter forbindelse
            public class Main {
            
              public static void main( String[] argv ) {
                ClientManager manager = new ClientManager( 6010 );
                manager.start();
                
                new SimpleClient().start();
                
                Sleeper.sleep( 3 );
                manager.shutdown();
              }
            }
          
            [ClientManager] Server online
            [Client] Connection open
            [ClientWorker] New connection from: 127.0.0.1
            [ClientWorker] sending line: You are connected
            [ClientWorker] sending line: to the server
            [Client] received line: You are connected
            [ClientWorker] sending line: <end>
            [Client] received line: to the server
            [ClientWorker] Connection closed
            [Client] received line: <end>
            [Client] Connection closed
            [ClientManager] Server offline
          
  Vi sover 3 sekunder før vi kalder shutdown på serveren. Det er kunstigt, men illustrerer anvendelsen af shutdown-metoden. Det er nødvendigt at sove en vis tid (dog ikke nødvndigvis 3 sekunder), for at klienten kan blive færdig med at bruge serveren.