Wielokrotna Serializacja

Wielokrotna Serializacja
antoniaklja
  • Rejestracja:około 14 lat
  • Ostatnio:około 9 lat
  • Postów:88
0

Witam.
Piszę grę z komunikacją klient <--> serwer opartą na wątkach. Podczas połączenia klienta z serwerem, serwer uruchamia dla niego wątek tworząc w konstruktorze ObjectInputStream i ObjectOutputStream potrzebne do serializacji. Klient wysyła obiekt do serwera a ten odbiera go w konkretnym wątku i wypisuje na konsolę.
Problem zaczyna się kiedy połączy się dwóch klientów, uruchomią się dwa wątki z osobnymi stworzonymi w konstruktorze kanałami komunikacyjnymi. Pierwszy klient wykonuje dowolną ilość akcji(po wykonaniu wysyłam obiekt do serwera), wątek odbiera obiekt i wypisuje na konsole pola zdeserializowanego obiektu. Potem przełączam okno na drugiego klienta i wykonuje akcje lecz dochodzi tylko jeden obiekt i następuje zawieszenie, w niektórych przypadkach dostaje wyjątki:

Kopiuj
java.io.StreamCorruptedException: invalid type code: 00
	at java.io.ObjectInputStream.readObject0(Unknown Source)
	at java.io.ObjectInputStream.readObject(Unknown Source)
	at siec.Server.readSerializedObject(Server.java:125)
	at siec.Server.run(Server.java:144)

oraz

Kopiuj
IOException

klasa serwera:

Kopiuj
package siec;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class Server extends Thread {
	public static final int PORT = 5000;
	public static final String IP = "127.0.0.1";
	static InetAddress addr = null;
	static ServerSocket serverSocket = null;
	static Socket socket = null;
	static BufferedReader in = null;
	PrintWriter out = null;
	static ObjectOutputStream oos = null;
	static ObjectInputStream ois = null;
	private int PLAYER_ID = 0;

	private int players = 0;

	public Server() {
		// /////////////
	}

	public void updatePlayerId(String playerId) {
		if (playerId.compareTo("1") == 0)
			PLAYER_ID = 1;
		if (playerId.compareTo("2") == 0)
			PLAYER_ID = 2;
		System.out.println("Nowy gracz o id: " + PLAYER_ID);
	}

	public Server(Socket s, String playerId) throws IOException {
		socket = s;
		updatePlayerId(playerId);
		in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
		System.err.println("-->Stworzono BufferedReader dla wątku");
		out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
				socket.getOutputStream())), true);
		System.err.println("-->Stworzono PrintWriter dla wątku");

		oos = new ObjectOutputStream(socket.getOutputStream());
		System.err.println("-->Stworzono ObjectOutputStream dla wątku");
		ois = new ObjectInputStream(socket.getInputStream());
		System.err.println("-->Stworzono ObjectInputStream dla wątku");

		start();
		System.err.println("-->Stworzono wątek dla klienta o id: " + PLAYER_ID);
	}

	public ServerSocket startServer(ServerSocket serverSocket, int port) {
		try {
			serverSocket = new ServerSocket(port);
			System.err.println("-->Serwer wystartował");
		} catch (IOException e) {
			System.err.println("-->Błąd startu servera");
		}
		return serverSocket;
	}

	/*
	 * public Socket acceptConnection(ServerSocket serverSocket, Socket socket)
	 * throws IOException { //serverSocket = new ServerSocket(port); try {
	 * socket = serverSocket.accept();
	 * System.err.println("Przyjeto połączenie o id: " + socket); } catch
	 * (Exception e) { //System.err.println("-->Błąd przyjmowania połączenia");
	 * } return socket; }
	 */

	public void addPlayer() {
		this.players++;
	}

	public String getPlayers() {
		return "" + this.players;
	}

	public String readData(Socket socket) throws IOException {
		String data = null;
		try {
			in = new BufferedReader(new InputStreamReader(
					socket.getInputStream()));
			data = in.readLine();
		} catch (IOException e) {
			System.err.println("-->Błąd czytania danych");
		}
		return data;
	}

	public void writeData(Socket socket, String data) throws IOException {
		try {
			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(
					socket.getOutputStream())), true);
			out.println(data);
		} catch (IOException e) {
			System.err.println("-->Błąd wysyłania");
		}
	}

	public static void writeSerializedObject(Gamer gamer) {
		try {
			oos.writeObject(gamer);
			oos.flush();
			oos.reset();
		} catch (IOException e) {
			System.err.println("Błąd wysyłania obiektu");
		}
	}

	public Gamer readSerializedObject() throws IOException,
			ClassNotFoundException {
		Gamer gamer = null;
		try {
			gamer = (Gamer) ois.readObject();
		} catch (IOException e) {
			System.out.println("Błąd odczytu obiektu");
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			System.out
					.println("Błąd ClassNotFoundException podczas czytania obiektu");
		}
		return gamer;
	}

	@Override
	public void run() {
		Gamer gamer = null;
		Gamer gamerToSend = null;
		String data = null;
		while (true) {
			try {

				gamer = readSerializedObject();
				// data = readData(socket);
				if (gamer != null) {
					if (gamer.getGamerId() == 1)
						System.out.println("Gracz 1: " + socket.getPort()
								+ "  " + gamer.toString());
					if (gamer.getGamerId() == 2)
						System.out.println("Gracz 2: " + socket.getPort()
								+ "  " + gamer.toString());
				}
			} catch (IOException e) {
				e.printStackTrace();
			} catch (ClassNotFoundException e) {
				e.printStackTrace();
			} catch (NullPointerException e) {
				e.printStackTrace();
			}

		}
	}
}

Kopiuj
package siec;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerManager {
	final static int PORT = 5000;
	final String IP = "127.0.0.1";
	static ServerSocket serverSocket = null;
	static Server server = new Server();
	
	static ByteArrayInputStream baisThread = null;	
	static ObjectInputStream oisThread = null;
	static ByteArrayOutputStream baosThread = null;
	static ObjectOutputStream oosThread = null;
	
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		baosThread = new ByteArrayOutputStream();
		oosThread = new ObjectOutputStream(baosThread);
		
		baisThread = new ByteArrayInputStream(baosThread.toByteArray());
		oisThread = new ObjectInputStream(baisThread);
		
		
		serverSocket = server.startServer(serverSocket, PORT);
		try {
			while(true) {
				Socket socket = serverSocket.accept();
				System.err.println("-->Nowe połączenie " + socket);
				server.addPlayer();
				new Server(socket, server.getPlayers());
				server.writeData(socket, server.getPlayers());
			}
		} catch (IOException e) {
				System.err.println("-->Błąd przyjmowania połącznenia");
		}
	}
}

Klient posiada te same metody co serwer więc nie ma sensu wklejać tyle kodu.

Chciałbym zadać przy okazji kolejne pytanie.
Otóż jeśli komunikacja klient <-->serwer<-->klient działałaby, jak mogę przesłać odebrany obiekt od pierwszego klienta w pierwszym wątku do drugiego wątku aby ten mógł wysłać go do drugiego klienta(wszystko za pośrednictwem serwera) ?
Z góry dziękuje za pomoc.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Pierwsze co rzuca się w oczy to to, że tworzysz tylko jeden stream, wspólny dla wszystkich klientów. Co więcej, czytasz z tego streamu w różnych wątkach bez żadnej synchronizacji...


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
antoniaklja
  • Rejestracja:około 14 lat
  • Ostatnio:około 9 lat
  • Postów:88
0

przecież w konstruktorze Servera tworzę stream dla każdego obiektu, po akceptacji połączenia jest tworzony nowy obiekt Servera i wątek korzysta właśnie z nich..
Jeśli jestem w błędzie to proszę o jakieś wskazówki.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

A fakt, tak to skomplikowałeś że trudno się w tym połapać. Bo u ciebie Server to jest tak na prawdę wątek obsługi klienta, a ServerManager to jest serwer...
Nadal widzę tu problem z synchronizacją, a raczej jej brakiem. Zapamiętaj że jeśli gdziekolwiek używasz wątków to na 99% trzeba to będzie synchronizować.
Ten kod w ogóle nie trzyma się kupy i trudno mi w ogóle ogarnać co ty tu chciałeś zrobić. Moja rada? Skasuj to i napisz ponownie, tym razem z głową.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
antoniaklja
  • Rejestracja:około 14 lat
  • Ostatnio:około 9 lat
  • Postów:88
0

ok tylko przy jakich założeniach mam to napisać? Jak stworzyć odrębne kanały komunikacyjne dla każdego klienta? Jak odbierać te dane na serwerze? I jak przysyłać serializowane obiekty między wątkami?

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Najprościej? Przez RMI ;]


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
antoniaklja
  • Rejestracja:około 14 lat
  • Ostatnio:około 9 lat
  • Postów:88
0

dobrze tylko w czym pomoże mi RMI? Nie zależy mi na zdalnym wykonywaniu metod na serwerze tylko na komunikacji i przesyłaniu obiektów klient --- serwer --- klient.
Przy wykorzystaniu RMI musiałbym przenieść wszystkie metody do interfejsu serwera i wykonywać je zdalnie za pomocą klienta i pobierać tylko ich wyniki i potem repaint w kliencie. Do tego musiałbym je również zostawić po stronie klienta żeby np. utworzyć obiekt gracza.

Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
0

Oczywiście, ale ja podałem najłatwiejsze rozwiązanie.
Twój pomysł tutaj jest dobry, ale za bardzo to zagmatwałeś.


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
antoniaklja
  • Rejestracja:około 14 lat
  • Ostatnio:około 9 lat
  • Postów:88
0

Rozumiem, zabieram się właśnie za pisanie tego od nowa :) Mógłbyś mi napisać jakieś wskazówki jak to dobrze zrealizować i czego się trzymać?

  1. Jak utworzyć dla każdego klienta osobny kanał komunikacyjny?
  2. Jak przesyłać wewnątrz serwera odebrane obiekty od klienta między działającymi wątkami?
  3. Jak synchronizować wykonywane operacje?
Shalom
  • Rejestracja:około 21 lat
  • Ostatnio:prawie 3 lata
  • Lokalizacja:Space: the final frontier
  • Postów:26433
1

Pisząc tego posta znalazłem twój błąd w kodzie: nie rozumiesz do czego służy static...

Ja to widzę tak:

  • klasa Server która zajmuje się nasłuchiwaniem na klientów, jest wątkiem. W main() tworzysz jej obiekt i go startujesz i tyle (!). Jeśli nowy klient się dołączy to tworzony jest nowy obiekt obsługi klienta. Wszystkie obiekty obsługi klienta są trzymane w jakiejś kolekcji.
  • klasa ClientHandlerThread zajmuje się obsługą jednego klienta i potrzebuje tylko socket do niego (a razem z socketem ma streamy).
    Synchronizacja nie będzie konieczna, bo w przeciwieństwie do twojego dziwnego rozwiazania nie ma tutaj współdzielenia danych przez wątki.

Czego w takim razie w twoim kodzie być nie powinno, a jest?

Kopiuj
        static Server server = new Server();
        static ByteArrayInputStream baisThread = null;        
        static ObjectInputStream oisThread = null;
        static ByteArrayOutputStream baosThread = null;
        static ObjectOutputStream oosThread = null;
Kopiuj
        public static final int PORT = 5000;
        public static final String IP = "127.0.0.1";
        static InetAddress addr = null;
        static ServerSocket serverSocket = null;
        static Socket socket = null;
        static ObjectOutputStream oos = null;
        static ObjectInputStream ois = null;
        private int PLAYER_ID = 0;

(socket i streamy nie mogą być statyczne bo będą wspólne dla każdego obiektu!)


"Nie brookliński most, ale przemienić w jasny, nowy dzień najsmutniejszą noc - to jest dopiero coś!"
Kliknij, aby dodać treść...

Pomoc 1.18.8

Typografia

Edytor obsługuje składnie Markdown, w której pojedynczy akcent *kursywa* oraz _kursywa_ to pochylenie. Z kolei podwójny akcent **pogrubienie** oraz __pogrubienie__ to pogrubienie. Dodanie znaczników ~~strike~~ to przekreślenie.

Możesz dodać formatowanie komendami , , oraz .

Ponieważ dekoracja podkreślenia jest przeznaczona na linki, markdown nie zawiera specjalnej składni dla podkreślenia. Dlatego by dodać podkreślenie, użyj <u>underline</u>.

Komendy formatujące reagują na skróty klawiszowe: Ctrl+B, Ctrl+I, Ctrl+U oraz Ctrl+S.

Linki

By dodać link w edytorze użyj komendy lub użyj składni [title](link). URL umieszczony w linku lub nawet URL umieszczony bezpośrednio w tekście będzie aktywny i klikalny.

Jeżeli chcesz, możesz samodzielnie dodać link: <a href="link">title</a>.

Wewnętrzne odnośniki

Możesz umieścić odnośnik do wewnętrznej podstrony, używając następującej składni: [[Delphi/Kompendium]] lub [[Delphi/Kompendium|kliknij, aby przejść do kompendium]]. Odnośniki mogą prowadzić do Forum 4programmers.net lub np. do Kompendium.

Wspomnienia użytkowników

By wspomnieć użytkownika forum, wpisz w formularzu znak @. Zobaczysz okienko samouzupełniające nazwy użytkowników. Samouzupełnienie dobierze odpowiedni format wspomnienia, zależnie od tego czy w nazwie użytkownika znajduje się spacja.

Znaczniki HTML

Dozwolone jest używanie niektórych znaczników HTML: <a>, <b>, <i>, <kbd>, <del>, <strong>, <dfn>, <pre>, <blockquote>, <hr/>, <sub>, <sup> oraz <img/>.

Skróty klawiszowe

Dodaj kombinację klawiszy komendą notacji klawiszy lub skrótem klawiszowym Alt+K.

Reprezentuj kombinacje klawiszowe używając taga <kbd>. Oddziel od siebie klawisze znakiem plus, np <kbd>Alt+Tab</kbd>.

Indeks górny oraz dolny

Przykład: wpisując H<sub>2</sub>O i m<sup>2</sup> otrzymasz: H2O i m2.

Składnia Tex

By precyzyjnie wyrazić działanie matematyczne, użyj składni Tex.

<tex>arcctg(x) = argtan(\frac{1}{x}) = arcsin(\frac{1}{\sqrt{1+x^2}})</tex>

Kod źródłowy

Krótkie fragmenty kodu

Wszelkie jednolinijkowe instrukcje języka programowania powinny być zawarte pomiędzy obróconymi apostrofami: `kod instrukcji` lub ``console.log(`string`);``.

Kod wielolinijkowy

Dodaj fragment kodu komendą . Fragmenty kodu zajmujące całą lub więcej linijek powinny być umieszczone w wielolinijkowym fragmencie kodu. Znaczniki ``` lub ~~~ umożliwiają kolorowanie różnych języków programowania. Możemy nadać nazwę języka programowania używając auto-uzupełnienia, kod został pokolorowany używając konkretnych ustawień kolorowania składni:

```javascript
document.write('Hello World');
```

Możesz zaznaczyć również już wklejony kod w edytorze, i użyć komendy  by zamienić go w kod. Użyj kombinacji Ctrl+`, by dodać fragment kodu bez oznaczników języka.

Tabelki

Dodaj przykładową tabelkę używając komendy . Przykładowa tabelka składa się z dwóch kolumn, nagłówka i jednego wiersza.

Wygeneruj tabelkę na podstawie szablonu. Oddziel komórki separatorem ; lub |, a następnie zaznacz szablonu.

nazwisko;dziedzina;odkrycie
Pitagoras;mathematics;Pythagorean Theorem
Albert Einstein;physics;General Relativity
Marie Curie, Pierre Curie;chemistry;Radium, Polonium

Użyj komendy by zamienić zaznaczony szablon na tabelkę Markdown.

Lista uporządkowana i nieuporządkowana

Możliwe jest tworzenie listy numerowanych oraz wypunktowanych. Wystarczy, że pierwszym znakiem linii będzie * lub - dla listy nieuporządkowanej oraz 1. dla listy uporządkowanej.

Użyj komendy by dodać listę uporządkowaną.

1. Lista numerowana
2. Lista numerowana

Użyj komendy by dodać listę nieuporządkowaną.

* Lista wypunktowana
* Lista wypunktowana
** Lista wypunktowana (drugi poziom)

Składnia Markdown

Edytor obsługuje składnię Markdown, która składa się ze znaków specjalnych. Dostępne komendy, jak formatowanie , dodanie tabelki lub fragmentu kodu są w pewnym sensie świadome otaczającej jej składni, i postarają się unikać uszkodzenia jej.

Dla przykładu, używając tylko dostępnych komend, nie możemy dodać formatowania pogrubienia do kodu wielolinijkowego, albo dodać listy do tabelki - mogłoby to doprowadzić do uszkodzenia składni.

W pewnych odosobnionych przypadkach brak nowej linii przed elementami markdown również mógłby uszkodzić składnie, dlatego edytor dodaje brakujące nowe linie. Dla przykładu, dodanie formatowania pochylenia zaraz po tabelce, mogłoby zostać błędne zinterpretowane, więc edytor doda oddzielającą nową linię pomiędzy tabelką, a pochyleniem.

Skróty klawiszowe

Skróty formatujące, kiedy w edytorze znajduje się pojedynczy kursor, wstawiają sformatowany tekst przykładowy. Jeśli w edytorze znajduje się zaznaczenie (słowo, linijka, paragraf), wtedy zaznaczenie zostaje sformatowane.

  • Ctrl+B - dodaj pogrubienie lub pogrub zaznaczenie
  • Ctrl+I - dodaj pochylenie lub pochyl zaznaczenie
  • Ctrl+U - dodaj podkreślenie lub podkreśl zaznaczenie
  • Ctrl+S - dodaj przekreślenie lub przekreśl zaznaczenie

Notacja Klawiszy

  • Alt+K - dodaj notację klawiszy

Fragment kodu bez oznacznika

  • Alt+C - dodaj pusty fragment kodu

Skróty operujące na kodzie i linijkach:

  • Alt+L - zaznaczenie całej linii
  • Alt+, Alt+ - przeniesienie linijki w której znajduje się kursor w górę/dół.
  • Tab/⌘+] - dodaj wcięcie (wcięcie w prawo)
  • Shit+Tab/⌘+[ - usunięcie wcięcia (wycięcie w lewo)

Dodawanie postów:

  • Ctrl+Enter - dodaj post
  • ⌘+Enter - dodaj post (MacOS)