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

Posted

技术标签:

【中文标题】在 ServerSocketChannel 执行接受后,在 SocketChannel 上读取到达流尾【英文标题】:Read on SocketChannel reaches end-of-stream after ServerSocketChannel performs accept 【发布时间】:2015-04-11 22:00:51 【问题描述】:

我在下面粘贴了一个服务器端代码 sn-p。此服务器代码在正常情况下工作,但是,以下场景设法破坏代码。 服务器和客户端在同一台机器上。我用的是loopback地址,和实际的IP地址没有区别。

场景

    服务器在线,客户端发出请求(WritableByteChannel.write(ByteBuffer src) 返回 12 字节,这是正确的大小,但研究表明这仅意味着将 12 字节写入 TCP 缓冲区)。 服务器程序已关闭。客户端注意到通道在远程端关闭并在自己端关闭它,它不发出任何请求。 服务器再次上线。 客户端尝试发出请求,但失败,因为通道已关闭/无效且无法重用(即使服务器再次在线)。 客户端检查服务器的在线状态,得到肯定的结果,再次连接并立即发出另一个请求。 服务器接受客户端(代码如下),然后处理带有key.isReadable() 条件的 if 子句,但是然后读取失败,这表明流结束。

创建SSCCE 太复杂,如果缺少重要信息或太抽象,请发表评论,我会提供更多信息。

问题

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

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


问题/附加信息

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

代码 sn-ps

片段 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();

片段 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】:

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

注意事项:

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

    你也不应该像现在这样循环。您应该只在 read() 返回正数时循环。目前,您在尝试读取可能永远不会到达的数据时使选择循环处于饥饿状态。

【讨论】:

这意味着我应该彻底修改重新连接过程。下次我上班的时候,我会检查一些隐藏的close()s(……下周,因为我是学生)并接受你的回答。 感谢您的第二点。我知道你的意思,但这只是小握手的一部分(有 2 个小协议单元,它们是一体发送的),但主要通信使用非阻塞读取 @mike 没关系。这是错误的,需要修复。否则,当它确实破坏了其他人时,可能会使情况变得更糟。以here为例。 我在问题中添加了更多信息。问题是我实现检查服务器是否在线的方法的方式(它尝试创建连接并关闭它。如果没有抛出异常。服务器的服务可用)。删除它后,重新连接过程有效,但我不明白为什么。我设法让wireshark运行,但即使这样也没有增加任何洞察力。你有什么想法吗? 我认为问题与您的第二点有关!服务器处于不一致的状态。我会接受你的回答!谢谢!

以上是关于在 ServerSocketChannel 执行接受后,在 SocketChannel 上读取到达流尾的主要内容,如果未能解决你的问题,请参考以下文章

ServerSocketChannel

网络编程模型之NIO-ServerSocketChannel

网络编程模型之NIO-ServerSocketChannel

Java NIO系列教程 ServerSocketChannel

NIO学习之ServerSocketChannel和SocketChannel

Java NIO的深入研究 ServerSocketChannel