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.
Nie ma powodu na sekundowe opóźnienie. Źle coś zaimplementowałeś.
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.
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.
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...
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 :)
- Executor Framework zamiast samodzielnego startowania wątków (w konstruktorze) znacznie poprawi jakość kodu. Będzie on czytelniejszy.
- Pula wątków, a nie wątek per user. Wraz z executor frameworkiem da lepsze rezultaty.
- Zamiast tablic można użyć kolekcji (
ArrayList
), a problem ograniczonego miejsca można rozwiązać za pomocą np.FixedSizeList
z biblioteki commons-collection.
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 :)