JDK-4531726 : (so) Detect loss of socket connection using java.nio.channels.Selector
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.nio
  • Affected Version: 1.4.0
  • Priority: P4
  • Status: Closed
  • Resolution: Cannot Reproduce
  • OS: windows_nt,windows_2000
  • CPU: x86
  • Submitted: 2001-11-27
  • Updated: 2002-03-19
  • Resolved: 2002-03-19
Description
derThread.java#315 to #332). This works fine for one client socket, but
does NOT work for a greater number of opened client sockets.

I modified my test case and attach it to this mail.
Simply expand in an arbitrary directory and compile with "javac *.java".

Run the server with 

        java ServerNIO -selectWait=xxx
where xxx is the time passed to the Selector.select call.

Try it with xxx=0

To start the client, use
        
        java Client -t=xxx
where xxx is the number of threads that open a connection to the server, send
1024 bytes of data and close the socket.

Please try it with 1, 10 and 100 client threads and watch the debug output of
the server window.
(Review ID: 134352)
======================================================================


Name: nt126004			Date: 11/27/2001


java version "1.4.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta3-b84)
Java HotSpot(TM) Client VM (build 1.4.0-beta3-b84, mixed mode)

I'm using the nio package to write a "proxy" type application, so I have a
ServerSocketChannel listening for incoming connections from a client and
then create a SocketChannel to connect to a server. Both SocketChannels have
a SelectionKey registered to the same Selector for OP_READ requests.

All works fine, until either the client or server drops the connection. I would
expect the SelectionKey to be triggered for one of the SocketChannels, but
nothing appears to happen. I have tried using Selector.select(timeout) and then
checking the status of each channel, but they still show isConnected() and
isOpen() set to true.

Shouldn't the OP_READ be triggered as the socket has been remotely shutdown ?


Here's some sample code called TestProxy.java... I've added comments at the top
of the file on how to use it...

/**
 * Sample proxy program to demonstrate use of new classes in java.nio package.
 *
 * This will use a default listening port of 1414 and target port of 8900 and
 * assumes client, server and proxy are all running on the same machine.
 *
 * For the client and server, use the TimeQuery and NBTimeServer samples
 * shipped with Java 1.4.
 *
 * Start TestProxy in a command prompt (it listens on port 1414 by default).
 *
 * Start NBTimeServer in a command prompt (it listens on port 8900 by default).
 *
 * Start TimeQuery in a command prompt with the following options :
 *
 *       java TimeQuery 1414 host_name
 *
 *      (host_name must be the fully qualified name for TimeQuery to work).
 *
 * You can run TimeQuery as many times as you like. Each invocation will
 * cause a new Thread to be started to handle the request. The Selector
 * in TestProxy will timeout after 10 seconds and the status of SelectorKeys
 * SocketChannels and Sockets will be displayed, including the local port
 * number (this is used to identify each thread).
 *
 * Even though the client has dropped the connection, the status shows it's
 * still active. In fact, if you stop NBTimeServer, the connections still
 * appear to be connected.
 *
 */

import java.io.*;
import java.nio.*;
import java.nio.channels.*;
import java.nio.channels.spi.*;
import java.net.*;
import java.util.*;

// Listen on a port for connections and write back the current time.
public class TestProxy {
    private static final int LISTENING_PORT = 1414;
    private static final int TARGET_PORT = 8900;

    private static Selector acceptSelector = null;

    // Constructor with no arguments creates a time server on default port.
    public TestProxy() throws Exception {
        acceptConnections(this.LISTENING_PORT);
    }

    // Constructor with port argument creates a time server on specified port.
    public TestProxy(int port) throws Exception {
        acceptConnections(port);
    }

    // Accept connections for current time. Lazy Exception thrown.
    private void acceptConnections(int port) throws Exception {
        // Selector for incoming time requests
        acceptSelector = SelectorProvider.provider().openSelector();

        // Create a new server socket and set to non blocking mode
        ServerSocketChannel ssc = ServerSocketChannel.open();
        ssc.configureBlocking(false);

        // Bind the server socket to the local host and port
        InetAddress lh = InetAddress.getLocalHost();
        InetSocketAddress isa = new InetSocketAddress(lh, port);
        ssc.socket().bind(isa);

        System.out.println("acceptConnections() listening on port " +
LISTENING_PORT);

        // Register accepts on the server socket with the selector. This
        // step tells the selector that the socket wants to be put on the
        // ready list when accept operations occur, so allowing multiplexed
        // non-blocking I/O to take place.
        SelectionKey acceptKey = ssc.register(acceptSelector,
SelectionKey.OP_ACCEPT);

        int keysAdded = 0;

        // Here's where everything happens. The select method will
        // return when any operations registered above have occurred, the
        // thread has been interrupted, etc.
        while ((keysAdded = acceptSelector.select()) > 0) {
            System.out.println("acceptConnections() get list of keys");
            // Someone is ready for I/O, get the ready keys
            Set readyKeys = acceptSelector.selectedKeys();
            Iterator i = readyKeys.iterator();

            // Walk through the ready keys collection and process date requests.
            while (i.hasNext()) {
                System.out.println("acceptConnections() get next key");
                SelectionKey sk = (SelectionKey)i.next();
                i.remove();
                
                // The key indexes into the selector so you
                // can retrieve the socket that's ready for I/O
                // Assumed to be conenct request
                ServerSocketChannel nextReady = (ServerSocketChannel)
sk.channel();
                
                // Accept the date request and send back the date string
                Socket s = nextReady.accept().socket();
                int p = s.getPort();
                
                // New connection accepted... pass to own thread to handle
                System.out.println("acceptConnections() calling proxy");
                SocketHandler sh = new SocketHandler(s, p);
                new Thread(sh).start();
                System.out.println("acceptConnections() wait for next
connection");
            }
        }
    }

    // Entry point.
    public static void main(String[] args) {
        try {
            TestProxy tp = new TestProxy();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    // This class acts a proxy. It gets given a socket, creates a connection
    // to a predefined target and then waits on OP_READ requests from either
    // side of the connection. Whatever it reads from one side, it writes to
    // the other side of the connection.
    public class SocketHandler implements Runnable {
        Socket socket;
        String port;
        SocketChannel channel;

        Selector ioSelector = null;

        // Constructor
        public SocketHandler(Socket soc, int p) {
            this.socket = soc;
            this.port = "proxy(" + p + ") ";
        }

        // Call main proxy code....
        public void run() {
            try {
                proxy(socket);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        // This is where the real work occurs.
        private void proxy(Socket sToCaller) throws Exception {

            System.out.println(port);

            ByteBuffer buff = ByteBuffer.allocateDirect(64000);

            SocketChannel scToCaller = sToCaller.getChannel();

            ioSelector = SelectorProvider.provider().openSelector();

            System.out.println(port + "create SocketChannel to Server");
            SocketChannel scToResp = SocketChannel.open();

            InetAddress lh = InetAddress.getLocalHost();
            System.out.println(port + "connecting Socket to localhost" +
TARGET_PORT);
            boolean b = scToResp.connect(new InetSocketAddress(lh,
TARGET_PORT));

            // Must be non-blocking mode
            scToResp.configureBlocking(false);
            scToCaller.configureBlocking(false);

            // Register both SocketChannels to the same Selector
            SelectionKey skForResp = scToResp.register(ioSelector,
SelectionKey.OP_READ + SelectionKey.OP_WRITE);
            SelectionKey skForCaller = scToCaller.register(ioSelector,
SelectionKey.OP_READ + SelectionKey.OP_WRITE);

            int keysAdded = 0;

            try {
                // Stay here forever... we should drop out when either the
client or server
                // drops the connection.
                while (true) {
                    // Here's where everything happens. The select method will
                    // return when any operations registered above have
occurred, the
                    // thread has been interrupted, etc.
                    while ((keysAdded = ioSelector.select(10000)) > 0) {
                        System.out.println(port + "get list of keys");
                        // Someone is ready for I/O, get the ready keys
                        Set readyKeys = ioSelector.selectedKeys();
                        Iterator i = readyKeys.iterator();

                        // Walk through the ready keys collection and process
requests.
                        while (i.hasNext()) {
                            System.out.println(port + "get next key");
                            SelectionKey sk = (SelectionKey)i.next();
                            showSelectionKey(sk);
                            i.remove();
                            // The key indexes into the selector so you
                            // can retrieve the socket that's ready for I/O
                            SocketChannel nextReady =
(SocketChannel)sk.channel();

                            // Clear the buffer
                            buff.clear();

                            // Ready for reading
                            if (sk.isReadable()) {
                                System.out.println(port + "Reading...");
                                nextReady.read(buff);

                                // Length of data read
                                int pos = buff.position();
                                System.out.println(port + "Data length = " +
pos);

                                buff.flip();

                                // Display data read
                                System.out.print(port);
                                try {
                                    for (int j = 0; j < pos; j++ ) {
                                        String ss =
Integer.toHexString(buff.get());
                                        if (ss.length() == 1)
System.out.print("0" + ss + " ");
                                        else if (ss.length() > 2)
System.out.print(ss.substring(6) + " ");
                                        else System.out.print(ss + " ");
                                    }
                                } catch (Exception e) {
                                    System.out.println(port + "Exception " +
e.getMessage());
                                }
                                System.out.println(port + " ");
                                buff.flip();

                                // Write data to other side of connection
                                if (sk.equals(skForCaller)) {
                                    System.out.println(port + "Writing to server
...");
                                    scToResp.write(buff);
                                } else {
                                    System.out.println(port + "Writing to client
...");
                                    scToCaller.write(buff);
                                }

                            } else
                                // Ready for writing... NOP
                                if (sk.isWritable()) {
                                System.out.println(port + "Writable...");
                            } else {
                                // Just in case...
                                System.out.println(port + "Uknown i/o");
                            }
                        }
                    }

                    System.out.println(port + "dropped out of select()");
                    showKeys();

                }
            } catch (ClosedSelectorException cse) {
                System.out.println(port + "Closed Selector exception...");
            } catch (IOException ioe) {
                System.out.println(port + "IOException... " + ioe.getMessage());
            } catch (Exception e) {
                System.out.println(port + "Exception... " + e.getMessage());
            }
        }

        // Display status of all SelectionKeys
        private void showKeys() {

            Set readyKeys = ioSelector.keys();
            Iterator i = readyKeys.iterator();

            // Walk through the ready keys collection and process date requests.
            while (i.hasNext()) {
                SelectionKey sk = (SelectionKey)i.next();
                showSelectionKey(sk);
            }
        }

        // Display status of SelectionKey
        private void showSelectionKey(SelectionKey sk) {

            System.out.println(port + "====SelectionKey=======");
            System.out.println(port + "interestOps : " + sk.interestOps());
            System.out.println(port + "readyOps : " + sk.readyOps());
            System.out.println(port + "isValid : " + sk.isValid());
            System.out.println(port + "isConnectable : " + sk.isConnectable());
            System.out.println(port + "isReadable : " + sk.isReadable());
            System.out.println(port + "isWritable : " + sk.isWritable());

            SocketChannel sc = (SocketChannel) sk.channel();
            showSocketChannel(sc);
        }

        // Display status of SocketChannel
        private void showSocketChannel(SocketChannel ch) {

            System.out.println(port + "=======Channel=======");
            System.out.println(port + "isConnected : " + ch.isConnected());
            System.out.println(port + "isInputOpen : " + ch.isInputOpen());
            System.out.println(port + "isOutputOpen : " + ch.isOutputOpen());
            System.out.println(port + "isOpen : " + ch.isOpen());

            Socket s = (Socket) ch.socket();
            showSocket(s);
        }

        // Display status of Socket
        private void showSocket(Socket s) {

            System.out.println(port + "=======Socket=======");
            System.out.println(port + "isBound : " + s.isBound());
            System.out.println(port + "isClosed : " + s.isClosed());
            System.out.println(port + "isConnected : " + s.isConnected());
            System.out.println(port + "isInputShutdown : " +
s.isInputShutdown());
            System.out.println(port + "isOutputShutdown : " +
s.isOutputShutdown());

        }
    }

}
(Review ID: 134656) 
======================================================================

Name: nt126004			Date: 11/27/2001


java version "1.4.0-beta3"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0-beta3-b84)
Java HotSpot(TM) Client VM (build 1.4.0-beta3-b84, mixed mode)

The problem is the behavior of the Selector if a Client closes its socket.

I found out that in this case Selector.select() returns and the SelectionKey signals
"isReadable". If I try to read data from the associated SocketChannel, I get the
value -1 from the read operation. In this case, I close the SocketChannel
(see Rea

Comments
EVALUATION The OP_READ event should occur when the socket is remotely shutdown. There are known bugs in beta3 that could cause this to fail. The isConnected() and isOpen() methods are not useful in this regard, though. They refer to whether the channel has been connected and whether it is open, respectively, which are different than the client closing the connection. ###@###.### 2002-03-13
13-03-2002