Selector.select() keys several times?

I try to write network server using NIO and met strange problems. I have Thread selector select keys and than I create Action that execute each readable request in thread pool. But during debug I found out that in select loop for each readable request object Action was created several times!!!

Also I have problem with closing connections. During testing I kill my client app so connection to server killed without and close() method. Ofcourse it cause exceptions on server side, but added some try catch to make server stable to network problems and clients drops. But my try catch do not help fully. After several such killing of my client server stop accepting incomming connections

Here is my code of acceptions connections on server:

import java.io.*;

import java.net.*;

import java.nio.channels.*;

import java.util.*;

import com.vimas.callcenter.server.data.*;

import java.nio.*;

import java.text.NumberFormat;

import java.text.DateFormat;

import java.text.*;

publicclass StatisticsServer{

privatestaticfinal Log log = Log.getInstance();

privatestaticfinalint DEFAULT_PORT = 7777;

privatestatic Properties prop =new Properties();

privatestatic Hashtable<Long, UserProfile> users =new Hashtable<Long, UserProfile> ();

private Thread_pool pool;

public StatisticsServer(){

}

publicstaticvoid main(String[] args){

StatisticsServer voiceChatServer =new StatisticsServer();

voiceChatServer.start();

}

publicvoid start(){

//read server properties:

int port, initial_pool_size, max_pool_size;

try{

prop.loadFromXML(new FileInputStream("config.xml"));

port = Integer.parseInt(prop.getProperty("port"));

initial_pool_size = Integer.parseInt(prop.getProperty("initial_pool_size"));

max_pool_size = Integer.parseInt(prop.getProperty("max_pool_size"));

Log.DEBUG = Boolean.valueOf(prop.getProperty("DEBUG")).booleanValue();

}

catch (Exception ex){

Log.DEBUG =true;

port = DEFAULT_PORT;

initial_pool_size = 100;

max_pool_size = 200;

log.info(

"Properties file (server.properties) or some keys not found, using default values: " + ex);

}

//create thread pool

pool =new Thread_pool(initial_pool_size, max_pool_size);

//create server socket

ServerSocketChannel serverChannel;

Selector selector;

try{

serverChannel = ServerSocketChannel.open();

ServerSocket ss = serverChannel.socket();

InetSocketAddress address =new InetSocketAddress(port);

ss.bind(address);

log.info("Server started on port " + port);

serverChannel.configureBlocking(false);

selector = Selector.open();

serverChannel.register(selector, SelectionKey.OP_ACCEPT);

}

catch (IOException ex){

ex.printStackTrace();

log.error("Error starting server!", ex);

return;

}

//start while loop for receiving requests

try{

while (selector.select() > 0){

Iterator keyIterator = selector.selectedKeys().iterator();

while (keyIterator.hasNext()){

//add try catch if some exception occurs it will not stop server work

try{

SelectionKey key = (SelectionKey) keyIterator.next();

keyIterator.remove();

if (key.isAcceptable()){

ServerSocketChannel server = (ServerSocketChannel) key.

channel();

SocketChannel client = server.accept();

log.debug("Accepted connection from " + client);

client.configureBlocking(false);

SelectionKey key2 = client.register(selector,

SelectionKey.OP_READ);

Long userID = genID();

UserProfile profile =new UserProfile(userID);

users.put(userID, profile);

key2.attach(userID);

}

elseif (key.isReadable()){

SocketChannel socketChannel = (SocketChannel) key.channel();

Long userID = (Long) key.attachment();

//craete Action for executing accepted command from client

Action a =new Action(userID, socketChannel, key);

//add action to thread pool for execution

pool.execute(a);

}

}

catch (Throwable ex2){

log.error("SelectionKey work fail error: " + ex2,ex2);

}

}

}

}

catch (IOException ex1){

ex1.printStackTrace();

}

}

publicstatic UserProfile getProfile(Long id){

return users.get(id);

}

publicstaticvoid removeProfile(Long id){

users.remove(id);

}

privatestatic Long genID(){

returnnew Long( (long) (Math.random() * 100000000));

}

}

I added system.out to Action constructor and find out that when one request is sent top sever, object Action creates several times.

When I kill client app, then I get exception in place where read from channel, and the most interesting thing that request was already executed and answer sent, so it must be exception are throw in second, 3-rd, etc. Action objects that were created for one request, but data were read by first Action object.

try{

recieveByteBuffer.clear();

if (!socketChannel.isConnected()){

return;

}

int nbytes = socketChannel.read(recieveByteBuffer);

if (nbytes == -1){

key.cancel();

socketChannel.close();

return;

}

if (nbytes > 0){

log.debug("read " + nbytes +" bytes");

recieveByteBuffer.flip();

parseBytes(recieveByteBuffer.array(), nbytes);

}

}

catch (Throwable ex){

try{

socketChannel.close();

}

catch (Exception ex2){

log.error("Action.run.catch error: " + ex2);

}

}

Exception:

java.io.IOException: An existing connection was forcibly closed by the remote host

at sun.nio.ch.SocketDispatcher.read0(Native Method)

at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:25)

at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:233)

at sun.nio.ch.IOUtil.read(IOUtil.java:206)

at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:236)

at com.vimas.callcenter.server.Action.run(Action.java:54)

at com.vimas.callcenter.server.Thread_pool$Pooled_thread.run(Thread_pool.java:60)

[10808 byte] By [Sanya_13a] at [2007-11-26 16:20:17]
# 1

> But during debug I found out

> that in select loop for each readable request object

> Action was created several times!!!

That would mean that more data arrived after you created the Action object.

> When I kill client app, then I get exception in place

> where read from channel, and the most interesting

> thing that request was already executed and answer

> sent, so it must be exception are throw in second,

> 3-rd, etc. Action objects that were created for one

> request, but data were read by first Action object.

Ditto.

> if (!socketChannel.isConnected()) {

> return;

This test just tells you whether this socket is connected. As you accepted it, the answer is always yes. It doesn't tell you about the state of the connection or the peer.

>if (nbytes == -1) {

>key.cancel();

>socketChannel.close();

The cancel is redundant, the close does that internally.

>if (nbytes > 0) {

>log.debug("read " + nbytes + " bytes");

>recieveByteBuffer.flip();

>parseBytes(recieveByteBuffer.array(), nbytes);

> }

There is no guarantee that you have a complete request at this point. You need to rethink this, and also your strategy of creating a new Action object per OP_READ event. If the channel already has a current Action you should ignore new OP_READs, or perhaps you could deregister OP_READ until the Action is complete.

> Exception:

> java.io.IOException: An existing connection was forcibly closed by the remote host

So what exactly is happening at the remote host?

ejpa at 2007-7-8 22:43:49 > top of Java-index,Core,Core APIs...
# 2

> > But during debug I found out

> > that in select loop for each readable request object

> > Action was created several times!!!

>

> That would mean that more data arrived after you

> created the Action object.

>

During my tests it was impossible, because I surely sent 1 request to server and server read in fully at once, and nobody else sent requests to server.

>

> if (!socketChannel.isConnected()) {

> return;

> st just tells you whether this socket is connected.

> As you accepted it, the answer is always yes. It

> doesn't tell you about the state of the connection or

> the peer.

Then what I have to check?

>if (nbytes == -1) {

> ey.cancel();

> >socketChannel.close();

>

> The cancel is redundant, the close does that

> internally.

I read some topics on this forum and found some recommendations that better also call cancel manually

>

> >if (nbytes > 0) {

> >log.debug("read " + nbytes + " bytes");

> >recieveByteBuffer.flip();

> >parseBytes(recieveByteBuffer.array(),

> nbytes);

> > }

>

> There is no guarantee that you have a complete

> request at this point. You need to rethink this, and

> also your strategy of creating a new Action object

> per OP_READ event. If the channel already has a

> current Action you should ignore new OP_READs, or

> perhaps you could deregister OP_READ until the Action

> is complete.

Can you provide some examples?

> > Exception:

> > java.io.IOException: An existing connection was

> forcibly closed by the remote host

>

> So what exactly is happening at the remote host?

I just killed remote application, without closing streams and sockets,

Sanya_13a at 2007-7-8 22:43:49 > top of Java-index,Core,Core APIs...
# 3

> During my tests it was impossible, because I surely

> sent 1 request to server and server read in fully at

> once, and nobody else sent requests to server.

There is no other reason for OP_READ to fire except data, a remote close, or a remote shutdownOutput at the other end. Maybe the Action object took so long to execute that your select() loop executed again and found the same data still unread. In which case you should deregister OP_READ. As I suggested before.

> Then what I have to check?

Check that the I/O operations (read and write) don't throw exceptions. That's all you can check.

> > The cancel is redundant, the close does that

> > internally.

>

> I read some topics on this forum and found some

> recommendations that better also call cancel

> manually

Bad luck, they were all wrong. They should have checked the Javadoc.

> So what exactly is happening at the remote host?

> just killed remote application, without closing

> streams and sockets

A 'connection reset' is exactly what you would expect in this situation.

ejpa at 2007-7-8 22:43:49 > top of Java-index,Core,Core APIs...
# 4
Thanks for answers, can you give me some examples of successfull strategy of usage of register/deregister OP_READ, if they exists of course.
Sanya_13a at 2007-7-8 22:43:49 > top of Java-index,Core,Core APIs...
# 5
http://java.sun.com/j2se/1.5.0/docs/api/java/nio/channels/SelectionKey.html#interestOps(int)Just clear the OP_READ bit in the interest-ops before you enqueue the Action object, and set it inside the Action once you've read the request (or decided you don't have the entire
ejpa at 2007-7-8 22:43:49 > top of Java-index,Core,Core APIs...
# 6
But what value I have to set?I know SelectionKey.OP_READ, OP_ACCEPT, OP_WRITE, OP_CONNECTBut I do not sure that they all suit me. Other values throw illegal arument exception.
Sanya_13a at 2007-7-8 22:43:50 > top of Java-index,Core,Core APIs...
# 7
Is setting and clearing a single bit really so mysterious? key.interestOps(key.interestOps() & ~SelectionKey.OP_READ); // clearkey.interestOps(key.interestOps() | SelectionKey.OP_READ); // setBlimey.
ejpa at 2007-7-8 22:43:50 > top of Java-index,Core,Core APIs...