0

我正在编写一个带有会话线程的服务器,用于与客户端建立开放连接。这相当简单,它只是使用缓冲读取器从输入中读取行。如果该行等于一个表示用户想要退出的特殊值,或者它为空,则循环终止。否则,消息将通过链向上传递到要处理它的模块。出于测试的目的,我只是远程登录到我的服务器并手动输入命令。

如果没问题,除非我通过键入 QUIT 以外的方式终止连接,例如通过关闭终端窗口。然后生成一条具有未知字符序列的消息,并且一条格式错误的消息沿链向上传递。在这个简单的测试用例中,它并不重要,但它确实表明了一个需要解决的问题。

我的代码如下。

    public void run () {
        BufferedReader  inReader;
        UpstreamMessage message;
        String          lastLine;

        Thread.currentThread ().setName ("UpstreamThread_" + outer.getId ());

        try {
            inReader    = new BufferedReader (new InputStreamReader (outer.clientSocket.getInputStream (), "UTF8"));
            while (!this.ending) {
                // Read whatever was in the input buffer
                lastLine    = inReader.readLine ();
                Logger.getLogger (this.getClass ().getName ()).log (Level.INFO, "Last data from input stream reader was \"{0}\"", lastLine);
                if (null != lastLine && !lastLine.equals ("QUIT")) {
                    message = new UpstreamMessage (outer.sessionId, lastLine);
                    outer.server.acceptMessage (message);
                } else {
                    // End the session
                    Logger.getLogger (this.getClass ().getName ()).log (Level.INFO, "Thread ending");
                    break;
                }
            }
        } catch (IOException | IllegalArgumentException ex) {
            Logger.getLogger (this.getClass ().getName ()).log (Level.SEVERE, ex.getMessage (), ex);
        } catch (InterruptedException ex) {
            // This is thrown when we're telling the thread to shut down so it's normal
            Logger.getLogger (this.getClass ().getName ()).log (Level.INFO, ex.getMessage (), ex);
        } finally {
            this.terminate ();
        }
    }

当我关闭终端窗口时,我的控制台会为日志记录输出以下内容:

2013 年 7 月 27 日 7:37:00 PM bikeshop.server.Session$UpstreamChannel 运行信息:来自输入流阅读器的最后数据是“”

或者有时,记录器会报告以下内容,表明 lastMessage 变量引用了一个空值,但空值检查失败。

2013 年 7 月 27 日 7:37:00 PM bikeshop.server.Session$UpstreamChannel 运行信息:来自输入流阅读器的最后数据为“null”

我显然做错了什么,但我不知道是什么。我的测试,'if (null != lastLine && !lastLine.equals ("QUIT"))' 没有在我退出终端时捕获缓冲区中的任何内容。如何更优雅地处理这种情况?

编辑:对日志和单步调试的更仔细分析表明,当您退出 telnet 客户端时,它会发送一系列控制字符。这从缓冲区中读取并向上传递,在我的程序的其他部分触发警告。然后会话再次通过它的循环并尝试从现在关闭的套接字中读取。这确实返回 null 并且循环终止。这就是从客户端读取的最后一行是否为空的混淆来源。最后读取的内容确实为空,但重要的是倒数第二个读取,触发异常行为的那个。

所以我想问题已经变成了a)当你退出它时,telnet 客户端会发送什么控制序列,或者b)Java 知道如何检查该控制序列?

4

1 回答 1

1

(决定将此移至答案)

BufferedReader.readLine()null如果按照文档关闭套接字,则返回。您的 if 语句的第一个条件如下所示:

if (null != lastLine && !lastLine.equals ("QUIT"))

将评估false是否是这种情况(或者,您的计算机坏了;))。

如果您的日志记录显示“null”并且它没有评估为false......那么该字符串包含实际文本“null”。把它放在调试器中,在这个循环中设置一个断点,然后逐步查看 ; 的值lastLine。你会看到发生了什么。

也就是说,编写服务器的第一条规则是永远不要信任客户端。就像在您使用 telnet 的情况下,您最终无法控制发送给您的内容。

当您从该套接字读取时,您需要进行更多输入验证,这样您就不会尝试处理无效的内容。您的处理方式会有所不同,但通常的解决方案是Map有效命令、验证输入结构的正则表达式等。不这样做会导致您发现的服务器软件非常脆弱。

于 2013-07-27T19:25:15.490 回答