在 ServerSocketChannel 执行接受后,在 SocketChannel 上读取到达流结束
Read on SocketChannel reaches end-of-stream after ServerSocketChannel performs accept
我在下面粘贴了一个服务器端代码片段。此服务器代码在正常情况下工作,但是,以下场景设法破坏代码。
服务器和客户端在同一台机器上。我用的是环回地址,和实际的IP地址没有区别。
场景
- 服务器在线,客户端发出请求(
WritableByteChannel.write(ByteBuffer src)
returns 12 字节,这是正确的大小,但研究表明这仅意味着 12 字节被写入 TCP 缓冲区)。
- 服务器程序已关闭。客户端注意到通道在远端关闭并在自己端关闭,它没有发出任何请求。
- 服务器又上线了。
- 客户端尝试发出请求,但失败了,因为通道是 closed/invalid 且无法重复使用(即使服务器再次在线)。
- 客户端检查服务器的在线状态,得到肯定的结果,再次连接并立即发出另一个请求。
- 服务器接受客户端(下面的代码),然后处理带有
key.isReadable()
条件的 if 子句, 但是 然后读取失败,这表明结束-流。
创建 SSCCE 太复杂了,如果缺少重要信息或太抽象,请发表评论,我会提供更多信息。
问题
新的 created/accepted 通道如何在读取操作时失败?
我错过了什么?我可以采取哪些措施来防止这种情况发生?
我已经尝试过 wireshark,但我无法在指定的 TCP 端口上捕获任何数据包,即使通信确实有效。
Problem/Additional 信息
- 可以使用 RawCap
将数据包捕获到 .pcap 文件中
- 问题出在客户端检查服务器状态的方式上。我已经添加了下面的方法。
代码片段
片段 1
while (online)
{
if (selector.select(5000) == 0)
continue;
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext())
{
SelectionKey key = it.next();
it.remove();
if (key.isAcceptable())
{
log.log(Level.INFO, "Starting ACCEPT!");
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
log.log(Level.INFO, "{0} connected to port {1}!",
new Object[] {channel.socket().getInetAddress().getHostAddress(), isa.getPort()});
}
boolean accepted = false;
if (key.isReadable())
{
log.log(Level.INFO, "Starting READ!");
SocketChannel channel = (SocketChannel) key.channel();
bb.clear();
bb.limit(Header.LENGTH);
try
{
NioUtil.read(channel, bb); // server fails here!
}
catch (IOException e)
{
channel.close();
throw e;
}
bb.flip();
片段 2
public static ByteBuffer read(ReadableByteChannel channel, ByteBuffer bb) throws IOException
{
while (bb.remaining() > 0)
{
int read = 0;
try
{
read = channel.read(bb);
}
catch (IOException e)
{
log.log(Level.WARNING, "Error during blocking read!", e);
throw e;
}
// this causes the problem... or indicates it
if (read == -1)
{
log.log(Level.WARNING, "Error during blocking read! Reached end of stream!");
throw new ClosedChannelException();
}
}
return bb;
}
片段 3
@Override
public boolean isServerOnline()
{
String host = address.getProperty(PropertyKeys.SOCKET_SERVER_HOST);
int port = Integer.parseInt(address.getProperty(PropertyKeys.SOCKET_SERVER_PORT));
boolean _online = true;
try
{
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), port);
SocketChannel _channel = SocketChannel.open();
_channel.connect(addr);
_channel.close();
}
catch (Exception e)
{
_online = false;
}
return _online;
}
解决方案
问题不是检查的方法,如果服务是 available/the 服务器在线。问题出在EJP提到的第二点。
服务器需要特定的输入,如果不满足该条件,它就会处于不一致的状态。
我已经添加了一些后备措施,现在重新连接过程 - 包括检查方法 - 工作正常。
很明显客户端已经关闭了连接。只有这样 read()
returns -1.
备注:
当 read()
returns -1 时,您正在抛出不合适的 ClosedChannelException
。当 you 已经关闭 channel 并继续使用它时,NIO 会抛出该异常。它与流结束无关,不应该用于此。如果一定要扔东西,就扔 EOFException
.
你也不应该像现在这样循环。您应该只在 read()
返回正数时循环。目前,您在尝试读取可能永远不会到达的数据时正在使 select 循环挨饿。
我在下面粘贴了一个服务器端代码片段。此服务器代码在正常情况下工作,但是,以下场景设法破坏代码。 服务器和客户端在同一台机器上。我用的是环回地址,和实际的IP地址没有区别。
场景
- 服务器在线,客户端发出请求(
WritableByteChannel.write(ByteBuffer src)
returns 12 字节,这是正确的大小,但研究表明这仅意味着 12 字节被写入 TCP 缓冲区)。 - 服务器程序已关闭。客户端注意到通道在远端关闭并在自己端关闭,它没有发出任何请求。
- 服务器又上线了。
- 客户端尝试发出请求,但失败了,因为通道是 closed/invalid 且无法重复使用(即使服务器再次在线)。
- 客户端检查服务器的在线状态,得到肯定的结果,再次连接并立即发出另一个请求。
- 服务器接受客户端(下面的代码),然后处理带有
key.isReadable()
条件的 if 子句, 但是 然后读取失败,这表明结束-流。
创建 SSCCE 太复杂了,如果缺少重要信息或太抽象,请发表评论,我会提供更多信息。
问题
新的 created/accepted 通道如何在读取操作时失败? 我错过了什么?我可以采取哪些措施来防止这种情况发生?
我已经尝试过 wireshark,但我无法在指定的 TCP 端口上捕获任何数据包,即使通信确实有效。
Problem/Additional 信息
- 可以使用 RawCap 将数据包捕获到 .pcap 文件中
- 问题出在客户端检查服务器状态的方式上。我已经添加了下面的方法。
代码片段
片段 1
while (online)
{
if (selector.select(5000) == 0)
continue;
Iterator<SelectionKey> it = selector.selectedKeys().iterator();
while (it.hasNext())
{
SelectionKey key = it.next();
it.remove();
if (key.isAcceptable())
{
log.log(Level.INFO, "Starting ACCEPT!");
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel channel = serverSocketChannel.accept();
channel.configureBlocking(false);
channel.register(selector, SelectionKey.OP_READ);
log.log(Level.INFO, "{0} connected to port {1}!",
new Object[] {channel.socket().getInetAddress().getHostAddress(), isa.getPort()});
}
boolean accepted = false;
if (key.isReadable())
{
log.log(Level.INFO, "Starting READ!");
SocketChannel channel = (SocketChannel) key.channel();
bb.clear();
bb.limit(Header.LENGTH);
try
{
NioUtil.read(channel, bb); // server fails here!
}
catch (IOException e)
{
channel.close();
throw e;
}
bb.flip();
片段 2
public static ByteBuffer read(ReadableByteChannel channel, ByteBuffer bb) throws IOException
{
while (bb.remaining() > 0)
{
int read = 0;
try
{
read = channel.read(bb);
}
catch (IOException e)
{
log.log(Level.WARNING, "Error during blocking read!", e);
throw e;
}
// this causes the problem... or indicates it
if (read == -1)
{
log.log(Level.WARNING, "Error during blocking read! Reached end of stream!");
throw new ClosedChannelException();
}
}
return bb;
}
片段 3
@Override
public boolean isServerOnline()
{
String host = address.getProperty(PropertyKeys.SOCKET_SERVER_HOST);
int port = Integer.parseInt(address.getProperty(PropertyKeys.SOCKET_SERVER_PORT));
boolean _online = true;
try
{
InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), port);
SocketChannel _channel = SocketChannel.open();
_channel.connect(addr);
_channel.close();
}
catch (Exception e)
{
_online = false;
}
return _online;
}
解决方案
问题不是检查的方法,如果服务是 available/the 服务器在线。问题出在EJP提到的第二点。
服务器需要特定的输入,如果不满足该条件,它就会处于不一致的状态。 我已经添加了一些后备措施,现在重新连接过程 - 包括检查方法 - 工作正常。
很明显客户端已经关闭了连接。只有这样 read()
returns -1.
备注:
当
read()
returns -1 时,您正在抛出不合适的ClosedChannelException
。当 you 已经关闭 channel 并继续使用它时,NIO 会抛出该异常。它与流结束无关,不应该用于此。如果一定要扔东西,就扔EOFException
.你也不应该像现在这样循环。您应该只在
read()
返回正数时循环。目前,您在尝试读取可能永远不会到达的数据时正在使 select 循环挨饿。