MultiThread,pytania

0

Witam,
Chciałbym Was prosić abyście ocenlili poprawność tego pomysłu i odpowiedzieli na kilka pytań . Dlaczego piszę tu a nie próbuję tego napisać w kodzie ?. Ponieważ już próbowałem i jest tutaj jakiś większy błąd logiczny którego nie potrafię znaleźć.
Otóż, do napisania mam program który opiera się na zasadzie Klient-Server . Klientów może być dowolna ilość,mogą przychodzić oraz odchodzić w każdej chwili . Klient komunikuje się z serverem poprzez sockety ( konkretnie DataIn/OutputStream ),server z klientem tak samo.
Myślałem zatem że dobrym pomysłem będzie napisanie tego na 3 klasach ( Klient,Server,Watek_Servera).
W klasie Server oczekuję na połączenie,jeżeli ono nastąpi do dodaję wątek klienta do Mapy :

 
private static Map<String, Watek_Serveraa> mapa = Collections.synchronizedMap( new HashMap<String,Watek_Serveraa>() );  
 
/**
   * 
   * 
   */ 
mapa.put(pseudonim_gracza,new Watek_Serveraa(klient,mapa,x,y) ) ;
Thread abc =mapa.get(pseudonim_gracza); 
abc.start();	 

Mapę tę przekazuję do klasy Watek_Servera ponieważ tam muszę pracować z wszystkimi wątkami.
W klasie Watek_Servera chciałem napisać coś mniej więcej takiego.
(Jestem już w metodzie run() pętli while(true) klasy WS )Za pomocą iteratora wybieram pierwszy element z mapy i wpisuję sobie do String jego własny ID. Następnie w 2 pętli for za pomocą iteratora2 wysyłam do wszystkich klientów informacje czyj ruch jest teraz
Chciałem to zrobić mniej więcej tak :

 
synchronized(this) { 
  for( Map.Entry<String, Watek_Serveraa> iterator1 : mapa.entrySet() )
 {  
		String kto_jedzie=iterator1.getKey();
		System.out.println(kto_jedzie);
		for( Map.Entry<String, Watek_Serveraa> iterator2 : mapa.entrySet() )
		{  
				try 
				{ 
					iterator2.getValue().dos.writeUTF(kto_jedzie); 
				} 
				catch(Exception ex) { System.out.println("Blad watek wysylanie do klientow kto jedzie "); }
		}  
               String czynnosc = iterator1.getValue().wejscie.readUTF(); 
		if( czynnosc.equals("Ruch") )
		{   
			try 
		        {
				if( vektor_ruchow.size() > 0 )
				{   
                                        iterator1.getValue().writeUF("Stare ruchy"); 
                                       /** wysłanie obiektami poprzednich ruchów
				} 
				else 
				{ 
					wyjscie.writeUTF("Nowe ruchy"); 
					//wyjscie.flush();
				}  
				boolean ruch_wykonany = false ; 
				while( ruch_wykonany == false )
				{ 
									
					String ruch_klienta =  iterator1.getValue().wejscie.readUTF(); 
					System.out.println("Ruch klienta :"+ruch_klienta);   
					drukuj();
					if( sprawdz(ruch_klienta)==true ) 
					{   
						//System.out.println("Jestem tu !");
						 iterator1.getValue().wyjscie.writeUTF("Poprawny");   
						//wyjscie.flush();
						//System.out.println("Jestem tu 2!");
						String wiadomosc_lokalna = iterator1.getValue().wejscie.readUTF();  
						System.out.println("Wiad: "+wiadomosc_lokalna);
						if( wiadomosc_lokalna.equals("Wykonany") )
						{    
							//System.out.println("Jestem tu 3!");
							ruch_wykonany = true;
							ruch(ruch_klienta,kto_jedzie); 
							if( (koniec_gry(kto_jedzie,liczba_symboli))) 
							{
								/**Zapis do pliku **/
							} 
											
						} 
						else if( wiadomosc_lokalna.equals("Nie wykonany") ) 
                                                {    
                                                           //System.out.println("Jestem tu 4!"); 
                                                 }
				        }
					else 
					{   
						//System.out.println("Jestem tu 5!");
						 iterator1.getValue().wyjscie.writeUTF("Nie poprawny");
					} 
				}
			} 
			catch(Exception ex) { System.out.println(" Blad watek wysylanie do klientow ruchu"); }
  }  
}

Klient z kolei rozpoznaje co Server mu napisał i w zależności od tego podejmuje konkretną akcje.
Wszystko psuję się w momencie nawiązania "dialogu" pomiędzy konkretnym klientem a serverem . Klient ma za zadanie powiedzieć serverowi co chce zrobić ( Ruszyć się,Wyjść .... ) Server dostaje komendę i w zależności od tego co to za komenda robi określone rzeczy,problem w tym że komunikacja pomiędzy Klientem a Serverm ( która opiera się na Stringach) zawodzi ponieważ drukując to co dostaje Klient okazuje się,że jest to nie ta komenda którą Klient miał otrzymać , staje w miejscu ...
Można by pomyśleć że wysyłam jakąś komendę i jej nie odbieram dlatego wszystko stoi . Jednak sprawdziłem 2 razy i każde wysłanie komendy przez Server/Klient ma przypisane odebranie jej przez stronę przeciwną . Podajrzewam że on jakoś gubi po drodze te komendy albo mu się klienci mylą .
Mam jeszcze takie pytania :

  1. Czy wyżej opisana komunikacja jest dobrym pomysłem ?.
  2. Czy chcąc wysłać komunikat do klienta którego wybieram w I pętli for używam : iterator1.getValue().dos.writeUTF(msg) czy też normalne dos.writeUTF();
  3. Komunikacja,w teorii,miała być zrobiona tak że nowy klient oczekuję aż stary się ruchy i dopiero wtedy jemu server wysyła info że może jechać(iterator zmieni obiekt). Jak to się dzieje że dla ID klienta( 1 : "A" 2 : "B") zachodzi oczekiwanie na ruch poprzedniego klienta a dla ID ( 1 : "A" 2 : "X") nagle obaj naraz chcą jechać ?. Dodawanie klientów mam zrobione tak :
 
boolean pseudonim = false ; 
while( pseudonim == false )
{  
	pseudonim = pseudonim_funkcja(); 	
 
}  
public static boolean pseudonim_funkcja() 
{    
		boolean pseudonim = false;
		try 
		{ 
			wyjscie.writeUTF("Pseudonim ?");  
			pseudonim_gracza = wejscie.readUTF(); 
			System.out.println("psuedo "+pseudonim_gracza); 
                        // Vector<String> vektor_nickow = new Vector <String> (); 
			if( vektor_nickow.contains(pseudonim_gracza) )
			{  
				wyjscie.writeUTF("Nick zajety.Wybierz inny ");  
				pseudonim = false ;
			} 
			else 
			{ 
				wyjscie.writeUTF("Gracz poprawnie zarejestrowany na serverze");   
				vektor_nickow.add(pseudonim_gracza);
				pseudonim = true; 
			} 
		} 
		catch(Exception ex){ System.out.println(" Server blad pseudonim");} 
		return pseudonim;
		
}  
  1. Jak to się dzieje że "gdy nagle obaj chcą jechać" to Server nie zgubi komunikatu i będą mogli jechać ( nastąpi po stronie klienta odpowiednia reakcja na komunikat servera "Poprawny" . Natomiast gdy jeden oczekuję na ruch drugiego,to ten drugi klient NIGDY nie otrzyma komunikatu "Poprawny" , zamiast tego w to miejsce otrzymuję komunikat "Nowe ruchy" przez co nie może ruszyć się dalej.

Z góry bardzo bardzo dziękuję za przeczytanie moich wypocin .
Pozdrawiam

0

Hej,

Na Twoim miejscu zacząłbym od napisania pseudokodu, ale w taki sposób żeby kod każdego komponentu miał tak ze 3 linijki. No i każdy komponent musi mieć jedno, precyzyjnie określone zastosowanie. (hint: jeśli w kodzie operacje "biznesowe" przeplatają się z komunikacją i synchronizacją wątków to coś jest nie tak)

Pamiętaj, że nie musisz koniecznie zakładać "jedno połączenie - jeden wątek". Może przydadzą się inne wątki, chodzi mi również po głowie pomysł z osobnymi wątkami wysyłającym i odbierającym dane z połączenia.

Jeśli chcesz mogę wysłać pseudokod, ale zacznij od tego co napisałem.

0

Server :

  1. odbierz połączenie od klienta
  2. daj mu nick.
  3. wyslij konfigurację planszy
  4. uruchom jego wątek.

WatekServera:

  1. wybierz klienta : X
  2. do reszty wyślij że jedzie X
  3. oczekuj na info od X co chce robić :
    a) if wyjscie than :
    b) if sprawdz_punkty than :
    c) if ruch than :
    1.1. Oczekuj na przesłanie ruchu od gracza.
    1.2. Sprawdz zgodność tego ruchu i wyślij mu info o tym.
    1.3 Nasłuchuj na info czy ruch wykonany(mógł zmienić pole).
    1.4 if nie idź do 1.1
    if tak
    1.1.1 Sprawdz czy koniec gry
    1.1.1.1 if tak zapisz punkt do pliku i coś jeszcze.
    1.1.1.2 idź do 1(oczywiście X już nie bierzemy).

Klient.

  1. Wpisz swój nick.
  2. Zdecyduj co robić.
    1.1 wyjscie.
    1.2 punkty
    1.3 if ruch :
    <przejście z metody run() do innej funkcji w której obsługuję się ruch klienta>
    1.1.1 wyślij na server propozycję ruchu.
    1.1.2 oczekuj na info czy ruch dobry czy nie.
    1.1.3 zdecyduj czy na pewno chcesz jechać tu.
    1.1.1.1 if nie idź do 1.1.1
    1.1.1.2 if tak
    1.1.1.1.1 powiadom server że ruch wykonany i wyjdź z funkcji obsługującej ruch powracając do wątku ( czyli do 2).

Nie do końca rozumiem co to znaczy "rozwiązanie biznesowe" .
Co do wątków to szczerze mówiąc trochę się ich boję bo trudno mi je poukładać w głowie dlatego najlepiej jak jest ich jak najmniej :P.
Pozdrawiam.

0

Nie do końca rozumiem co to znaczy "rozwiązanie biznesowe" .
Co do wątków to szczerze mówiąc trochę się ich boję bo trudno mi je poukładać w głowie dlatego najlepiej jak jest ich jak najmniej :P.
Pozdrawiam.

Chodzi o to, żeby kod zajmujący się uruchamianiem wątków, synchronizacją itp był oddzielony od kodu wykonujacego ruchy, obliczającego punkty itp. W ten sposób dzielisz całość zadania na kawałki łatwe do opanowania.
Jeśli lubisz testy jednostkowe, to dodatkowy argument za silnym rozdzieleniem tych aspektów: testowanie kodu wykonującego tylko logikę gry nie powinno być trudne. Testowanie kodu, który zarządza wątkami, oblicza punkty w grze, komunikuje się z graczami przez TCP/IP będzie praktycznie niemożliwe.

0
  1. Rozumiem, że to jakaś gra. (toczy się w turach, gracze kolejno wykonują ruchy, jest plansza)

  2. Jak ma działać program na samym początku, kiedy gracze dopiero się podłączają ? (wyobraźmy sobie, że podłączył się tylko 1 klient - czy powinien mieć możliwość wykonywana ruchów ? pewno nie ?)

  3. Wygląda na to, że chcesz mieć wątki "Server" - zawsze dokładnie 1, "WatekServera" - zawsze dokładnie 1, "Klient" - tyle watkow ile klientow. To chyba ma ręce i nogi. Ale wątek "Klient" ma sie w punkcie 1.1.2 zatrzymać, przekazać pałeczkę do "WatekServer" i poczekać na rezultat. Chyba będzie prościej zrezygnować z wątku "Server" i niech "Klient" sam wykona odpowiednią metodę na współdzielonym obiekcie. Oczywiście ten obiekt trzyma całą logikę (i stan) gry i musi być odpowiednio synchronizowany.
    Dodałbym wątki zajmujące się wyłącznie wysyłką odpowiedzi do klientów, bo w obecnym modelu nie jest dla mnie jasne jak taką wysyłkę zrobić.

  4. Mechanizmy komunikacji pomiędzy wątkami
    Wątki oczywiście muszą się pomiędzy sobą komunikować - pozostaje kwestia jak konkretnie. IMHO najprościej jest wykorzystać kolejki synchronizowane (java.util.concurrent.BlockingQueue) - bezproblemowa implemetacja wzorca producent-konsument. (producentów i konsumentów może być >1) Jeśli będziesz pamiętał żeby do takiej kolejki wrzucać tylko obiekty immutable, to super.

  5. Zastanów się nad złośliwymi przypadkami: gracze próbujący wykonać ruch poza kolejnością, nagłe wyjątki przy komunikacji, itp

0
marcinj3 napisał(a):
  1. Rozumiem, że to jakaś gra. (toczy się w turach, gracze kolejno wykonują ruchy, jest plansza)

  2. Jak ma działać program na samym początku, kiedy gracze dopiero się podłączają ? (wyobraźmy sobie, że podłączył się tylko 1 klient - czy powinien mieć możliwość wykonywana ruchów ? pewno nie ?)

  3. Wygląda na to, że chcesz mieć wątki "Server" - zawsze dokładnie 1, "WatekServera" - zawsze dokładnie 1, "Klient" - tyle watkow ile klientow. To chyba ma ręce i nogi. Ale wątek "Klient" ma sie w punkcie 1.1.2 zatrzymać, przekazać pałeczkę do "WatekServer" i poczekać na rezultat. Chyba będzie prościej zrezygnować z wątku "Server" i niech "Klient" sam wykona odpowiednią metodę na współdzielonym obiekcie. Oczywiście ten obiekt trzyma całą logikę (i stan) gry i musi być odpowiednio synchronizowany.
    Dodałbym wątki zajmujące się wyłącznie wysyłką odpowiedzi do klientów, bo w obecnym modelu nie jest dla mnie jasne jak taką wysyłkę zrobić.

  4. Mechanizmy komunikacji pomiędzy wątkami
    Wątki oczywiście muszą się pomiędzy sobą komunikować - pozostaje kwestia jak konkretnie. IMHO najprościej jest wykorzystać kolejki synchronizowane (java.util.concurrent.BlockingQueue) - bezproblemowa implemetacja wzorca producent-konsument. (producentów i konsumentów może być >1) Jeśli będziesz pamiętał żeby do takiej kolejki wrzucać tylko obiekty immutable, to super.

  5. Zastanów się nad złośliwymi przypadkami: gracze próbujący wykonać ruch poza kolejnością, nagłe wyjątki przy komunikacji, itp

Przepraszam,że tak późno odpisuję.

A więc tak :

  1. Tak to ma być gra .

  2. Pojedynczy klient gra sam. Jeżeli na samym początku mam mam 2 klientów oni sobie grają( nazwijmy ich X,Y) . Teraz jest kolej X,nagle podłączy się Z . Gracz Z powinien wykonać swój ruch dopiero po Y ( chciałem to wykonać za pomocą Mapy,Server z mapy ściąga gracza i wysyła do wątku informacje,że jest jego kolej).

  3. Klasa Server która nasłuchuje na porcie X na zgłoszenie,jeżeli zgłoszenie tworzy obiekt klasy Watek_Servera i dodaję go do Mapy,którą to z kolei przekazuje do wątku klasy Watek_Servera( Tu jest do Ciebie pytanie . Jeżeli mam 2 graczy i oni sobie grają,nagle przyjdzie gracz Z dla którego zostanie( w w/w modelu) wygenerowany nowy wątek to przecież wątek gracza Z i wątek graczy 1,2 nijak się ze sobą nie będę komunikowały(mówię o 2 wątkach klasy Watek_Serveraa) . W zamyślę chciałem mieć tak że Wątek_Servera zostanie uruchomiony raz i najwyżej nowi gracze będę dodawani do jego Mapy . Jednak to już zupełnie się gubię :( .
    "i niech "Klient" sam wykona odpowiednią metodę na współdzielonym obiekcie." <- mógłbyś to rozszerzyć ? :)
    Co do tego że to obiekt klasy Klient ma sprawdzać logikę to niestetyy w zadaniu jest narzucone że ma to robić server . Gracze tylko grają a server sprawdza czy ów ruch jest wykonalny czy też nie,czy jego wykonanie spowoduje zakończenie gry czy też nie.

  4. Masz,jak rozumiem, na myśli komunikację wątek Klienta - wątek Watek_Serveraa ( bo o innej chyba nie może być mowy ). Dlaczego złym rozwiązaniem jest komunikacja na metodzie .writeUTF(<String>) dla gniazdo (DataOut/DataInt)putStream . Zupełnie nie rozumiem idei zastosowanie kolejki blokującej tutaj ?. Co miało by być tutaj wrzucane. Przecież te wątki mają się komunikować na zasadzie . Klient("Chce się ruszyć na A1") ---- > WS("Ruch mozliwy") --(jeżeli klient wykona ruch na A1,bo może się rozmyślić i podać B2) --> WS("Ruch nie spowoduje konca gry",zmiana gracza) . Jak to zrobić niby za pomocą kolejki ?

Kod źródłowy zrobię jutro. Coś zmieniłem zgodnie z Twoimi zaleceniami,ale to chyba nadal nie to :D

0

ad. 2. OK, poradzisz sobie, po prostu trzeba pamiętać o takich rzeczach.

ad. 3. Ogólnie to ma ręce i nogi.
Co do rejestracji nowych klientów, no tak, jak ktoś się podłączy to musi zostać w jakiś sposób zarejestrowany.
Nie rozumiem pytania - skąd się wzięły 2 wątki Watek_Serwera ? Myslałem że ma być zawsze 1 ?

"i niech "Klient" sam wykona odpowiednią metodę na współdzielonym obiekcie." <- mógłbyś to rozszerzyć ?
sure. Gdzieś w Twoim programie musi pojawić się obiekt StanGry, prawda? Z metodami
w rodzaju "postaw pionek na pozycji x=5 y=8", itp. Ktoś musi te metody wykonywać.
Sugerowałem że może to robić wątek klienta.

ad. 4. Między innymi. Kolejki to bardzo uniwersalny mechanizm. Wydaje mi się, że
najwygodniejszy do wszystkich operacji typu "wątek A przekazuje info do wątku B".

Komunikacja polegająca na tym, że wątek klienta X wykonuje operacje I/O na danych
dotyczących klienta Y oznacza że outputStream'y wszystkich klientów są praktycznie
publiczne i każdy może do nich pisać. To prawie zawsze oznacza kłopoty.
Po drugie, może się zdarzyć, że do klienta X będą jednocześnie pisali Y i Z. Możliwe kłopoty.
Po trzecie, kod się komplikuje - wszystkie wątki potrzebują dostępu do listy
innych wszystkich wątków, która to struktura może się złośliwie zmieniać w czasie wysyłania wiadomości.
Po czwarte, błędy komunikacji z tylko jednym klientem odbiją się czkawką we wszystkich wątkach które do niego pisały.
Czyli jest do zrobienia kilka dodatkowych semaforów i bardzo staranna obsługa błędów.

A pseudokod takiego wątku-wysyłacza może być bardzo prosty:

while (true) {
komunikat=queue.take()
try {
wyslij komunikat przez sieć
} catch (Exception e) {
poinformuj kogo trzeba o bledzie - pewno trzeba wyrejestrowac klienta.
}
}

Jeśli zdecydujesz się na osobny wątek serwera, ok. Może opiekować się listą klientów i stanem gry.
Jego pseudokod może być bardzo prosty:

while (true) {
polecenie=queue.take()
//polecenie to operacja na grze albo dołączenie / odłączenie gracza
wykonaj polecenie
roześlij informacje o wykonaniu polecenia do klientów (bo pewno chcesz
informować klientów że ktoś się dołączył, ktoś wykonał ruch itp)
}

Pseudokod watku klienta napisz sam, tez bedzie prosty ;-)
Został tylko jeden nieprzyjemny problem - nadawanie nicka.

1 użytkowników online, w tym zalogowanych: 0, gości: 1