【问题标题】:Checking for abnormal socket termination检查异常套接字终止
【发布时间】:2013-07-27 18:48:58
【问题描述】:

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

如果没问题,除非我通过键入 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 bikeshop.server.Session$UpstreamChannel 运行 信息:来自输入流阅读器的最后一个数据是 " "

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

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

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

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

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

【问题讨论】:

  • readLine() 返回 null 如果按照文档关闭套接字。如图所示,您的if 语句的第一个条件将看到(或者,您的计算机已损坏;))。如果您的日志记录显示“null”并且它不是......那么该字符串包含实际文本“null”。把它放在调试器中并单步执行,你会看到发生了什么。
  • 我已经做到了,变量 watch 表示当你退出终端时,你会在最后一个 readline 中得到一个字符串。它们似乎是不可打印的字符,因此我在日志记录和调试器中显示了一串空格。显然一串不可打印的字符不为空,走错分支
  • 第二个注意事项:telnet 不是“原始”套接字协议。它会做一些你可能不想要的额外的东西。使用真正的测试客户端,不是 telnet。

标签: java sockets


【解决方案1】:

(决定将其移至答案)

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

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

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

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

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

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

【讨论】:

  • 我正在使用 telnet 客户端进行调试。似乎当您退出它时,它会发送一系列控制字符。这些字符不是 null 或 QUIT,因此它们会被发送到链上,服务器会再次绕过它的循环。当它通过从缓冲区读取的循环时确实返回 null 并且循环结束。这就是为什么看起来我在日志记录中得到了一个空值,但测试失败了。
  • 我明白这一点。重点是,对于发送给您的内容,您无能为力。我进行了编辑以扩展输入验证部分 - 在发送输入之前,您需要查找不是您期望的内容。
  • @GordonM Brian 在这里。如果您无法处理 Telnet 在终止时发送的额外内容,请不要使用 Telnet 作为客户端,或者在服务器上实现 Telnet 协议以便您理解它。
猜你喜欢
  • 2020-08-30
  • 2011-05-28
  • 2011-08-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-06-16
相关资源
最近更新 更多