在ServerSocketChannel执行接受后,读取SocketChannel到达流末尾

时间:2015-02-11 13:45:49

标签: java sockets tcp channel socketchannel

我已粘贴下面的服务器端代码段。此服务器代码在正常情况下工作,但是,以下方案设法破坏代码。 服务器和客户端在同一台机器上。我使用了环回地址和实际的IP地址,没有区别。

方案

  1. 服务器在线,客户端发出请求(WritableByteChannel.write(ByteBuffer src)返回12字节,这是正确的大小,但研究显示只有12个字节被写入TCP缓冲区。)
  2. 服务器程序已关闭。客户注意到该频道在远程端关闭并在其自身关闭,它不会发出任何请求。
  3. 服务器再次在线。
  4. 客户端尝试发出请求但失败,因为频道已关闭/无效且无法重复使用(即使服务器再次联机)。
  5. 客户检查服务器的在线状态,获得正面结果,再次连接并立即发出其他请求。
  6. 服务器接受客户端(下面的代码),然后处理带有key.isReadable()条件的if子句,然后读取失败,这表示流结束。
  7. 创建SSCCE会过于复杂,如果缺少重要信息或者过于抽象,请发表评论,我会提供更多信息。

    问题

    如何在读取操作中新创建/接受的通道失败? 我错过了什么?我可以采取哪些措施来防止这种情况发生?

    我已经尝试过wireshark,但是我无法捕获指定TCP端口上的任何数据包,即使通信正在正常工作。


    问题/其他信息

    • 可以使用RawCap
    • 将数据包捕获到.pcap文件中
    • 问题是客户端检查服务器状态的方式。我已经添加了以下方法。

    代码段

    Snippet 1

        while (online)
        {
          if (selector.select(5000) == 0)
            continue;
    
          Iterator<SelectionKey> it = selector.selectedKeys().iterator();
          while (it.hasNext())
          {
            SelectionKey key = it.next();
            it.remove();
    
            if (key.isAcceptable())
            {
              log.log(Level.INFO, "Starting ACCEPT!");
              ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
              SocketChannel channel = serverSocketChannel.accept();
    
              channel.configureBlocking(false);
              channel.register(selector, SelectionKey.OP_READ);
    
              log.log(Level.INFO, "{0} connected to port {1}!",
                  new Object[] {channel.socket().getInetAddress().getHostAddress(), isa.getPort()});
            }
    
            boolean accepted = false;
            if (key.isReadable())
            {
              log.log(Level.INFO, "Starting READ!");
              SocketChannel channel = (SocketChannel) key.channel();
    
              bb.clear();
              bb.limit(Header.LENGTH);
              try
              {
                NioUtil.read(channel, bb); // server fails here!
              }
              catch (IOException e)
              {
                channel.close();
                throw e;
              }
              bb.flip();
    

    Snippet 2

      public static ByteBuffer read(ReadableByteChannel channel, ByteBuffer bb) throws IOException
      {
        while (bb.remaining() > 0)
        {
          int read = 0;
          try
          {
            read = channel.read(bb);
          }
          catch (IOException e)
          {
            log.log(Level.WARNING, "Error during blocking read!", e);
            throw e;
          }
    
          // this causes the problem... or indicates it
          if (read == -1)
          {
            log.log(Level.WARNING, "Error during blocking read! Reached end of stream!");
            throw new ClosedChannelException();
          }
        }
    
        return bb;
      }
    

    摘录3

      @Override
      public boolean isServerOnline()
      {
        String host = address.getProperty(PropertyKeys.SOCKET_SERVER_HOST);
        int port = Integer.parseInt(address.getProperty(PropertyKeys.SOCKET_SERVER_PORT));
    
        boolean _online = true;
        try
        {
          InetSocketAddress addr = new InetSocketAddress(InetAddress.getByName(host), port);
          SocketChannel _channel = SocketChannel.open();
          _channel.connect(addr);
          _channel.close();
        }
        catch (Exception e)
        {
          _online = false;
        }
        return _online;
      }
    

    解决方案

    问题不是检查的方法,如果服务可用/服务器在线。问题是EJP提到的第二点。

    预计服务器会发生特定输入,如果不满足条件,则会保持不一致状态。 我已经添加了一些后备措施,现在重新连接过程 - 包括检查方法 - 工作正常。

1 个答案:

答案 0 :(得分:3)

显然,客户端必须已关闭连接。这是read()返回-1的唯一方式。

注意:

  1. ClosedChannelException返回-1时,您正在抛出不合适的read()。当已经关闭频道并继续使用它时,NIO会抛出该异常。它与流的结束无关,不应该用于此。如果你必须扔东西,扔掉EOFException

  2. 你也不应该循环你的方式。您应该只在read()返回正数时循环。目前,在尝试读取可能永远不会到达的数据时,您正在使选择循环挨饿。