简体   繁体   中英

Read on SocketChannel reaches end-of-stream after ServerSocketChannel performs accept

I've pasted a server side code snippet below. This server code works under normal circumstances, however, the following scenario manages to break the code. Server and client are on the same machine. I used the loopback address, and the actual IP address, it makes no difference.

Scenario

  1. Server is online, Client makes request ( WritableByteChannel.write(ByteBuffer src) returns 12 byte, which is the correct size, but as research revealed that only means the 12 bytes are written to the TCP buffer).
  2. Server program is turned off. Client notices that the channel is closed on the remote side and closes it on its own side, it doesn't make any requests.
  3. Server is online again.
  4. Client tries to make a request, but fails, because the channel is closed/invalid and can't be reused (even though server is online again).
  5. Client checks server's online status , gets positive result, connects again and immediately makes another request.
  6. Server accepts client (code below), after that processes the if clause with the key.isReadable() condition, but then fails on the read, which indicates end-of-stream.

It would be too complex to create an SSCCE , please comment if important information is missing or this is too abstract and I'll provide further information.

Question

How can a freshly created/accepted channel fail on the read operation? What am I missing? What steps can I undertake to prevent this?

I already tried wireshark, but I can't capture any packets on the designated TCP port, even if the communication is acutally working.


Problem/Additional Info

  • It's possible to capture packets into .pcap file with RawCap
  • The problem was the way the client checked the server status. I've added the method below.

Code snippets

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

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

Solution

The problem was not the method that checked, if the service is available/the server is online. The problem was the second point EJP mentioned.

Specific input was expected from the server and it was left in an inconsistent state if that conditions were not met. I've added some fallback measures, now the the reconnect process - including the check method - is working fine.

Clearly the client must have closed the connection. That's the only way read() returns -1.

Notes:

  1. You're throwing the inappropriate ClosedChannelException when read() returns -1. That exception is thrown by NIO when you've already closed the channel and continue to use it. It has nothing to do with end of stream, and shouldn't be used for that. If you must throw something, throw EOFException .

  2. You also shouldn't loop the way you are. You should only loop while read() is returning a positive number. At present you are starving the select loop while trying to read data that may never arrive.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM