客户端/服务器Swing程序在使用线程时卡住了

我的Java程序有问题。 我有这个代码:

Host.java

public class Host { protected static void start(JFrame window) { ServerSocket server = null; try { server = new ServerSocket(); SocketAddress addr = new InetSocketAddress(hostname, port); server.bind(addr); Socket socket = server.accept(); window.setVisible(false); Thread thread = new Thread(new Incomming(socket.getInputStream())); thread.start(); thread.join(); socket.close(); } catch (UnknownHostException e) { [...] } } 

Incomming.java

 public class Incomming implements Runnable { private DataInputStream is; public Incomming(InputStream is) { MyFrame frame = new MyFrame(); frame.setVisible(true); frame.pack(); this.is = new DataInputStream(is); } public void run() { try { while(!Thread.currentThread().isInterrupted()) { int n = is.readInt(); if(n == -1) { break; } byte[] b = new byte[n]; is.readFully(b); [...] // working with bytes } System.out.println("Stream closed."); } catch(IOException e) { [...] } } } 

Client.java与Host.java非常相似,它也使用Incomming.java作为socket.getInputStream()。

所以问题是:客户端连接到主机,但是当它应该在服务器端以及客户端显示MyFrame窗口时,它不会完全加载它。 并且旧JFrame窗口的关闭按钮(两侧)都没有做任何事情。

我尝试使用thread.join()删除该行,然后MyFrame窗口完全加载并关闭按钮工作,但它会因socket closed消息而抛出exception,因此客户端不再连接到主机。

我怎么能解决这个问题? 谢谢你的回复。

  1. Thread#join将阻塞,直到线程死亡。 这会阻止您关闭窗口,因为您阻止了事件调度线程。 有关更多详细信息,请参阅Swing中的并发 。
  2. accept一个传入的套接字,启动一个新的Thread来处理该套接字,然后立即关闭套接字(很可能在线程有机会开始读取它之前)。 相反,一旦竞争处理了流,就关闭线程内的套接字

更新

Swing是一个单线程框架。 这意味着所有与UI的交互(创建,修改)必须在事件调度线程的上下文中执行。 阻止此线程的任何操作都将阻止EDT处理事件,包括重绘,鼠标和键盘事件。

您应该传递套接字,而不是将套接字的输入放入流传递给线程。 这将管理套接字的责任传递给该线程,从而释放当前线程。

然后在您的Incomming类中,您应该获取对套接字输入流的引用,执行您需要的操作,最后在完成后关闭输入放置流和套接字。

 protected static void start(JFrame window) { ServerSocket server = null; try { server = new ServerSocket(); SocketAddress addr = new InetSocketAddress(hostname, port); server.bind(addr); Socket socket = server.accept(); window.setVisible(false); // Pass the socket to the thread to allow it to perform the work Thread thread = new Thread(new Incomming(socket)); thread.start(); } catch (IOException ex) { //...// } } public class Incomming implements Runnable { private final Socket socket; public Incomming(Socket socket) { //?? What's this for, this is VERY wrong // UI Interaction should ONLY occur within the context of the EDT MyFrame frame = new MyFrame(); frame.setVisible(true); frame.pack(); this.socket = socket; } public void run() { if (socket != null) { DataInputStream is = null; try { is = new DataInputStream(socket.getInputStream()); while (!Thread.currentThread().isInterrupted()) { int n = is.readInt(); if (n == -1) { break; } byte[] b = new byte[n]; is.readFully(b); //...// } System.out.println("Stream closed."); } catch (IOException e) { } finally { // Finally clean up... try { is.close(); } catch (Exception e) { } try { socket.close(); } catch (Exception e) { } } } } } 

必须阅读Swing中的Concurrency

如果您打算在处理套接字时更新UI,则很可能希望使用SwingWorker而不是Thread 。 这提供了额外的function,使更容易将更新同步回事件调度线程

你编写程序的方式意味着只有一条“执行路径”。 在任何时候你都没有2个线程同时处理事情。 这是因为你调用了thread.join() ,它等待线程退出。 这意味着Incomming类中的代码正在执行,或者其外部的代码是。

要加载UI,您需要同时运行2个线程,这意味着需要重新考虑网络连接的处理方式。

第一步是处理关闭Incomming类内部的套接字。 这将处理因尝试从已关闭的套接字读取而导致的SocketClosedException

您可以做的第二件事是在UI和Incomming类之间进行不同的通信,因为线程中断是从长时间运行的网络读取中唤醒线程的好方法,但是没有提供关于为什么要唤醒线程的信息起来。 最简单的方法是在调用函数时在Incomming类上设置一个布尔标志。

ex(请原谅伪代码):

 class Incomming Socket socket; volatile boolean running; run(): while( running ){ try { /* stuff */ }catch( InterruptedException e ){ // nothing here // we are just waking up the // thread to unblock io } } socket.close() stop(): running = false; 
  1. thread.join()将阻塞当前线程,直到线程结束
  2. socket.close(); 将在你的主题结束之前执行。 你必须将此代码块移动到线程run()方法中。