【问题标题】:Make a Server wait for input from two different controllers让服务器等待来自两个不同控制器的输入
【发布时间】:2018-12-04 13:13:29
【问题描述】:

我制作了一个客户端-服务器应用程序,其中服务器必须向客户端发送电子邮件列表,然后将其加载到 ListView 中,从而可以通过 menuBar 删除它们。在客户端,所有这些操作都是在数据模型中进行的(我遵循 MVC 模式)。这是服务器:

class ThreadedEchoHandler implements Runnable {

    private Socket incoming;

    private String nomeAccount = "";

    public void run() {
        try {
            incoming = s.accept();
        } catch (IOException ex) {
            System.out.println("Unable to accept requests");
        }
        contenutoTextArea.append("Connected from: " + incoming.getLocalAddress() + "\n");
        textarea.setText(contenutoTextArea.toString());
        try {
            //PHASE 1: The server receives the email
            try {
                BufferedReader in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
                nomeAccount = in.readLine();
            } catch (IOException ex) {
                System.out.println("Not works");
            }

            //PHASE 2: I'm getting all the emails from the files
            File dir = new File("src/server/" + nomeAccount);
            String[] tmp = new String[100];
            int i = 0;
            for (File file : dir.listFiles()) {
                if (file.isFile() && !(file.getName().equals(".DS_Store"))) {
                    try (BufferedReader br = new BufferedReader(new FileReader(file))) {
                        String line;
                        while ((line = br.readLine()) != null) {
                            tmp[i++] = line;
                        }
                    } catch (IOException ex) {
                        System.out.println("Cannot read from file");
                    }
                }
            }

            //PHASE 3: The server sends the ArrayList to the client
            PrintWriter out = new PrintWriter(incoming.getOutputStream(), true);
            for (int j = 0; j < i; j++) {
                out.println(tmp[j]); // send the strings to the client
            }
        } catch (IOException ex) {
            System.out.println("Cannot send the strings to the client");
        }

        //PHASE 4: Here I loop and wait for the client choise
        BufferedReader in;
        String op;
        try {
            in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
            while ((op = in.readLine()) != null) {                   
                if (op.equals("Elimina")) {
                    String tmp = in.readLine();
                    File file = new File("src/server/" + nomeAccount + "/" + tmp + ".txt");
                    file.delete();
                } else if (op.equals("Invia")) {
                    //...
                } else {
                    //...
                }
            }
        } catch (IOException ex) {
            System.out.println("Non so");

        } finally {
            try {
                incoming.close();
            } catch (IOException ex) {
                System.out.println("Cannot closing the socket");
            }
        }
    }
}

这些是客户端的方法:

public void loadData() throws IOException, ClassNotFoundException, ParseException {

    try {
        s = new Socket("127.0.0.1", 5000);
        ArrayList<Email> email = new ArrayList<Email>();
        DateFormat format = new SimpleDateFormat("dd/MM/yyyy");
        Date data;

        /* PHASE 1: The client sends a string to the server */
        //try {
            PrintWriter out = new PrintWriter(s.getOutputStream(), true);
            out.println(account); // send the account name to server

            /* PHASE 2: The client receives the ArrayList with the emails */
            BufferedReader in = new BufferedReader(new InputStreamReader(s.getInputStream()));
            String line;
            String message[] = new String[5];
            for (int j=0; (line = in.readLine()) != null;) {
                message[j++] = line;
                if (j==5) {
                    data = format.parse(message[3]);
                    email.add(new Email((Integer.parseInt(message[0])), message[1], account, message[2], message[4], data));
                    j=0;
                }
            }

            //Casting the arrayList
            emailList = FXCollections.observableArrayList(email);

            //Sorting the emails
            Collections.sort(emailList, (Email o1, Email o2) -> {
                if (o1.getData() == null || o2.getData() == null) {
                    return 0;
                }
                return o1.getData().compareTo(o2.getData());
            });

        /*} finally {
            s.close();*/
        //}
    } catch (SocketException se) {
        emailList.setAll(null, null);
    }
}

public void deleteMail(Email da_elim) throws IOException {
    int id_del = da_elim.getID();
    emailList.remove(da_elim);
    PrintWriter out = new PrintWriter(s.getOutputStream(), true);
    out.println("Elimina");
    out.println(id_del);
}

Server 的 PHASE 1, 2, 3 用于上传邮件,使用 loadData() 方法。如果没有 PHASE 4,程序就可以工作。现在,如果我编写该循环,则客户端的 GUI 不会加载,并且我无法按下 DELETE 按钮(这应该使输入将某些内容(在此文件的消除)中嵌入到该循环中。为什么客户端即使它们是两个不同的线程也不会加载?为什么没有那个循环它可以工作?

编辑:实现了 Listener 类但仍然无法正常工作

//PHASE 4: Here I loop and wait for the client choise      
        BufferedReader in;
        String op;
        try {
            in = new BufferedReader(new InputStreamReader(incoming.getInputStream()));
            /*while ((op = in.readLine()) != null) {
                System.out.println("OP: " + op);
                if (op.equals("Elimina")) {
                    String tmp = in.readLine();
                    contenutoTextArea.append("Ho eliminato la mail ").append(tmp).append(" \n");
                    textarea.setText(contenutoTextArea.toString());
                    File file = new File("src/server/" + nomeAccount + "/" + tmp + ".txt");
                    file.delete();
                }
            }*/
            Listener lis = new Listener(in, new LinkedBlockingQueue<String>());
            lis.run();
            System.out.println("bbbbb");
        } catch (IOException ex) {
            System.out.println("Unable to read messages");
        } finally {
            try {
                incoming.close();
            } catch (IOException ex) {
                System.out.println("Cannot close the socket");
            }
        }

【问题讨论】:

  • 请提供一个minimal reproducible example 来说明问题。
  • 无论您的代码做什么,它必须访问 fx 应用程序线程上的节点(及其所有属性)(您的 sn-p 违反了该规则)
  • 你对节点意味着什么?因为客户端有一个存储和获取所有数据的DataModel,而服务器的唯一功能是将存储的信息发送到一堆.txt文件中
  • textArea.setText 听起来像是将文本设置在... TextArea ;)
  • @kleopatra 我不明白问题出在哪里。我可以从我的控制器访问所有 fx 元素,因为它们被声明为全局变量,所以它们可以在我想要的任何地方使用(在我的线程等中)

标签: java sockets javafx server


【解决方案1】:

我认为您应该运行 jvisualvm(它是与 jdk 一起安装在 jdk 的 /bin/ 位置的工具)并查找您在服务器上创建的线程生命周期。还要检查你的线程是否没有通过代码,只是结束他的生命,跳过等待客户。

这个线程是否以某种方式与客户端连接?因为您无法运行客户端应用程序。他们分开了吗?我想到的另一个想法是使用

Platform.runLater(()->{
});

如果您的客户端 GUI 使用 JavaFX。如果您正在创建 GUI、更改字段中的值以及您在 GUI 上执行的任何操作,请使用它。也许您的服务器正在等待用户响应,并且在构建 GUI 之后?这导致您无法按 DELETE 按钮。

【讨论】:

  • 客户端在不同的包中。服务器生命周期不会结束,因为它卡在while ((op = in.readLine()) != null) line 中。当我尝试执行客户端时,什么都没有出现,但是如果我关闭服务器,那么它似乎会“解锁”并加载 GUI。
  • 我也尝试过 Platform.runLater 但没有任何改变
【解决方案2】:

我目前无法发表评论,因此无法要求澄清,但我认为我正确地解释了问题所在。 “程序在进入等待来自两个控制器的输入的循环时挂起”。假设我做对了那部分,最可能的罪魁祸首是缓冲阅读器无限期挂起,因为它没有接收输入。当我第一次遇到这个问题时,我把它扔到它自己的“接收器”类中,并使用一个队列将它接收到的任何东西总线传输到我的主类中的一个循环中。我的代码看起来像这样:

import java.io.BufferedReader;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;

public class Listener implements Runnable
{
    private BufferedReader br;
    private BlockingQueue<String> q;
    private boolean shouldClose = false;

    public Listener(BufferedReader br, BlockingQueue<String> q)
    {
        this.q = q;
        this.br = br;
    }

    public void run()
    {
        loop();
        System.out.println("listener has stopped");
    }

    public void loop()
    {
        String line = "";

        try
        {
            while((line = br.readLine()) != null && !shouldClose)
            {
                q.put(line);
            }
        }
        catch (IOException | InterruptedException e)
        {
            e.printStackTrace();
        }
    }

    public void shutdown()
    {
        shouldClose = true;
    }
}

抱歉,如果我有任何误解,或者遗漏了您的代码中的某些内容。

【讨论】:

  • 我用适用于此类的那部分代码更新了问题,但除非我不关闭客户端,否则客户端仍然不会加载 GUI。我在 Listener 类的 while 之前写了一个 println,它可以工作,但我把它放在 while 或外面它不起作用,所以我认为它仍然卡在那个条件下。
  • 你的监听器对象是它自己的线程吗?如果是,你应该调用 lis.start();如果不是,那么我的建议没有多大优势。可能是我的坏。我从未明确表示这需要多线程。那么你可以让第 4 阶段成为一个循环,检查队列是否有东西,然后采取相应的行动。
  • Listener 对象在一个控制器线程中,但它不会启动一个新的。新循环不会像 readLine 函数那样阻塞程序吗?
  • 你是对的,拥有 readline 功能几乎总是会导致程序挂起。将它放在自己的线程中的循环中的意义在于,它可以在不停止程序其余部分的情况下随意挂起。这样就清楚了吗?
猜你喜欢
  • 2015-12-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-08-10
  • 2012-06-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多