Java 服务器GUI不一致地冻结、中断或未正确返回
好吧,我和一个朋友一直在尝试制作我们自己的服务器客户端,连接工具只需键盘扫描仪和println语句就可以正常工作。。。直到我们尝试实现gui,事情才开始变得奇怪 有时它会起作用。。。有时它会冻结,而其他时候什么都不会发生 我们很确定问题出在ask()方法中,因为没有发生任何事情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
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语句,看看是否所有的麻烦都消失了。感谢您的建议,它与您发送的链接一起非常有用…:)