我可以使用Java NIO从Socket读取可用字节吗?

时间:2012-09-27 11:34:29

标签: java sockets nio

我想知道在Java NIO套接字中是否有可读字节。在C / C ++中,可以使用以下内容完成:

int available(int fd){
  long readable = 0;

    if (ioctl(fd, FIONREAD, &readable) < 0){
      // error
    }

  return (int) readable;
}

可以使用Java NIO(使用SocketChannelSelectorSocket等)执行相同的操作吗?

5 个答案:

答案 0 :(得分:7)

无法完成。正式来说,API通过SocketChannel.socket().getInputStream().available()存在,但getInputStream()操作将在非阻塞通道上失败,因此无法在您的环境中使用。

编辑:既然你已经照亮了我们一点点,你所需要的东西在Java中仍然不存在,但是当你处于非阻塞模式时,它无关紧要。只需读入一个至少与套接字接收缓冲区一样大的缓冲区:读取的字节数是可以在不阻塞的情况下读取的字节数,这正是您刚才所做的。

答案 1 :(得分:4)

NIO背后的想法是提供一种方法来同时从多个连接中的任何一个连接等待事件(例如“连接已准备好可读数据”),这听起来就像你正在寻找的那样。看看java.nio.channels.Selector。您基本上使用此选择器对象注册所有连接的“可读”事件,然后调用三个select方法中的一个,这些方法将等待您的一个连接上的事件:

  1. select() - 阻止直到某个事件可用(如果您的程序没有其他任何事情可以使用)
  2. select(long timeout) - 在事件可用或发生超时之前阻塞(如果您希望节省CPU负载并提高网络响应性,请使用,如果程序运行速度稍慢,则可以提高网络响应能力)
  3. selectNow() - 立即返回(如果您的程序需要继续运行,请使用)
  4. 然后使用selectedKeys方法获取所有等待事件的连接的列表(例如,准备好读取数据)。然后只需遍历此列表并从仅包含数据的连接中读取,并忽略列表中没有的其他连接,因为它们没有可用的数据。

    这将允许您无阻塞地检查数据是否可用(列表中是否有连接),但是没有多少数据可用。但是,如果您随后读取有关可用数据的连接,它将立即返回而不会阻塞,并返回尽可能多的数据,如果您已经给它足够大的缓冲区。然后,您可以选择在某处缓冲此数据,并使缓冲区中的数据量可用,但有些事情告诉我您不需要这样做,只是想继续处理数据。

答案 2 :(得分:2)

我已经阅读了大量文档,论坛以及您的每个回复。我开发并测试了一个解决方案。它不是最好的,但它是我最好的近似值。

我使用selectNow()Selector方法。如果Selector名为selector且注册了SocketChannel,我可以使用此方法来了解关联的Socket是否具有可读字节:

public boolean isReadable() {
    try {
        selector.selectNow();
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
        while(keyIterator.hasNext()) {
            SelectionKey selectionKey = keyIterator.next();
            if(!selectionKey.isValid()) {
                continue;
            }
            if(selectionKey.isReadable()) {
                return true;
            }
            keyIterator.remove();
        }
    } catch(IOException e) {
        // error
    }
    return false;
}

这是我发现知道Socket是否具有可读字节而不读取字节的唯一方法。你知道更好的方法吗?或者通过这种方式做到这一点的不利之处?

修改

这个解决方案第一次工作,但它有一个缺点:即使0字节是可读的,它总是返回true。即使没有可用的数据,套接字也是可读的。这不是我想要的行为,因为当读缓冲区中没有数据时我不想读取。

EDIT2 BIS:

测试此代码:

public static boolean isReadable(Selector selector) {
    try {
        selector.selectNow();
        Set<SelectionKey> selectedKeys = selector.selectedKeys();
        Iterator<SelectionKey> keyIterator = selectedKeys.iterator();
        while (keyIterator.hasNext()) {
            SelectionKey selectionKey = keyIterator.next();
            if (!selectionKey.isValid()) {
                continue;
            }
            if (selectionKey.isReadable()) {
                    keyIterator.remove();
                return true;
            }
            keyIterator.remove();
        }
    } catch (IOException e) {
        System.err.println("Error!");
        e.printStackTrace();
    }
    return false;
}

public static void read() {
    try {
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open();

        Socket socket = socketChannel.socket();

        System.out.println("Connecting to server...");
        socket.connect(new InetSocketAddress("localhost", 9999));
        System.out.println("Connected to server.");

        socketChannel.configureBlocking(false);

        socketChannel.register(selector, SelectionKey.OP_READ);

        ByteBuffer bb = ByteBuffer.allocate(1024);

        while (true) {
            boolean readable = isReadable(selector);
            if (readable) {
                System.out.println("Readable data found! Let's read...");
                bb.clear();
                int readBytes = socketChannel.read(bb);
                if (readBytes < 0) {
                    System.out.println("End of Stream found");
                    break;
                }
                byte[] readArray = new byte[readBytes];
                System.arraycopy(bb.array(), 0, readArray, 0, readBytes);
                System.out.println("Read (" + (readBytes) + " bytes): "
                        + new String(readArray, Charset.forName("UTF-8")));
                bb.flip();
                socketChannel.write(bb);
                Thread.sleep(1000);
            } else {
                System.out.println("No data to read, sleeping");
                Thread.sleep(1000);
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
}

public static void main(String[] args) {
    read();
}

首先,我执行一个侦听连接和写入数据的应用程序:我使用netcat使用此命令:nc -l 9999。然后我执行我的Java程序。最后,我可以在netcat中编写文本行,并且我的Java程序可以正常工作。

记住它是概念的证明,而不是真正的编程解决方案。事实上,这是一个非常糟糕的做法。谢谢@EJP和@Markus!

答案 3 :(得分:1)

我不知道NIO中有任何方法可以做到这一点。一个人就可以继续阅读。

答案 4 :(得分:-3)

Bytebuffer.limit将返回缓冲区中可用的长度。

sctpChannel.receive(byteBuffer, null, null);
byteBuffer.flip();

 if (byteBuffer.limit() > 0) 
  {
         .....................//Do your work here
    //             read the data in a byte array
    byteBuffer.clear();  
  }