Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Медленный отклик


Автор: Рубильник 10.5.2018, 12:06
Есть простой сервер и клиент.
Клиент отправляет пакет из 10-20 байт в котором есть число, сервер прочитывает это число, прибавляет 1 и возвращает обратно. Как только клиент прочитывает возвращенное значение, он отсылает пакет серверу обратно.

Т.е. самописная система для тестирования отклика по сети. Использованы только базовые средства java.
После записи делается flush() в обязательном порядке.

Если тестить локально, то получается 1000 откликов занимает 50-60 мс. Если сервер и клиент на разных компах (но внутри одной сети), то 100 откликов занимает целых 33 секунды, т.е. один отклик занимает треть секунды.

В чем может быть дело? Пингование стандартными средствами показывает гораздо быстрый отклик.

Может ли в этом виноватой быть ОС Windows XP?

Автор: AntonSaburov 10.5.2018, 12:45
Я сталкивался с ситуацией, когда пакет может "зависать" на свичах, если он маленький. 
Попробуйте организовать многопоточный вариант - например потоков 50 и каждый делает, что описано. На сервере тоже многопоточный вариант. Может сдвинется. Хотя гарантий конечно же нет.

Автор: Рубильник 10.5.2018, 13:13
Система строится по концепции тонкого клиента, где многие действия пользователя формирует обращение к серверу. Требовалось оценить скорость отклика, чтобы у пользователя не возникало дискомфорта.

Не очень понимаю, чем может помочь здесь много поточность? Меня же интересует именно отклик, а не пропускная способность.

В играх сигнал идет от клиента, обрабатывается на сервере и возвращается (для визуализации). Пусть реально это 20 fps, т.е. отклик всего 50 мс. И меня бы это устроило, но треть секунды - это очень много, и визуально очень заметно, это ещё без времени для отработки на самом сервере (кто знает какие там запросы).

Меня интересует "кто виноват?"

Если причина тому сама Java - это одно, значит придется менять концепцию. Если виновата ОС или сетевые настройки, то это совсем другое дело.

На всякий случай выкладываю код теста.
Код

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;

public class ClientFrame extends JFrame{
    public JButton buttonStart = new JButton("Тест");
    public JLabel label = new JLabel("          ");
    public Socket socket = null;
    
    public void configurateGUI(){   
        setBounds(100, 100, 300, 200);
        setTitle("Клиентская сторона");
        JPanel mainPanel = new JPanel();
        mainPanel.add(buttonStart);
        mainPanel.add(label);
        getContentPane().add(mainPanel);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    
        buttonStart.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                buttonStart.setEnabled(false);
                try {
                    socket = new Socket(ServerFrame.HOST_NAME, ServerFrame.HOST_PORT);
                    DataInputStream inputStream = new DataInputStream(socket.getInputStream());
                    DataOutputStream outputStream = new DataOutputStream(socket.getOutputStream());
                    long timeStart = System.currentTimeMillis();
                    outputStream.writeUTF("Packet ");
                    outputStream.writeInt(0);
                    outputStream.flush();
                    while(true){
                        String s = inputStream.readUTF();
                        int value = inputStream.readInt();
                        if(value<1000){
                            outputStream.writeUTF(s);
                            outputStream.writeInt(value);
                            outputStream.flush();
                        }else{
                            break;
                        }
                    }
                    socket.close();
                    long timeFinish = System.currentTimeMillis();
                    label.setText("Ушло "+(timeFinish-timeStart));
                    buttonStart.setEnabled(true);
                } catch (IOException ex) {
                }
            }
        });
    }
    
    public static void main(String _a[]){
        ClientFrame frame = new ClientFrame();
        frame.configurateGUI();
        frame.setVisible(true);
    }
}


Код

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JPanel;

public class ServerFrame extends JFrame{
    
    public static final String HOST_NAME="127.0.0.1";
    //public static final String HOST_NAME="192.168.0.17";
    public static final int HOST_PORT = 1049;
    
    public ServerSocket server = null;
    public boolean isRun = true;
    
    JButton buttonStart = new JButton("Старт");
    JButton buttonFinish = new JButton("Окончание");
    
    public void configurateGUI(){
        
        setBounds(100, 100, 300, 200);
        setTitle("Серверная сторона");
        buttonFinish.setEnabled(false);
        JPanel mainPanel = new JPanel();
        mainPanel.add(buttonStart);
        mainPanel.add(buttonFinish);
        getContentPane().add(mainPanel);
        setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
        
        buttonStart.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                buttonFinish.setEnabled(true);
                try {
                    server = new ServerSocket(HOST_PORT);
                    Thread serverThread = new Thread(new Runnable() {
                        @Override
                        public void run() {
                            try {
                                while(isRun){
                                    Socket s = server.accept();
                                    SocketRunner runner = new SocketRunner(s, ServerFrame.this);
                                    Thread thread = new Thread(runner);
                                    thread.start();  
                                }
                            } catch (IOException ex) {
                            }
                        }
                    });
                    serverThread.start();
                } catch (IOException ex) {
                    System.out.println("Не удалось создать сервер");
                    System.exit(0);
                }
                buttonStart.setEnabled(false);
            }
        });
        buttonFinish.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent ae) {
                isRun = false;
                try {
                    server.close();
                } catch (IOException ex) {
                }
                System.exit(0);
            }
        });
    }
    
    public static void main(String _a[]){
        ServerFrame frame = new ServerFrame();
        frame.configurateGUI();
        frame.setVisible(true);
    }
}


Код


import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class SocketRunner implements Runnable{
    public Socket socket = null;
    public DataInputStream inputStream = null;
    public DataOutputStream outputStream = null;
    public ServerFrame frame = null;
            
    
    public SocketRunner(Socket _socket, ServerFrame _frame){
        frame = _frame;
        socket = _socket;
    }

    @Override
    public void run() {
        try {
            inputStream = new DataInputStream(socket.getInputStream());
            outputStream = new DataOutputStream(socket.getOutputStream());
            while(frame.isRun){
                String s = inputStream.readUTF();
                int value = inputStream.readInt();
                
                outputStream.writeUTF(s);
                outputStream.writeInt(value+10);
                outputStream.flush();
            }
        } catch (IOException ex) {
            try {
                socket.close();
            } catch (IOException ex1) {
            }
        }
    }
    
}

Автор: AntonSaburov 10.5.2018, 14:07
Да просто маленькие пакеты могут тупо зависать на свичах. Т.к. вы их кидаете последовательно, то свич просто не прокидывает дальше. А вот если пакетов "накапливается" много, то есть вероятность, что они будут проброшены дальше. Т.к. Вы каждый раз отправляете маленький пакет и ждеме, то и получаете.
В случае многопоточности вы пробрасываете много пакетиков и их суммарный объем может позволить свичу их пробросить.

Но это я делаю только предположение. Не факт, что проблема здесь.

Автор: Рубильник 10.5.2018, 15:28
Ради интереса поставил сниффер.

В логе задержка между приемом пакета от клиента и отправкой следующего составляет примерно 150 мс, и столько же примерно между отправкой клиента и получением нового. В сумме получается примерно 300 мс.

Получается, что виновата не сеть. Если бы был затык в сети (в оборудовании), то между приемом и отправкой была бы маленькая пауза, и большая между отправкой и приемом. Или я неправильно что-то понимаю?

Автор: AntonSaburov 10.5.2018, 16:35
Ну тогда надо профилировать - какой оператор "жрет много" и у кого. Самое простое - понатыкать везде логи.

Автор: Рубильник 14.5.2018, 08:48
Разобрался. Всё дело в socket.setTcpNoDelay(true).

Delayed ACK нужно отключать и на сервере и на клиенте.
Теперь время отклика 0.5-1.5 мс. Что есть очень хорошо. 

Автор: AntonSaburov 14.5.2018, 13:52
Ну да - точно. Буферизуется пакет, если маленький. Как я и предполагал. Правда я не на тот девайс подумал smile

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)