What happens when not all data is read from a NIO.SocketChannel?

I just wrote some code to try and implement a simple packet protocol using NIO.SocketChannel. I’m having trouble with reading data from the channels though. What I’m doing is reading in a certain number of bytes which form the header of my protocol during one selectNow() operation. I do this so I can create a new ByteBuffer with the exact size of the packet. That part works great. But then I never get another SelectionKey.isReadable() event from any future selectNow() operations. It’s like NIO just discards the rest of the data. Does anyone know why this would happen, or if there is a way to get the Selector to fire off another SelectionKey.isReadable() event?

Here’s my code (trimmed down to the vital stuff):

Snippet from SMLIOManager (the class that contains the Selector and lets the SMLIOSocketPacket classes know when their SelectionKeys are ready):


/*
 * This is where the selector is checked for readiness
 */
void fire()
{
	//The timer has fired, so check for I/O readiness
	selector.selectedKeys().clear(); //Make sure the set is cleared
	try
	{
		selector.selectNow(); //Fill the set with ready keys
	}
	catch (Exception e) //Some not so nice happened, so log it
	{
		LogManager.getLogManager().getLogger("sml.io").info("Exception in SMLIOManager.fire: " + e);
	}
	Iterator i = selector.selectedKeys().iterator(); //Get an iterator
	
	if (selector.selectedKeys().size() > 0) //Log how many keys are ready (if there are any ready)
		LogManager.getLogManager().getLogger("sml.io").finer("SMLIOManager.fire.selector.selectNow returned " +
			selector.selectedKeys().size() + " of " +  
			selector.keys().size() + " keys.");
	
	while (i.hasNext()) //Go through all the keys in the set
	{
		SelectionKey key = (SelectionKey)i.next(); //Get the next SelectionKey
		
		((SMLIO)key.attachment()).stateReady(); //Tell the protocol manager that there is some I/O readiness, and to process it
	}
}

Snippet from SMLIOSocketPacket (the class that implements the protocol) (and extends SMLIO)


void stateReady()
{
	LogManager.getLogManager().getLogger("sml.io").info(name + " entered stateReady"); //Log entrance into the method
	
	SocketChannel chan = (SocketChannel)key.channel(); //Get the channel
	
	if (key.isWritable()) //Is writable, so write as much as possible
	{
		LogManager.getLogManager().getLogger("sml.io").info(name + ".key.isWritable"); //Log is writable
		try
		{
			ByteBuffer out = (ByteBuffer)outQueue.getFirst(); //Get the first buffer in the queue of outgoing buffers
			
			if (out.position() == out.limit()) //Reached the end of the buffer
			{
				outQueue.removeFirst(); //Remove the buffer
				
				if (outQueue.size() > 0) //More buffers left in the queue, so use the next one
				{
					out = (ByteBuffer)outQueue.getFirst();
				}
				else //No more left
				{
					key.interestOps(key.interestOps() & ~SelectionKey.OP_WRITE); //Say we don't want to write anymore
					out = null; //Prevent errors
				}
			}
			
			if (out != null) //If the buffer exists
			{
				chan.write(out); //Write the buffer
			}
		}
		catch (Exception e) //Something happened :(
		{
			LogManager.getLogManager().getLogger("sml.io").info(name + ".stateReady: " + e);
		}
	}
	
	if (key.isReadable()) //Is readable, so read in as much as possible
	{
		LogManager.getLogManager().getLogger("sml.io").info(name + ".key.isReadable"); //Log readability
		try
		{
			if (inBuff.position() == inBuff.limit()) //The buffer has been filled, so act appropriately
			{
				if (isHeader == true) //A header, so process it
				{
					inBuff.flip(); //Flip it to read the header
					
					int len = inBuff.getInt(); //Get the length of the packet
					
					inBuff = ByteBuffer.allocate(len); //Make a buffer to hold the packet
					
					isHeader = false; //Not a header any more
				}
				else //The data itself
				{
					listener.recieveInput(inBuff, this); //Dispatch this packet (this is implemented in an outside class, but is yet to be called in pratice :( )
					
					inBuff = ByteBuffer.allocate(HEADER_LEN); //Make a buffer to hold the header
					
					isHeader = true; //Now a header
				}
			}
			
			int val = chan.read(inBuff); //Read as much as we can (non-blocking)
			
			LogManager.getLogManager().getLogger("sml.io").info(name + " read " + val + " bytes of data."); //Log how much has been written
		}
		catch (Exception e) //Something happened :(
		{
			LogManager.getLogManager().getLogger("sml.io").info("Exception in " + name + ".stateReady: " + e);
		}
	}
}

Looking at my log file, I noticed that only 1 “name read XXX bytes of data.” is recorded, and it was the size of the header. Also, there was only 1 “name.key.isReadable” in the log file as well.

Any suggestions???

Nevermind, I figured out what was happening wrong. In the method where I add a ByteBuffer to the outgoing queue, I flipped the buffer to record the length in the first 4 bytes, then FLIPPED it again to get back to the start. That second flip made NIO see my buffer as 4 bytes long instead of however long it really was. I just switched the method call from a .flip() to a .rewind() and it works beautifully now (turns out NIO DOES buffer un-read data for TCP socket channels). ::slight_smile:

Don’t forget to remove() your SelectionKey from the Iterator after each next()

You have to “consume” the “events”.