Как я могу избежать блокировки с помощью Java ServerSocket? - PullRequest
13 голосов
/ 23 января 2010

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

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

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

Приветствия

Ответы [ 3 ]

15 голосов
/ 23 января 2010

Вот небольшой пример, чтобы начать работу с 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.*;
8 голосов
/ 23 января 2010

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

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

8 голосов
/ 23 января 2010

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

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

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

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

...