Java 服务器GUI不一致地冻结、中断或未正确返回

Java 服务器GUI不一致地冻结、中断或未正确返回,java,swing,sockets,user-interface,Java,Swing,Sockets,User Interface,好吧,我和一个朋友一直在尝试制作我们自己的服务器客户端,连接工具只需键盘扫描仪和println语句就可以正常工作。。。直到我们尝试实现gui,事情才开始变得奇怪 有时它会起作用。。。有时它会冻结,而其他时候什么都不会发生 我们很确定问题出在ask()方法中,因为没有发生任何事情 public String ask(String string) { println(string); hasInput = false; w

好吧,我和一个朋友一直在尝试制作我们自己的服务器客户端,连接工具只需键盘扫描仪和println语句就可以正常工作。。。直到我们尝试实现gui,事情才开始变得奇怪

有时它会起作用。。。有时它会冻结,而其他时候什么都不会发生

我们很确定问题出在ask()方法中,因为没有发生任何事情

public String ask(String string)
    {
        println(string);        
        hasInput = false;  

        while(true)
        { 
            //System.out.println("working");                
            if(hasInput)
            {
                println("done");
                return processLastInput();//removes the carot ">" from the input and returns it              
            }
        }        
    }
但是如果你取消对println语句的注释 不一致地工作 …以下是代码的其余部分供您查看

import java.awt.Color;
import java.awt.Font;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.KeyStroke;


public class ConsoleGUI
{
    private  JFrame FRAME;
    private  JPanel PANEL;
    private  JTextArea CMD_TEXT;
    private  JTextArea CMD_HISTORY;
    private  JScrollPane CMD_HISTORY_SCROLLER;
    private String LAST_INPUT = "";
    private boolean hasInput = false;   

    private final  int screenX = (int)Toolkit.getDefaultToolkit().getScreenSize().getWidth();
    private final  int screenY = (int)Toolkit.getDefaultToolkit().getScreenSize().getHeight(); 

    public ConsoleGUI(String terminalHeader)
    {   
        FRAME = new JFrame(terminalHeader);
        PANEL = new JPanel();
        CMD_TEXT = new JTextArea(">");
        CMD_HISTORY = new JTextArea();
        CMD_HISTORY_SCROLLER = new JScrollPane(CMD_HISTORY_SCROLLER);

        FRAME.setBounds(screenX/2-250,screenY/2-150,500,300);        
        FRAME.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);        
        FRAME.getContentPane().add(PANEL);
        FRAME.setResizable(false);      

        CMD_TEXT.setBackground(Color.BLACK);
        CMD_TEXT.setForeground(Color.GREEN);
        CMD_TEXT.setFont(new Font("courier new",Font.PLAIN,15));
        CMD_TEXT.setBorder(BorderFactory.createTitledBorder("COMMAND:"));
        CMD_TEXT.setBounds(0,220,490,50);        

        CMD_TEXT.getInputMap().put(KeyStroke.getKeyStroke("ENTER"), "NEXT");       
        CMD_TEXT.getActionMap().put("NEXT",new ActivateInputAction());

        CMD_HISTORY.setEditable(false);
        CMD_HISTORY.setBackground(Color.BLACK);
        CMD_HISTORY.setForeground(Color.GREEN);
        CMD_HISTORY.setFont(new Font("courier new",Font.PLAIN,20));
        CMD_HISTORY.setBorder(BorderFactory.createTitledBorder("CONSOLE:")); 

        CMD_HISTORY_SCROLLER = new JScrollPane(CMD_HISTORY);
        CMD_HISTORY_SCROLLER.setBounds(0,0,490,220);         

        PANEL.setBackground(Color.GRAY);        
        PANEL.setFocusable(true);
        PANEL.setLayout(null);         
        PANEL.add(CMD_HISTORY_SCROLLER);
        PANEL.add(CMD_TEXT);          

        FRAME.setVisible(true);       
    }    

    public void print(Object ob)
    {
        CMD_HISTORY.append(ob.toString());
    }
    public void println(Object ob)
    {
        CMD_HISTORY.append(ob.toString()+"\n");
    }

    public String getLastInput(){return LAST_INPUT;}
    public String processLastInput()
    {
        String newString = LAST_INPUT.replace(">","");
        return newString;
    }
    public boolean hasInput(){return hasInput;}    

    public String ask(String string)
    {
        println(string);        
        hasInput = false;  

        while(true)
        {                 
            if(hasInput)
            {
                println("done");
                return processLastInput();                
            }
        }        
    }

    class ActivateInputAction extends AbstractAction
    {
        @Override
        public void actionPerformed(ActionEvent e)
        {            
            LAST_INPUT = CMD_TEXT.getText();
            println(LAST_INPUT);
            CMD_TEXT.setText(">");
            CMD_HISTORY.setCaretPosition(CMD_HISTORY.getText().length());  
            hasInput = true;  
        }
    }    
}
如果您愿意,服务器代码也在下面

import java.io.*;
import java.net.*;
import java.util.Scanner;

public class Server 
{
    private static ServerSocket service = null;
    private static String line;
    private static BufferedReader input;
    private static PrintStream output;
    private static Socket clientSocket = null;
    //private static final Scanner keyboard = new Scanner(System.in);
    private static ConsoleGUI Console = new ConsoleGUI("SERVER");

    public static void main(String[] args)
    {
        try
        {                   
            int port = Integer.parseInt(Console.ask("PORT:")); 
            Console.println("PORT = "+port);
            service = new ServerSocket(port);
            Console.println(service.getLocalSocketAddress());
            Console.println("WAITING FOR CLIENT TO CONNECT");            
            clientSocket = service.accept();
            input = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
            output = new PrintStream(clientSocket.getOutputStream());
            Console.println("CLIENT CONNECTED");
            Console.println(service.getInetAddress());
            while(true)
            {

                line = input.readLine();
                output.println(line);
            }
        }
        catch(Exception e)
        {
            Console.println(e);
        }
    }
}

在ask()中,hasInput被初始化为false,因此您将立即进入一个无限循环。这将导致奇怪的行为,因为它会吸收任何可用的循环

Swing是一个单线程环境,也就是说,与UI的所有交互都将通过事件调度线程完成。任何阻止EDT的操作都会阻止UI响应用户或处理新事件

您似乎在这方面遇到了麻烦,试图在EDT上下文之外更新textarea,这似乎会导致某种线程锁定(至少在Java8下)

因此,我从更新您的
print
方法开始,从EDT的上下文中更新内容

public void print(final Object ob) {
    Runnable run = new Runnable() {
        @Override
        public void run() {
            CMD_HISTORY.append(ob.toString());
        }
    };
    if (EventQueue.isDispatchThread()) {
        run.run();
    } else {
        EventQueue.invokeLater(run);
    }
}

public void println(Object ob) {
    Runnable run = new Runnable() {
        @Override
        public void run() {
            CMD_HISTORY.append(ob.toString() + "\n");
        }
    };
    if (EventQueue.isDispatchThread()) {
        run.run();
    } else {
        EventQueue.invokeLater(run);
    }
}
我还将您的
LAST\u输入
hasInput
变量更新为
volatile

private volatile String LAST_INPUT = "";
private volatile boolean hasInput = false;
这将确保它们跨线程边界正确更新

与其尝试使用
boolean
标志作为更多信息的指示符,不如使用对象监视器锁,其主要原因是它会使等待的线程进入睡眠状态,因此不会消耗任何CPU

private final Object inputLock = new Object();

//...

public String ask(String string) {
    println(string);
    hasInput = false;

    do {
        synchronized (inputLock) {
            try {
                System.out.println("Wait");
                inputLock.wait();
            } catch (InterruptedException ex) {
            }
        }
    } while (!hasInput);
    println("done");
    return processLastInput();
}

我强烈建议你在继续之前通读一遍。上面的例子充其量只是黑客…

如何调用
ask
?Swing是一个单线程环境,您永远不想做任何可能阻塞UI线程的事情。您可能还想考虑读一读YAH,对惯例感到抱歉,我知道它们是缺乏的,至于ASK如何被调用,我忘记了早些时候添加服务器代码,但是它现在在那里,所以您可以看到,<代码> AsQue>代码>是怎么调用的?我在你的代码中没有看到它被调用的地方?它在服务器中被调用,在初始运行时,服务器如何在不同的机器上调用客户机方法?您的客户代码需要呼叫
ask
这是我们的想法,但是。。。它有时确实完成了循环,因为我们确实得到了回报。这很奇怪,因为它每运行八次左右就会不一致地工作,所以它实际上会返回一个值,但在其他情况下它不会返回任何值,或者冻结。还有什么可以设置输入?正如上面提到的@Mad,ask()是如何调用的?如果主事件循环正在调用它,您将冻结它,否则您可能会有一个进程循环并消耗CPU周期,使主事件循环饥饿。hasInput在actionPerformed(ActionEvent e)中设置ConsoleGUI中类的方法,它是jtextarea CMD_text中enter键的重写。我将继续我之前的注释。当main调用ask()时,您将进入一个无限繁忙的循环,直到有人单击文本区域。尝试在ask()while循环中添加一个sleep语句,看看是否所有的麻烦都消失了。感谢您的建议,它与您发送的链接一起非常有用…:)