【问题标题】:Java: Proper Network IO handling?Java:正确的网络 IO 处理?
【发布时间】:2010-03-01 03:33:22
【问题描述】:

我遇到的问题是,当我使用 InputStream 读取字节时,它会阻塞,直到连接完成。例如:

        InputStream is = socket.getInputStream();
        byte[] buffer = new byte[20000];
        while (is.read(buffer) != -1) {
            System.out.println("reading");
        }
        System.out.println("socket read");

“socket read”在实际接收到 FYN 数据包之前不会打印出来,从而关闭连接。在不阻塞和等待连接断开的情况下接收所有字节的正确方法是什么?

【问题讨论】:

    标签: java network-programming io


    【解决方案1】:

    看看有non-blocking IO支持的java.nio

    【讨论】:

    • 在这一点上这听起来像是一个主要的矫枉过正。我认为在考虑非阻塞io之前,他应该先掌握阻塞io。
    【解决方案2】:

    Reading until you get -1 意味着你想读到 EOS。如果您不想在 EOS 之前阅读,请不要循环到 -1:尽早停止。问题是“什么时候?”

    如果您想阅读完整的“消息”而不是更多,则必须以阅读者可以找到其结尾的方式发送消息:例如,类型-长度-值协议,或者更简单地说是大小每条消息前的单词,或 XML 等自描述协议。

    【讨论】:

      【解决方案3】:

      对于传统套接字,关键是您通常确实希望它们阻塞:当您从逻辑上不希望程序阻塞时,您所做的是将您的读/写代码放在另一个线程中,以便单独的读/写线程阻塞,而不是整个程序。

      如果做不到这一点,您可以使用 available() 方法在读取之前查看是否有任何可用的输入。但是你需要注意不要通过不断调用available()来循环烧毁CPU。

      编辑:如果问题是你很乐意阻塞直到 bytes 到达,但直到连接断开(这就是正在发生的事情),那么你需要做另一端的客户端在发送字节后在其输出流上调用 flush()。

      【讨论】:

      • 是的,关于最后一点烧 CPU 是正确的。有没有一种优化的方式不会对 CPU 周期或上下文切换造成伤害(Thread.sleep(long) + 轮询会导致性能下降吗?
      • 调用 Thread.sleep() 会导致实际 I/O 的一些性能损失(例如,您可以休眠 500 毫秒,但数据在 100 毫秒后准备就绪,并且您没有收到它另外400)。在上下文切换方面,每秒额外的几个 C/S 并不重要。但我仍然不清楚:你为什么不能让套接字阻塞(如果需要,在它自己的线程中)?
      • 对不起,我确实希望它阻止第一次循环,读取字节,我的问题是我对 java IO 不熟悉,因为我再次调用 read,然后我不得不等待EOS。
      【解决方案4】:

      试试这个:

              InputStream is = socket.getInputStream();
              byte[] buffer = new byte[20000];
              int bytesRead;
              do {
                  System.out.println("reading");
                  bytesRead = is.read(buffer);
              }
              while (is.available() > 0 && bytesRead != -1);
              System.out.println("socket read");
      

      更多信息:https://docs.oracle.com/javase/1.5.0/docs/api/java/io/InputStream.html#available()

      【讨论】:

      • 当您发现它并不总是有效时,请重新访问我的答案。
      • 不应该在is.read() 调用之前调用is.available(),考虑到如果第一次读取时没有可用字节,is.read() 可能会阻塞?
      • 我确实希望它在第一轮可用字节之前阻塞。我发现这是最优化的,因为它不消耗 CPU 资源并且不轮询。此外,这段代码对我来说工作得很好,今天早上测试了它。如果我遇到问题,我会更新。
      【解决方案5】:

      示例取自exampledepot on java.nio

      // Create a direct buffer to get bytes from socket.
      // Direct buffers should be long-lived and be reused as much as possible.
      ByteBuffer buf = ByteBuffer.allocateDirect(1024);
      
      try {
          // Clear the buffer and read bytes from socket
          buf.clear();
          int numBytesRead = socketChannel.read(buf);
      
          if (numBytesRead == -1) {
              // No more bytes can be read from the channel
              socketChannel.close();
          } else {
              // To read the bytes, flip the buffer
              buf.flip();
      
              // Read the bytes from the buffer ...;
              // see Getting Bytes from a ByteBuffer
          }
      } catch (IOException e) {
          // Connection may have been closed
      }
      

      一定要了解缓冲区翻转,因为它会引起很多麻烦。基本上,您必须反转缓冲区才能从中读取。如果要重用该缓冲区以使套接字写入其中,则必须再次翻转它。但是 clear() 会重置缓冲区方向。

      【讨论】:

      • 跟进:有没有办法阻止这个?我想阻塞直到它连接,但是使用 while(!sChannel.finishedConnect()) 会花费很多 cpu 周期。
      • 在 OP_CONNECT 上选择并在获得它时检查 finishConnect(),否则只需在阻塞模式下连接。
      【解决方案6】:

      代码可能没有按照您的想法执行。 read(buffer) 返回它读取的字节数,换句话说:无论如何都不能保证填满缓冲区。 填满整个数组的代码见 DataInputStream.readFully():

      或者你可以使用这个函数(基于 DataInputStream.readFully()):

      public final void readFully(InputStream in, byte b[]) throws IOException
      {
          readFully(in, b, 0, b.length);
      }
      
      public final void readFully(InputStream in, byte b[], int off, int len) throws IOException
      {
          if (len < 0) throw new IndexOutOfBoundsException();
          int n = 0;
          while (n < len)
          {
              int count = in.read(b, off + n, len - n);
              if (count < 0) throw new EOFException();
              n += count;
          }
      }
      

      您的代码如下所示:

      InputStream is = socket.getInputStream();
      byte[] buffer = new byte[20000];
      readFully(is, buffer);
      System.out.println("socket read");
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-06-11
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多