I've found that the NIO is poorly documented at best except for the simplistic case. Even so, I've been through the tutorials and several refactors and ultimately pushed back to the simplest case and I'm still occasionally having isReadable firing off with a 0 byte SocketChannel read. It's not happening every execution.
I used to be calling the read from the attached object in a separate thread and thought it might be race conditions but I've since gone to having the read occur in the selector's thread and still the problem persists. I imagine it could be my test client, but I'm not sure what would be triggering it inconsistently as the client socket shouldn't be closing until it receives a response from the server.
So in the code included, the "hello" message sent by this snippet makes it across fine every single time as I would expect
out.write("hello".getBytes()); out.write(EOT); out.flush();
It is after this that I will occasionally get a 0 length socket channel. And sometimes get the proper response from this snippet:
out.write(dataServerCredentials.getBytes()); out.write(EOT); out.flush();
Any insight into this would be appreciated, it's killing my slowly. I've already tried finding answers here and the one question that seemed relevant didn't really shed much light onto my problems.
Thanks in advance!
Code snippets below:
Selection method:
public void execute()
{
initializeServerSocket();
for (;;)
{
try
{
System.out.println("Waiting for socket activity");
selector.select();
Iterator<SelectionKey> selectedKeys =
this.selector.selectedKeys().iterator();
while(selectedKeys.hasNext())
{
SelectionKey key = selectedKeys.next();
selectedKeys.remove();
if (!key.isValid())
{
continue;
}
if (key.isAcceptable())
{ // New connection
// TODO: Create helper method for this that configures user info?
System.out.println("Accepting connection");
ServerSocketChannel serverSocketChannel =
(ServerSocketChannel)key.channel();
SocketChannel socketChannel =
serverSocketChannel.accept();
socketChannel.socket().setSoTimeout(0);
socketChannel.configureBlocking(false);
SelectionKey newKey =
socketChannel.register(selector, SelectionKey.OP_READ);
// Create and attach an AuthProcessor to drive the states of this
// new Authentication request
newKey.attach(new AuthenticationRequestProcessor(newKey));
}
else if (key.isReadable())
{ // Socket has incoming communication
AuthenticationRequestProcessor authProcessor =
(AuthenticationRequestProcessor)key.attachment();
if (authProcessor == null)
{ // Cancel Key
key.channel().close();
key.cancel();
System.err.print("Cancelling Key - No Attachment");
}
else
{
if (authProcessor.getState() ==
AuthenticationRequestProcessor.TERMINATE_STATE)
{ // Cancel Key
key.channel().close();
key.cancel();
}
else
{ // Process new socket data
authProcessor.process(readStringFromKey(key));
}
}
}
}
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
Read Method (Ignore some of the stupidities in here, this was yanked from another thread)
protected String readStringFromKey(SelectionKey key)
{
SocketChannel socketChannel = (SocketChannel)key.channel();
readBuffer.clear();
String message = null;
try
{
final int bytesRead = socketChannel.read(readBuffer);
if (-1 == bytesRead)
{ // Empty/Closed Channel
System.err.println("Error - No bytes to read on selected channel");
}
else
{ // Convert ByteBuffer into a String
System.out.println("Bytes Read: " + bytesRead);
readBuffer.flip();
message = byteBufferToString(readBuffer, bytesRead);
readBuffer.clear();
}
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
// Trim EOT off the end of the message
return message.trim();
}
Client snippets:
public void connect()
{
boolean connectionStatus = false;
String connectionHost = null;
int connectionPort = 0;
String connectionAuthKey = null;
try
{ // Login
authenticationSocket = new Socket(AUTH_HOST, AUTH_PORT);
out = authenticationSocket.getOutputStream();
in = new BufferedInputStream(authenticationSocket.getInputStream());
out.write("hello".getBytes());
out.write(EOT);
out.flush();
StringBuilder helloResponse = new StringBuilder();
// Read response off socket
int currentByte = in.read();
while (currentByte > -1 && currentByte != EOT)
{
helloResponse.append((char)currentByte);
currentByte = in.read();
}
outgoingResponses.offer(Plist.fromXml(helloResponse.toString()));
System.out.println("\n" + helloResponse.toString());
out.write(credentials.getBytes());
out.write(EOT);
out.flush();
// Read status
int byteRead;
StringBuilder command = new StringBuilder();
do
{
byteRead = in.read();
if (0 < byteRead)
{
if (EOT == byteRead)
{
Logger.logData(command.toString());
Map<String, Object> plist = Plist.fromXml(command.toString());
outgoingResponses.offer(plist);
// Connection info for Data Port
connectionStatus = (Boolean)plist.get(STATUS_KEY);
connectionHost = (String)plist.get(SERVER_KEY);
connectionPort = (Integer)plist.get(PORT_KEY);
connectionAuthKey = (String)plist.get(AUTH_KEY);
Logger.logData("Server =>" + plist.get("server"));
command = new StringBuilder();
}
else
{
command.append((char)byteRead);
}
}
}
while (EOT != byteRead);
}
catch (UnknownHostException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
catch (XmlParseException e)
{
Logger.logData("Invalid Plist format");
e.printStackTrace();
}
finally
{ // Clean up handles
try
{
authenticationSocket.close();
out.close();
in.close();
}
catch (IOException e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("Connection status =>" + connectionStatus);
System.out.println("Connection host =>" + connectionHost);
System.out.println("Connection port =>" + connectionPort);
if (connectionStatus)
{
dataServerHost = connectionHost;
dataServerPort = connectionPort;
dataServerAuthKey = connectionAuthKey;
System.out.println("Connecting to data server @: " + dataServerHost + ":" + dataServerPort);
connectToDataServer();
}
}