Edycja: teraz wyskakuje GUI (dzięki matowi), ale jak wciskam przycisk start program kompletnie się zawiesza i muszę go zakończyć w jGrasp.

Mam problem z java NIO, gdzie mój GUI nie wyskakuje po uruchomieniu kodu serwera.

Oto kod:

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);
  }
} 

Co ja tu robię źle? Postępowałem zgodnie z tym samouczkiem i po prostym debugowaniu ({ {X0}} brakowało części <SelectionKey>), skompilowałem go, uruchomiłem i ... nic. To jest cały kod, który napisałem i nie rozumiem, co robię źle.

0
Petar Zanetic 7 kwiecień 2020, 17:27

2 odpowiedzi

Najlepsza odpowiedź

Musisz rozdzielić swoje zadania. Jednym z nich jest uruchomienie GUI, a drugim uruchomienie serwera.

Zrób jedno zadanie jako serwer.

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;
              }
           }
        }
     }

}

Następnie zrób drugie zadanie jako 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);
}

Teraz Twoja główna metoda może zostać zredukowana do.

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

W ten sposób GUI jest uruchamiane na EDT, a pętla serwera, która nigdy się nie kończy, zajmuje główny wątek. Kolejna mała zmiana. Nie rozszerzaj JFrame, po prostu utwórz JFrame w metodzie startGui. W ten sposób cała praca GUI jest wykonywana na EDT.

Nie znaczy to, że usunąłem również twoją obsługę wyjątków i właśnie sprawiłem, że metody rzucają wyjątek. W ten sposób zobaczysz StackTrace.

Jeśli chodzi o twoje nowsze pytanie, dlaczego GUI się zawiesza. To dlatego, że blokujesz na EDT. Twoja metoda start() nigdy nie wychodzi. Najbardziej prymitywną metodą rozwiązania tego problemu jest.

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

}

To, co zrobi, to uruchomienie nowego wątku, aby uruchomić serwer. Zwróć uwagę na najbardziej oczywisty problem, jeśli klikniesz Start więcej niż jeden raz, rozpocznie się więcej niż jeden wątek!

0
matt 7 kwiecień 2020, 15:50

W takim przypadku używanie nieblokujących wejść / wyjść prawdopodobnie nie jest konieczne. Zamiast tego główny wątek mógłby kontynuować wykonywanie operacji we / wy (przy użyciu tradycyjnych wywołań blokujących), planując aktualizacje interfejsu użytkownika za pomocą SwingWorker. Te aktualizacje pojawią się w wątku wysyłania zdarzeń Swing, więc nie musisz się nawet martwić o tworzenie własnych wątków.

Nieblokujące We / Wy pozwala sprawdzić, czy kanały są gotowe do wykonywania określonych operacji bez blokowania. Jeśli żadne operacje we / wy nie są gotowe, wątek może wykonać inne zadania i sprawdzić później.

Ale w swoim programie napisałeś kod, który efektywnie czeka (w „pętli zajętości”), aż operacje będą gotowe. Ponieważ konstruktor nigdy nie zwraca, wywołanie setVisible() nigdy nie jest nawet wywoływane. Nigdy nie dajesz wątkowi szansy na wykonanie żadnej innej pracy, w tym malowanie interfejsu użytkownika.

Ponadto w przypadku Java Swing istnieje specjalny wątek, który wykonuje wszystkie aktualizacje interfejsu użytkownika. Kiedy tworzysz komponenty lub aktualizujesz to, co wyświetlają, ta praca musi być wykonana z tym wątkiem. Swing zapewnia SwingWorker do planowania krótkich zadań, które manipulują interfejsem użytkownika. Możesz dowiedzieć się więcej o współbieżności w Swingu, wykonując Współbieżność w Swingu tutorial.

0
erickson 7 kwiecień 2020, 14:43