0

I noticed that, when I run my application on a Linux OS, after a while, the server just stops accepting clients. wireshark result This is a screenshot of wireshark when trying to connect to the Server from my host, after it stopped accepting. As you can see the first request is [FIN,ACK] and it seems like that my server can't handle that.

Starting up the server, opens the selector, binds, blocking mode is set to false and the server channel registers for OP_ACCEPT. (the usual standard stuff) And this is the core code of the networking:

private void update(int timeout) throws IOException {

    updateThread = Thread.currentThread();

    synchronized (updateLock) {
    }

    long startTime = System.currentTimeMillis();
    int select = 0;

    if (timeout > 0) {
        select = selector.select(timeout);
    }
    else {
        select = selector.selectNow();
    }

    if (select == 0) {
        ++emptySelects;
        if (emptySelects == 100) {

            emptySelects = 0;
            long elapsedTime = System.currentTimeMillis() - startTime;

            try {
                if (elapsedTime < 25)
                    Thread.sleep(25 - elapsedTime);
            } catch (InterruptedException ie) {
                log.error(ie.getMessage());
            }
        }
    } else {

        emptySelects = 0;
        Set<SelectionKey> keys = selector.selectedKeys();

        synchronized (keys) {
            for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext();) {

                // keepAlive();
                SelectionKey selectionKey = iter.next();
                iter.remove();
                Connection fromConnection = (Connection) selectionKey.attachment();

                try {
                    if (!selectionKey.isValid())
                        continue;

                    if (fromConnection != null) {
                        if(selectionKey.isReadable()) {
                            try {
                                while (true) {

                                    Packet packet = fromConnection.getTcpConnection().readPacket();
                                    if(packet == null)
                                        break;
                                     fromConnection.notifyReceived(packet);
                                }
                            } catch (IOException ioe) {
                                fromConnection.notifyException(ioe);
                                fromConnection.close();
                            }
                        } else if (selectionKey.isWritable()) {

                            try {
                                fromConnection.getTcpConnection().writeOperation();
                            } catch (IOException ioe) {
                                fromConnection.notifyException(ioe);
                                fromConnection.close();
                            }
                        }
                    }
                    else {
                        if (selectionKey.isAcceptable()) {
                            ServerSocketChannel serverSocketChannel = this.serverChannel;

                            if (serverSocketChannel != null) {
                                try {
                                    SocketChannel socketChannel = serverSocketChannel.accept();
                                    if (socketChannel != null)
                                        acceptOperation(socketChannel);
                                }
                                catch (IOException ioe) {
                                    log.error(ioe.getMessage());
                                }
                            }
                        }
                        else {
                            selectionKey.channel().close();
                        }
                    }
                }
                catch (CancelledKeyException cke) {
                    if(fromConnection != null) {
                        fromConnection.notifyException(cke);
                        fromConnection.close();
                    }
                    else {
                        selectionKey.channel().close();
                    }
                }
            }
        }
    }

    long time = System.currentTimeMillis();
    List<Connection> connections = this.connections;

    for (Connection connection : connections) {
        if (connection.getTcpConnection().isTimedOut(time)) {
            connection.close();
        }
        /* else {

            if (connection.getTcpConnection().needsKeepAlive(time))
                connection.sendTCP(new Packet((char) 0x0FA3));
        }*/
        if (connection.isIdle())
            connection.notifyIdle();
    }
}

When a write Operation is done, the selection key is accordingly changed to OP_READ and in case of a partial write OP_READ | OP_WRITE.

When Exception is thrown, close() is called from the connection object, which basically does this among other things:

try {
    if(socketChannel != null) {
        socketChannel.close();
        socketChannel = null;

        if(selectionKey != null)
            selectionKey.selector().wakeup();
    }
} catch (IOException ioe) {
    // empty..
}

acceptOperation - method does this:

private void acceptOperation(SocketChannel socketChannel) {
    Connection connection = newConnection();
    connection.initialize(4096, 4096);
    connection.setServer(this);

    try {
        SelectionKey selectionKey = connection.getTcpConnection().accept(selector, socketChannel);
        selectionKey.attach(connection);

        int id = nextConnectionID++;
        if(nextConnectionID == -1)
            nextConnectionID = 1;

        connection.setId(id);
        connection.setConnected(true);
        connection.addConnectionListener(dispatchListener);

        addConnection(connection);
        connection.notifyConnected();
    } catch (IOException ioe) {
        log.error(ioe.getMessage());
        connection.close();
    }
}

accept-Method of the connection object does this:

try {
    this.socketChannel = socketChannel;
    socketChannel.configureBlocking(false);
    Socket socket = socketChannel.socket();
    socket.setTcpNoDelay(true);
    socket.setKeepAlive(true);

    selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);

    lastReadTime = lastWriteTime = System.currentTimeMillis();

    return selectionKey;
} catch (IOException ioe) {
    close();
    throw ioe;
}

Pleae note, I have no impact on the client networking code.
When doing netstat -tulnp after the server suddenly stopped accepting, I can see that Recv-Q column increments by 1 for every connect try.
I hope you can help me out, since I have no idea why it is happening.

Sorry for the maybe long text and thanks in forward!

Stefan
  • 67
  • 9
  • 1
    There is no need to wakeup the selector when closing a registered channel. Your select timeout logic is needlessly elaborate. Just use `select(timeout)` and get rid of the `emptySelect` logic and the sleep. – user207421 Oct 29 '20 at 23:51
  • are you closing the connections only in case of exceptions? no finally blocks? no try with resources? – Sergey Benner Feb 09 '21 at 14:09
  • In case of exception, disconnect or when the client closes the connection (read returns -1) Not in finally blocks because all the places where a close should be called is placed. I don't think calling it in a finally block will change the behaviour. – Stefan Feb 09 '21 at 14:17

0 Answers0