I've pasted a server side code snippet below. This server code works under normal circumstances, however, the following scenario manages to break the code. Server and client are on the same machine. I used the loopback address, and the actual IP address, it makes no difference.
Scenario
WritableByteChannel.write(ByteBuffer src)
returns 12 byte, which is the correct size, but as research revealed that only means the 12 bytes are written to the TCP buffer).key.isReadable()
condition, but then fails on the read, which indicates end-of-stream.It would be too complex to create an SSCCE, please comment if important information is missing or this is too abstract and I'll provide further information.
Question
How can a freshly created/accepted channel fail on the read operation? What am I missing? What steps can I undertake to prevent this?
I already tried wireshark, but I can't capture any packets on the designated TCP port, even if the communication is acutally working.
Problem/Additional Info
Code snippets
Snippet 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();
Snippet 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;
}
Snippet 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;
}
Solution
The problem was not the method that checked, if the service is available/the server is online. The problem was the second point EJP mentioned.
Specific input was expected from the server and it was left in an inconsistent state if that conditions were not met. I've added some fallback measures, now the the reconnect process - including the check method - is working fine.
Clearly the client must have closed the connection. That's the only way read()
returns -1.
Notes:
You're throwing the inappropriate ClosedChannelException
when read()
returns -1. That exception is thrown by NIO when you've already closed the channel and continue to use it. It has nothing to do with end of stream, and shouldn't be used for that. If you must throw something, throw EOFException
.
You also shouldn't loop the way you are. You should only loop while read()
is returning a positive number. At present you are starving the select loop while trying to read data that may never arrive.