Gordon Beaton said:
A TCP connection consists of two independent streams, one in each
direction. If read() indicates EOF, it does not necessarily mean that
the connection has been terminated, although that is often the case.
It may still be possible to write to the remaining stream. Note that
write() can indicate EOF too.
Look up e.g. Socket.shutdownOutput(), or the shutdown() system call.
A typical use of this feature is when a client sends a request to a
server, then uses shutdown() to indicate EOF to the server. The server
can then send the response on the same connection, then use close() to
indicate EOF to the client.
/gordon
This clarification actually helped me quite alot with a problem I was
having - thank you. I glanced over the javadoc of shutdownOutput() but
failed to realize its significance. It incidently led led to a very
interesting relationship between java.net.Socket and
java.nio.SocketChannel I was not aware of.
My problem is that I am caching open socketChannels for re-use on the
client side. The protocol I'm working with allows it. However, calling
SocketChannel.close() somehow still left the server thinking the
connection was open. Your explanation led to this code which suprised
me:
SocketChannel target = (SocketChannel) socketCache.get(ii);
//ArrayList
try
{
if (null != target)
{
Socket socket = target.socket();
socket.shutdownOutput();
socket.shutdownInput();
socket.close();
target.close();
target = null;
socket = null;
}
}
I was thinking SocketChannel and nio was completely seperate from the
old Socket class. Guess again.
As a side note, the code I posted before had a nasty file descriptor
leak due to not closing the Selector. Even the shutdownOutput() code
above didn't fix it. Here's the revised code in case anyone googles on
it:
public static String readFromChannel (SocketChannel sChannel,
final int size) throws IOException
{
int numBytes = 0;
boolean hasRead = false;
CharsetDecoder decoder = ascii.newDecoder();
ByteBuffer inBuffer = ByteBuffer.allocateDirect(size);
CharBuffer charBuffer = CharBuffer.allocate(size+100);
StringBuffer dataRead = new StringBuffer();
//Use Selector for activity readiness
Selector selector = Selector.open();
// Register read activity on socket
sChannel.register(selector, SelectionKey.OP_READ);
try
{
// Read response with 20000 mililiseconds timeout
//IMPORTANT! this time limit can be overridden by
java.utl.timer,
//set in the invoking class, which therefore could interrupt
the channel
//and cause a read timeout less that 20000 milliseconds
//This behavior is more predictable if this read timeout is
_LESS_
//then the java.util.Timer timeout used by the current thread
of the
//invoking object of this method
while (selector.select(20000) > 0)
{
// Get set of ready objects
Set readyKeys = selector.selectedKeys();
Iterator readyItor = readyKeys.iterator();
// Walk through set
while (readyItor.hasNext())
{
// Get key from set
SelectionKey key = (SelectionKey)readyItor.next();
// Remove current entry
readyItor.remove();
// Get channel
ReadableByteChannel keyChannel =
(SocketChannel)key.channel();
if (key.isReadable())
{
// Read what's ready in response
// Think this little loop is odd? Well it is. The
reason for it
// is because TCP stacks have a tendency to activate
with only 1 byte
// available, and a subsequent read immediately gets the
rest of the data.
// Very annoying, and this leads to us in the common
sense scenario with
// non-blocking to have to inject multiple READ events
for no good reason.
// This little loop tries the read again if it didn't
complete the first
// time around, and provides a noticable performance
boost.
for (int i = 0; ; i++)
{
int count = 0;
if ((count = keyChannel.read (inBuffer)) < 0) // EOF
{
keyChannel.close();
throw new IOException ("Socket lost connection
during read operation");
}
numBytes += count;
//loop until buffer full
if (!inBuffer.hasRemaining())
{
//just in case - good for debugging
hasRead = true;
System.out.println("has read");
System.out.println("SUCCESSFUL, length of data read:
" + numBytes);
// Make buffer readable
inBuffer.flip();
// Decode buffer
decoder.decode(inBuffer, charBuffer, false);
// Make buffer readable
charBuffer.flip();
// VERY IMPORTANT!!! This Selector _MUST_ be
cancelled, or it will
// create a file descriptor leak
selector.close();
// Build and return string recieved
return dataRead.append(charBuffer).toString();
}
//one byte trick as described above
if (i >= 2)
{
break;
}
}//end for loop
}//end key.isReadable()
}//end while iterator
}//end while selector
if (false == hasRead)
{
System.err.println("has _NOT_ read");
System.err.println("length of data read: " + numBytes);
System.err.println("length of data read: " + numBytes);
if (numBytes > 0)
{
// Make buffer readable
inBuffer.flip();
// Decode buffer
decoder.decode(inBuffer, charBuffer, false);
// Make buffer readable
charBuffer.flip();
System.err.println("ERROR, data read: " +
dataRead.append(charBuffer).toString());
}
throw new IOException ("Socket read operation timed out");
}
else
{
throw new IOException ("Invalid Read Socket state");
}
}//end try
finally
{
// Guarantee the closure of the Selector, preventing a file
descriptor leak
// If already closed this will have no effect. If a "too many
open files"
// error occurs, a linux 'lsof | wc -l' command should show a
growing
// list of files open, possibly indicating an unclosed
selector with Keys
// that have not been 'deregistered' according to the Selector
javadocs
// Note that a simple SocketChannel.close() will _not_
deregister the keys
selector.close();
}
}
iksrazal