Programowanie sieciowe - Przesyłanie danych

0

Witajcie. Robię sobie w Javie "małą" gierkę i chce do niej zrobić multiplayer. Wszystko zrobione, wszystko przemyślane, przetestowane na tym samym komputerze, OK.
Dodam, że kiedyś próbowałem już robić aplikacje sieciowe ale mi nie wyszło. Myślałem, że tym razem jest już wszystko ok, przyszedł czas na testy. Akurat się składa, że mam w domu PC i laptop więc chciałem przetestować jak to działa (wiedziałem, że jak na jednym komputerze śmiga, to wcale nie musi być ok). Włączam, testuje i widzę, że informacje w Stringach są dostarczane po ~1000ms. Jest pewne, że jest to wina systemu przesyłania itd. Dziwne jest to, że jak na jednym komputerze odpalę serwer, na drugim klient wszystko hula. Problem pojawia się kiedy do serwera podłączy się drugi klient wtedy to opóźnienie wynosi +- 1000ms. Więc po tym niepotrzebnym wstępie zadaję pytanie które zapewne wiele razy zostało zadane ale nie znalazłem w innych tematach odpowiedzi. Jaki jest dobry ale i łatwy sposób przesyłania informacji?
Aplikacja jest na socketach. Ogólnie to mam swoją bibliotekę i zależy mi żeby sposób był łatwy do zaimplementowania.

0

Nie ma powodu na sekundowe opóźnienie. Źle coś zaimplementowałeś.

0
Gargulec napisał(a)

Problem pojawia się kiedy do serwera podłączy się drugi klient wtedy to opóźnienie wynosi...

A teraz zgaduj zgadula ile razy użyłeś słowa kluczowego synchronize w swoim kodzie?

Najprawdopodobniej komunikacja jak i logika są synchronizowane. IMO, najprościej jest odpalić serwer, podpiąć dwa klienty, do procesu serwera podpiąć VisualVM, albo Mission Control i zobaczyć jak wygląda sprawa z blokowaniem się wątków.

0

Serwer.java

package StudiumLibrary.Siec.Serwer;

import java.io.*;
import java.net.*;
import java.util.Date;

import StudiumLibrary.Funkcje.Funkcje;

public class Serwer extends Thread{

	/**Instancja**/
	public static Serwer serwer;
	
	//Gniazda klientow
	public static User userzy[];
	
	//Gniazdo serwera
	public static ServerSocket socket;
	
	//Tworzenie serwera
	public void stworzSerwer(int port, int iloscUzytkownikow)
	{
		try 
		{
			//Ustalanie instancji
			serwer = this;
			//Tworzenie gniazda serwera
			socket = new ServerSocket(port);
			System.out.println(Funkcje.czas() + "Serwer został uruchomiony!");
			
			//Ustalanie ilości miejsc
			userzy = new User[iloscUzytkownikow];
			
			//Uruchamianie serwera
			start();
		} 
		catch (IOException e) 
		{
			e.printStackTrace();
		}
	}
	
	//Praca wątku
	public void run()
	{
		//Nasłuchiwanie polączeń
		while(true)
		{
			System.out.println(Funkcje.czas() + "Oczekuje na połączenia...");
			try 
			{
				przyjmijPolaczenie(socket.accept());
			} 
			catch (IOException e) 
			{
				e.printStackTrace();
			}
		}
	}
	
	//Sprawdz wolne miejsca
	public static boolean sprawdzMiejsca()
	{
		int zajete = 0;
		for(int i = 0; i < userzy.length; i++)
		{
			if(userzy[i] != null)
				zajete++;
		}
		
		if(zajete == userzy.length + 1)
			return true;
		
		return false;
	}
	
	//Wyszukiwanie wolnego miejsca
	public static int wolneMiejsce()
	{
		for(int i = 0; i < userzy.length; i++)
		{
			if(userzy[i] == null)
				return i;
		}
		
		return 0;
	}
	
	//Przyjmowanie klienta
	public void przyjmijPolaczenie(Socket socket)
	{
		//Wyszukiwanie miejsca
		int miejsce = wolneMiejsce();
		//Wiadomosc dla serwera
		System.out.println(Funkcje.czas() + "Klient o adresie \"" + socket.getInetAddress() + "\" i numerze " + miejsce + " podłączony!");
		//Tworzenie instancji klienta
		userzy[miejsce] = new User(miejsce, socket);	
		//Funkcja "klient podlaczony"
		serwer.klientPodlaczony(userzy[miejsce]);
	}
	
	//Wiadomość odebrana od klienta
	public void odebranaWiadomosc(User klient, String wiadomosc)
	{
		System.out.println(Funkcje.czas() + "Otrzymano wiadomosc od klienta(" + klient.getNumer() + "): " + wiadomosc);
		
		//Rozsylanie wiadomosci do innych klientow
		for(int i = 0; i < Serwer.userzy.length; i++)
		{
			if(Serwer.userzy[i] != null && i != klient.getNumer())
			{
				Serwer.userzy[i].wyslij(wiadomosc.split(":")[0], wiadomosc.split(":")[1]);
				System.out.println(Funkcje.czas() + "Wyslano wiadomosc od klienta(" + i + "): " + wiadomosc.split(":")[1]);
			}
		}
	}

	//Rozsyłanie wiadomosci
	public static void rozeslij(User autor, String prefix, String wiadomosc)
	{
		for(int i = 0; i < userzy.length; i++)
		{
			//Jeśli klient istnieje
			if(userzy[i] != null)
			{
				//Jeśli autor istnieje, czy to nie ten sam klient?
				if(autor == null || autor != userzy[i])
				{
					//Wysyłanie wiadomosci
					userzy[i].wyslij(prefix, wiadomosc);
				}
			}
		}
	}
	
	/**Zdarzenia serwerowe**/
	public void klientPodlaczony(User klient)
	{
		//Wiadomość powitalna
		klient.wyslij("Serwer", "Witaj " + klient.getNick() + "!");
		//Rozsyłanie wiadomosci
		rozeslij(null, "Serwer", kodujZnaki(klient.getNick() + " łączy się z serwerem."));
	}
	public void klientRozlaczony(User klient)
	{
		//Rozsyłanie wiadomosci
		rozeslij(null, "Serwer", klient.getNick() + " opuszcza szerwer.");
		klient.wyslij("", "koniec");
	}
}

User.java

package StudiumLibrary.Siec.Serwer;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStreamReader;
import java.net.Socket;

import StudiumLibrary.Funkcje.Funkcje;

public class User extends Thread{

	private int numer;
	private String nick;
	private Socket klient;
	private DataOutputStream pw;
	private BufferedReader br;
	
	public User(int numer, Socket klient)
	{
		//Przyjmowanie gniazda
		setNumer(numer);
		setKlient(klient);
		if(klient != null)
		{
			//Inicjacja
			init();
			//Praca
			start();
		}
	}

	//Inicjacja wątku
	public void init()
	{
		try
		{
			//Print writter
			setPw(new DataOutputStream(getKlient().getOutputStream()));
			//BufferedReader
			setBr(new BufferedReader(new InputStreamReader(getKlient().getInputStream())));
			
			//Klient podłączony
			klientPodlaczony();
			
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	//Praca watku
	public void run()
	{				
		while(!getKlient().isClosed())
		{			
			try
			{
				//Odczytywanie wiadomosci
				String str = getBr().readLine();
				if(str != null)
				{
					//Sprawdzanie polaczenia
					if(str.equalsIgnoreCase("koniec:koniec"))
						rozlacz();
					
					System.out.println("(" + getNumer() + ")" + Funkcje.czas());
					Serwer.serwer.odebranaWiadomosc(this, str);
				}				
			}
			catch(Exception e)
			{
				e.printStackTrace();
				rozlacz();
			}
		}
	}
	
	//Użytkownik połączony
	public void klientPodlaczony()
	{
		try
		{
			//Pobieranie nicku
			setNick(Serwer.rozkodujZnaki(getBr().readLine()));
		}
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	//Rozłączenie wątku
	void rozlacz()
	{
		//Zwracanie miejsca
		Serwer.userzy[getNumer()] = null;
		//Wiadomosc dla serwera
		System.out.println(Funkcje.czas() + "Klient(" + getNumer() + ") rozłączył się!");
		//Funkcja "klient rozlaczony"
		Serwer.serwer.klientRozlaczony(this);
		//Zatrzymywanie wątku
		this.stop();
	}
	
	//Wysyłanie wiadomosci
	public void wyslij(String prefix, String wiadomosc)
	{
		try
		{
			getPw().writeBytes(Serwer.kodujZnaki(prefix + ":" + wiadomosc + "\n"));
			getPw().flush();
		}
		catch(Exception e){}
	}
}

Klient.java

package StudiumLibrary.Siec.Klient;

import java.io.*;
import java.net.*;
import java.util.concurrent.ThreadPoolExecutor.DiscardOldestPolicy;

import javax.swing.JOptionPane;

import org.lwjgl.opengl.Display;

import StudiumLibrary.Funkcje.Funkcje;
import StudiumLibrary.Okno.Okno;

public class Klient extends Thread{

	public static Socket gniazdo = null;
	public static DataOutputStream out;
	
	//Nick
	public static String nick = "";
	//Adres IP
	public static String adresIP = "localhost";
	//Port
	public static int port = 7777;
	
	//Łączenie z serwerem
	public void połącz()
	{
		//Uruchamianie wątku
		start();
	}
	
	boolean wlaczony = true;
	public void run()
	{
		//Łączenie z serwerem
		try
		{
			gniazdo = new Socket(adresIP, port);
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
			
			//Wyświetlenie komunikatu
			JOptionPane.showMessageDialog(null, "Serwer o takim adersie IP nie istnieje!");
			//Zatrzymanie wątku
			this.stop();
		}
			
		//Strumień wejscia (odbiera dane)
		InputStream in;
		try 
		{
			in = gniazdo.getInputStream();
				
			BufferedReader br = new BufferedReader(new InputStreamReader(in));
				
			out = new DataOutputStream(gniazdo.getOutputStream());
				
			//Nastąpiło połączenie z serwerem
			polaczony();
				
			while(wlaczony)
			{
				//Odbieranie wiadomosci			
				String str = br.readLine();
					
				if(str != null)
				{
					
					if(str.equalsIgnoreCase("koniec:koniec"))
						rozlacz();
					else
					{
						daneOdebrane(str);
					}
				}
				
				sleep(10);
			}
		} 
		catch (Exception e) 
		{
			e.printStackTrace();
		}
	}
	
	/**Wysyłanie wiadomości**/
	public void wyslij(String prefix, String str)
	{
		try
		{
			//Wysyłanie nicku do serwera
			out.writeBytes(kodujZnaki(prefix + ":" + str + "\n"));
			out.flush();
			
			daneWyslane(str);
		}	
		catch(Exception e)
		{
			e.printStackTrace();
		}
	}
	
	/**Zrywanie połączenia z serwerem**/
	public void rozlacz()
	{
		wyslij("koniec", "koniec");
		wlaczony = false;
		this.stop();
	}
	
	/**Zdarzenia**/
	//Serwer uruchomiony
	public void polaczony()
	{
		//Wysyłanie nicku serwerowi
		wyslij("", nick);
	}
	//Dane wysłane serwerowi
	public void daneWyslane(String str){}
	//Dane odebrane od serwera
	public void daneOdebrane(String str)
	{
		System.out.println("Odebrano dane od serwera: " + rozkodujZnaki(str));
	}
}

Klasy te są zawarte w mojej bibliotece i potem z nich dziedziczę ale zapewne tutaj jest problem.

1

No serwer to woła o pomstę do nieba. Słyszałeś kiedyś o takich rzeczach jak kolekcje? Jak jakieś ArrayList? Juz nawet nie mówię o tych zupełnie niepotrzebnych staticach i spaghetti code.
Poza tym organizacja tego kodu jest dość słaba. Wątek który startuje się samodzielnie w swoim konstruktorze to jest bardzo dziwny pomysł. Za puste bloki catch powinieneś dostać zakaz używania komputera. Poza tym masz tu synchroniczne rozsyłanie wiadomości co jest raczej słabe. Skoro User to wątek to czemu nie wykonujesz akcji wysyłania za pomocą tego wątku i prostej kolejki tylko robisz to w wątku serwera? Jak będziesz miał 100 userów to ten ostatni trochę się naczeka...

0

Też jestem samoukiem i tak analizując Twój kod oraz wypowiedzi forumowiczów, to czy ktoś mógłby Mi wyjaśnić dlaczego catch są "puste"? Bo nie rozumiem :)

2
  1. Executor Framework zamiast samodzielnego startowania wątków (w konstruktorze) znacznie poprawi jakość kodu. Będzie on czytelniejszy.
  2. Pula wątków, a nie wątek per user. Wraz z executor frameworkiem da lepsze rezultaty.
  3. Zamiast tablic można użyć kolekcji (ArrayList), a problem ograniczonego miejsca można rozwiązać za pomocą np. FixedSizeList z biblioteki commons-collection.
0

Zdecydowanie muszę się wziąć za książki bo programuje aplikację (może bardziej staram się), ale jak widzę Twoją wiedzę @Koziołek to jestem pod wrażeniem i jednocześnie pod samoniezadowloeniem.
Pozdrawiam!

@Niezdecydowany (piszę niżej zbyt dużo znaków)
To skąd ma wziąć wiedzę na temat prawidłowego budowania kodu samouk? Aktualnie piszę aplikację, która wygląda jak Instagram, z listą zdjęć z algorytmem, który dopasowuje je do siebie w zależności od ich rozmiarów. Mam logowanie, rejestracje, robienie zdjęcia i wrzucanie go do aplikacji, filtry, kategorie i tak właściwie to jestem z niej zadowolony pod względem działania (wiadomo muszę ją dopracować). Jednak jakby któryś z Was zobaczyłby kod to wydaję mi się, że byłyby podobne komentarze jak do kodu poprzednika. Ten z "zakazem dotykania komputera" mnie dobił. Dlatego Moim zdaniem napisać COŚ można się nauczyć samemu, ale napisać COŚ z odpowiednią strukturą to już inna bajka i nie wiem skąd taką wiedzę pozyskać jak nie z książek?. Na studiach mało się nauczyłem, ale to może Moja wina :)

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