java.nio против нового потока для каждого сокета

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

Поскольку сокет IO блокируется. Я ищу решение для этого.

Может ли кто-нибудь сказать мне, что хорошо/плохо для каждого из двух решений?

  1. использовать java.nio
  2. новый отдельный поток для каждого подключенного клиента.

Спасибо


person Leon    schedule 02.02.2011    source источник
comment
Один поток для каждого клиента? Разве это не два потока на клиента, один для чтения и один для записи? В противном случае вы ничего не сможете сделать, пока поток заблокирован в read().   -  person Sergei Tachenov    schedule 02.02.2011
comment
@Сергей, блокировка записи является настоящим виновником, поскольку ее нельзя прервать, а в случае плохого ввода-вывода поток блокируется до тех пор, пока ОС не решит, что это круто. Поток чтения можно управлять с помощью SO_TIMEOUT. Так что это 2 потока на сокет.   -  person bestsss    schedule 03.02.2011
comment
@bestsss, разве SO_TIMEOUT не влияет на write()? И если есть плохой ввод-вывод, обычно потоку нечего делать, кроме как пытаться писать в любом случае.   -  person Sergei Tachenov    schedule 03.02.2011
comment
@Сергей, нет. Здесь нет операций прерывания-записи, даже если бы они были, вы все равно не можете знать, сколько было записано. Такой дизайн действительно отстой по сравнению с произвольными «плохими» клиентами, которые читают несколько байтов в секунду. Вы даже не можете остановить потоки во время записи. С потоками чтения вы даже можете использовать доступные (но это может снизить производительность для еще одного вызова JNI)   -  person bestsss    schedule 03.02.2011


Ответы (5)


В любом подходе нет ничего плохого. Если у вас ограниченное количество клиентов, второго варианта будет достаточно (и, возможно, даже процветать на основе многоядерной архитектуры), в противном случае может быть полезно позволить java.nio управлять вашими ресурсами.

См. этот вопрос по той же теме, а также этот другой пост или почему бы не рассмотреть эта запись в блоге, в которой аргументируется против использования java.nio для большинства сценариев.

person Johan Sjöberg    schedule 02.02.2011

Отдельные темы

  • Вы можете использовать простой API InputStream/OutputStream (или Reader/Writer) и обертывать потоки друг вокруг друга. В моем текущем проекте я использую стек

    • MessageFormatter, a custom formatting class
    • PrintWriter
    • OutputStreamWriter
    • DebugOutputStream (пользовательский класс, который создает копию для целей отладки)
    • DeflatorOutputStream (фактически пользовательский подкласс, который поддерживает сброс)
    • OutputStream SSLSocket

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

  • вам нужен новый поток (или даже пара потоков, в зависимости от архитектуры) для каждого соединения.

    (В моем проекте у меня есть MessageParser-Thread для каждого соединения, которое затем дает отдельные задания в ThreadPool, и эти задания затем могут писать в одно или несколько открытых соединений, а не только в то, которое их породило. Запись синхронизирована. , конечно.)

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

В случае недолговечных соединений вам на самом деле не нужен новый поток для каждого соединения, а только новый Runnable, выполняемый в ThreadPool, поскольку создание новых потоков занимает немного времени.

неблокирующий ввод-вывод

  • если у вас такая многослойная архитектура с множеством преобразований, то вам придется все это устраивать самостоятельно. Это возможно:

    • my MessageFormatter can write to a CharBuffer as well as to a Writer.
    • для форматирования Char-to-Byte используйте CharsetEncoder/CharsetDecoder (переносит данные между CharBuffer и ByteBuffer).
    • для сжатия я использовал классы-оболочки вокруг Deflater/Inflater, которые передают данные между двумя ByteBuffers.
    • для шифрования я использовал SSLEngine (по одному для каждого соединения), который также использует ByteBuffers для ввода и вывода.
    • Затем напишите в SocketChannel (или в другом направлении, прочитайте из него).

    Тем не менее, накладные расходы на управление доставляют немало хлопот, так как вы должны отслеживать, где находятся данные. (На самом деле у меня было два конвейера для каждого соединения и один или два потока, управляющих данными во всех конвейерах, между этим ожиданием в селекторе новых данных в сокетах (или нового пространства там, в исходящем случае). ( Фактическая обработка после синтаксического анализа сообщений все еще происходила в порожденных Runnables в ThreadPool.)

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

person Paŭlo Ebermann    schedule 02.02.2011

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

Другие части NIO — это общая неспособность большинства разработчиков кодировать и использовать конечные автоматы, поэтому они слишком сильно копируют буферы.

person bestsss    schedule 02.02.2011

NIO разумен для производительности и должен использовать не более #cores потоков, умноженных на коэффициент ввода-вывода вашего приложения, где коэффициент ввода-вывода — это процент вашего приложения, ожидающего завершения дискового ввода-вывода.

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

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

Если вы используете потоки, вы получаете следующие преимущества:

  • вы можете хранить информацию о сеансе в ThreadLocals.
  • вам не нужно управлять информацией о сеансе другими способами.
person Daniel    schedule 02.02.2011
comment
java.nio не обязательно более эффективен. Нитки дешевые, к тому же вы не принимаете во внимание SMT. - person Johan Sjöberg; 02.02.2011

Я попробовал Apache MINA, это действительно очень хорошо. Я настоятельно рекомендую это.

person Leon    schedule 03.02.2011