5

我想知道在 Java NIO Socket 中是否有可用的字节被读取。在 C/C++ 中,可以使用以下方法来完成:

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

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

  return (int) readable;
}

可以用Java NIO做同样的操作吗(使用SocketChannelSelectorSocket等)?

4

5 回答 5

7

做不到。形式上讲,存在一个 API via SocketChannel.socket().getInputStream().available(),但是getInputStream()在非阻塞通道上操作将失败,因此不能在您的情况下使用它。

编辑:现在您已经对我们有所启发,您需要的东西在 Java 中仍然不存在,但是由于您处于非阻塞模式,所以一点也不重要。只需读入一个至少与您的套接字接收缓冲区一样大的缓冲区:读取的字节数是可以在没有阻塞的情况下读取的字节数,这正是您刚刚所做的。

于 2012-10-03T10:43:37.887 回答
6

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

  1. select()- 阻塞直到事件可用(如果您的程序没有其他事情可做,请使用)
  2. select(long timeout)- 阻塞,直到有事件可用或发生超时(如果你想节省 CPU 负载并增加网络响应能力,如果你的程序慢一点没关系,则使用)
  3. selectNow()- 立即返回(如果您的程序需要继续运行,请使用)

然后,您使用该selectedKeys方法获取所有等待事件的连接的列表(例如,准备好读取数据)。然后只需遍历此列表并仅从带有数据的连接中读取数据,并忽略不在列表中的其他连接,因为它们没有可用的数据。

这将允许您在不阻塞的情况下检查是否有数据可用(列表中是否存在连接),但不能检查有多少数据可用。但是,如果您随后对可用数据的连接进行读取,它将立即返回而不会阻塞并返回尽可能多的可用数据,前提是您为其提供了足够大的缓冲区。然后,您可以选择在某处缓冲这些数据并使缓冲区中的数据量可用,但有些事情告诉我,您实际上并不需要它,只想继续处理数据。

于 2012-10-06T00:36:36.527 回答
2

我已经阅读了大量的文档、论坛,当然还有你的每一个回复。我已经开发并测试了一个解决方案。这不是最好的,但它是我最好的近似值。

我使用Selector的selectNow()方法。给定一个已注册SocketChannel的命名,我可以使用此方法来了解关联的Socket是否具有可读字节:Selectorselector

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;
}

这是我发现知道套接字是否具有可读字节而不读取字节的唯一方法。您知道这样做的更好方法或这样做的缺点吗?

编辑:

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

EDIT2 之二:

测试这段代码:

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!

于 2012-10-08T14:12:28.900 回答
1

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

于 2012-09-27T16:28:33.480 回答
-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();  
  }
于 2012-10-03T10:22:42.807 回答