Pull to refresh

Comments 41

Классно. Читал предыдущие части, когда еще не был на Хабре.
А какие у вас проблемы возникли с Идеей? Я использую xsbt 0.10.1 для сборки и идея плагин к нему, а в идею установил sbt консоль — хотя и не предел мечтаний но работать вполне себе можно.
Да в общем то больших проблем нет… просто после нетбинса она как гента после убунты… )
В нетбинсе например создал проект, написал код, нажал «Скомпилить» и в папочке dist лежит jar файл, и тут же папочке libs аккуратно сложены все используемые библиотеки…
В идее чтобы получить jar файл надо артефакты какие-то создавать… В общем все руками создавать…
Да и вообще не логичная она какая-то для меня… Автообновляться не умеет, только сообщает что есть обновления и их надо руками скачивать и устанавливать…
Если доку по нетбинсу я вообще ни разу в жизни не открывал, там все как-то логично названо и по менюшкам раскидано. То в идее у меня сайт с докой постоянно был открыт. Те же артифакты ну нифига не логично названы… хотя это для меня все было не логично… может для других программеров это как раз и логично…
Понимаю.
По этой же причине использую maven для java и sbt для scala.
sbt не работает с fsc. А без него нет инкрементивной компиляции. А без нее и свет не мил на скале)
В идее в два счета настраивается fsc и тогда процесс билда действительно становиться гораздо приятнее)
Вы не правы. Fsc это всего лишь сервер, который держит внутри разогретые библотеки компилятора. Sbt точно так же не завершает процесс и держит эти библиотеки разогретыми, а также анализирует и при вызове compile перекомпилирует только изменённый файлы.
Не прав? В чем именно?
Я вроде и не говорил, что fsc что-то большее чем сервер. Однако он работает сам по себе. А вот sbt напротив — приходиться закрывать, чтобы перезапустить запущенное приложение. Понятно что он компилирует только измененные, однако не говорите ерунды — это совсем не тоже, что fsc.
И при чем тут разогретость? Этот термин больше применим к jvm машине. fsc же их просто держит в памяти и разогретость либ здесь ну никак не требуется. Достаточно того факта, что они уже находятся в памяти.

Поработайте с fsc и почувствуйте разницу. Sbt на сегодняшний день годиться разве что для автоматической подкачки недостающих либ.
А вот sbt напротив — приходиться закрывать, чтобы перезапустить запущенное приложение.

Зачем каждый раз завершать sbt, чтобы перезапустить приложение?

Под разогретостью я имею ввиду, что класслоадеру после первой компиляции в том же процессе не нужно бегать и загружать библотеки компилятора по новой.
Спасибо за статью. Скажите, а правильно ли я понял, что описанный протокол никак не защищен от читеров и что сменить его в будущих версиях программы будет проблематично?
Начинающим важно понять суть обмена данными. А защита это уже следующий этап.

Да и опытным разработчикам зацикливаться на суперзащите не стоит. Лучше тратить время на что-то другое, более полезное, иначе программа будет долго находится в стадии разработки. А универсального решения нет. Это постоянно борьба между защитой и способами ее обхода.
Я понимаю, что игра демонстрационная. Но на мой взгляд все же не помешало бы делать оговорку, что в реальных программах следовало бы добавить в протокол номер версии этого протокола, а также пару лишних проверок, чтобы игрок не мог прыгнуть с координаты (10,10) на (100500,100500).
Первое — хороший бонус, но для веб-игры не так критично, так как новые версии клиента можно разворачивать параллельно с разверткой новой версии сервера, и реальное количество неправильных подключений будет ничтожным.
Второе — такое обрабатывается в движке, а не в клиент-серверной части.
1. SocketChannel.read() в общем не гарантирует Вам, что будет прочитанно именно 16 байт. Хотя это и маловероятно, но зависит от низлежащих сетевых протоколов, и полагнаться на это не стоит. Теоретически сетевой пакет может быть раздроблен на два. Поэтому надо читать в цикле, пока ByteBuffer не будет заполнен.

2. Точно также SocketChannel.write() не гарантирует, что все будет записано — зависит от состояния буфера. В общем случае пакеты нужно писать циклически и удалять, когда remaining=0. Если буфер заполнен, селектор автоматически не будет возвращать writeable-ключи до тех пор, пока данные из буфера не прососутся по каналу.

3. Несмотря на то, что buffer — это val, он будет создаваться каждый раз, как мы что-то читаем. Лучше его создать наверху один раз, используя при этом ByteBuffer.allocateDirect()
… хабр обрезал коментарий…

4. Не совсем уверен, что корректно делать Select в одном треде, а обрабатывать SelectionKey в другом (ClientHandler). Мне кажется keys, они валидны только до следующей операции select, посему обрабатываться должны в том же треде.

5. Ваш сервер не обрабатывает отсоединение клиентов. Именно тут начнутся танцы с бубном. Наперед скажу, что не гарантируется, что для каждого отвалившегося клиента селектор вернет key.isValid()=false. Поэтому дисконнект нужно делать по IOException.
Кроме того, есть клиенты, которые при потере связи не пошлют TCP CLOSE-WAIT, и на сервере они останутся висеть бесконечно долго. Timeout возникнет только, если в буфере есть что послать клиенту. Поэтому чтобы такого не произошло, нужно пинговать клиента.
1,2,3,5 — Вы абсолютно правы. Но цель была показать общие принципы. Если сразу написать все как положено, то кода будет намного больше и новички могут просто не разобраться и будут тупо копипастить… Статьи, как я уже говорил, будут идти по принципу от простого к сложному и в следующих статьях будет оптимизация сервера. Добавится контроль сессий, поясню про Nagle алгоритм и т.д.

4 — Вполне корректная ситуация. keys в общем-то нужны только для того чтобы понять в каком канале и какие произошли события. А актор умеет накапливать сообщения. Поэтому проблем с обработкой в разных тредах не будет. Кстати довольно популярный SmartFoxServer именно так работает. Только у него не акторы (он на java) а 3 пула тредов фиксированного размера. На accept, read и write. На тесте у меня при 6000 сообщений в сек и 100 клиентах ни одно сообщение не потерялось…
У меня игровой сервер. Держит каждый день 2500 юзеров, в целом 100 сообщений в секунду. Пиковая нагрузка была 5000+, сервер выдерживал без проблем. Я делаю все I/O в одном треде. Только после того как буфер с сообщением будет полностью прочитан, я передаю его пулу обработчиков. Как показывает практика, сама работа с I/O некритична и не жрет процессорное время, т.к. занимается исключительно тупой переброской данных между JVM и низлежащей ОС. Пытался одно время распараллелить I/O, вешая каналы на разные треды и селекторы, но под линуксом обнаружился очень странный эффект: он не давал открыть больше одного селектора на процесс, хотя под виндами все работало.
JVM под линухом построен не так как под windows.
Например тот же NIO под линухом сделан через epoll а в винде через winsocket. Они по разному работают. Возможно почитав про epoll будет понятно в чем причина.
И кстати… можно по подробнее узнать про ваш сервер. Просто 100 сообщений на 2500 юзеров маловато будет… игра пошаговая наверно?
www.buho21.com
Сообщений конечно же не 100, это я ошибся. 700 партий где-то по секунде-полторы на ход, то есть 500-700 сообщений в секунду. Обратный треффик больше: сообщение приходится реплицировать нескольким клиентам. В случае общего чата каждое сообщение или изменение графа отправляется всем пользователям.
Сервер разделен на 4 зала. Коннектор — отдельный процесс, который обслуживает все соединения с клиентами во всех игровых залах. Своего рода диспетчер пакетов и соединений. Интерфейс с серверами ввиде очереди сообщений через обычный Socket с блокирующим I/O. Сейчас, при 2000 пользователях он показывается в top с 7% CPU. Основное время тратится на шифровку/дешифровку пакетов.
Если вы читали статьи, то я везде говорю, что в реальных проектах лучше использовать проверенные временем решения. Для написания своих велосипедов должны быть веские причины.

Но есть один ньюанс… когда пытаешься нарисовать свой маленький велосипедик происходит процесс просветления и понимания… а это бесценно… ибо после этого абсолютно сознательно приходишь к использованию готовых библиотек типа netty.
Вобщем-то это был вопрос к всем комментаторам выше. Я чётко понимаю, что пьеса не об этом :)

Дело в том, что товарищ Throwable так чётко обсуждает NIO, как будто про Netty слыхом не слыхивал.
На самом деле есть свои «за», но очень много «против». Но это уже тема отдельного разговора — что лучше использовать: готовые решения или свои собственные. Недостаток подобных библиотек в том, что они пытаются быть универсальными и покрыть как можно большее число потенциальных задач. Тогда как реальная задача требует всего одного-единственного решения, зато хорошо контролируемого и легко адаптируемого.

Навскидку я не уверен, что Netty мне обеспечит:
— контроль длины и валидности входящих сообщений
— лимитирование коннекшнов с одного IP (против DoS)
— blacklist IP и сеток
— heartbeat, и автоматическое определение времени ping-а
— flooding-контроль
— remote-контроль валидности клиентского кода
— отсоединение неактивных клиентов
— если внезапно понадобится еще что — допишу

Другой недостаток библиотек — это синдром «черного ящика», когда не ясно как чужой код ведет себя при определенных условиях. Требует экспериментальных проверок, а иногда уже на production выползают очень интересные «фичи». Ну и естественно зависимость от производителя…

И все-таки причиной создания «велосипеда» было то, что он был написан, когда появился NIO (1.4.1). Netty и подобных фреймворков и в помине не было. Раньше были танцы с бубном, когда надо было обойти blocking io.

P.S. Про netty кстати не знал, спасибо. Хотя уверен, это не единственное решение (напр. Apache Camel).
Многие вещи есть из коробки. Также входящий и исходящий каналы подключены к так называемым пайплайнам каждый их которых содержит цепочку обработчиков. Обработчик может быть сколь угодно сложным. Никто не мешает на нужном уровне вставить нужный контроль чего угодно :)
Спасибо, не знал про эту вещь.
Спасибо, поправлю. Только о таких очепятках принято в личные сообщения писать, что бы не захламлять каменты.
Простите, не сразу понял, что это опечатка.
(Не силён в Scala)
Подскажите, в addPlayerMsg мы защитили session с помощью lock.
Но, как мне кажется, в GameServer:Loop тоже нужны:
1)Синхронизация: sessions += channel -> actor
2)Барьер: val actor = sessions.get(channel).get.asInstanceOf[ClientHandler] — иначе поток может не увидеть изменений в sessions сделаных другим потоком.

Я прав? или в Scala есть какие-то дополнительные гарантии?
Вы правы. Пофиксю в исходниках. А Scala сама по себе не дает гарантий. Она дает лишь инструменты для решения.
Про гарантии я имел ввиду — что возможно трансляция из scala в байткод jvm устроена отличным от траснялции java->байткод и создаёт какие-то дополнительные отношения «предшествования», о которых могут знать те, кто пишет на scala.
Спасибо, буду ждать статью про разные методы синхронизации в играх: что такое интерполяция, эталонные состояния, etc.
в смысле работающую версию игры описываемой, наверное, запущенную где-то :)
Игра будет. Но не в таком же виде ее выкладывать?
Как только добавлю контроль игроков (сессий) и возможность стрелять — сразу выложу на своем сервере.
Там она и будет развиваться.
спасибо за пост
вставьте в 1ых двух статьях ссылки на эту часть
О как!
Прикольно увидеть скриншот из своей игры на хабре, хоть и не понятно зачем он тут :)
Просто искал в инете картинку с танками. :)
Если низя ее тут, то уберу.
Нет, нет, никаких претензий, конечно можно :)
Sign up to leave a comment.

Articles