Как избежать блокировки с помощью Java ServerSocket?

Im работает с прослушивателем сокета, который должен прослушивать 2 порта для двух типов данных (порт 80 и порт 81). Эти данные очень похожи, как в случае операций, выполняемых на данных, и просто отличаются, потому что они приходят n разных портов. Я пошел вперед и закодировал реализацию с использованием класса Java ServerSocket, только чтобы понять позже, что метод accept() класса ServerSocket является блоком, и моя реализация не может этого себе позволить. Так что теперь я думал о том, чтобы реализовать то же самое с помощью Java NIO, но после того, как я прошел через некоторые учебные пособия, я думаю, что я более смущен, чем то, как я начал. Было бы здорово, если бы кто-то здесь мог пройти меня через весь процесс, даже если бы он был в псевдокоде или техническом "что делать дальше". Это то, что я планирую достичь.

Слушайте, как навсегда на 2 портах, вызывая 2 похожих потока (не блокируя) Удаленное устройство из какого-либо сетевого местоположения соединяется, отправляет данные и затем отключается.

Я думаю, что если только знание того, как NIO можно использовать для настройки сервера для прослушивания на порту, говорит порт 80, на локальном хосте, все остальное довольно легко реализовать.

Приветствия

Ответы

Ответ 1

NIO вызывается, когда вам нужно масштабировать до нескольких тысяч одновременных подключений.

В противном случае я бы предложил использовать несколько потоков. Для каждого порта (и его соответствующего ServerSocket) создайте поток, который вызывает accept() в цикле. Эти вызовы будут блокироваться, но это нормально, потому что другие потоки запущены, заботясь о любых доступных задачах.

Когда новый Socket принят, создайте еще один поток, посвященный этому соединению. Это зависит от приложения, но обычно этот поток читается из сокета (операция блокировки) и выполняет запрошенную операцию, записывая результаты обратно в сокет.

Эта архитектура будет масштабироваться до многих сотен подключений на большинстве настольных платформ. И модель программирования довольно проста, если каждое соединение самодостаточно и независимо от других (это позволяет избежать проблем concurrency). Введение NIO обеспечит большую масштабируемость, но требует большой сложности.

Ответ 2

Вот небольшой пример, чтобы начать работу с NIO.

Это сервер, который прослушивает порты 80 и 81 и печатает все, что получено на стандартном выходе. Соединение будет закрыто после приема пакета, начинающегося с CLOSE; весь сервер отключается после получения пакета, начинающегося с QUIT. Отсутствие части отправки и обработки ошибок может быть немного лучше.: -)

public static void main() throws IOException {
    ByteBuffer buffer = ByteBuffer.allocate(1024);
    Selector selector = Selector.open();

    ServerSocketChannel server1 = ServerSocketChannel.open();
    server1.configureBlocking(false);
    server1.socket().bind(new InetSocketAddress(80));
    server1.register(selector, OP_ACCEPT);

    ServerSocketChannel server2 = ServerSocketChannel.open();
    server2.configureBlocking(false);
    server2.socket().bind(new InetSocketAddress(81));
    server2.register(selector, OP_ACCEPT);

    while (true) {
        selector.select();
        Iterator<SelectionKey> iter = selector.selectedKeys().iterator();
        while (iter.hasNext()) {
            SocketChannel client;
            SelectionKey key = iter.next();
            iter.remove();

            switch (key.readyOps()) {
                case OP_ACCEPT:
                    client = ((ServerSocketChannel) key.channel()).accept();
                    client.configureBlocking(false);
                    client.register(selector, OP_READ);
                    break;
                case OP_READ:
                    client = (SocketChannel) key.channel();
                    buffer.clear();
                    if (client.read(buffer) != -1) {
                        buffer.flip();
                        String line = new String(buffer.array(), buffer.position(), buffer.remaining());
                        System.out.println(line);
                        if (line.startsWith("CLOSE")) {
                            client.close();
                        } else if (line.startsWith("QUIT")) {
                            for (SelectionKey k : selector.keys()) {
                                k.cancel();
                                k.channel().close();
                            }
                            selector.close();
                            return;
                        }
                    } else {
                        key.cancel();
                    }
                    break;
                default:
                    System.out.println("unhandled " + key.readyOps());
                    break;
            }
        }
    }
}

Obs: поля SelectionKey (OP_ACCEPT...) статически импортируются:

import static java.nio.channels.SelectionKey.*;

Ответ 3

Многие фреймворки, такие как Apache MINA и Netty, были реализованы на основе Java NIO для повышения неблокирующего программирования ввода-вывода. Я настоятельно рекомендую им сделать вашу программу NIO радостью, а не кошмаром. Они соответствуют вашей проблеме.

Кроме того, попробуйте использовать эффективный протокол как в размере транспортного сообщения, так и при кодировании/декодировании (сериализации/десериализации). Буферы протокола Google - это надежное решение в этой области. Также посмотрите Kryo и KryoNet, Они могут быть полезны.