ServerSocketChannel перестает принимать через некоторое время на Linux - PullRequest
0 голосов
/ 04 августа 2020

Я заметил, что когда я запускаю свое приложение в ОС Linux, через некоторое время сервер просто перестает принимать клиентов. результат wirehark Это снимок экрана wirehark при попытке подключиться к серверу с моего хоста после того, как он перестал принимать. Как видите, первый запрос - [FIN, ACK], и похоже, что мой сервер не может с этим справиться.

Запуск сервера, открытие селектора, привязка, режим блокировки установлен на false и канал сервера регистрируется для OP_ACCEPT. (обычный стандартный материал) И это основной код сети:

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

Когда операция записи выполняется, клавиша выбора соответственно изменяется на OP_READ, а в случае частичной записи OP_READ | OP_WRITE.

Когда выбрасывается Exception, close () вызывается из объекта подключения, который в основном делает это, помимо прочего:

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

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

acceptOperation - метод делает следующее:

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 объекта подключения делает следующее:

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

Пожалуйста, обратите внимание, я не влияю на сетевой код клиента. При выполнении netstat -tulnp после того, как сервер внезапно перестал принимать, я вижу, что столбец Recv-Q увеличивается на 1 при каждой попытке подключения. Надеюсь, вы поможете мне, так как я понятия не имею, почему это происходит.

Извините за возможно длинный текст и спасибо в пересылке!

...