Java NIO все еще блокирует GUI - PullRequest
0 голосов
/ 07 апреля 2020

Редактировать: GUI теперь всплывает (спасибо Мэтту), но когда я нажимаю кнопку пуска, программа полностью зависает, и мне приходится заканчивать ее в jGr * ​​1014 *.

У меня есть проблема с java NIO, когда мой GUI не появляется при запуске кода сервера.

Вот код:

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import javax.swing.*;
import java.awt.*;
import java.util.*;
import java.awt.event.*;

public class Server extends JFrame implements ActionListener{

  JButton start = null;

  public Server(){
     JPanel panel = new JPanel(new FlowLayout());
     start = new JButton("Start");
     add(panel);
     panel.add(start);
     start.addActionListener(this);
  }

  public void start(){
     try{
        Selector selector = Selector.open();

        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        InetSocketAddress hostAddress = new InetSocketAddress("localhost", 0);
        serverChannel.bind(hostAddress);

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
           int readyCount = selector.select();
           if (readyCount == 0) {
              continue;
           }
        // process selected keys...
           Set<SelectionKey> readyKeys = selector.selectedKeys();
           Iterator<SelectionKey> iterator = readyKeys.iterator();
           while (iterator.hasNext()) {
              SelectionKey key = iterator.next();
           // Remove key from set so we don't process it twice
              iterator.remove();
           // operate on the channel...
           // client requires a connection
              if (key.isAcceptable()) {
                 ServerSocketChannel server = (ServerSocketChannel)  key.channel();    
              // get client socket channel
                 SocketChannel client = server.accept();
              // Non Blocking I/O
                 client.configureBlocking(false);
              // record it for read/write operations (Here we have used it for read)
                 client.register(selector, SelectionKey.OP_READ);
                 continue;
              }
           }
        }
     }
     catch(IOException ioe){}
  }

  public void actionPerformed(ActionEvent e) {    
     if(e.getSource()==start){
        start();
     }
  }

  public static void main(String []args){
     Server gui = new Server();
     gui.setTitle("Server");
     gui.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
     gui.pack();
     gui.setLocationRelativeTo(null);
     gui.setResizable(false);
     gui.setVisible(true);
  }
} 

Что я делаю здесь неправильно ? Я следовал этому учебнику и после простой отладки (Iterator<SelectionKey> iterator = readyKeys.iterator(); отсутствовала часть <SelectionKey>), я скомпилировал его, запустил и ... ничего. Это весь код, который я написал, и я не понимаю, что я делаю неправильно.

Ответы [ 2 ]

0 голосов
/ 07 апреля 2020

В этом случае использование неблокирующего ввода-вывода, вероятно, не является необходимым. Вместо этого основной поток может продолжать выполнять ввод / вывод (используя традиционные блокирующие вызовы), планируя обновления для пользовательского интерфейса с помощью SwingWorker. Эти обновления будут происходить в потоке диспетчеризации событий Swing, поэтому вам даже не нужно беспокоиться о создании собственных потоков.

Неблокирующий ввод-вывод позволяет проверить, готовы ли каналы для выполнения определенных операций без блокировки , Если никакие операции ввода / вывода не готовы, поток может выполнить другие задачи и проверить позже.

Но в вашей программе вы написали код, который эффективно ожидает (в виде "busy-l oop" ) пока операции не будут готовы. Поскольку конструктор никогда не возвращается, вызов setVisible() даже никогда не вызывается. Вы никогда не дадите потоку возможность выполнить какую-либо другую работу, включая рисование пользовательского интерфейса.

Кроме того, с Java Swing существует специальный поток, который выполняет все обновления пользовательского интерфейса. Когда вы создаете компоненты или обновляете то, что они отображают, эта работа должна выполняться с этим потоком. Swing предоставляет класс SwingWorker для планирования коротких задач, которые управляют пользовательским интерфейсом. Вы можете узнать больше о параллелизме в Swing, пройдя Параллелизм в Swing учебник.

0 голосов
/ 07 апреля 2020

Вам нужно разделить ваши задачи. Одним из них является запуск gui, а другим - запуск сервера.

Сделайте одну задачу сервером.

public void runServer() throws Exception{
        Selector selector = Selector.open();
        ServerSocketChannel serverChannel = ServerSocketChannel.open();
        serverChannel.configureBlocking(false);

        InetSocketAddress hostAddress = new InetSocketAddress("localhost", 0);
        serverChannel.bind(hostAddress);

        serverChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
           int readyCount = selector.select();
           if (readyCount == 0) {
              continue;
           }
           Set<SelectionKey> readyKeys = selector.selectedKeys();
           Iterator<SelectionKey> iterator = readyKeys.iterator();
           while (iterator.hasNext()) {
              SelectionKey key = iterator.next();
              iterator.remove();
              if (key.isAcceptable()) {
                 ServerSocketChannel server = (ServerSocketChannel)  key.channel();    
              // get client socket channel
                 SocketChannel client = server.accept();
              // Non Blocking I/O
                 client.configureBlocking(false);
              // record it for read/write operations (Here we have used it for read)
                 client.register(selector, SelectionKey.OP_READ);
                 continue;
              }
           }
        }
     }

}

Затем сделайте другую задачу gui.

public void startGui(){
    JPanel panel = new JPanel(new FlowLayout());
    JButton start = new JButton("Start");
    add(panel);
    panel.add(start);
    setTitle("Server");
    setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    pack();
    setLocationRelativeTo(null);
    setResizable(false);
    setVisible(true);
}

Теперь ваш основной метод может быть уменьшен до.

public static void main(String[] args) throws Exception{
    Server server = new Server();
    EventQueue.invokeAndWait( server::startGui );
    server.runServer();
}

Таким образом, gui запускается на EDT, и сервер l oop, который никогда не завершается, занимает основной поток. Еще одно небольшое изменение. Не расширяйте JFrame, просто создайте JFrame в методе start Gui. Таким образом вся gui работа выполняется на EDT.

Не то чтобы я также удалил вашу обработку исключений и просто заставил методы вызывать исключение. Таким образом, вы увидите StackTrace.

В связи с вашим новым вопросом, почему gui зависает. Это потому что ваша блокировка на EDT. Ваш start() метод никогда не завершается. Самый грубый способ исправить это:

public void actionPerformed(ActionEvent e) {    
 if(e.getSource()==start){
      new Thread( ()->start();).start();
 }

} ​​

Что это будет делать, это запустить новый поток для запуска вашего сервера. Обратите внимание на наиболее очевидную проблему: если вы нажмете кнопку «Пуск» более одного раза, она запустит несколько потоков!

...