【发布时间】: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。