1

I'm trying to wait on multiple socket channels for data to read. I've copied example code from the web but it's not working for me. The problem is that the "select" call properly blocks until the first data is received, but thereafter immediately returns 0 (not ready). My code (for a single channel) follows.

    final String HOST = "<REDACTED>.com";
    final int PORT    = 2000;
    SocketChannel MyChannel;
    Selector MySelector;

    void doSelect()
    {
        SelectionKey key;
        int nready;
        long nr;

        try {
            ByteBuffer bytebuf = ByteBuffer.allocate( 1024 );              // allocate bytebuffer
            InetSocketAddress addr = new InetSocketAddress( HOST, PORT);          // form addr
            MyChannel = SocketChannel.open();                   // create socketchan
            MyChannel.connect( addr );                           // connect to data src
            while( !MyChannel.finishConnect() ) Thread.sleep( 1000 ); // wait til connected
            MyChannel.configureBlocking( false );               // set non-blocking
            MySelector = Selector.open();                       // open new selector
            key = MyChannel.register( MySelector, SelectionKey.OP_READ ); // register for reads

            while( true ) {                                     // loop forever
                nready = MySelector.select();                   // block til something ready
                if( nready <= 0 ) {                             // nothing ready?
                    logit( "not ready: select returned "+nready );
                    MyChannel.close();
                    break;                                      // give up
                }
                if( key.isReadable() ) {                        // readable?
                    nr = MyChannel.read( bytebuf );             // read data
                    logit( "read "+nr+" bytes" );
                }
            }   // end while
        }   // end try
        catch( Exception e ) {
            logit( "doSelect: "+e );
            return;
        }
    }

    //@ Log a message to logcat
    void logit(String msg) { Log.i( "MYAPP", msg ); }

UPDATE 10/19/2019

Based on a suggestion by @user207421, I modified the code to iterate over selected key. This made no difference. The select() call is still not blocking after the first reception and the read from the channel says 0 bytes read..

Excerpt from the new code (some variables renamed for (my) readability)

    while( true ) {                                     // loop forever
        nready = selector.select();                   // call select: block til something ready
        if( nready <= 0 ) {                             // nothing ready? (should never happen)
            break;                                      // give up
        }

        // PROCESS ALL SELECTED KEYS
        Set<SelectionKey> selected  = selector.selectedKeys();  // get selected key set
        Iterator<SelectionKey> iter = selected.iterator();    // create iterator
        while( iter.hasNext() ) {                             // while has more
            key = iter.next();                                // get next key
            if( key.isReadable() ) {                           // check if ready to read
                nr = chan.read( bb );                          // read from channel
                if( nr > 0 ) {                                 // got some data?
                  printByteBuffer( bb );
                }
                else {
                  // should never happen! (but it does)
                }
            }
            else if( key.isWritable()) {}                       // never happens!
            else if( key.isAcceptable()) {}                     // never happens!
            else if( key.isConnectable()) {}                    // never happens!
            iter.remove();
        }  // end while hasnext
    }   // end while  

   //@ Convert ByteBuffer to byte array and print
    void printByteBuffer(
      ByteBuffer bb )                       // BB to print
    {
        int nrem;
        byte[] bytes;
        String str;

        bb.flip();                         // reset position
        nrem = bb.remaining();
        bytes = new byte[nrem];            // alloc byte array
        bb.get( bytes );                   // get bytes from bytebuf
        str = new String( bytes );         // cnvt to string
        log( "received \""+str+"\"" );
    }  

Any suggestions greatly appreciated.

DontPanic
  • 2,164
  • 5
  • 29
  • 56
  • You're doing the connect while still in blocking mode. Nothing wrong with that, but it means you don't have to call `finishConnect()` at all. If `read()` returns -1 you need to close the channel and stop selecting. – user207421 Oct 18 '19 at 19:52
  • Sorry for my previous post with inaccurate wordings on the explanation which attracted some downvotes, so I've deleted it. By the way, although the select() method would return from blocking the thread, but it doesn't mean you must get anything incoming while doing the "chan.read(bb)". It is because even if selector.select() method returns, it only means the keys represented channels are "ready for" registered operations, "ready for" doesn't means the situation is occurring in the meanwhile. So you still have to do the additional handling while doing the "chan.read(bb)" in case returns 0 or -1 – garykwwong Oct 20 '19 at 09:31
  • The "additional handling" means you may start over again from select() to read(), and process the incoming bytes until there are anything returned form read() method. – garykwwong Oct 20 '19 at 10:25
  • @DontPanic `read()` can return <= 0, despite your comment in the code that it shouldn't happen. Zero means there was no space in the buffer, because you aren't clearing or compacting it , and -1 means that the peer has disconnected, in which you must close the channel, after which you will no have no more channels registered with the selector, so you should close that too and exit the select loop. – user207421 Oct 20 '19 at 11:01
  • @garykwwong Your claim about 'ready for' not meaning 'situation is occurring' is meaningless at best, incorrect at worst. 'Ready for read' means there is data available to be read without blocking. 'Ready to write' means there is data space available for writing without blocking. – user207421 Oct 20 '19 at 11:03
  • @user207421 thanks for your comment. For my curiosity, how did you know the meaning of returned 0 and -1? Is it documented anywhere? – garykwwong Oct 21 '19 at 02:15
  • @user207421 oh, this statement "If the input side of a socket is shut down by one thread while another thread is blocked in a read operation on the socket's channel, then the read operation in the blocked thread will complete without reading any bytes and will return -1." at the class heading is telling one of the behavior of read() method, is it right? – garykwwong Oct 21 '19 at 02:15
  • @garykwwong 1. Yes it is documented. 2. Yes, that is 'telling another behaviour of read', in blocking mode. Same action in non-blocking mode would cause `select()` to return, the key to be readable, and `read()` to return -1 (after any other pending data had been read in both cases). – user207421 Oct 21 '19 at 07:38

1 Answers1

0

I found the solution based on a comment. Apparently the ByteBuffer was "full". I fixed the problem by issuing a ".clear()" on it just before the channel read().

I don't understand why this was necessary because the ByteBuffer was way bigger (1000 bytes) than any received data, but nontheless this works.

Re: blocking mode: Every example I found on the web said the channel should be set to non-blocking for Selector select() to work properly. I briefly tried blocking mode anyway but no joy with that.

DontPanic
  • 2,164
  • 5
  • 29
  • 56
  • The `ByteBuffer` never restores its own position to zero, to create more room. You have to `clear()` or `compact()` it after getting data to do that. You should do that after the `get()`, to be symmetrical with the `flip()`, and In fact to kind of undo it. I prefer `compact()` to clear()`, in case you didn't get all the data out of it for some reason, e.g. protocol. If you did, they are equivalent. – user207421 Oct 22 '19 at 00:01
  • All I said about blocking mode was that as you *are* using it for `connect()` you don't need to call `finishConnect()`. I'm not a fan of non-blocking mode in clients as it happens, but nobody has said it not to use it here, or that you can use blocking mode with a selector. You can't. – user207421 Oct 22 '19 at 00:16