Server - aplikacja wielowątkowa

0

Problemy które tutaj omówię są dosyć ogólne i dotyczą konstruowania aplikacji serverowej z użyciem wielowątkowości. Nie wiem czy problemy te wynikają z mojej niewiedzy, złego podejścia do zagadnienia czy słabych punktów języka, w każdym razie:

Próbuję napisać server gry. Korzystam z "nonblocking I/O" w oparciu o artykuł: http://www-128.ibm.com/developerworks/java/library/j-javaio/ Nie wnikając w szczegóły opisanej w tym artykule techniki, powiem tylko że nie do końca jest ona "nonblocking". Prgoram wykorzystujący tą technikę blokuje się gdy zaczyna nasłuchiwać nowego połączenia tak więc w moim serverze obsługą nawiązanych już połączeń mogę się zając w "głównym wątku" ale na potrzebę nasłuchu nowych połączeń muszę utworzyć oddzielny wątek. Zgodnie z moją koncepcją server składałby się z trzech wątków:

1.Wątek nasłuchu nowych połączeń: nasłuchujue nowego połączenia, akceptuje je i przekazuje nawiązane połączenie do wątku głównego.
2.Wątek główny: Odbiera informacje od klientów, przetwarza je i odsyłą z powrotem.
3.Wątek połączenia z bazą danych: Server ma się łączyć i utrzymywać komunikację z bazą SQL która będzie się znajdować na innej maszynie. na razie nie zagłębiałem się w to zagadnienie ale podejrzewam że jest tam masa operacji blokujących więc przydzielam temu zagadnieniu oddzielny wątek.

Z takiego podziału wynika że różne wątki muszą operować na tych samych danych np: wątek 1 po nawiązaniu nowego połączenia musi dodać nowego klienta do jakiejś umownej listyKlientów, natomiast wątek 2 tą samą listę będzie czytał w celu przetwarzania danych. Ponieważ taka czynność może grozić zepsuciem danych, z pomocą przychodzi nam synchornizacja. W przykładach w oparciu o które omówiony jest rozdział "Wielowątkowość" w książce "JAVA 2 - Techniki Zaawansowane" synchronizowana jest jedna metoda operująca na jakiś tam danych. Metoda ta jest wywoływana w wielu wątkach i dlatego deklarowana jest ze słowem kluczowym synchronized albo tworzona w niej jest jawna blokada. No i problem z głowy. Jednak cała ta idea synchronizacji zdaje się nie mieć zastosowania w moim przypadku. Wątek 1 wywoływać będzie przykładową metodą DodajKlientaDoListy(Klient klient) a wątek 2 wywoływać będzie zupełnie inną metodę (np: DaneOKliencie PrzeczytajDaneOKliencie(Klient klient) ) która operować będzie na tych samych danych, tak więc to że użyję przy deklaracji obydwu metod słowa kluczowego synchronized nie uchroni mnie w żaden sposób przed popsuciem danych. No i cała koncepcja synchronizacji pada.
Pierwsze moje pytanie to: Czy wyciągnięty powyżej wniosek jest prawidłowy?
Jesli tak to drugie pytanie brzmi: Jak sobie poradzić z tym problemem?

No i jeszcze jedno zagadnienie:
Załóżmy że mój server składa się tylko z watków numer 1 i 2. Wątek numer 1 ma to do siebie że musi wywołać metodę blokującą żeby zaakceptować połączenie i dzieje się tak nawet gdy żadne połączenie nie oczekuje na zaakceptowanie bo nie da się sprawdzić czy jakieś połączenie czeka na zaakceptowanie bez wywołania tej samej metody blokującej. W praktyce oznacza to że wątek ten będzie się po zaakceptowaniu każdego nowego połaczenia zawieszał aż do momentu nadejścia nowego połączenia. I o ile mi wiadomo wątek taki będzie działał dopuki nie wywłaszczy go system operacyjny stwierdzając że działa on już za długo. Natomiast wątek numer 2 będzie zawierał zdecydowaną większość kodu servera i będzie zdecydowanie bardziej czasochłonny. Tak więc chcąc mu dać możliwie dużo czasu na działanie nie będe go zawieszał po każdej pętli tylko pozwole mu działać aż wywłaszczy go system. Zakładam że system wywłaszczać będzie każdy z wątków po takim samym czasie czyli procesor będzie poświęcał tyle samo czasu wątkowi numer 1 jaki i wątkowi numer 2. Ale biorąc pod uwagę że 95% kodu znajduje się w wątku numer 2 to w praktyce oznaczać to będzie że przez połowę czasu pracy servera procesor pracuje na pełnych obrotach a przez połowę czasu nie robi nic czekając aż zakończy swoje działanie jedna metoda blokujaca, innymi słowy mój server będzie miał w praktyce dostęp tylko do połowy mocy obliczeniowej komputera. Czy tak rzeczywiście będzie? Czy można jakoś zmusić wątek nr 1 do szybszego wywłaszczenia? Czy można w jakiś sposób stwierdzić że wątek numer 1 wykonuje obecnie metodę blokującą i zmienić status wątku? A może problem ten wynika z mojego błędnego podejścia do zagadnienia?

0

MainThread (opcjonalnie) : Wątek główny ma 1 czynność do wykonania : start serwera i przydzielanie gniazdek dla klientów oraz tworzenie wątków które będą nasłuchiwały na tym gniazdku.

ClientThread : Wątek nasłuchujący na gniazdku które został utworzony przez główny wątek serwera.

Z takiego podziału wynika że różne wątki muszą operować na tych samych danych np: wątek 1 po nawiązaniu nowego połączenia musi dodać nowego klienta do jakiejś umownej listyKlientów, natomiast wątek 2 tą samą listę będzie czytał w celu przetwarzania danych. Ponieważ taka czynność może grozić zepsuciem danych, z pomocą przychodzi nam synchornizacja. W przykładach w oparciu o które omówiony
Pierwsze moje pytanie to: 1 .Czy wyciągnięty powyżej wniosek jest prawidłowy?
Jesli tak to drugie pytanie brzmi: 2. Jak sobie poradzić z tym problemem?

1.Nie.
2. Nie ma problemu o ile metody kontenera są synchronizowane (są gotowe kontenery które mają zsynchronizowane wszystkie metody modyfikujące ich dane)

No i jeszcze jedno zagadnienie:
Załóżmy że mój server składa się tylko z watków numer 1 i 2. Wątek numer 1 ma to do siebie że musi wywołać metodę blokującą żeby zaakceptować połączenie i dzieje się tak nawet gdy żadne połączenie nie oczekuje na zaakceptowanie bo nie da się sprawdzić czy jakieś połączenie czeka na zaakceptowanie bez wywołania tej samej metody blokującej. W praktyce oznacza to że wątek ten będzie się po zaakceptowaniu każdego nowego połaczenia zawieszał aż do momentu nadejścia nowego połączenia. I o ile mi wiadomo wątek taki będzie działał dopuki nie wywłaszczy go system operacyjny stwierdzając że działa on już za długo.1. Czy tak rzeczywiście będzie? 2.Czy można jakoś zmusić wątek nr 1 do szybszego wywłaszczenia? 3.Czy można w jakiś sposób stwierdzić że wątek numer 1 wykonuje obecnie metodę blokującą i zmienić status wątku? 4. A może problem ten wynika z mojego błędnego podejścia do zagadnienia?

1.Skąd taki pomysł ?
2 .Nie nie można
3.Nie , ale można sprawdzić można czy coś ma do zrobienia metodą isAlive() . A zmienić status np. poprzez wywołanie metody interrupt wątku który jest zablokowany operacją I/O.
4.Tak . Szukasz dziury w całym [green] i nie czytasz tutoriali suna http://209.85.135.104/search?q=cache:gIfGV-8WT8UJ:java.sun.com/developer/onlineTraining/Programming/BasicJava2/socket.html+multithread+socket+server+java&hl=pl&ct=clnk&cd=1&gl=pl

0

MainThread (opcjonalnie) : Wątek główny ma 1 czynność do wykonania : start serwera i przydzielanie gniazdek dla klientów oraz tworzenie wątków które będą nasłuchiwały na tym gniazdku.

ClientThread : Wątek nasłuchujący na gniazdku które został utworzony przez główny wątek serwera.
To chbya jakiś inny program bo u mnie jest:

Zgodnie z moją koncepcją server składałby się z trzech wątków:

1.Wątek nasłuchu nowych połączeń: nasłuchujue nowego połączenia, akceptuje je i przekazuje nawiązane połączenie do wątku głównego.
2.Wątek główny: Odbiera informacje od klientów, przetwarza je i odsyłą z powrotem.
3.Wątek połączenia z bazą danych: Server ma się łączyć i utrzymywać komunikację z bazą SQL która będzie się znajdować na innej maszynie. na razie nie zagłębiałem się w to zagadnienie ale podejrzewam że jest tam masa operacji blokujących więc przydzielam temu zagadnieniu oddzielny wątek.
Zresztą nawet jakbym zmienił koncepcję na tą którą zasugerowałeś to nie rozwiązuje to a żadnym stopniu omówionych problemów...

1.Nie.
Aha to wszystko wyjaśnia.

Nie ma problemu o ile metody kontenera są synchronizowane (są gotowe kontenery które mają zsynchronizowane wszystkie metody modyfikujące ich dane)
Tyle że ja z takich kontenerów nie będe korzystał ani wgoóle z żadnych kontenerów... Więc problem jednak jest.

1.Skąd taki pomysł ?
Przecież wytłumaczyłem jak wpadłem na taki pomysł. I nie grzecznie jest odpowiadać pytaniem na pytanie...

4.Tak . Szukasz dziury w całym i nie czytasz tutoriali suna http://209.85.135.104/search?q[...]amp;ct=clnk&cd=1&gl=pl
Nie wydaje mi się, i po co ten link? przecież napisałem:

Korzystam z "nonblocking I/O" w oparciu o artykuł: http://www-128.ibm.com/developerworks/java/library/j-javaio/

0

Wkleje kawalek mojego kodu, moze Ci sie przyda (Korzystam z MVC)

public interface IncomingListener{
	public void incoming(Socket s);
}

public class ServerDaemon extends Thread{
	private ServerSocket socket;
	private IncomingListener incomingListener;
	public ServerDaemon(IncomingListener incomingListener) {
		try{
			socket=new ServerSocket(13969);
			this.incomingListener=incomingListener;
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void run() {
		try{
			while(!interrupted()){
				incomingListener.incoming(socket.accept());
			}
		}catch (Exception e) {
			e.printStackTrace();
		}
	}
	public void interrupt() {
		super.interrupt();
		try{
			socket.close();
		}catch (Exception e) {
		}
	}
}
//Gdzies w main
ServerDaemon demon=new ServerDaemon(new IncomingListener(){
			public void incoming(Socket s) {
				ObjectInputStream in=new ObjectInputStream(s.getInputStream());
				ObjectOutputStream out=ObjectOutputStream(s.getOutputStream());
				try{
					ConnectionHandler connectionHandler=new ConnectionHandler(s,in,out);	
					dialog=new ServerDialog(mainShell);
					new ServerControler(connectionHandler,dialog);
					dialog.open();
				}catch (Exception e) {
					if(dialog!=null){
						dialog.close();
					}
					if(out!=null){
						try{
							s.close();
						}catch (Exception ex) {
							ex.printStackTrace();
						}
					}
				}					
			}
		});
		demon.start();

pozdrawiam

0

Z takiego podziału wynika że różne wątki muszą operować na tych samych danych np: wątek 1 po nawiązaniu nowego połączenia musi dodać nowego klienta do jakiejś umownej listyKlientów, natomiast wątek 2 tą samą listę

Tyle że ja z takich kontenerów nie będe korzystał ani wgoóle z żadnych kontenerów... Więc problem jednak jest.

Coś dziwnego ? (bo lista to rodzaj kontenera/kolekcji z tego co wiem ;) ) .
Zapytam więc "niegrzecznie" jaki problem ?
Przecież jeśli metoda jest synchronizowana to zamkiem jest obiekt na którym wywoływana jest metoda synchronizowana i tyle.

Co do reszty to nie będę się wypowiadał żeby nie burzyć koncepcji.

0

z tego co wyczytałem to trochę dziwne podejście. Ogólnie zazwyczaj jest mniej więcej tak

  1. wątek główny aplikacji (obsługa GUI itp) - jeden na aplikację
  2. wątek główny (nasłuchujący) serwera - jeden na aplikację
  3. wątek połączenia - jeden na połączenie

ad.1. chyba oczywiste
ad.2. wątek, który sobie działa i nasłuchuje na konkretnym, wybranym przez nas porcie. Po zaakceptowaniu połączenia tworzy nowy wątek (3) i przekazuje mu połączenie i dalej sobie słucha
ad.3. zajmuje się obsługą pojedyńczego połączenia

taki sposób daje Ci:

  1. każde połaczenie jest obsługiwane na bieŻąco (Boże, widzisz takie błędy i nie grzmisz) (NIE MA połączeń czekających na obsługę)
  2. połączenia nie blokują się wzajemnie ani aplikacji
  3. każde połączenie ma własną pulę zmiennych lokalnych
    wady:
  4. skomplikowana synchronizacja dostępu do zasobów globalnych (w tym GUI)
  5. większe zapotrzebowanie na zasoby

dodatkowo

  1. połączenie z bazą nie musi być w osobnym wątku (może być w wątku GUI)
  2. każde połaczenie z klientem może mieć własne połączenie z bazą i wtedy nie ma "walki" o dostęp do bazy

a całość napisałem bo

1.Wątek nasłuchu nowych połączeń: nasłuchujue nowego połączenia, akceptuje je i przekazuje nawiązane połączenie do wątku głównego.
2.Wątek główny: Odbiera informacje od klientów, przetwarza je i odsyłą z powrotem.

jest wg mnie conajmniej dziwne (szczególnie obsługa połączeń w wątku głównym)

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