Vydělávej až 160.000 Kč měsíčně! Akreditované rekvalifikační kurzy s garancí práce od 0 Kč. Více informací.
Hledáme nové posily do ITnetwork týmu. Podívej se na volné pozice a přidej se do nejagilnější firmy na trhu - Více informací.

Lekce 14 - Komunikace Klient/Server - Úprava serveru a klienta

V minulé lekci, Komunikace Klient/Server - Klient, jsme si naprogramovali našeho klienta a vyzkoušeli, jak komunikuje se serverem.

V dnešním Java tutoriálu o síťové komunikaci Klient/Server si server upravíme tak, aby se na něj mohlo připojit více klientů. Také upravíme klienta, aby mohl přijímat zprávy od serveru. Využijeme k tomu znalosti z kurzu o práci s vlákny Multithreading v Javě.

Úprava klienta

V klientovi nejprve vytvoříme nový objekt BufferedReader s použitím InputStreamReader a vstupního proudu soketu clientSocket. Tímto způsobem se připraví čtečka, která bude zpracovávat data obdržená ze serveru.

Dále vytvoříme nové vlákno s názvem receiveThread(), které se bude starat o vypsání zprávy obdržené ze serveru. V cyklu while zavoláme metodu readLine() na instanci serverReader. Ta pokaždé načte jednu řádku textu ze serveru. Přečtená zpráva se vypíše v konzoli. V případě, že dojde k výjimce IOException, vypíše se její stopa:

this.serverReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

Thread receiveThread = new Thread(new Runnable() {
  public void run() {
        try {
            while (true) {
                  String message = serverReader.readLine();
                  System.out.println("Zpráva od serveru: " + message);
            }
       } catch (IOException e) {
               e.printStackTrace();
       }
 }
});
receiveThread.start();

Konečný zdrojový klienta pak vypadá takto:

public class Client {

    private Socket clientSocket;
    private BufferedReader serverReader;

    public static void main(String[] args) {
        Client client = new Client();
    }

    public Client() {
        try {
            this.clientSocket = new Socket("localhost", 8080);
            System.out.println("Spuštění klienta proběhlo úspěšně.");

            this.serverReader = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));

            Thread receiveThread = new Thread(new Runnable() {
                public void run() {
                    try {
                        while (true) {
                            String message = serverReader.readLine();
                            System.out.println("Zpráva od serveru: " + message);
                        }
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            });
            receiveThread.start();

            BufferedWriter out = new BufferedWriter(new OutputStreamWriter(this.clientSocket.getOutputStream()));
            Scanner in = new Scanner(System.in);
            while (true) {
                String message = in.nextLine();
                if (message.equalsIgnoreCase("exit")) {
                    break;
                }
                out.write(message + "\r\n");
                out.flush();
                System.out.println("Zpráva \"" + message + "\" byla odeslána.");
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Úprava serveru

Po server nejdříve vytvoříme novou třídu ClientHandler.

Třída ClientHandler

Jedná se o jednoduchou informační třídu, která bude držet informace o připojeném klientovi. Třída bude při inicializaci přijímat Socket a PrintWriter a bude obsahovat metody:

  • getSocket() pro získání soketu klienta,
  • getWriter() vrátí objekt PrintWriter pro odesílání zpráv klientovi,
  • getAddress() pro získání řetězce představujícího IP adresu klienta.

Celý kód třídy ClientHandler bude vypadat takto:

private static class ClientHandler {
    private Socket socket;
    private PrintWriter writer;

    public ClientHandler(Socket socket, PrintWriter writer) {
        this.socket = socket;
        this.writer = writer;
    }

    public Socket getSocket() {
        return this.socket;
    }

    public PrintWriter getWriter() {
        return this.writer;
    }

    public String getAddress(){
        return this.socket.getInetAddress().toString();
    }
}

Metoda broadcastMessage()

Do třídy Server nyní doplníme dvě privátní metody.

Metodu broadcastMessage() využijeme pro odeslání zprávy všem klientům připojeným na serveru.

Každý klient je reprezentován instancí třídy ClientHandler, která obsahuje odkaz na objekt PrintWriter pomocí kterého bude odeslána zpráva klientovi.

Nejdříve synchronizujeme kolekci klientů, poté vytvoříme Iterator<ClientHandler>, který bude procházet jednotlivé prvky v kolekci clients a následně bude v cyklu while odesílat zprávu jednotlivým klientům:

private void broadcastMessage(String message) {
       synchronized (clients) {
           Iterator<ClientHandler> iterator = clients.iterator();
           while (iterator.hasNext()) {
               ClientHandler clientHandler = iterator.next();
               clientHandler.getWriter().println(message);
               clientHandler.getWriter().flush();
           }
       }
   }

Úprava třídy Server

Třídě Server přidáme jeden privátní atribut ArrayList<ClientHandler> jménem clients. Z konstruktoru odstraníme vše kromě inicializace serverSocket a přidáme inicializaci clients. Nakonec zavoláme naši metodu clients(), kterou vzápětí doplníme:

public Server() {
        try {
            this.serverSocket = new ServerSocket(8080);
            System.out.print("Spuštění serveru proběhlo úspěšně.\nČekám na připojení klienta...\n");
            this.clients = new ArrayList<>();

            clients();
        } catch (IOException e) {
            e.printStackTrace();
        }
}

Metoda clients()

V této metodě nejprve vytvoříme a spustíme nové vlákno jménem acceptThread, kterému jako argument předáme rozhraní Runnable s metodou run():

private void clients() {
        Thread acceptThread = new Thread(new Runnable() {
            public void run() {
                while (true) {
                    ...
                }
            }
        });
        acceptThread.start();
    }

V cyklu while poté vytvoříme Socket pro klienta, jménem clientSocket. Inicializujeme PrintWriter, který bude přijímat OutputStream od clientSocket. Vytvoříme také instanci třídy ClientHandler a předáme ji odkaz na Socket a PrintWriter. Synchronizujeme kolekci clients typu ArrayList a přidáme do ní instanci třídy clientHandler pro nově připojeného klienta. Pak vypíšeme hlášku o připojení klienta. Celý kód nezapomeneme obalit blokem try – catch:

try {
    Socket clientSocket = serverSocket.accept();
    PrintWriter writer = new PrintWriter(new OutputStreamWriter(clientSocket.getOutputStream()), true);
    ClientHandler clientHandler = new ClientHandler(clientSocket, writer);
    synchronized (clients) {
         clients.add(clientHandler);
         }
    System.out.println("Klient" + clientHandler.getAddress() + " se připojil.");
} catch (IOException e) {
    e.printStackTrace();
}

V druhé části naší metody vytvoříme další nekonečný while cyklus a v něm sekci synchronized, abychom mohli z tohoto vlákna přistupovat do clients a nedošlo k race condition:

while(true) {
    synchronized(clients) {

    }
}

Bez synchronizace by nastal problém, pokud by přistupovalo více vláken k seznamu ve stejnou chvíli. Více informací o synchronizaci vláken naleznete v sekci Vícevláknové aplikace v Javě.

Nyní už zbývá poslední věc. Vytvoříme Iterator<ClientHandler> a další while cyklus. Inicializujeme objekt BufferedReader, který slouží ke čtení zpráv ze vstupního proudu soketu (clientHandler) klienta. Pokud bude BufferedReader nějakého klienta obsahovat text, tak si ho vypíšeme.

Celý kód druhé části naší metody vypadá následovně:

while (true) {
    synchronized (clients) {
        Iterator<ClientHandler> iterator = clients.iterator();
        while (iterator.hasNext()) {
            ClientHandler clientHandler = iterator.next();
            try {
                BufferedReader reader = new BufferedReader(new InputStreamReader(clientHandler.getSocket().getInputStream()));
                if (reader.ready()) {
                    String message = reader.readLine();
                    System.out.println("Přijata zpráva od klienta " + clientHandler.getAddress() + " : " + message);
                    broadcastMessage(message);
                }
            } catch (IOException e) {
                e.printStackTrace();
                iterator.remove(); // Safely remove the clientHandler from the list
            }
        }
    }
}

Nyní můžeme aplikaci spustit a vyzkoušet. Archiv se zdrojovým kódem je ke stažení pod touto lekcí.

V následujícím kvízu, Kvíz - Komunikace Klient/Server v Javě, si vyzkoušíme nabyté zkušenosti z předchozích lekcí.


 

Měl jsi s čímkoli problém? Stáhni si vzorovou aplikaci níže a porovnej ji se svým projektem, chybu tak snadno najdeš.

Stáhnout

Stažením následujícího souboru souhlasíš s licenčními podmínkami

Staženo 163x (31.24 kB)
Aplikace je včetně zdrojových kódů v jazyce Java

 

Předchozí článek
Komunikace Klient/Server - Klient
Všechny články v sekci
Síť v Javě
Přeskočit článek
(nedoporučujeme)
Kvíz - Komunikace Klient/Server v Javě
Článek pro vás napsal Filip Stryk
Avatar
Uživatelské hodnocení:
14 hlasů
Aktivity