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

时间:2013-07-11 20:17:57

标签: java multithreading 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消息的异常,因此客户端不再连接到主机。

我该如何解决这个问题? 谢谢你的回复。

3 个答案:

答案 0 :(得分:4)

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

    <强>更新

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

    必须阅读Concurrency in Swing

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

答案 1 :(得分:0)

你编写程序的方式意味着只有一条“执行路径”。在任何时候你都没有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;

答案 2 :(得分:0)

  1. thread.join()将阻止当前线程,直到线程结束
  2. socket.close();将在你的主题结束之前执行。你必须将这个代码块移动到你的线程run()方法。