NOVINKA - Online rekvalifikační kurz Java programátor. Oblíbená a studenty ověřená rekvalifikace - nyní i online.
NOVINKA – Víkendový online kurz Software tester, který tě posune dál. Zjisti, jak na to!

Diskuze: komunikace klient-server

V předchozím kvízu, Online test znalostí Java, jsme si ověřili nabyté zkušenosti z kurzu.

Aktivity
Avatar
Jenkings
Tvůrce
Avatar
Jenkings:24.1.2018 9:56

Zdravím.
podle zdejšího tutoriálu (https://www.itnetwork.cz/…rava-serveru) jsem si zkoušel komunikaci server-klient. Chci teď docílit obousměrné komunikace s jednotlivými klienty.

Po výsledné úpravě jsem ale narazil na jeden problém s kterým si nevím rady. Konkrétně jde o úpravu metody Clients. Při připojení pouze jednoho klienta je vše v pořádku, nicméně když se připojí druhý klient, vyhodí program chybu: "Exception in thread "main" java.util.Con­currentModifi­cationExcepti­on"

 while(true) {
            synchronized(users) { // zde dochází k chybě
                users.forEach((u) -> {
                    try {
                        if(u.in.ready()) {
                            String text = u.in.readLine();
                            serverHandleMessage(u,text);
                        } else {
                                //TODO
                        }
                    } catch (IOException e) {
                    }
                });
}
Odpovědět
24.1.2018 9:56
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:24.1.2018 10:04

Otázka je, jaký máš ten zámek. Problém je už podle názvu chyby ve vláknu. Tuto chybu jsem kdysi taky hledal. Problém vzniká tím, že se o ten zámek pere několik synchronizačních bloků najednou. Předpokládám, že chceš docílit toho, že se pro každého klienta uvolní zámek v momentě, kdy zpracuješ toho, který je na řadě, že?
Jde o to, že musíš to vlákno těmi klienty (respektive ten zámek) i uvolňovat a zamykat. Jestli to najdu, hodím ti sem kousek kódu, kterým to vyřešíš (nebo by to snad někdo dokázal napsat z patra). Přiznám se, že já se synchronizaci snažím co nejvíc obcházet, jak to jen jde. Osobně mě nejvíc sere ta náhodnost - nemůžeš třeba nikdy zaručit, komu se zámek předá. Prosttě se předává náhodně někomu, kdo zrovna čeká. A prý v tom hrají roli i priority vlákna, ale když jsem to zkoušel na soukromých projektících, tak mi to nefungovalo:) Ale tuším že v tomto případě by ti nemuselo vadit, v jakém pořadí se budou klienti o vlákno dělit, hlavně aby ho pak měli všichni.

Nahoru Odpovědět
24.1.2018 10:04
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:24.1.2018 10:09

no:) našel jsem to, ale mám to jako jednu samostatnou ukázkovou třídu z takového přehledu vláken:) (pro zajímavost - když já třeba učím vlákna, tak nejedu standardním stylem, jako většina, ale právě jsem vytyčil několik základních případů, kdy a jak je potřeba využít vlákno. Takže tato třída ti samostatně nepůjde, je to prostě výňatek, ale můžeš z toho vysosat tu práci se zámky

package threads;

import java.awt.Color;
import test.IThread;
import tools.Delay;

/**
 * Lze takhle vyuzit pouze pro 2 objekty. Pokud by se podobnym zpusobem delilo o
 * zamek vice objektu, tak tento zpusob nezarucuje, komu se bude pesek (zamek)
 * predavat, protoze se predava nahodne tomu, kdo stoji ve fronte nehlede na
 * prioritu. Vice objektu by slo v samostatnych vlaknech ridit pomoci rucne
 * vytvoreneho algoritmu, ktery by zajistoval, aby se zamek dostaval ve spravnem
 * poradi ke konrketnimu vlaknu. To by ovsem slo i jinak nez az pouziti zamku.
 *
 * @author lubor
 */
public class ESynchronizovanaVlaknaSoucasne extends IThread {

    private static final Object LOCK = new Object();

    public ESynchronizovanaVlaknaSoucasne() {
        super(Color.cyan);
    }

    @Override
    public void run() {
        int delay = 2;
        array.get(8).setVisible(true);
        array.get(9).setVisible(true);
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < panel.getHeight() - array.get(8).getHeight(); i++) {
                    synchronized (LOCK) {
                        array.get(8).setLocation(array.get(8).getX(), array.get(8).getY() + 1);
                        LOCK.notify();
                        try {
                            LOCK.wait();
                        } catch (InterruptedException ex) {
                        }
                        Delay.wait(delay);
                    }
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < panel.getHeight() - array.get(9).getHeight(); i++) {
                    synchronized (LOCK) {
                        array.get(9).setLocation(array.get(9).getX(), array.get(9).getY() + 1);
                        LOCK.notify();
                        try {
                            LOCK.wait();
                        } catch (InterruptedException ex) {
                        }
                        Delay.wait(delay);
                    }
                }
            }
        };
        t1.start();
        t2.start();
    }
}
Nahoru Odpovědět
24.1.2018 10:09
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:24.1.2018 10:18

no když se na to koukám, tak to ještě teda trošku okomentuju, abys věděl, na co se můžeš vykašlat a co k čemu je.

  • Konstruktor pouze nastavuje barvičku (protože v projektu používám pro ukázku vlákna let balonků a odlišuju je barvičkami:) ), takže není třeba řešit
  • Hodně podstatné je, aby zámek byl pokud možno konstantní, aby si třeba každá nová instance nevytvářela svůj vlastní - s tím jsem taky kdysi bojoval
  • ty kolekce, tak ty obsahují právě balonky, které mají nějakou počáteční pozici a já jim v každé ukázce vláken pouze nastavuju barvu a tím je vizuálně odděluju, takže taky teď nepodstatné
  • třída Delay je můj tool, který pouze obsahuje try catch se statickou metodou Thread.sleep(int); Používám to, protože nesnáším závorky a díky tomu mám celou pauzu na jednom řádku:) takže to je jen moje úprava. Vykonává prostě jen sleep a nic víc.
  • Podstatný je tedy hlavně ten lock a že jej musíš vždy probudit (notify) a uspat (wait). A když toto uděláš v cyklu - pro všechny, tak nemusíš mít pochopitelně dvě samostatná vlákna jako já, ale jak píšu v tom úvodu - nezaručíš, že půjdou postupně. Ale to by ti nemuselo v tomto případě vadit;)
Akceptované řešení
+20 Zkušeností
+2,50 Kč
Řešení problému
Nahoru Odpovědět
24.1.2018 10:18
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Jenkings
Tvůrce
Avatar
Odpovídá na Lubor Pešek
Jenkings:24.1.2018 21:55

Děkuji za podrobné rady. Věřím že se s tím už nějak poperu. Upřímně řečeno jsem se s vlákny dříve moc nesetkal, takže mi to celkem dost pomohlo.

Nahoru Odpovědět
24.1.2018 21:55
Největší časovou náročnost má výpočet časové náročnosti..
Avatar
Lubor Pešek
Člen
Avatar
Lubor Pešek:24.1.2018 22:30

V podstatě jde o to, že musíš mít jeden jednotný zámek. Jak jsem psal, je dobré jej deklarovat jako konstantu a víš, že si ho žádné vlákno nezmění.
No a potom si dělej co chceš, ale musíš najít místo, ve kterém chceš, aby se běh vlákna zastavil a předal se zámek dalšímu synchronizačnímu bloku (ovšem náhodnému).
Abys to zastavil, tak na ten zámek zavoláš metodu wait() (tu má každý objekt) a zabalíš do try catche s interrupted výjimkou.
No a ještě před tím, než tento blok zastavíš, tak "probudíš" ten zámek u někoho jiného. Musí to být v tomto pořadí, protože po wait() už to vlákno čeká, než jej někdo probudí. No a notifyAll probudí logicky všechna zastavené synchronizované bloky.

Nahoru Odpovědět
24.1.2018 22:30
Existují dva způsoby, jak vyřešit problém. Za prvé vyhoďte počítač z okna. Za druhé vyhoďte okna z počítače.
Avatar
Neználek
Člen
Avatar
Odpovídá na Jenkings
Neználek:25.1.2018 21:51

Tutoriál obsahuje závažnou chybu.
Smyčka while vytíží naplno jádro procesoru.

while(true) {
    synchronized(clientBufReaders) {
        for(BufferedReader in :  clientBufReaders) {
            try {
                if(in.ready()) {
                    System.out.println(in.readLine());
                } else {
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

Doporučoval bych spíše tutoriál od Oracle
https://docs.oracle.com/…tServer.html

 
Nahoru Odpovědět
25.1.2018 21:51
Avatar
Odpovídá na Neználek
Marian Benčat:26.1.2018 13:25

Neni nahodou neco uvnitr toho loopu blokujici? Pak by to opravdu CPU na 100% nevytizilo, protoze by scheduler odebral CPU time v momente, kdy se ceka na nejake IO - treba dokud neprijde neco po siti.

Ale zdrojak jsem nezkoumal. Jen jsem chtel rici, ze infinite loop rozhodne automaticky neznamena, ze je CPU na 100%.

Nahoru Odpovědět
26.1.2018 13:25
Totalitní admini..
Avatar
Petr
Člen
Avatar
Odpovídá na Marian Benčat
Petr:26.1.2018 15:45

Myslim ze v tom loopu neni blokujici nic. V podstate principem te aplikace je ze ma 2 vlakna v jednom prijima nova spojeni a pridava si je do nejake kolekce a v druhem (hlavnim) vlakne tou kolekci prochazi a zjistuje jestli je potreba zpracovat nejaka data z klienta. Asi by za tim synchronized blokem mel byt nejaky sleep, treba na 100-500ms podle potreby. Cele CPU to nesezere, protoze skoro vsechna CPU maji dnes vice jader, vytizi to jedno jadro na 100%.

Technicky pri architekture klient server existuji dva pristupy jak implementovat server. Prvni v dnesni dobe rozsirenejsi je nastartovat nove vlakno pro kazdeho noveho klienta a resit veskerou komunikaci s klientem v ramci tohoto vlakna. To je popsane v tom oracle tutorialu.
Druhy pristup, ktery je prezentovany v tomto tutorialu, je ten ze jedno vlakno resi vsechny klienty najednou (pro ty co znaji jazyk C je to podobne jako pouziti funkce select na socketu).
Oba pristupy maji neco do sebe, da se obecne rici ze startovat nove vlakno se da pokud predpokladame ze tech klientu budou stovky, max tisice, ale pokud by aplikace mela resit desitky tisic klientu pres vlakna tak ji to pravdepodobne polozi, nebo vyznamne zpomali, protoze prepnuti kontextu mezi vlakny neco stoji, nehlede na zamky, ktere jsou nutne pro synchronizaci dat a extra pamet, kterou si vlakna rezervuji.
Kdyz mrknete na implementaci nginx nebo apache, tak taky nestartuji vlakno pro kazdeho noveho klienta, ale resi to vsechno v ramci jednoho nebo vice vlaken.

 
Nahoru Odpovědět
26.1.2018 15:45
Avatar
Neználek
Člen
Avatar
Odpovídá na Petr
Neználek:26.1.2018 21:53

Kód tutoriálu jsem testoval a opravdu vytěžoval jedno jádro na 100%.

Souhlasím, že existují dva přístupy pro práci se sockety:

  • blokující mód - řeší se vláknem pro každého klienta (viz Oracle tutoriál)
  • neblokující mód - celý server lze řešit jedním vláknem. V Javě k tomu slouží NIO.

Nesouhlasím s tím, že chybu v programu by řešil sleep. Kleslo by sice vytížení procesoru, ale na druhou stranu by úplně zbytečně stoupla doba odezvy serveru.

 
Nahoru Odpovědět
26.1.2018 21:53
Avatar
Petr
Člen
Avatar
Odpovídá na Neználek
Petr:27.1.2018 0:54

Na to co ten program ma delat, tzn. vypisuje po radcich co chodi socketem na standardni vystup, ten sleep staci. Pokud bychom se bavili o realne implementaci, kde bych mel seznam takovychto readeru, tak bych tam pridal fixni thread pool, do ktereho bych predhazoval callable instance s klientem, ktery je zrovna aktivni. Pokud by hrozilo, ze klienti budou vytezovat thread prilis dlouho, tak bych tam jeste pridal limit na prenesene data nebo cas a thread bych uvolnil, s tim ze klient by se zaradil na konec fronty klientu kteri cekaji na volny thread. Zaroven bych pouzil asi selectory z nio, ktere jsi zminil.

 
Nahoru Odpovědět
27.1.2018 0:54
Děláme co je v našich silách, aby byly zdejší diskuze co nejkvalitnější. Proto do nich také mohou přispívat pouze registrovaní členové. Pro zapojení do diskuze se přihlas. Pokud ještě nemáš účet, zaregistruj se, je to zdarma.

Zobrazeno 11 zpráv z 11.