Почему стоит использовать неблокирующие или Blocking-сокеты? [закрыто]
Проблема: Неясный вопрос о потоках и сокетах в MMORPG на StackOverflow
Я столкнулся с вопросом относительно проектирования многопользовательского MMORPG-сервера и нуждаюсь в помощи. Вот что меня интересует:
- Какой подход является лучшим? Использовать отдельный поток для каждого клиента или создать один поток, который будет обрабатывать все неблокирующие сокеты?
- Каковы преимущества и недостатки каждого из этих подходов?
Понимаю, что вопрос может показаться неопределённым, но я надеюсь, что вы сможете прояснить его для меня, чтобы я мог понять, как лучше организовать серверную часть MMORPG. Спасибо за помощь!
2 ответ(ов)
Ваш вопрос заслуживает более детального обсуждения, но вот краткий ответ:
- Использование блокирующих сокетов означает, что в любом потоке может быть активен только один сокет в любой момент времени (из-за блокировки в ожидании активности).
- Работа с блокирующими сокетами, как правило, проще, чем с неблокирующими (асинхронное программирование зачастую оказывается более сложным).
- Вы могли бы создать один поток на сокет, как вы и упомянули, но потоки имеют накладные расходы и очень неэффективны по сравнению с неблокирующими решениями.
- С неблокирующими сокетами вы сможете обрабатывать гораздо больший объем клиентов: масштабирование может достигать сотен тысяч в одном процессе, но код становится немного более сложным.
При использовании неблокирующих сокетов (в Windows) у вас есть несколько вариантов:
- опрос (polling)
- на основе событий (event-based)
- перекрытый ввод-вывод (overlapped I/O)
Перекрытый ввод-вывод обеспечит вам наилучшее качество работы (тысячи сокетов на процесс) в ущерб более сложной модели для понимания и правильной реализации.
В конечном счете, дело сводится к производительности против сложности программирования.
ПРИМЕЧАНИЕ
Вот лучшее объяснение, почему использование модели потоков/сокетов — плохая идея:
В Windows создание большого числа потоков крайне неэффективно, поскольку планировщик не может должным образом определить, какие потоки должны получать процессорное время, а какие нет. Это, в сочетании с накладными расходами на память для каждого потока, означает, что вы исчерпаете память (из-за пространства стека) и процессорные циклы (из-за накладных расходов на управление потоками) на уровне операционной системы задолго до того, как у вас возникнут проблемы с количеством обрабатываемых сокетных подключений.
Я официально заявляю, что для почти всего, кроме игрушечных программ, вы должны по умолчанию использовать неблокирующие сокеты.
Блокирующие сокеты создают серьезные проблемы: если машина на другом конце (или любая часть вашего соединения с ней) недоступна во время блокирующего вызова, ваш код будет заблокирован до тех пор, пока не произойдет тайм-аут стека IP, который в типичном случае составляет около 2 минут — это совершенно неприемлемо для большинства задач. Единственный способ1 прервать блокирующий вызов — завершить поток, который его выполнил, но завершение потока само по себе почти всегда неприемлемо, так как это практически невозможно сделать безопасно и вернуть ресурсы, которые он выделил. Неблокирующие сокеты позволяют легко прерывать вызов, когда это необходимо, без каких-либо действий с потоком, который его инициировал.
Можно заставить блокирующие сокеты работать более-менее приемлемо если вы используете многопроцессную модель. Здесь вы просто создаете совершенно новый процесс для каждого соединения. Этот процесс использует блокирующий сокет, и когда/если что-то идет не так, вы просто убиваете весь процесс. Операционная система умеет очищать ресурсы процесса, поэтому проблемы с очисткой не возникает. Тем не менее, это может иметь и другие потенциальные проблемы: 1) вам практически нужен монитор процессов для завершения процессов при необходимости, и 2) создание нового процесса обычно стоит значительно дороже, чем просто создание сокета. Тем не менее, это может быть жизнеспособным вариантом, особенно если:
- Вы имеете дело с небольшим количеством соединений одновременно
- Вы, как правило, проводите обширную обработку для каждого соединения
- Вы работаете только с локальными хостами, поэтому ваше соединение с ними быстрое и надежное
- Вас больше беспокоит оптимизация разработки, чем выполнение
^1. Технически это не единственный способ, но большинство альтернатив относительно неудобны — если быть более конкретным, то к тому времени, как вы добавите код для определения проблемы и затем исправите её, вы, вероятно, сделаете больше работы, чем если просто используете неблокирующий сокет.
Как удалить элемент из std::vector<> по индексу?
`unsigned int` против `size_t`: когда и что использовать?
Какова разница между "new", "malloc" и "calloc" в C++?
Что означает && в конце сигнатуры функции (после закрывающей скобки)?
Инициализация std::string из char* без копирования