Autor Wątek: [Java] Serwer low-latence  (Przeczytany 3012 razy)

Offline Radomiej

  • Użytkownik
    • Blog

# Kwiecień 24, 2014, 13:46:59
Witam, piszę serwer pod grę. Niestety po wgraniu na dedykowany serwer i połączeniu z serwerem występują spore opóźnienia ok(450 ms) - tyle czasu wysyła się pętla. Czy jest jakiś sposób żeby je zminimalizować?
Bo nie wiem już czy wysyłam za dużo danych czy po prostu muszę użyć jakiejś innej techniki wysyłania.
Dane wysyłane z/do serwera dla każdego z graczy(2 graczy na pokój):
while(){
                  //Odebranie pozycji gracza     
                  players[player].byteToMove(in.readByte());
                  players[player].body.x = in.readFloat();
                  players[player].body.y = in.readFloat();                 
                  if(in.readBoolean()){         
                     //Wykonuje akcje
                  }
                  //Wysyła stany bloków |25 X 21|zazwyczaj dłużej niż co 1s               
                  if(blockChanged){ //Jeśli zmieniono bloki                     
                     synchronized(fields){
                        out.writeBoolean(true);
                        for(int x = 0; x < MAP_WIDTH; x++){
                           for(int y = 0; y < MAP_HEIGHT; y++){
                              out.writeByte(fields[x][y].GetByte());
                           }
                        }
                     }
                  }else out.writeBoolean(false);                 
                 
                  //2 graczy
                  for(Player otherPlayer : players){                     
                     out.writeByte(otherPlayer.MoveToByte());
                     out.writeFloat(otherPlayer.body.x);
                     out.writeFloat(otherPlayer.body.y);   
                  }
                     
                 
                  synchronized(events){
                     for(GameEvent ge : events){
                        out.writeBoolean(true);                           
                        //Wysyła dane zdarzenia
                        out.writeByte(typ);
                        out.writeFloat(ge.x);
                        out.writeFloat(ge.y);
                        out.writeByte(ge.owner);
                     }
                     out.writeBoolean(false);
                     events.clear();
                  }                 
                  local_events.clear();
                 
                  outToServer.reset();
                  outToServer.writeObject(stan);
                     
                  Thread.sleep(25);
               }

Oczywiście lokalnie wszystko chodzi dobrze.

Offline Mr. Spam

  • Miłośnik przetworów mięsnych

Offline Liosan

  • Redaktor

# Kwiecień 24, 2014, 15:14:51
Najpierw powiedz co to są in i out, i jakie ilości danych wysyłasz do serwera, i gdzie ten serwer się znajduje (jaki masz ping do niego).

Liosan

Offline Radomiej

  • Użytkownik
    • Blog

# Kwiecień 24, 2014, 15:41:09
Korzystam z takich klas:
DataOutputStream out
DataInputStream in
ObjectOutputStream outToServer
ObjectInputStream inFromServer
ping z serwerem oscyluje średnio na (40-50ms)

ilość danych która się przewala to:
~4000 bajtów / sekundę wysyła serwer. wychodzi po 2000 na każdego z klientów
~200 bajtów / s wysyłają klienci

Offline Liosan

  • Redaktor

# Kwiecień 24, 2014, 15:51:42
DataOutputStream, ObjectOutputStream to wrappery. Jakie strumienie są pod spodem?

Próbuje się dowiedzieć, czy Twój strumień sieciowy używa buforowania :) Te ilości danych są malutkie.

Liosan

Offline Radomiej

  • Użytkownik
    • Blog

# Kwiecień 24, 2014, 15:59:47
Korzystam ze zwykłych strumieni pobranych z socketa:
public SimpleClient(Socket client) throws IOException{
this.client = client;
inStream = client.getInputStream();
outStream = client.getOutputStream();
}

no własnie nie są to duże dane więc powoli się skłaniam że to wina serwera, albo wykorzystania procesora przez mój serwer. Być może dodanie to do reszty wątków powinno nieco poprawić wydajność...

EDIT: Dodałem sleepy() do paru pętli. Na maszynie lokalnej widać poprawę(pozycja graczy już się nie zcina), zużycie procka też spadło. Miejmy nadzieję że to wina tego własnie była. Jak przetestuje na serwerze dam znać:)
« Ostatnia zmiana: Kwiecień 24, 2014, 16:07:27 wysłana przez Radomiej »

Offline magik6000

  • Użytkownik

# Kwiecień 24, 2014, 16:08:00
Miło było by zobaczyć odrobinę większy kawałek klasy. Pytanie raczej czy powodem spowolnienia nie jest jakiś BufferedStream po drodze/lub czy może coś innego nie laguje serwera. Co do pierwszego polecam używać metody .flush() po wpisaniu danych, a jeśli BufferedStreamów nie używasz, to sprawdzenie gdzie dochodzi do 'zatoru'(z tego co pamiętam sockety w javie w połączeniu z Object[...]Stream potrafiły się czasami blokować, podobno pomagały BufferedStreamy z flush'ami[czy jakoś tak, głowy nie daje]). Co do drógiego to VPN/hamachi/cosinnegozszyfrowaniem+JMX+VisualVM

Offline Radomiej

  • Użytkownik
    • Blog

# Kwiecień 24, 2014, 16:19:45
Oto cały kod tej klasy:
Klasa SimpleClient przechowuje Socket klienta i zwraca standardowe strumienie tego socketa.
//Wątek aktualizujący stan gry
class Connector implements Runnable{

volatile public boolean connect;

SimpleClient client;
InputStream inStream;
OutputStream outStream;
ObjectOutputStream outToServer;
        ObjectInputStream inFromServer;
        DataOutputStream out;
        DataInputStream in;
        Timer timer;
        Timer ping;
        int player;

        /*
         * Stany aktualizacji zdarzeń w grze
         */
        /**
         * Określa czy bloki w grze nie zostały zmienione
         */
volatile public boolean blockChanged;
volatile ArrayList<GameEvent> events;
/**
* Event
*/
volatile private ArrayList<GameEvent> local_events;
public Connector(int i, SimpleClient c) {
// TODO Auto-generated constructor stub
events = new ArrayList<GameEvent>();
local_events = new ArrayList<GameEvent>();
player = i;
client = c;
timer = new Timer();
}

@Override
public void run() {
// TODO Auto-generated method stub
//Oczekiwanie na clienta
try {
inStream = client.InputStream();
outStream = client.OutputStream();
outToServer = new ObjectOutputStream(outStream);
        inFromServer = new ObjectInputStream(inStream);
        outToServer.flush();        
        out = new DataOutputStream(outStream);
        in = new DataInputStream(inStream);          

        //Wysyła gracza
        outToServer.writeObject(players[player]);
        outToServer.writeObject(players);
        outToServer.flush();
        out.writeInt(player);
connect = true;
       

} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
MasterExit = true;
}

//PĘTLA SERWERA
boolean ending = false;
while(!exit || !ending){
if(exit) {
ending = true;
//Zmiana statusu klienta
if(client.getStatus() != ConnectionStatus.DISCONNECT) client.setStatus(ConnectionStatus.END_PLAYING);
return;
}


//LUDZI
Player[] p = players;
try {
//clientMove[player] = (Player.MOVE)inFromServer.readObject();
//Odebranie pozycji gracza
//System.out.println("SerwerRoom: " + "Wczytuje pozycje gracza");
players[player].byteToMove(in.readByte());
players[player].body.x = in.readFloat();
players[player].body.y = in.readFloat();
if(in.readBoolean()){
players[player].throwBomb = true;
System.out.println("SerwerRoom: " + "Stawia bombe");
}
//Wysyła stany bloków
//if(timer.GetTime() > 200){
if(blockChanged){ //Jeśli zmieniono bloki
System.out.println("SerwerRoom: " + "Aktualizuje bloki");
timer.Reset();
synchronized(fields){
out.writeBoolean(true);
for(int x = 0; x < MAP_WIDTH; x++){
for(int y = 0; y < MAP_HEIGHT; y++){
out.writeByte(fields[x][y].GetByte());
}
}
}
}else out.writeBoolean(false);

for(Player otherPlayer : players){
//Tutaj powinno być ręczne wysyłanie odpowiednich informacji o graczach
//out.writeBoolean(true);
out.writeByte(otherPlayer.MoveToByte());
out.writeFloat(otherPlayer.body.x);
out.writeFloat(otherPlayer.body.y);
}
//out.writeBoolean(false);

synchronized(events){
for(GameEvent ge : events){
out.writeBoolean(true);
//local_events.add(ge);
byte typ = 0;
if(ge.event == EventType.CREATE_BOMB);
else if(ge.event == EventType.DESTROY_WALL) typ = 1;
else if(ge.event == EventType.SPAWN_BONUS_BOMB) typ = 2;
else if(ge.event == EventType.SPAWN_BONUS_RANGE) typ = 3;
else if(ge.event == EventType.SPAWN_BONUS_SPEED) typ = 4;
out.writeByte(typ);
out.writeFloat(ge.x);
out.writeFloat(ge.y);
out.writeByte(ge.owner);

}
out.writeBoolean(false);
events.clear();
}
//outToServer.writeObject(local_events);
local_events.clear();

outToServer.reset();
outToServer.writeObject(stan);

Thread.sleep(25);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
client.setStatus(ConnectionStatus.DISCONNECT);
exit = true;
return;
//MasterExit = true;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}

}

Co do zatorów to jeśli ci chodzi o pernamentne blokowanie, to racja zdarzało się tak, ale poradziłem sobie z tym. Chyba że chodzi ci o czasowe, jeśli tak to postaram się zastąpić ObjectStreamy
« Ostatnia zmiana: Kwiecień 24, 2014, 16:22:04 wysłana przez Radomiej »

Offline owyn

  • Użytkownik

# Kwiecień 24, 2014, 19:06:56
Spróbuj dodać buforowanie - opakować strumienie w BufferedInputStream / BufferedOutputStream, ustawić socket.setSendBufferSize, socket.setReceiveBufferSize.

Offline Radomiej

  • Użytkownik
    • Blog

# Kwiecień 28, 2014, 13:21:22
Dobra poradziłem sobie, heh. Teraz jeszcze poprawię co trzeba i będę testował:)

Jakby ktoś w przyszłości próbował bawić się w opakowanie strumieni to tutaj zamieszczam małą ściąge pakowania strumieni:

//Podstawowe strumienie
inStream = client.getInputStream();
outStream = client.getOutputStream();

//Wrapper do podstawowych strumieni
bIn = new BufferedInputStream(inStream);
bOut = new BufferedOutputStream(outStream);
//Wraper na bufforowy strumień || jak nie trzeba wysyłać obiektów to lepiej używać jego
//dos = new DataOutputStream(outStream);
//dis = new DataInputStream(inStream);

//Wraper na bufforowy strumień
//Tworzymy strumień wyjściowy
oos = new ObjectOutputStream(bOut);
//Zapisujemy do niego przykładowy obiekt który wyślemy do drugiego socketu
oos.writeObject(new SerializeObject());
//Wysyłamy
oos.flush();
//A sami czekamy na to samo co wysłaliśmy powyżej, od drugiej strony
//Inaczej nie odblokujemy strumienia Object, który musi sprawdzić czy wszystko jest ok
ois = new ObjectInputStream(bIn);
try {
//Jak nasz strumień Object sprawdził że wszystko jest ok to musimy zczytać obiekt
ois.readObject();
} catch (ClassNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//Dalej możemy działać jak na zwykłym ObjectOutputStream a nie dziedziczącym z bufferedOutputStream.

Pytanie co jest wydajniejsze Object...Stream vs Data...Stream czy operowanie na samym Buffered...Stream?
« Ostatnia zmiana: Kwiecień 29, 2014, 15:31:18 wysłana przez Radomiej »