1

我目前正在尝试编写一个非常简单的聊天应用程序来向自己介绍 java 套接字编程和多线程。它由 2 个模块组成,一个伪服务器和一个伪客户端,但是我的设计让我相信我正在尝试实现一个不可能的概念。

服务器

服务器在 localhost 端口 4000 上等待连接,当它收到一个连接时,它启动 2 个线程,一个侦听器线程和一个扬声器线程。扬声器线程不断等待用户对控制台的输入,并在收到所述输入时将其发送给客户端。对于客户端发送的任何消息,侦听器线程阻塞到套接字的 ObjectInputStream,然后将消息打印到控制台。

客户端

客户端将用户连接到服务器的 4000 端口,然后启动 2 个线程,一个监听器和一个扬声器。这些线程具有与服务器线程相同的功能,但出于显而易见的原因,它们以相反的方式处理输入/输出。

第一个问题

我遇到的问题是,为了结束聊天,用户必须输入“Bye”。现在,由于我的线程已被循环以阻止输入:

    while(connected()){

        //block for input
        //do something with this input
        //determine if the connection still exists (was the message "Bye"?)
    }

然后,当尝试退出应用程序时,它变成了一个非常有趣的场景。如果客户端键入“Bye”,则它返回发送线程,服务器上侦听“Bye”的线程也返回。这给我们留下了客户端监听器和服务器端扬声器不知道“Bye”已被键入的问题,因此继续执行。

我通过创建一个包含两个线程以同步方式访问的布尔变量的类 Synchronizer 解决了这个问题:

public class Synchronizer {

    boolean chatting;

    public Synchronizer(){

        chatting = true;
        onChatStatusChanged();
    }

    synchronized void stopChatting(){

        chatting = false;
        onChatStatusChanged();
    }

    synchronized boolean chatting(){

        return chatting;
    }

    public void onChatStatusChanged(){

     System.out.println("Chat status changed!: " + chatting);
    }
}

然后,我将这个类的同一个实例传递到创建它的线程中。不过还有一个问题。

第二个问题

这就是我推断我正在尝试做的事情是不可能使用我目前使用的方法的地方。鉴于一个用户必须键入“Bye”才能退出聊天,其他 2 个未使用的线程仍继续通过连接检查并开始阻塞 I/O。在阻塞时,原来的 2 个线程意识到连接已经终止,但是即使他们改变了布尔值,其他 2 个线程已经通过了检查,并且已经阻塞了 I/O。

这意味着即使您将在循环的下一次迭代中终止线程,您仍将尝试从已正确终止的其他线程接收输入。这使我得出了最后的结论和问题。

我的问题

是否可以以我尝试的方式异步接收和发送数据?(每个客户端/服务器两个线程都阻塞 I/O)或者我必须在服务器和请求任何新数据的客户端之间每隔几毫秒来回发送一个心跳,并使用这个心跳来确定断开连接吗?

问题似乎在于我的线程在意识到伙伴线程已断开连接之前就阻塞了 I/O。这导致了主要问题,您将如何异步停止 I/O 线程阻塞?

我觉得这似乎是应该能够做到的事情,因为这种行为在整个社交媒体中都可以看到。

任何澄清或建议将不胜感激!

4

2 回答 2

1

只是为了澄清将来可能偶然发现这篇文章的任何人,我最终通过稍微调整线程的语法来解决这个问题。首先,我必须删除我的旧线程,并分别用 AsyncSender 和 AsyncReader 替换它们。无论用户输入如何,这些线程都会不断地发送和接收。当没有用户输入时,它只是发送/接收一个空白字符串,并且仅在它不是空白字符串时才将其打印到控制台。

解决方法

try{        
    if((obj = in.readObject()) != null){

    if(obj instanceof String)
        output = (String) obj;

    if(output.equalsIgnoreCase("Bye"))
    s.stop();
    }
}
catch(ClassNotFoundException e){

    e.printStackTrace();
}
catch(IOException e){

    e.printStackTrace();
}

在接收线程的这个迭代中,它不会阻塞输入,而是测试读取的对象是否为空(流中没有对象)。在发送者线程中也是如此。

这成功地绕过了必须停止阻塞 I/O 的线程的问题。

请注意,还有其他方法可以解决此问题,例如使用InterruptableChannel

于 2012-10-28T19:55:18.033 回答
1

我不知道Java,但是如果它有线程,可以在线程上调用函数,并且可以杀死线程,那么即使它没有任务,您也可以添加任务,这就是您所需要的启动构建自己的异步接口。

就此而言,如果您可以杀死线程,那么退出的线程就可以杀死其他线程。

此外,在窗口关闭且连接打开的任何情况下,都应发送“再见”(或其他代码) - 如果 Java 有事件,并且您正在使用的窗口有关闭事件,那么就是这个地方把它。

或者,您可以测试有效/打开的窗口,如果窗口无效/关闭,则发送“Bye”。把它想象成一个穷人的事件处理程序。

此外,请确保您知道如何(并有权)手动将例外添加到网络的防火墙。

此外,请始终通过实时网络对其进行测试。仅仅因为它在环回中工作,并不意味着它会在网络上工作。虽然你可能已经知道了。

于 2012-10-27T01:51:09.953 回答