Как написать свое ядро для сервера майнкрафт

  1. Как написать своё ядро.
    [​IMG]

    Приветствую вас, вы читаете статью о том, где сумасшедший вроде меня будет объяснять вам как написать своё ядро для серверов майнкрафт. Подразумевается, что эта статья — объяснение документации простыми словами.

    Начало.
    Для начала, нам нужно определиться с языком и некоторыми другими вещами. В этой статье мы будем использовать Python 3, так как он более лёгкий для новичков, а ещё потому, что мне так захотелось. Так же стоит определиться где мы будем писать, а именно в каком IDE. Нет, вы конечно можете использовать удобные Nano и Vim, но не все люди любят терминалы. Я могу предложить вам несколько IDE.

    1. Visual Studio Code. Поделка Microsoft. Присутствует версия DEB.
    2. PyCharm. Поделка JetBrains. Как по мне его минус — написан он на java. Отсутствует DEB.
    3. Sublime Text. Поделка не знаю кого. Это вообще не IDE. DEB присутствует.

    Когда мы выбрали IDE, можно приступать к созданию проекта. Лично я буду пользоваться Visual Studio, так как он мне удобнее. Надо бы создать проект. Как делать это, показывать не буду — Вам нечего делать тут если вы не знаете как создавать проект в своей IDE. После того как у нас есть пустая папка, нам надо создать скелет проекта. Первое что мы создадим будет файлом requirements.txt. Он нужен для удобной установки зависимостей. В него мы будем писать названия всех библиотек которые используем. После создания сразу запишем туда строчки.

    Это наши две зависимости. Поясню, quarry — библиотека для удобной работы с протоколом майнкрафт, а loguru это просто рай для программиста, она позволяет работать с логами очень удобно. Далее мы создадим виртуальное окружение которое нужно, чтобы не засорять систему разными библиотеками нужными для проекта. Сделать это легко и просто можно командой:

    $ virtualenv venv -p python3

    У нас создастся как бы «отдельная система» с установленным питоном 3-ей версии. Теперь надо войти в это окружение. Для этого используем команду:

    $ source venv/bin/activate

    Теперь надо бы установить все наши зависимости, сделаем это через PiP:

    $ pip install -r requirements.txt

    Нам осталось совсем немного. Создадим лишь файл оболочки в корневой папке для запуска проекта. В моём случае это будет start.sh. Он пока остаётся пустым, так как самой «начинки» у нас ещё нету.

    Библиотека Quarry
    Как было упомянуто ранее, для работы с протоколом мы будем использовать библиотеку quarry. Более 90% работы она будет выполнять за нас. Первым делом, давайте создадим главную папку, пусть она называется так же как и проект, только с маленькой буквы. В ней мы создадим файл main.py. Он будет служить стартовой точкой для программы, будет разбирать аргументы и запускать сам сервер. Давайте напишем в него примерно такой код:

    from loguru import logger # Логирование
    import argparse # Аргументы
    
    def main():
      parser = argparse.ArgumentParser()
      parser.add_argument("-m", "--motd", default="Test!", type=str, help="Server motd")
      args = parser.parse_args()
      logger.add("latest.log", rotation="5 MB")
      logger.info("All works!")
    
    main()

    Этот короткий код-заготовка при запуске вызовет главную функцию, распарсит аргументы и положит их в args. После этого создастя файл latest.log который будет пересоздаваться каждый раз когда будет достигать 5 MB веса и выведется сообщение «All works!». Окей, давайте теперь напишем основную логику. Создадим файл с любым названием. Теперь нам нужно создать для него некоторую заготовку. Для этого импортируем некоторые классы.

    from twisted.internet import reactor
    from quarry.net.server import ServerProtocol, ServerFactory

    Далее, нам нужно создать классы наследники.

    class ExampleProtocol(ServerProtocol):
    
      def player_joined(self): # Игрок зашёл
        ServerProtocol.player_joined(self) # Базовые действия при заходе игрока
        self.close("You disconnected because you {0}".format(self.display_name)) # Кикаем игрока по причине "You disconnected because you [имя игрока]"
    
    class ExampleFactory(ServerFactory):
      protocol = ExampleProtocol # Устанавливаем протокол
      motd = "My shiny test motd!" # Устанавливаем мотд для сервера

    На этом и подошла к концу первая часть. Вторую я напишу, если эта часть окажется хоть кому то полезной и/или интересной. Хочу отметить, что созданный проект не дописан и не должен работать.

    Последнее редактирование: 4 окт 2020
  2. Быстрая раскрутка сервера Minecraft

  3. Это ответ тем, кто говорил, что мне стоит написать туториал о чём то оригинальном.

    Последнее редактирование: 4 окт 2020
  4. Как написать своё ядро. Часть II.
    [​IMG]

    Приветствую вас, вы читаете продолжение статьи о том, где сумасшедший вроде меня наконец изъяснится до конца вам о том, как написать ядро для сервера майнкрафт. Если вы не видели 1-ую статью, советую прочитать сначала её. Цель данной статьи — показать, насколько легко написать своё ядро.

    Продолжение
    Вспомним про наш уже существующий проект и допишем его. Определимся с целью нашего ядра, давайте сплагиатим небольшую часть из моего проекта и будет генерировать для каждого игрока отдельный токен. Входим в виртуальное окружение и открываем все нужные файлы в нашем IDE.

    Приступим к написанию кода. Сначала напишем логику самого сервера. Импортируем библиотеку secrets(она стандартная для python3 и заносить в requirements.txt её не надо)

    Данная библиотека поможет нам в генерации ключа игрока. Добавим пару строчек перед этим.

    self.close("You disconnected because you {0}".format(self.display_name))

    Вот, что мы допишем.

    token = secrets.hex(5) # 5 - Длина ключа, может быть любой

    Данный код сгенерирует HEX-последовательность и положит её в переменную token. Далее нам надо немного подправить файл main.py где обрабатываются аргументы. Добавим в него несколько аргументов. Вот что у нас получится.

    from loguru import logger # Логирование
    import argparse # Аргументы
    
    def main():
      parser = argparse.ArgumentParser()
      parser.add_argument("-a", "--api", required=True type=str, help="HTTPS API to send requests")
      parser.add_argument("-A", "--api-token", required=True, type=str, help="API  TOKEN for authorization.")
      args = parser.parse_args()
      logger.add("latest.log", rotation="5 MB")
      logger.info("All works!")
    
    main()

    Теперь для запуска нашего сервера, обязательно надо будет указать адрес сайта, на который будет отправляться POST запрос с данными и токен, чтобы сайт мог определить, что запрос именно от нашего сервера. В основном файле нам осталось только отправить запрос. Но сначала мы доделаем файл main.py. Допишем в наш основной файл функцию(не относящуюся к какому либо классу).

    def main(args):
      f = ExampleFactory()
      f.api = args.api
      f.token = args.api_token
      f.listen('localhost', 25565)
      reactor.run()

    Она нужна для того, чтобы мы могли вызвать её из main.py передав аргументы как аргумент. Разберём её подробнее, в ней мы создаём экземпляр фабрики и устанавливаем несколько переменных у этого экземпляра. Это нужно для того, чтобы мы могли получить эти переменные в классе-протоколе. Дальше, мы заставляем фабрику слушать ‘localhost’ на порте 25565(стандартный порт майнкрафт серверов). Теперь мы можем со спокойной совестью импортировать эту функцию в main.py и вызвать её. Наш сервер уже способен запуститься и генерировать токен на каждого игрока. Давайте кстати изменим причину кика в основном файле на причину содержащую токен. Например «Your token is: [token]». Остаётся только отправить запрос, это сделаем при помощи стандартной библиотеки request. Импортируем её.Определим несколько переменных отвечающих за данные запроса и его хёдеры. Назовём их к примеру headers и data. Это всё должно находиться перед закрытием соединения. Вот что мы допишем.

    headers = {"Authorization":"Bearer " + self.factory.token}
    data = {"token":token,"username":self.display_name,"uuid":self.uuid}

    Разберёмся. Начнём с хёдеров, первым и единственным у нас будет хёдер Authorization. Он служит в запросах для авторизации в чём то или где то. Мы используем его для того, чтобы сказать серверу о том что это мы. Дальше идёт переменная data — тело запроса. Оно содержит такие параметры как token — токен сгенерированный для пользователя в коде выше, username — имя пользователя(игрока), uuid — UUID игрока. Давайте отправим это всё дело на сервер при помощи данного кода.

    try:
      request = requests.post(self.factory.api, headers=headers, data=data)
    except:
      self.logger.error("HTTP API cannot be requested.")
      self.close("Internal Server Error")

    В данном коде мы пробуем отправить POST-запрос на адрес из переменной api в фабрике с хёдерами headers и с данными data. Если возникает ошибка — печатаем в консоль повесть о том, что мы не можем достучаться до сервера и закрываем соединение с пользователем по причине «Internal Server Error».

    Открываем файл оболочки(у меня start.sh) и записываем туда команду для вызова main.py файла.

    Вот впринципе и всё.


  5. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    Интересно, но так бессмысленно…
    Почему Python?
    Это не ядро, а какой-то скрипт, который выводит motd и кикает при коннекте.
    Тема интересная, развития никакого, к сожалению.

  6. Я дал лишь основу, чтобы из этого можно было сделать что либо ещё, по своей задумке. Python разве не легче для понимания и не занимает меньше оперативы? К тому же библиотека Quarry благодаря которой я написал статью, попалась мне на глаза первее, чем любая библиотека под яву(Они вообще такие есть под неё?).


  7. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    Я твой посыл понял, лайк бы поставил на теме. Мне не понравилось то, что как-то не до конца раскрыта тема, наверное. Не знаю.
    Не уверен, что прочитав эту статью кто-то захочет написать свое ядро сервера, да и к тому же на Python, когда все вокруг Minecraft крутится на Java. Java — де-факто для мира Minecraft и никакой другой язык в ближайшее время его не заменит. К тому же, менять Java на Python плохая идея т.к Python еще менее производительнее чем Java (как минимум из-за того, что Python интерпретируемый язык). К тому же, количество выделяемой памяти под приложение Java/Python не играет роли, если итоговая производительность приложения ниже.
    Я точно не знаю как создаются ядра для серверов, но, если я не ошибаюсь, есть ядро Bukkit, а все остальное — его форки в той или иной степени.
    Также при написании своего ядра на Python возникнет ряд проблем с интеграцией Java плагинов.
    Да и в целом Python не предназначен для высоконагруженной системы (могу ошибаться)

  8. Java к слову майнкрафт скорее всего выбрал не потому что им так захотелось, а потому что он просто кроссплатформенный. Но компиляция Java насколько знаю — не превращение кода в бинарник, Java кроссплатформенная из за принципа её работы. Она компилирует приложение в понятный для себя байткод, его к слову прочитать можно легко и без особых проблем, а только потом уже при запуске она превращает его в понятный для машины код(это всё не точно, я не обновлял знания по работе Java давно). А выделенная оперативка играет роль, так как чем меньше надо оперативки — тем лучше, ведь запустить прогу ты сможешь на машине с характеристиками чуть хуже. А на счёт Bukkit — необязательно, это не какой то закон.


  9. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    Java выбрал Маркус Перссон т.к он на нем изначально программировал => все последующее развитие Minecraft на Java.

    Java являются JIT-компилируемыми. Для JIT-компиляции требуется промежуточный язык, позволяющий разбивать код на блоки (или фреймы).  Сам по себе JIT не делает выполнение быстрее, ведь он по-прежнему выполняет те же последовательности байт-кода. Однако, он позволяет выполнять оптимизацию в рантайме. Хороший JIT-оптимизатор видит, какие части приложения выполняются чаще, и помечает их тегом "hot spot", чтобы в следующий раз выполнение прошло быстрее.
    

    Сейчас заканчивается 2020г, а не 90-00, когда размер памяти играл большую роль.
    К тому же, зачем тебе много оперативки, если у тебя стоит никчемный процессор? Оперативная память нужна для того, чтобы хранить в ней какие-то данные и быстро ими манипулировать. А т.к у нас не текстовый редактор а сервер, то нам нужна многопоточность и быстрота процессора => оперативная память на втором месте. Если у тебя будет все тупить в следствие того, что процессор не может обработать такое кол-во операций, то пиши пропало, оператос не спасет.

  10. Так поэтому и хорошо делать программы с мелким потреблением оперативы, чтобы ты мог вбухать больше денег в процессор и при этом программа всё равно запустилась. Да и вообще, всегда будет лучше, если программа требует меньше ресурсов.


  11. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    Разница в количестве потребляемой оперативной памяти не такая большая, чтобы пренебрегать скоростью работы

  12. Ну и в конце концов, этоn конечно скорее всего не будет применяться для написания таких вещей где важна скорость работы буквально RealTime.


  13. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    :good:

  14. Этот quarry в любом случае нигде не будет применятся да. Ибо это не ядро, а пустой протокол майна. Внутри него ничего нет. И таких протоколов много на гитхабе, даже на node js есть

  15. Бред.
    Сейчас все еще смотрят на бенчи. Все что быстрее эквивалентно тому что это лучше.
    Большую роль играет удобство, но если у тебя всё хавает тонну озу и нагружает процессор до предела — люди будут перестраиваться под другой продукт.
    Ну есть майн на плюсах который намного производительнее оригинала т.к не содержит тонны абстракций на абстракциях, глупые реализации простейших вещей, и прочее.

    ладно…….

    ТС’у:
    Тема полнейший бред, это не ядро.
    Это больше похоже на какую-то проксю для обработки запросов с клиента.


  16. SimMiMo

    SimMiMo
    Активный участник
    Пользователь

    Сейчас обычный недорогой дедик имеет 32-64 гб памяти и это все будет дешеветь с каждым годом.
    Майн на мобилу.. покет едишн. Если честно, под него вообще нормальных ядер нет, одно говно.
    Да и любителей телефончиков не настолько чтобы дофига. Не представляю, как там пэхаться.
    Но опять же, это не вина самого майна пе.

    Последнее редактирование: 20 окт 2020

  17. iForgotPassword

    iForgotPassword
    Активный участник
    Пользователь

    Баллы:
    66
    Имя в Minecraft:
    iForgotPassword

    майнна плюсах это же Bedrock edition, то бишь PE, PS4 и пр. что != Java edition

  18. Дак бедрок есть не только на телефоны o_0
    Что ****ь за бред ты несешь? Причём тут то что он не Java Edition? Мы говорим то что он в разы производительнее чем оригинал (даже если убрать тот факт что он нативный), т.к в нём нету сотни тысяч абстракций, глупых реализаций, и просто врапперов над врапперами.
    Каждый прыжок по сегментам занимает кучу времени, особенно если забить на то что твой код нужно оптимизировать т.к ты работаешь через виртуалку.
    Инфляция это who

  19. Так потому он и прекрасен, он уже убирает мороку с подключением и даёт какой никакой API для работы с пакетами. При этом над пакетами play у тебя есть полный контроль. К тому же там есть примеры, из который вполне будет понятно как реализовать определённую вещь.

  20. Ну дает и что? А серверлогики то нет. Портировать весь кубач на питон? Ну такое.

Поделиться этой страницей

Русское сообщество Bukkit

Bukkit по-русски - свой сервер Minecraft

Время на прочтение
7 мин

Количество просмотров 6.9K

Тема, которая будет писаться в многих частях будет в основном представлять этапы написания своего серверного ядра для мультиплеера популярной игры Minecraft.

Об игре Minecraft

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

О серверах игры

В самой игре нет централизированной системы серверов, что позволяет создавать сервера где угодно, но чаще под них арендуют услуги хостинга. Сервера можно найти очень легко, например введя в поисковик гугл «Мониторинг майнкрафт серверов», а так-же вы можете и создать свой. В основе большенства серверов лежат ядра, которые в свою очередь основаны от одного ядра — Vanilla. Вот малый список ядер, которые часто использут: Paper, Purpur, Spigot, Sponge, Glowstone.

Программная часть серверов

Все ядра, которые были перечислены выше имеют одну и ту же проблемную зависимость — они написаны почти полностью на Java. Сам по себе этот язык классный, много классных особенностей, но в случае огромных проектов часто бывают проблемы например с потреблением ОЗУ, а уже процессор часто бывает второстепенный. Обычный сервер, без дополнений (плагины), при тех же 20-40 игроках может спокойно использовать и 1 или даже 2 гигабайта ОЗУ, а что же говорить о том, что при долгой работе без перезагрузок потребление может занимать и более 6ГБ ОЗУ. Поэтому находятся люди, которые пытаются создавать собственные ядра например на Rust ведётся разработка одного из таких ядер. Я же планирую по частям описывать создание своего ядра, но уже на Go.

Почему же Go?

Я не раз пробовал разные языки для всего подряд (да, даже PHP для серваков…), но мне показался самым классным Go. В основном нравиться его простота, удобство и экосистема, ведь есть много классных библиотек. Так-же бы я выделил его производительность, ведь она тут очень замечательная.

Зачем?

Да просто :D. А если говорить по правде, то я просто давно хотел сделать, что-то интересное не только для меня, но и другим. А может даже и поможет это кому-то… Ну и конечно возможно это будет очень удобно использовать где-то у себя.

Перед началом

В данном посте я планирую сделать лишь основы, поэтому в следующей части будет больше интересного).

Сначала я бы хотел уточнить, что буду использовать Go последней версии (на момент поста 1.17.5), а так-же редактор GoLand от JetBrains и буду надеяться на вашу поддержу :)
Пока на самом начале сервер будет поддерживаться только на 1.12.2, потому-что моё супер вычислительное устройство очень плохо работает в связке GoLand + Minecraft 1.16.5 и выше.

Начало

Называться ядро пока-что будет именно ULE, поэтому будет инициализироваться проект в GoLand, создаём main.go в качестве запускатора.

Инициализация проекта

Инициализация проекта

В качестве упрощения в создании будем использовать одну библиотеку для протокола игры. Она очень мощная, но из неё мне понадобиться только функционал для парсинга сообщений и чтении/записи пакетов, а так-же небольшой обработке игроков. Поэтому установим её таким образом:

go get github.com/Tnze/go-mc@master

go.mod после добавления go-mc

go.mod после добавления go-mc

После чего данная библиотека будет автоматически добавлена в go.mod и мы сможем её использовать в нашем коде обращаясь по «github.com/Tnze/go-mc/».

Теперь для удобства я создам директорию server и в нём файл server.go со следующим содержимым:

package server

// Импортируем пакеты
import (
	"github.com/Tnze/go-mc/net"
	"log"
)

// InitSRV - Функция запуска сервера
func InitSRV() {
	// Запускаем сокет по адрессу 0.0.0.0:25565
	loop, err := net.ListenMC(":25565")
	// Если есть ошибка, то выводим её
	if err != nil {
		log.Fatalf("Ошибка при запуске сервера: %v", err)
	}

	// Цикл обрабатывающий входящие подключеня
	for {
		// Принимаем подключение или ждём
		connection, err := loop.Accept()
		// Если произошла ошибка - пропускаем соденение
		if err != nil {
			continue
		}
		// Принимаем подключение и обрабатываем его не блокируя основной поток
		go acceptConnection(connection)
	}
}

Как мы видим для работы мы используем net из go-mc для подключений, а так-же принимаем их с помощью нашей функции acceptConnection, которая объявлена в server/accepter.go и её код уже такой:

package server

import (
	"github.com/Distemi/ULE/server/protocol/serverbound"
	"github.com/Tnze/go-mc/net"
)

func acceptConnection(conn net.Conn) {
	defer func(conn *net.Conn) {
		err := conn.Close()
		if err != nil {
			return
		}
	}(&conn)
	// Читаем пакет-рукопожатие(HandSnake)
	_, nextState, _, _, err := server.ReadHandSnake(conn)
	// Если при чтении была некая ошибка, то просто перестаём обрабатывать подключение
	if err != nil {
		return
	}

	// Обрабатываем следющее состояние(1 - пинг, 2 - игра)
	switch nextState {
	case 1:
		acceptPing(conn)
	default:
		return
	}
}

Здесь вы можете уже заметить, что в списке инпутов есть свой пакет, в папке server/protocol/serverbound, а там находиться уже файл handsnake.go для «рукопожатий», но перед этим стоит разобрать код функции для принятия подключений, в ней мы пока используем при чтении только nextState, так-как в первой части будет готов только пинг и поэтому в обработке типа подключения из HandSnake мы используем только 1, который означает, что это пинг.

Далее по очереди у нас очень важный компонент в работе ядра — чтение HandSnake, который как описывал был расположен в server/protocol/serverbound/handsnake.go и всё что находиться в директории связанной с протоколом конечно будет всё, что с ним связано и делиться всё на ServerBound (для сервера) и ClientBound (для клиента), поэтому при таком разделении у нас будет именно чтение HandSnake со следующим содержимым:

package serverbound

import (
	"github.com/Tnze/go-mc/net"
	"github.com/Tnze/go-mc/net/packet"
)

// ReadHandSnake - чтение HandSnake пакета( https://wiki.vg/Protocol#Handshake )
func ReadHandSnake(conn net.Conn) (protocol, intention int32, address string, port uint16, err error) {
	// Переменные пакета
	var (
		p                   packet.Packet
		Protocol, NextState packet.VarInt
		ServerAddress       packet.String
		ServerPort          packet.UnsignedShort
	)
	// Читаем входящий пакет и при ошибке ничего не возращаем
	if err = conn.ReadPacket(&p); err != nil {
		return
	}
	// Читаем содержимое пакета
	err = p.Scan(&Protocol, &ServerAddress, &ServerPort, &NextState)
	// Возращаем результат чтения в привычной форме для работы(примитивные типы)
	return int32(Protocol), int32(NextState), string(ServerAddress), uint16(ServerPort), err
}

Ну и написанными комментариями я думаю расписал всё довольно подробно и если вы хотите почитать сами про структуру пакета и почему всё так читаем, то можете ознакомиться на официальном источнике.

После чтения HandSnake пакета мы так-же решаем что делать с ним и поэтому в accepter.go при обработке состояния при 1 — принимаем в виде пинга в функции acceptPing, но принятие пинга уже кому-то может составить проблему из-за большого кода, ведь тут вся функция пинга обходиться нам в server/accepter_ping.go не в 20 строчек:

package server

import (
	"encoding/json"
	"github.com/Distemi/ULE/config"
	"github.com/Tnze/go-mc/chat"
	"github.com/Tnze/go-mc/net"
	"github.com/Tnze/go-mc/net/packet"
	"github.com/google/uuid"
	"log"
)

// Получаем пинг-подкючение(PingList)
func acceptPing(conn net.Conn) {
	// Инициализируем пакет
	var p packet.Packet
	// Пинг или описание, будем принимать только 3 раза
	for i := 0; i < 3; i++ {
		// Читаем пакет
		err := conn.ReadPacket(&p)
		// Если ошибка - перестаём обрабатывать
		if err != nil {
			return
		}
		// Обрабатываем пакет по типу
		switch p.ID {
		case 0x00: // Описание
			// Отправляем пакет со списком
			err = conn.WritePacket(packet.Marshal(0x00, packet.String(listResp())))
		case 0x01: // Пинг
			// Отправляем полученный пакет
			err = conn.WritePacket(p)
		}
		// При ошибке - прекращаем обработку
		if err != nil {
			return
		}
	}
}

// Тип игрока для списка при пинге
type listRespPlayer struct {
	Name string    `json:"name"`
	ID   uuid.UUID `json:"id"`
}

// Генерация JSON строки для ответа на описание
func listResp() string {
	// Строение пакета для ответа( https://wiki.vg/Server_List_Ping#Response )
	var list struct {
		Version struct {
			Name     string `json:"name"`
			Protocol int    `json:"protocol"`
		} `json:"version"`
		Players struct {
			Max    int              `json:"max"`
			Online int              `json:"online"`
			Sample []listRespPlayer `json:"sample"`
		} `json:"players"`
		Description chat.Message `json:"description"`
		FavIcon     string       `json:"favicon,omitempty"`
	}

	// Устанавливаем данные для ответа
	list.Version.Name = "ULE #1"
	list.Version.Protocol = config.ProtocolVersion
	list.Players.Max = 100
	list.Players.Online = 5
	list.Players.Sample = []listRespPlayer{{
		Name: "Пример игрока :)",
		ID:   uuid.UUID{},
	}}
	list.Description = config.MOTD

	// Превращаем структуру в JSON байты
	data, err := json.Marshal(list)
	if err != nil {
		log.Panic("Ошибка перевода в JSON из обьекта")
	}
	// Возращаем результат в виде строки, переведя из байтов
	return string(data)
}

Обошлось нам всё в 83 строчки… Наверное это ещё очень мало так-как под некоторое была выделена папка config в которой вскоре будет располагаться вся конфигурация, но пока поговорим о нашем любимом пинге. Принятый пинг-подключение мы читать будем не более 3-х раз так-как обычному клиенту этого хватить должно в большинстве случаев, но это и частично нас обезопасит от ping-аттак.. частично… ну ладно, если не получилось отправить пакет, то перестаём его обрабатывать, поэтому каждый раз прочитывая пакет мы так-же узнаём его тип, так-как:

  • 0x00 — получение описания

  • 0x01 — пинг игрока

И вот тут самое интересное, при пинге игрока нам надо просто возвращать отправленный игроком пакет, а вот уже при описании нам приходиться генерировать JSON из нашей структуры, но сама генерация идёт в функции listResp, но для неё у нас есть структура данных listRespPlayer, которая говорит частично за себя ведь она описывать игрока для ответа, другая структура в самой функции генерации ответа уже гораздо больше, которая соответствует минимальному стандарту ответа. Мы так-же устанавливаем в структуру значения по дефолту

	list.Version.Name = "ULE #1"
	list.Version.Protocol = config.ProtocolVersion
	list.Players.Max = 100
	list.Players.Online = 5
	list.Players.Sample = []listRespPlayer{{
		Name: "Пример игрока :)",
		ID:   uuid.UUID{},
	}}
	list.Description = config.MOTD

И мы тут можем заметить, что идёт обращение к какому-то config, а это просто в корне проекта config/basic.go:

package config

import "github.com/Tnze/go-mc/chat"

var (
	ProtocolVersion uint16       = 340
	MOTD            chat.Message = chat.Text("Тестовое ядро §aULE")
)

И в нём установлены некоторые дефолтные значения по типу версии протокола (для 1.12.2 версия протокола — 340), а так-же MOTD или же то что вы видите в виде текста под названием сервера.

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


Итог

Вот и конец первой части истории о самописном серверном ядре для Minecraft Java. Весь исходный код доступен на GitHub. И я буду очень сильно надеяться на вашу поддержку.

В итоге первой части мы получили:

Результат пинга

Результат пинга

При этом ядро пока использует 2.1МБ ОЗУ, но стоит учесть, что на Linux он будет использовать гораздо меньше так-как размер потребления указан на Windows 11.

Спасибо за прочтение статьи и скоро выйдет новая часть, посвящённая написанию своего ядра! :)

Icosider


  • #2

Возьми за основу обычное ядро майна, а если нужно моды использовать то forge, если хочешь ещё и плагины то бери ведро или кран. И ещё такой момент, термос параша, тот же KCauldron лучше будет.
«в качестве исходного материала ядро thermos, которое недавно перестали разрабатывать.» — даже не удивлён, что его перестали разрабатывать…

  • #3

Они сначала написали, что просто не разрабатывают, потом еще и дополнили: «не решаем issues, просто закрываем»

Мне хочется стартовать от термоса, т.к. он хорошо себя показал за год работы сервера. В чем он и параша: иногда случаются краши, почти все связаны с ConcurrentModificationException, но после рестарта все норм

Спасибо, попробую начать от forge, но как к нему прикрутить bukkit?

  • #4

Народ, вы все тут конечно мегапрограммисты… Объясните плиз, что значит разработка ядра?
Типа майн с нуля написать?

  • #5

Типо написать сервер. Который будет майн запускать.

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

Icosider


  • #6

Maxik001 написал(а):

Типо написать сервер. Который будет майн запускать.

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

«Потому что это изначально планировалась браузерная игра.» — впервые такую чушь слышу.

  • #7

Да ты чего. Поищи повнимательнее. Первая CaveGame запускалась в браузере, чувак…

Совсем первая. Прям вообще. Там никаких блоков не было, ничего. Тупо статичная карта, по которой можно побегать и все.
И она даже помойму не так называлась.

Icosider


  • #8

hohserg написал(а):

Они сначала написали, что просто не разрабатывают, потом еще и дополнили: «не решаем issues, просто закрываем»

Мне хочется стартовать от термоса, т.к. он хорошо себя показал за год работы сервера. В чем он и параша: иногда случаются краши, почти все связаны с ConcurrentModificationException, но после рестарта все норм

Спасибо, попробую начать от forge, но как к нему прикрутить bukkit?

Декомпильне bukkit ядро(если нет исходников) и смотри реализацию. Ведро ж написано с использованием обычного mc сервера, просто оно дополняет его, как тот же самый forge для mc.


Maxik001 написал(а):

Да ты чего. Поищи повнимательнее. Первая CaveGame запускалась в браузере, чувак…

Совсем первая. Прям вообще. Там никаких блоков не было, ничего. Тупо статичная карта, по которой можно побегать и все.

CaveGame и Minecraft это две разные игры, и то что когда-то там в далёком 2010-2011 годах её запускали в браузере, это не значит что её писали для веба. Да и все браузерные игры шлак в основе которых лежит донат.

  • #9

«Ведро ж написано с использованием обычного mc сервера, просто оно дополняет его, как тот же самый forge для mc.» Мм, делать то, что уже кто-то сделал…


Liahim написал(а):

Народ, вы все тут конечно мегапрограммисты… Объясните плиз, что значит разработка ядра?
Типа майн с нуля написать?

Это значит взять ядро серверное и доделать его.

Icosider


  • #10

hohserg написал(а):

«Ведро ж написано с использованием обычного mc сервера, просто оно дополняет его, как тот же самый forge для mc.» Мм, делать то, что уже кто-то сделал…

Напиши своё, будет много чиловек пользоваться:D Глянь как сделаны другие ядра и поймёшь.

  • #11

CaveGame и Minecraft это две разные игры, и то что когда-то там в далёком 2010-2011 годах её запускали в браузере, это не значит что её писали для веба. Да и все браузерные игры шлак в основе которых лежит донат.

и всеже CaveGame это прародитель майна

  • #12

WildHeart написал(а):

Возьми за основу обычное ядро майна, а если нужно моды использовать то forge, если хочешь ещё и плагины то бери ведро или кран. И ещё такой момент, термос параша, тот же KCauldron лучше будет.
«в  качестве исходного материала ядро thermos, которое недавно перестали разрабатывать.» — даже не удивлён, что его перестали разрабатывать…

А есть какие-то пруфы того, что утверждаешь в такой агрессивной форме? С какого это чёрта Thermos хуже?

Icosider


  • #13

Credence написал(а):

WildHeart написал(а):

Возьми за основу обычное ядро майна, а если нужно моды использовать то forge, если хочешь ещё и плагины то бери ведро или кран. И ещё такой момент, термос параша, тот же KCauldron лучше будет.
«в  качестве исходного материала ядро thermos, которое недавно перестали разрабатывать.» — даже не удивлён, что его перестали разрабатывать…

А есть какие-то пруфы того, что утверждаешь в такой агрессивной форме? С какого это чёрта Thermos хуже?

А вот и фанбой прибежал защищать говно. Ответ на твой вопрос дал сам ТС «В чем он и параша: иногда случаются краши, почти все связаны с ConcurrentModificationException, но после рестарта все норм». Так же в нём дожую ошибок и само по себе ядро не стабильное.

  • #14

WildHeart написал(а):

Credence написал(а):

WildHeart написал(а):

Возьми за основу обычное ядро майна, а если нужно моды использовать то forge, если хочешь ещё и плагины то бери ведро или кран. И ещё такой момент, термос параша, тот же KCauldron лучше будет.
«в  качестве исходного материала ядро thermos, которое недавно перестали разрабатывать.» — даже не удивлён, что его перестали разрабатывать…

А есть какие-то пруфы того, что утверждаешь в такой агрессивной форме? С какого это чёрта Thermos хуже?

А вот и фанбой прибежал защищать говно. Ответ на твой вопрос дал сам ТС «В чем он и параша: иногда случаются краши, почти все связаны с ConcurrentModificationException, но после рестарта все норм». Так же в нём дожую ошибок и само по себе ядро не стабильное.

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

Icosider


  • #15

Credence написал(а):

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

У меня Sponge и мне пофиг на термос. «с чем отлично справлялись, до того как поняли что это неблагодарная работа» — почему тогда spigot, sponge, forge всё ещё живут? И если у тебя ошибок нет, это не значит, что их вообще нет…

  • #16

Maxik001 написал(а):

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

Сейчас бы браузерные игры на java писать…


Термос это же просто отдельная ветка котла. И появилась точно так же — просто не хотели фиксить баги, кто-то пофиксил и залил.

  • #17

Попробовал собрать KCauldron — та же ошибка


Кажется, недоступны некоторые источники для библиотек
И лог не совсем такой же:

C:servercoreKCauldron>gradlew build
:buildSrc:compileJava UP-TO-DATE
:buildSrc:compileGroovy UP-TO-DATE
:buildSrc:processResources UP-TO-DATE
:buildSrc:classes UP-TO-DATE
:buildSrc:jar UP-TO-DATE
:buildSrc:assemble UP-TO-DATE
:buildSrc:compileTestJava UP-TO-DATE
:buildSrc:compileTestGroovy UP-TO-DATE
:buildSrc:processTestResources UP-TO-DATE
:buildSrc:testClasses UP-TO-DATE
:buildSrc:test UP-TO-DATE
:buildSrc:check UP-TO-DATE
:buildSrc:build UP-TO-DATE
Download https://repo1.maven.org/maven2/org/fusesource/jansi/jansi/1.8/jansi-1.8
.pom
Download https://repo1.maven.org/maven2/org/fusesource/jansi/jansi-project/1.8/j
ansi-project-1.8.pom

FAILURE: Build failed with an exception.

* Where:
Build file ‘C:servercoreKCauldronbuild.gradle’ line: 161

* What went wrong:
A problem occurred evaluating root project ‘KCauldron’.
> Could not resolve all dependencies for configuration ‘:libraries’.
   > Could not resolve net.md-5:SpecialSource:1.7-SNAPSHOT.
     Required by:
         pw.prok:KCauldron:1.7.10-1492.UNOFFICIAL
      > Could not resolve net.md-5:SpecialSource:1.7-SNAPSHOT.
         > Unable to load Maven meta-data from https://repo.prok.pw/net/md-5/Spe
cialSource/1.7-SNAPSHOT/maven-metadata.xml.
            > Could not GET ‘https://repo.prok.pw/net/md-5/SpecialSource/1.7-SNA
PSHOT/maven-metadata.xml’.
               > peer not authenticated
   > Could not resolve net.minecraft:server:1.7.10.
     Required by:
         pw.prok:KCauldron:1.7.10-1492.UNOFFICIAL
      > Could not resolve net.minecraft:server:1.7.10.
         > Could not get resource ‘https://repo.prok.pw/net/minecraft/server/1.7
.10/server-1.7.10.pom’.
            > Could not GET ‘https://repo.prok.pw/net/minecraft/server/1.7.10/se
rver-1.7.10.pom’.
               > peer not authenticated
   > Could not resolve pw.prok:KImagine:0.1.12.
     Required by:
         pw.prok:KCauldron:1.7.10-1492.UNOFFICIAL
      > Could not resolve pw.prok:KImagine:0.1.12.
         > Could not get resource ‘https://repo.prok.pw/pw/prok/KImagine/0.1.12/
KImagine-0.1.12.pom’.
            > Could not GET ‘https://repo.prok.pw/pw/prok/KImagine/0.1.12/KImagi
ne-0.1.12.pom’.
               > peer not authenticated

* Try:
Run with —stacktrace option to get the stack trace. Run with —info or —debug
option to get more log output.

BUILD FAILED

Total time: 2 mins 12.365 secs

C:servercoreKCauldron>

Как написать ядро для сервера майнкрафт

Всем привет. Сегодня я хочу вам рассказать как правильно выбрать ядро для вашего Minecraft сервера.

Статья была взята с просторов интернета.

Все ниже указанные ссылки предоставлены чисто в ознакомительных целях. P.S. никакой рекламы.
Итак приступим.

Всего существует 5 ядер (самые популярные):
Vanilla
CraftBukkit
Spigot
MCPC+
Spout (ещё не до конца разработан)

Теперь расскажу про каждый из них:

BungeeCord-BotFilter 1.8-1.14.2 — ядро для сервера майнкрафт, в которое уже встроена бот проверка. BotFilter отлично защищает сервера от ботов. BungeeCord представляет собой связку серверов, с помощью которого можно связать несколько серверов, а потом переходить по этим серверам без выхода из игры. Переход по серверам можно настроить через npc-жителей, порталы, меню сервера.

В этой статье вы найдете ядро Spigot версий 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14. Недавно была выпущена статья про ядра CraftBukkit . И мы решили не обходить стороной такие замечательные ядра для сервера Майнкрафт как Spigot.В этом разделе вы найдете серверное ядро Spigot всех версий для вашего сервера MineCraft.


Ядро — самое главное что нужно для сервера, ведь оно обязательно нужно, для запуска вашего сервера MineCraft. В этой статье вы найдете ядро CraftBukkit версий 1.7, 1.8, 1.9, 1.10, 1.11, 1.12, 1.13, 1.14 Серверных ядер огромное количество, например CraftBukkit, Spigot, PaperSpigot, BungeeCord, MCPE и т.д В этом разделе вы найдете серверное ядро CraftBukkit всех версий для вашего сервера MineCraft.

Для начала ознакомьтесь с обучающим видео:

Если Вы читаете данную статью, то перед Вам сейчас стал вопрос как установить своё ядро на сервер Майнкрафт на хостинге.

Для установки нам понадобится следующее:

  • Ядро сервера(Это главный jar файл), который Вы предварительно скачали.
  • Навыки работы с FTP клиентом Filezilla. Инструкцию можно найти ЗДЕСЬ.

1)Переходим в «управление сервером» и выключаем сервер.

2)Подключаемся к серверу через FTP клиент Filezilla, как указано ЗДЕСЬ.

3)Удаляем всё, кроме файла server.properties:

4)Теперь закачиваем подготовленное зараннее ядро:

Загрузка…

Adblock
detector

Всем привет! Уже столько времени прошло с прошлой статьи, в которой я писал про реализацию своей небольшой версии, написанной на Go, как всегда исходный код доступен на GitHub. Сразу думаю сказать, что за это время успел уже перейти на линукс(Mint Cinnamon), получить проблемы с интегрированной GPU, но в конце концов наконец я смог нормально работать с редактором от JetBrains и сделать переход с Go на Rust, это было сделано так-как я думал изначально писать на расте, но было очень проблематично компилировать… Но вот и был сделан всё-таки переход с улучшениями как производительности так и возможностей!)

Причина перехода с Go на Rust

  1. Изначально я задумывался о создании на нём, но не мог нормально скомпилировать код.

  2. Код на расте работает в разы быстрее и безопаснее.

  3. Теперь скорость можно измерять в наносекундах…)

Немного важных уточнений

При переходе на Rust я решил, что стоит делать по-умолчанию английский язык, так-как его знают большинство, а это значит если человек, который не знает русского — сможет вполне использовать, из-за этого все комментарии в статье и коде на GitHub будут написаны на английском языке, при этом может быть и много ошибок… Получилось так-же много файлов, но есть которые почти пустые и только обьединяют несколько модулей, поэтому такие файлы я не буду комментировать, а только оставлю код.

Продолжение #1.1

Код на расте вышел всё-таки в разы больше так-как я пытался использовать как можно больше своего, но всё-таки для лучшего результата использовал много, но важных библиотек и прописал их в Cargo.toml:

Содержимое Cargo.toml

[package]
name = "ule"
version = "0.1.0"
edition = "2021"
publish = true
authors = [
    "Distemi <distemi.bot@mail.ru>"
]
homepage = "https://github.com/Distemi/ULE"
repository = "https://github.com/Distemi/ULE"

[dependencies]
# Быстрый HashMap и другое.
ahash = "0.7.6"
# Глобальные переменные
lazy_static = "1.4.0"
# Struct <-> JSON
serde = { version = "1.0.136", features = ["derive"] }
serde_json = "1.0.78"
# Утилиты логирования
log = "0.4.14"
fern = { version = "0.6", features = ["colored"] }
# Время
chrono = "0.4.19"
# Асинхронность(скоро точно понадобится)
async-std = "1.10.0"

# Однопоточный TCP и UDP сервер
[dependencies.mio]
version = "0.8.0"
default-features = false
features = [
    "os-ext",
    "net"
]


[profile.release]
opt-level = "z"

Как раз наш Cargo.toml является одним из основных файлов для проекта, а следющий по важности src/main.rs:

Наш main.rs

#![allow(unused_must_use)]
use crate::config::{ADDRESS, ADDRESS_PORT};
use crate::logger::start_input_handler;
use crate::network::network_server_start;
use fern::colors::Color;
use std::error::Error;
use std::process;
use std::sync::mpsc::channel;
use std::sync::Arc;
use std::time::SystemTime;
use std::{fmt, thread};
use utils::logger;

// Use a macros from serde(Serialize and Deserialize), log(Logging) and lazy_static(Global variables)
#[macro_use]
extern crate serde;
#[macro_use]
extern crate log;
#[macro_use]
extern crate lazy_static;

pub mod config;
pub mod network;
pub mod utils;

// Main function of application
fn main() {
    let start = SystemTime::now();
    // Initialize logger
    println!("Starting ULE v1.0.0...");
    if let Err(err) = logger::setup_logger() {
        eprintln!("Failed to initialize logger: {}", err);
        process::exit(1);
    }
    // Creating channel for multithreading communication with main's thread and network's thread
    let (tx, rx) = channel::<bool>();
    // Generate server's address and make it accessible with thread safe
    let address = Arc::new(String::from(format!(
        "{}:{}",
        ADDRESS,
        ADDRESS_PORT.to_string()
    )));
    // Start network in another thread
    thread::spawn({
        let address = address.to_string();
        move || {
            // Start network
            // If failed to start when return error
            if let Err(err) = network_server_start(address, &tx) {
                error!("{}", err);
                tx.send(false);
            }
        }
    });
    // Wait for status from server's network
    if rx.recv().unwrap_or(false) {
        // If Server successful started
        info!("Server started at {}", address);
        // Showing about the full launch and showing the time to start
        {
            let elapsed = start.elapsed().unwrap();
            info!(
                "The server was successfully started in {}",
                if elapsed.as_secs() >= 1 {
                    format!("{}s", elapsed.as_secs())
                } else if elapsed.as_millis() >= 1 {
                    format!("{}ms", elapsed.as_millis())
                } else {
                    format!("{}ns", elapsed.as_nanos())
                }
            );
            drop(elapsed);
        };
    } else {
        // If Failed to start Server
        error!("Failed to start server on {}.", address);
        process::exit(1);
    }
    // Remove channel
    std::mem::drop(rx);
    // Start console input handler(input commands)
    start_input_handler();
}

// Custom error(yes, not std::io:Error)
#[derive(Debug)]
pub struct SimpleError(String, Option<std::io::Error>);

impl Error for SimpleError {}

impl fmt::Display for SimpleError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        // Check is error provided
        if self.1.is_some() {
            write!(f, "{}: {:?}", self.0, self.1)
        } else {
            write!(f, "{}", self.0)
        }
    }
}

// Custom Result with custom Error
pub type SResult<T> = Result<T, SimpleError>;

В нашем main.rs в самом начале инициализируем отсчёт времени, который вскоре будем использовать для показа время запуска. Потом мы пытаемся инициализировать логгер(fern + log), при неудаче — выводим ошибку и «убиваем» процесс. Следующим шагом у нас идёт создания некого канала, а после строки адресса сервера, но канал, который на самом деле выглядит по методам как UDP, но с блокировкой потоков, он нам нужен для ожидания основного потока, который ждёт результат запуска от потока сетевого сервера(TCP сервер), получилось — выводим информацию об успешном запуске сетевого сервера и за сколько времени запустилось, при ошибке — выводим информацию о проблеме запуска. Если получилось запустить наш TCP сервер, то удаляем наш канал связи и начинаем слушать ввод с консоли. Как видно есть типы SResul(из сокращения SimpleResult) и SimpleError, первый говорит сам за себя, как и второй, но для которого идёт приминение разных trait для показа ошибок.

Наш метод инициализации логгера лежит в файле src/utils/logger/input.rs, но я покажу так-же src/utils/mod.rs и src/utils/logger/mod.rs, так-как они зависимы:

src/utils/mod.rs

pub mod chat;
pub mod logger;

Просто импортируем модули чата и логгера публично

src/utils/logger/mod.rs

mod input;
mod log_lib;

pub use {input::start_input_handler, log_lib::setup_logger};

Тут идёт импорт input.rs и log_lib.rs, а так-же экспорт методов start_input_handler и setup_logger

Содержимое файла с методом инициализации логгера

use crate::Color;
use fern::colors::ColoredLevelConfig;
use std::fs;

// Logger's initialize(fern, color and log)
pub fn setup_logger() -> Result<(), fern::InitError> {
    // Removing latest log if exists
    fs::remove_file("latest.log");
    // Setting colors
    let colors = ColoredLevelConfig::new()
        .info(Color::BrightBlack)
        .warn(Color::Yellow)
        .error(Color::Red)
        .trace(Color::BrightRed);
    // Setting fern
    fern::Dispatch::new()
        // Setting custom format to logging
        .format(move |out, message, record| {
            out.finish(format_args!(
                "{} [{}] {}",
                chrono::Local::now().format("[%m-%d %H:%M:%S]"),
                colors.color(record.level()),
                message
            ))
        })
        // Setting log-level
        .level(log::LevelFilter::Info)
        // Setting target's logger
        .chain(std::io::stdout())
        // Setting log's file
        .chain(fern::log_file("latest.log")?)
        // Applying settings
        .apply()?;
    // If successful setting - returning ok
    Ok(())
}

При инициализации логера мы в первую очередь удаляем файл последнего лога latest.log, потом устанавливаем на каждый уровень лога свой цвет(INFO = серый, WARN — жёлтый, ERROR — красный, TRACE — ярко-красный). Позже идёт инициализация самого логгера fern и для него мы устанавливаем формат: [ДАТА] [УРОВЕНЬ] СООБЩЕНИЕ, цвет имеет только уровень, а дата и сообщение стандартным цвветом консоли. Для логгера устанавливаем вывод в stdout(консоль вывода), минимальный уровень вывода — INFO, а так-же вывод в лог-файл и принимаем эти изменения. Если не было ошибок при этих действиях — возращяем успешный пустой результат.

Далее у нас через main.rs создаётся канал mpsc, который передаётся в другой поток сетевого сервера TCP и это делается через network_server_start из пакета network, который имеет много «пустых» файлов, но оттуда нам нужен лишь протокол, сервер, буферы и обработчики. Сам сетевой сервер располагается по пути src/network/server.rs:

Содержимое сетевого сервера

use crate::network::handler::{handshaking, status_handler};
use crate::network::network_client::ConnectionType::HANDSHAKING;
use crate::network::network_client::NetworkClient;
use ahash::AHashMap;
use mio::net::TcpListener;
use mio::{Events, Interest, Poll, Token};
use std::io;
use std::sync::mpsc::Sender;
use std::sync::Mutex;
use std::time::Duration;

// Declare global variables
lazy_static! {
    // Server need to shut down? (true - yes, needs to shutdown network server).
    pub static ref SHUTDOWN_SERVER: Mutex<bool> = Mutex::new(false);
    // Server's works status.
    pub static ref NET_SERVER_WORKS: Mutex<bool> = Mutex::new(true);
}

// Server's Token(ID)
const SERVER: Token = Token(0);

// Next Token
fn next(current: &mut Token) -> Token {
    let next = current.0;
    current.0 += 1;
    Token(next)
}

// Start a network server
pub fn network_server_start(address: String, tx: &Sender<bool>) -> std::io::Result<()> {
    // Creating Network Pool
    let mut poll = Poll::new()?;
    // Creating Network Events Pool
    let mut events = Events::with_capacity(256);
    // Converting String's address to SocketAddr
    let addr = address.parse().unwrap();
    // Starting a Network Listener
    let mut server = TcpListener::bind(addr)?;
    // Register server's Token
    poll.registry()
        .register(&mut server, SERVER, Interest::READABLE)?;

    // Creating a list of connections
    let mut connections: AHashMap<Token, NetworkClient> = AHashMap::new();
    // Creating a variable with latest token.
    let mut unique_token = Token(SERVER.0 + 1);
    // Send over the channel that the server has been successfully started
    tx.send(true);

    // Network Events getting timeout
    let timeout = Some(Duration::from_millis(10));
    // Infinity loop(while true) to handing events
    loop {
        // Checks whether it is necessary to shutdown the network server
        if *SHUTDOWN_SERVER.lock().unwrap() {
            *NET_SERVER_WORKS.lock().unwrap() = false;
            info!("Network Server Stopped!");
            return Ok(());
        }
        // Getting a events from pool to event's pool with timeout
        poll.poll(&mut events, timeout)?;
        // Handing a events
        for event in events.iter() {
            // Handing event by token
            match event.token() {
                // If it server's event
                // Reading a all incoming connection
                SERVER => loop {
                    // Accepting connection
                    let (mut connection, _) = match server.accept() {
                        // If successful
                        Ok(v) => v,
                        // If not exists incoming connection
                        Err(e) if e.kind() == io::ErrorKind::WouldBlock => {
                            break;
                        }
                        // If failed to get incoming connection
                        Err(e) => {
                            return Err(e);
                        }
                    };

                    // Generating new token for this connection
                    let token = next(&mut unique_token);
                    // Registering connection with token
                    poll.registry().register(
                        &mut connection,
                        token,
                        Interest::READABLE.add(Interest::WRITABLE),
                    )?;
                    // Pushing connection into connection's list
                    connections.insert(
                        token,
                        NetworkClient {
                            stream: connection,
                            conn_type: HANDSHAKING,
                        },
                    );
                },
                // Handing event from client
                token => {
                    // Handing event by connection's stage
                    let done = if let Some(connection) = connections.get_mut(&token) {
                        let m = match &connection.conn_type {
                            HANDSHAKING => handshaking,
                            _ => status_handler,
                        };
                        // Trying to handing
                        m(connection, &event).unwrap_or(false)
                    } else {
                        false
                    };
                    // If needs to close connection - removing from list, unregister and close connection's stream
                    if done {
                        if let Some(mut connection) = connections.remove(&token) {
                            poll.registry().deregister(&mut connection.stream)?;
                            connections.remove(&token);
                        }
                    }
                }
            }
        }
    }
}

Да, уже целых 125 строчек, но это ещё мало

В нём мы инициализируем глобальные переменные используя lazy_static, обратите внимание, что тип bool завёрнут в оболочку Mutex, который гарантирует мультипоточный доступ к переменной, к чтении и записи, но для получения этих переменных блокируется поток ожидая информации. Создаётся так-же простая примитивная структура SERVER, который имеет значение айди сервера в сетевом сервере. Далее идёт next, который работает как ++ для переменных(в расте не ++, а +=1), а следует за этой функцией уже другая — network_server_start. Функция сетевого сервера на старте инициализирует два пула, один отвечает за хранилища событий и дальше идёт парсинг строки в адресс, а следом попытка запустить TCP сервер на этом адрессе, который регестрируем в пуле как только-чтение, после мы создаём список подключений и уникальный токен на новое подключение, если не было ошибок, то отправляем в канал связи — true, который означать о успешном запуске. Переменная timeout используется как лимит ожидания событий, чтобы начать цикл обработки запросов заного, что есть в цикле: проверка на нужду в отключении сервера и если надо, то просто устанавливаем статус, что сервер выключен и останавливаем цикл, остальную работу по одключении слушателя и тд делаем сам компилятор. Если же останавливать нам не надо, то мы ждём до 10мс сетевые события и позже обрабатываем существующие события. Серверные события бывают только — принятие нового подключения, поэтому мы создаём ещё цикл в котором принимаем все подключения, регистрируем их. В случае если это события связанные с клиентами(присланный пакет например) — в зависимости от типа подключения(HandShaking, Status и тд) передаём соответствующему обработчику и в случае если обработчик возращает true, то мы разрываем соединение и удаляем его из хранилища подключений.

Протокол, обработка подключений, чтение и создание пакетов

Так-как ядро я пытаюсь сделать менее зависимым от библиотек, то для чата и протокола будет всё написано с 0 и для обеспечения большего контроля над чтением и записью.

Было решено хранить буферы пакетов в виде векторов к которым добавлены методы чтения и записи(только для Vec<u8>):

Чтение буферов

use crate::{SResult, SimpleError};

/// Reader [Vec] of bytes
pub trait PacketReader {
    // 1-Byte
    fn get_u8(&mut self) -> u8;
    fn get_i8(&mut self) -> i8;
    // 2-Byte
    fn get_u16(&mut self) -> u16;
    fn get_i16(&mut self) -> i16;
    // 4-Byte
    fn get_varint(&mut self) -> SResult<i32>;
    // 8-Byte
    fn get_i64(&mut self) -> i64;
    // Another
    fn get_string(&mut self) -> SResult<String>;
    fn read_base(&mut self) -> SResult<(i32, i32)>;
}

// Apply reader to Vec
impl PacketReader for Vec<u8> {
    // Read a single byte as u8 ( 8-Bit Unsigned Integer )
    fn get_u8(&mut self) -> u8 {
        self.remove(0)
    }

    // Read a single byte as i8 ( 8-Bit Integer )
    fn get_i8(&mut self) -> i8 {
        self.remove(1) as i8
    }

    // Read a two bytes as u16 ( 16-Bit Unsigned Integer )
    fn get_u16(&mut self) -> u16 {
        u16::from_be_bytes([self.get_u8(), self.get_u8()])
    }

    // Read a two bytes as i16 ( 16-Bit Integer )
    fn get_i16(&mut self) -> i16 {
        i16::from_be_bytes([self.get_u8(), self.get_u8()])
    }

    // Read a VarInt ( Dynamic-length 32-Bit Integer )
    fn get_varint(&mut self) -> SResult<i32> {
        // Result variable
        let mut ans = 0;
        // Read up to 4 bytes
        for i in 0..4 {
            // Read one byte
            let buf = self.get_u8();
            // Calculate res with bit moving and another
            ans |= ((buf & 0b0111_1111) as i32) << 7 * i;
            // If it's limit when stop reading
            if buf & 0b1000_0000 == 0 {
                break;
            }
        }
        // Return result as successful
        Ok(ans)
    }

    // Read a Long ( 64-Bit Integer )
    fn get_i64(&mut self) -> i64 {
        // Yes, read 8 bytes
        i64::from_be_bytes([
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
            self.get_u8(),
        ])
    }

    // Read a String ( VarInt as len; bytes[::len] )
    fn get_string(&mut self) -> SResult<String> {
        // Getting string-length
        let len = self.get_varint()?;
        // Create String's bytes buffer
        let mut buf = Vec::new();
        // Reading Bytes
        for _ in 0..len {
            buf.push(self.get_u8())
        }
        // Convert Bytes to UTF8 String
        match String::from_utf8(buf) {
            Ok(v) => Ok(v),
            Err(_) => Err(SimpleError(String::from("Failed to parse chars"), None)),
        }
    }
    // Read first two VarInt(Packet's length and id)
    fn read_base(&mut self) -> SResult<(i32, i32)> {
        let len = self.get_varint()?;
        let pid = self.get_varint()?;
        Ok((len, pid))
    }
}

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

Запись в буферы пакетов

/// Writer [Vec] of bytes
pub trait PacketWriter {
    // 1-Byte
    fn write_u8(&mut self, value: u8);
    fn write_i8(&mut self, value: i8);
    // 2-Byte
    fn write_u16(&mut self, value: u16);
    fn write_i16(&mut self, value: i16);
    // 4-Byte
    fn write_varint(&mut self, value: i32);
    // 8-Byte
    fn write_i64(&mut self, value: i64);
    // Another
    fn write_vec_bytes(&mut self, bytes: Vec<u8>);
    fn write_string(&mut self, value: String);
    fn create_packet(&mut self, pid: i32) -> Vec<u8>;
}

impl PacketWriter for Vec<u8> {
    // Writing byte
    fn write_u8(&mut self, value: u8) {
        self.push(value);
    }

    // Writing byte
    fn write_i8(&mut self, value: i8) {
        self.push(value as u8)
    }

    // Writing 2-byte unsigned integer
    fn write_u16(&mut self, value: u16) {
        self.extend_from_slice(&value.to_be_bytes());
    }

    // Writing 2-byte unsigned integer
    fn write_i16(&mut self, value: i16) {
        self.extend_from_slice(&value.to_be_bytes());
    }

    // Writing bytes as VarInt
    fn write_varint(&mut self, mut value: i32) {
        // Bytes buffer
        let mut buf = vec![0u8; 1];
        // Byte's length
        let mut n = 0;
        // Converts value to bytes
        loop {
            // Break if it's limit
            if value <= 127 || n >= 8 {
                break;
            }
            // Pushing a byte to buffer
            buf.insert(n, (0x80 | (value & 0x7F)) as u8);
            // Moving value's bits on 7
            value >>= 7;
            value -= 1;
            n += 1;
        }
        // Pushing byte, because it lower that 256(<256)
        buf.insert(n, value as u8);
        n += 1;
        // Pushing converted bytes into byte's buffer
        self.extend_from_slice(&buf.as_slice()[..n])
    }

    // Writing Long ( 64-Bit Integer )
    fn write_i64(&mut self, value: i64) {
        self.extend_from_slice(value.to_be_bytes().as_slice())
    }

    // Alias of extend_from_slice, but works with Vec, not Slice
    fn write_vec_bytes(&mut self, mut bytes: Vec<u8>) {
        self.append(&mut bytes);
    }

    // Write String (VarInt as len and string's bytes)
    fn write_string(&mut self, value: String) {
        // Getting String as Bytes
        let bytes = value.as_bytes();
        // Writing to buffer a length as VarInt
        self.write_varint(bytes.len() as i32);
        // Writing to buffer a string's bytes
        self.extend_from_slice(bytes);
    }

    // Packet's base builder
    fn create_packet(&mut self, pid: i32) -> Vec<u8> {
        // Creating empty packet's buffer
        let mut packet = Vec::new();
        // Creating length's bytes buffer and fill it as VarInt
        let mut len_bytes: Vec<u8> = Vec::new();
        len_bytes.write_varint(pid);
        // Writing full packet's length(content + length's bytes)
        packet.write_varint((self.len() + len_bytes.len()) as i32);
        // Writing length bytes
        packet.extend_from_slice(len_bytes.as_slice());
        // Drop(Free) length bytes buffer
        drop(len_bytes);
        // Writing some packet's content
        packet.extend_from_slice(self.as_slice());
        // Returning result
        packet
    }
}

Запись к буферам выглядит в разы интересней из-за больших требований к стандарту протокола MineCraft.

Обработчики пакетов на статусы 0(HandShaking) и 1(Status) расположены в одном файле src/network/handler.rs и в нём на каждый тип своя функция.

Вот например HandShaking:

pub fn handshaking(conn: &mut NetworkClient, event: &Event) -> SResult<bool> {
    // Checking if we can read the package
    if !event.is_readable() {
        return Ok(false);
    }
    // Reading packet
    let handshake = read_handshake_packet(conn);
    // Checking if is error
    if handshake.is_err() {
        return Ok(true);
    }
    // Getting results
    let (_, _, _, next_state) = handshake.unwrap();
    // Change types
    conn.conn_type = match next_state {
        1 => STATUS,
        _ => STATUS,
    };
    Ok(false)
}

Хоть функция и имеет 20 строчек, но в ней мы требуем лишь чтения первого пакета для определения следующего статуса ну и основное чтение пакета происходит в read_handshake_packet:

Функция чтения HandShake

pub fn read_handshake_packet(client: &mut NetworkClient) -> SResult<(u32, String, u16, u32)> {
    // Read bytes from client
    let (ok, p, err) = match client.read() {
        Ok((ok, p)) => (ok, Some(p), None),
        Err(err) => (false, None, Some(err)),
    };
    // If failed to read when...
    if !ok || err.is_some() {
        return Err(SimpleError(
            String::from("Failed to read packet"),
            if err.is_some() { err.unwrap().1 } else { None },
        ));
    }
    // Reading packet
    let mut p: Vec<u8> = p.unwrap();
    // Try to read Length and PacketID from packet(on handshaking stage only 0x00)
    p.read_base()?;
    // Reading version, address and etc.
    let ver = p.get_varint()? as u32;
    let address = p.get_string()?;
    let port = p.get_u16();
    let next_state = p.get_varint()? as u32;
    // States can be only 1 - status, 2 - play
    if next_state >= 3 {
        return Err(SimpleError(String::from("Invalid client"), None));
    }
    // Returning results
    Ok((ver, address, port, next_state))
}

Мы читаем пакет и при ошибке возращаем её, а если получилось прочитать полностью, то и возращаем результаты чтения.

Для обработки статуса у нас есть иная функция:

pub fn status_handler(conn: &mut NetworkClient, event: &Event) -> SResult<bool> {
    // Checking if we can read and write
    if !event.is_readable() || !event.is_writable() {
        return Ok(false);
    }
    // Getting a input's bytes
    let (ok, p, err) = match conn.read() {
        Ok((ok, p)) => (ok, Some(p), None),
        Err(err) => (false, None, Some(err)),
    };
    // Checking if a read or not
    if !ok {
        return Ok(err.is_some());
    }
    // Packet's bytes
    let mut p: Vec<u8> = p.unwrap();
    // Cloning bytes(for ping-pong)
    let bytes = p.clone();
    // Reading a packet's length(and remove...) and PacketID
    let (_, pid) = p.read_base()?;
    match pid {
        // Is Ping List
        0x00 => {
            drop(bytes);
            conn.stream.write_all(&*create_server_list_ping_response());
        }
        // Is Ping-Pong
        0x01 => {
            conn.stream.write_all(bytes.as_slice());
            match conn.stream.peer_addr() {
                Ok(v) => info!("Server pinged from {}", v),
                Err(_) => {
                    info!("Server pinged.")
                }
            }
        }
        _ => {}
    }
    Ok(false)
}

В ней снова при вызове в первую очередь проверяем на возможность не только чтения, но и записи так-как на этом этапе мы всегда отдаём некий результат. Снова читаем буффер и пытаемся прочитать его начал сохранив при этом айди пакета(0x00 — Список, 0x01 — Пинг-Понг) я так-же реализовал небольшой генератор буффера для списка:

Сам генератор ответа на список

// Structs for status MOTD response
#[derive(Debug, Serialize)]
pub struct ListPingResponse {
    pub version: ListPingResponseVersion,
    pub players: ListPingResponsePlayers,
    pub description: ChatMessage,
}

#[derive(Debug, Serialize)]
pub struct ListPingResponseVersion {
    pub name: String,
    pub protocol: u32,
}

#[derive(Debug, Serialize)]
pub struct ListPingResponsePlayers {
    pub max: u32,
    pub online: u32,
    pub sample: Vec<ListPingResponsePlayerSample>,
}

#[derive(Debug, Serialize)]
pub struct ListPingResponsePlayerSample {
    pub name: String,
    pub id: String,
}
/// Build packet's bytes as result
pub fn create_server_list_ping_response() -> Vec<u8> {
    // Initialize empty byte's vector
    let mut bytes = Vec::new();
    // Generating String and convert to bytes.
    // String generated as JSON by serde and serde_json libraries
    bytes.write_string(
        serde_json::to_string(&ListPingResponse {
            version: ListPingResponseVersion {
                name: String::from("ULE"),
                protocol: PROTOCOL_VERSION,
            },
            players: ListPingResponsePlayers {
                max: 10,
                online: 0,
                sample: vec![],
            },
            // Some clients can read colors and so on without convert into JSON
            description: ChatMessage::str("&a&lHello!"),
        })
        .unwrap(),
    );
    // Build completed packet. Server List Ping - PacketID is 0x00
    bytes.create_packet(0x00)
}

Для него мы сначала используем информацию о том как должен выглядеть JSON ответа и для ответа мы делаем пустой буффер, записываем байты переведённой сструктуры в JSON и генерируем пакет с айди 0x00. Для серелизации используем serde и serde_json.

Если пингануть сервер, то можно увидеть будет результат. При получении пинг-понг мы просто отправляем копию буфера так-как это более экономный вариант так-как иначе бы пришлось читать Long и другое, что нагружало бы процессор и ОЗУ больше чем просто копия буффера.

Последнее… Input в консоли или же STDIN.

Последняя функция из main.rs — ввод комманд, пока он будет очень примитивный и иметь всего stop и вывод введённого. Так-как имеется не так много возможностей казалось бы, то и ничего важного не будет, но нет! Он будет нам блокировать основной поток приложения возволяя ему работать, ведь если основной поток будет остановлен, то и вся программа остановится. Поэтому как выглядит функция так:

use crate::network::{NET_SERVER_WORKS, SHUTDOWN_SERVER};
use std::time::Duration;
use std::{io, process, thread};

// Loop for handling input
pub fn start_input_handler() -> std::io::Result<()> {
    // Input buffer
    let mut inp = String::new();
    // STDIN - os input
    let stdin = io::stdin();
    // loop for infinity handling
    loop {
        // Before write buffer we need to clear buffer
        inp.clear();
        // Reading a line
        stdin.read_line(&mut inp)?;
        // Clearing input's buffer
        inp = inp.replace("n", "");
        // Simple realisation of stop command, but in updates be removed from here in another place
        if inp.starts_with("stop") {
            // Sending status to shutdown network server
            *SHUTDOWN_SERVER.lock().unwrap() = true;
            info!("Stopping server...");
            // Running process killing in 6 secs if failed to common shutdown
            thread::spawn(|| {
                thread::sleep(Duration::from_secs(6));
                process::exit(0);
            });
            // Waiting for shutdown network's server
            loop {
                if *NET_SERVER_WORKS.lock().unwrap() == true {
                    thread::sleep(Duration::from_millis(25));
                } else {
                    break;
                }
            }
            // Disabling the input
            return Ok(());
        }
        // If it's not stop command - when display buffer, but in updates be removed
        info!("Entered: {}", inp);
    }
}

Мы сначала создаём буффер и stdin, а так-же запускаем цикл в котором сначала очищаем буффер от прошлого ввода и потом блокируем поток в ожидании ввода. При получении ввода проверяем на содержмиое и если это stop, то устанавливаем сетевому серверу информацию о том, что надо выключать слушатель и дожидаемся выключения, но если за 6 секунд не произошло отключения — завершаем процесс с кодом 0, если же у нас была введена иная комманда, то просто выводим её в консоль с уровнем INFO, да, просто выводим.

Итог части #1.1

Данная часть была как-бы заменой #1 и в данной конечно из важного было:

  1. Переход на Rust с языка Go

  2. Основной язык проекта — Английский

  3. Улучшение производительности в разы благодаря Rust

  4. Создание логгера и ввода

  5. Полная работа ядра пока в 2 потоках, а не как выходило в Go

Вот такие изменения я думаю стоили такого перехода, тем более учитывая, что Rust мне больше нравиться благодаря своей приближённости к устройству и отличная работа с ОЗУ:

Использование ресурсов при активном пинге

Использование ресурсов при активном пинге
Результат при пинге сервера. Почти тоже самое, что и в прошлой части.
Результат при пинге сервера. Почти тоже самое, что и в прошлой части.

Сервер пингуется легко, потребляя 128КБ на Linux, а на моём 4300U запускался за 735236ns.


Я надеюсь вам интересно читать статьи о разработке ядра и снова скажу:

Исходный код ядра доступен на GitHub и если вы хотите поддержать меня валютой, то у меня есть patreon.

Напишите можалуйста ваше мнение о моём процессе. Буду стараться отвечать на все.

Взаранее скажу, что плагины скоро буду вводить и они будут на основе WASM, скорее всего благодаря движку Wasmer.

Продвинутое создание сервера Minecraft на основе LinuxOS

> Данная статья была изменена! Вы можете столкнуться с проблемами нахождения прошлых команд

image
image
image
image
image

Создайте свой личный Minecraft Проект с использованием продвинутой и удобной информации`

Что будет еще в след. обновлениях?

Плановые изменения по работе с Linux OS

  • Работа с системой (База от DIDIRUS)

Нацеленность будущего гайда исключительно для работы с вашим сервером Minecraft.

  • Продвинутая настройка Linux Firewall (База от DIDIRUS)

Настройки netfilter, nat с использованием утилиты iptables, возможно еще ufw. Исключительно для работы с вашим сервером Minecraft.

Вероятнее всего будет меньше информации о самой игре Minecraft, поскольку автору в данный момент не особо интересно это.
Однако, Linux OS будет всегда с нами :)

О документе

Версия документа v3.11 от 06.10.2022

* Автор занят другими Проектами на Github, вы можете в данный момент получать дополнительную информацию *
* Данная статья создана для удобства в управлении вашим Проектом в Minecraft под управлением Linux *

* На системах Арч линукс нет команды apt (Пример: pamac/pacman). Учтите этот факт при настройке вашей UNIX подобной системы *

Некоторые рекомендации из пунктов могут работать некорректно на некоторых системах
За подробной поддержкой обращайтесь в мой дискорд - https://discord.gg/7XkGYJbtZg

Для полноценной настройки рекомендую использовать Adoptium Java

<  !  >  ПОЖАЛУЙСТА ЧИТАЙТЕ ВНИМАТЕЛЬНО И НЕ ПРЕДЪЯВЛЯЙТЕ ПРЕТЕНЗИЙ АВТОРУ СТАТЬИ  <  !  >

Основные ссылки на контент

OpenJDK | Java |

FabricMC | Fabric |

PaperMC | Server Software |

PurPur | Server Software |

Petal | Server Software |

Velocity Website Velocity From PaperMC | Proxy Software |

Настройка вашего Linux сервера

Основное

  • Базовые компоненты для вашего сервера
  • Некоторые команды выполняются от ~ root пользователя, либо через sudo

Обновление пакетов машины


sudo apt update -y && sudo apt upgrade -y

Специально для CentOS 8 (Не поддерживается автором статьи)

yum

yum update

yum upgrade

dnf

sudo dnf install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm

dnf install htop

dnf install screen

Полезные утилиты для вашего сервера

sudo apt install htop  -  Утилита для мониторинга всех запущенных процессов (Подобно top, но красивее)
sudo apt install screen  -  Важная утилита для создания сессий на вашей серверной машине
sudo apt install zip unzip  -  Утилита для архивации файлов в .zip
sudo apt install iptables  -  Полезная утилита для настройки IPv4 и IPv6 флагов (Firewall) (Можно управлять портами)
sudo apt install neofetch  -  Утилита для красивого отображения вашей ОС и некоторых параметров
sudo apt install fontconfig - Данный пакет шрифтов может потребоваться для некоторых плагинов*

# Удобная установка всех полезных пакетов в одну строку + FIREWALLD (Работает с IPTables)
sudo apt install htop screen zip unzip iptables nload neofetch dnsutils iptraf-ng vnstat fontconfig smartmontools firewalld -y

# Удобная установка всех полезных пакетов в одну строку + UFW (Работает с IPTables)
sudo apt install htop screen zip unzip iptables nload neofetch dnsutils iptraf-ng vnstat fontconfig smartmontools ufw -y

Может решить вашу проблему с портами на Oracle Cloud — откройте порт UDP/TCP 25565

# Использование с UFW утилитой
sudo ufw enable # <- По умолчанию он выключен, поэтому его следует включить.
sudo ufw allow 25565 comment "Данный порт открыт по UDP/TCP протоколам для всех входящих соединений"
sudo ufw reload # <- На всякий случай.

# Использование с FIREWALLD утилитой
sudo apt install firewalld # <- Установка пакета не требуется, если вы установили его в начале этой статьи.
sudo firewall-cmd --permanent --zone=public --add-port=25565/tcp
sudo firewall-cmd --permanent --zone=public --add-port=25565/udp
sudo firewall-cmd --reload # <- Обычно он всегда требует перезагрузки для того, чтобы новые правила вступили в силу.

# < ! > Обратите особое внимание во избежание утраты доступа к вашей Linux машине < ! >
# По умолчанию FIREWALLD и UFW утилиты закрывают все порты, конечно в них имеются исключения для 22/tcp порта,
# Однако в любом случае рекомендуется вручную открыть OpenSSH порт.
# Использование в UFW и FIREWALLD:

# sudo ufw allow 22/tcp comment "Порт для использования удалённого подключения к данной машине по SSH протоколу"
# sudo firewall-cmd --permanent --zone=public --add-port=25565/tcp
# sudo firewall-cmd --reload # <- Обычно он всегда требует перезагрузки для того, чтобы новые правила вступили в силу.

Установка Java на вашу серверную машину

  • Вы научитесь легко и просто устанавливать и удалять Java с вашего сервера
  • Для начала зайдите в SFTP клиент и перейдите в раздел ~/opt (Можно любой, но этот в качестве основы)
  • В Linux изначально придумана команда для упаковки архивов и распаковки архивов
  • Использовать команду tar можно с использованием следующих параметров:
# -c | --create — создать архив
# -a | --auto-compress — дополнительно сжать архив с компрессором который автоматически определить по расширению архива.
# -r | --append — добавить файлы в конец существующего архива
# -x | --extract, --get — извлечь файлы из архива
# -f | --file — указать имя архива
# -t | --list — отобразить список файлов и папок в архиве
# -v | --verbose — выводить список обработанных файлов
# -u | --update — Обновить архив новыми файлами
# -d | --diff, --delete — Проверить начилие архивов, удалить файл из архива
  • Пример перемещения файлов по системе Linux
# < ! > НЕ ОБЯЗАТЕЛЬНЫЕ КОМАНДЫ < ! >

mv /home/others/Test /others2
# Также вы можете использовать флаг* -v чтобы увидеть подроную информацию о процессе
# ~ — тильда, дает понять системе, что это корневой каталог root (~)
# Т.е ~/others2 и т.д

mv -v ~/home/others/Test ~/others2

# Вы можете также использовать приставку sudo к команде mv

# Теперь вы знакомы с командой для перемещения файлов, но рекомендуется еще раз закрепить материал
# Попробуйте данные команды на каком-то пустом сервере, либо можете установить WSL2 на вашу систему
# Рекомендуемый дистрибутив ОС для создания Проектов (Серверов) — Ubuntu, Debian

Начало процесса установки Java на ваш Linux сервер

  • Установка и распаковка архива при помощи «tar» — встроенная утилита упаковки/распаковки архива с файлами в Linux
# Архив уже должен быть установлен / перемещен в выбранную вами директорию
# Чтобы установить архив, вы можете использовать в качестве передачи файлов SFTP приложение
# WinSCP, либо же скачать сразу из консоли - wget <url>
# Команду писать без <>
# Вы также можете использовать - curl
# Если в вашей системе данная утилита отсутствует, то вы можете установить её самостоятельно

# sudo apt install curl

# Пример использования:
# curl https://api.papermc.io/v2/projects/paper/versions/1.19.2/builds/125/downloads/paper-1.19.2-125.jar -o paper.jar

# Теперь давайте его распакуем — после распаковки появится папка с нашей Java
# Выполните команду:

tar -xvf archive.tar.gz

# Например архив с Java называется:
Скачиваем к примеру Adoptium JDK Java 18 под amd64 платформу (Ubuntu, Debian, Arch)

# Данная команда скачает и установит вам Java JDK 18.0.2.1_1 (Предыдущая была 18.0.2.9 (Старее)) от Adoptium, вам нужно лишь только ввести её в терминал
cd /opt && sudo wget https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.2.1%2B1/OpenJDK18U-jdk_x64_linux_hotspot_18.0.2.1_1.tar.gz && sudo tar -xf OpenJDK18U-jdk_x64_linux_hotspot_18.0.2.1_1.tar.gz && sudo ln -svf /opt/jdk-18.0.2.1+1/bin/java /usr/bin/java && java -version

# Данная команда скачает и установит вам Java JDK 19+36 новейшая релизная Java от Adoptium, вам нужно лишь только ввести её в терминал
cd /opt && sudo wget https://github.com/adoptium/temurin19-binaries/releases/download/jdk-19%2B36/OpenJDK19U-jdk_x64_linux_hotspot_19_36.tar.gz && sudo tar -xf OpenJDK19U-jdk_x64_linux_hotspot_19_36.tar.gz && sudo ln -svf /opt/jdk-19+36/bin/java /usr/bin/java && java -version

# < ! > Название архива может быть иное < ! >
# Информацию об установке Java ниже можете не применять при вводе команды выше.
# Для распаковки архива потребувется ввести всего одну команду (Уже применена в примерной установке выше):

tar -xvf OpenJDK18U-jdk_x64_linux_hotspot_18.0.2_9.tar.gz

# На момент создания статьи последняя Java, которую лично я проверял у себя на Проектах
# Выполните данные команды для скачивания архива:

sudo wget ССЫЛКА
# Из примера выше
## curl https://api.papermc.io/v2/projects/paper/versions/1.19.2/builds/125/downloads/paper-1.19.2-125.jar -o paper.jar

# В нашем случае это не PaperMC, а Java, поэтому:
sudo wget https://github.com/adoptium/temurin18-binaries/releases/download/jdk-18.0.2%2B9/OpenJDK18U-jdk_x64_linux_hotspot_18.0.2_9.tar.gz

# Также можно использовать утилиту CURL, заместо WGET
sudo curl ссылка -o выходной_файл.расширение

# Все версии Java доступны по этой ссылке: https://github.com/orgs/adoptium/repositories

# Если вы сделали всё правильно, то теперь вы получите информацию о вашей Java при вводе данной команды:
java -version

Создание «Линка» для нашего файла Java в папке с самой Java

  • Вы можете подробно изучить про команду ln и ее параметры
# Линки — тоже самое что ярлык, те кто когда либо имели систему Windows / MacOS 
# Могут знать про создание ярлыка при помощи клика ПКМ по файлу
# Но у нас ведь нет интерфейса, поэтому будем использовать команду "ln"
# *Кстати можно делать линки также через WinSCP — Клиент SFTP, FTP . . .
# Создаем ярлык (линк) для нашего исполняемого файла

# Выполните команду:
ln -svf /opt/.../bin/java /usr/bin/java

# Заместо ... пишем название папки с Java — /opt/jdk-18.0.2+9/bin/java
ln -svf /opt/jdk-18.0.2+9/bin/java /usr/bin/java

# Если вы все сделали правильно, то вы успешно установили Java на вашу машину

Удаление Java с нашего сервера

  • Многие скажут, что есть команда sudo apt remove java и т.д, но это самый простой способ — можно также и через команды
# Для начала перейдем в корень сервера ~
# Далее переходим по пути ~/usr/bin
# Используем удобный вам способ для поиска файлов — мне было удобно через WinSCP Клиент
# Находим файл "Java" — он должен быть единственный в данной директории!
# Спокойно без боязни удаляем его — Готово вы удалили активную Java с вашего сервера, однако
# Она все еще существует как папку и архив по пути ~/opt
# Можно сделать это через команды

cd /usr/bin && sudo rm java

# Проверить наличие активной Java, можно введя команду

java -version

# При успехе, у вас должно вывести ошибку на счёт команды java

Настройка безопасного входа на сервер — Linux

  • В качестве альтернативы простым паролям, мы будем использовать Rsa_Keys с форматом шифрования SHA

  • Генерация и установка ключей на сервер

# Открываем приложение основной терминал системы (Terminal, Powershell, Konsole (Manjaro) . . .)

ssh-keygen # По умолчанию генерирует 2048 Битный ключ

ssh-keygen -b 4096 # Генерация ключа с мощностью 4096 Бит (Лучше чем 2048)
ssh-keygen -b 8192 # Генерация ключа с мощностью 8192 Бит (Лучше чем 4096)

# Далее внимательно читаем логи, вы уже почти создали пару ключей на вашем ПК ~/users/ВашЮзер/.ssh

# Чтобы передать ключ на наш сервер, воспользуемся данной командой (Вам потребуется войти с использованием "старого" пароля)

cat ~/.ssh/id_rsa.pub | ssh USER@IP "mkdir -p ~/.ssh && touch ~/.ssh/authorized_keys && chmod -R go= ~/.ssh && cat >> ~/.ssh/authorized_keys"

# < ! > Подсказка для всех < ! >
# Если вы не хотите использовать такой способ авторизации, то установите очень сильный, надежный, ультра мега крутоооой пароль :)
# Самое главное не палите своего юзера, ведь только тогда вас не будут брутить, 
# Т.к попросту не будут знать пользователя для старта брутфорса. Однако перейдем к нашим RSA ключикам.
# Вы можете вручную просто вписать информацию из публичного RSA ключа в '~/.ssh/authorized_keys'
# На своем устройстве получите информацию из файлы id_rsa.pub, например cat id_rsa.pub
# Впишите данную информацию из файла id_rsa.pub в файл authorized_keys вашего юзера Linux '~/.ssh/authorized_keys'
# При использовании данного способа, пожалуйста соблюдайте бдительность, чтобы ничего не сломать в вашей системе

# Останется ввести только и вы примените изменения из подсказки выше:
sudo systemctl restart ssh


# ... USER@IP ...
# Подставьте ваши данные заместо шаблона
# USER — Логин, ваш пользователь на серверной машине
# IP — Ваш IP от серверной машины


# ВНИМАНИЕ! Мы начинаем отключать парольную аутентификацию на сервере, будьте аккуратны
# *Автор не несет ответвенности, если вы вдруг сломаете вход на вашу серверную машину,
# Обязательно создавайте бэкапы ваших игровых серверов*


# Перед этим убедитесь, что у вас установлены пакеты:

apt install sudo
apt install nano

# sudo — Нужно использовать если у вас основной user != root
# nano — Удобный редактор текста через SSH

# Как сохранить файл через nano

# CTRL + X , Y (yes), ENTER

# Готово, теперь вы умеете сохранять файлы, но все таки перейдем к отключению парольной авторизации
# Вводим команду:

sudo nano /etc/ssh/sshd_config

# Вам нужно найти или написать данную строчку:

PasswordAuthentication no

# После сохраняем файл (Мы используем nano в качестве редактора текстовых файлов)
# Далее нам необходимо перезагрузить SSH клиент
# < ! > Советуем проверить доступ к SSH < ! >
# Вернитесь в PowerShell и введите

ssh USER@IP

ssh USER@IP -i ./ключ

# Если вы пытаетесь зайти через GNU Linux, то используйте команду ниже

sudo ssh USER@IP -i ключ

# ... USER@IP
# Подставьте ваши данные заместо шаблона
# USER — Логин, ваш пользователь на серверной машине
# IP — Ваш IP от серверной машины

# Далее если все хорошо, перезагружаем ssh

sudo systemctl restart ssh

# Готово, теперь зайти на вашу серверную машину через пароли не получится, используем только ключи авторизации (rsa)

Различные команды и бенчи, которые возможно вам понадобятся

  • В основном подойдет для мониторинга и наблюдения за жизнедеятельностью вашей серверной машины
# Информация по системе

# Скорость интернета на вашем сервере (Установка + автозапуск)
sudo apt install speedtest-cli && speedtest-cli

# Через бенчмарк
sudo wget -qO- bench.sh | bash 

# Информация о системе (Установка + автозапуск)
sudo apt install neofetch && neofetch

# Встроенные Linux команды (Примеры)
lscpu
lspci
uname -a

IPTables (UFW/FIREWALLD) — Закрытие порта SSH, SFTP (22) + ICMP DROP

  • На самом деле не рекомендую делать такое с динамическим IP, иначе вы рискуете потерять доступ к вашей серверной машине
# Быстродействие (Без закрытия SSH порта):
# sudo apt install ufw && sudo ufw allow 22/tcp && sudo nano /etc/ufw/before.rules
# Для before.rules:

# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j ACCEPT
-A ufw-before-input -p icmp --icmp-type time-exceeded -j ACCEPT
-A ufw-before-input -p icmp --icmp-type parameter-problem -j ACCEPT
-A ufw-before-input -p icmp --icmp-type echo-request -j ACCEPT

# ИЗМЕНИТЬ НА СЛЕДУЮЩЕЕ ЗНАЧЕНИЯ ufw-before-input
# ok icmp codes for INPUT
-A ufw-before-input -p icmp --icmp-type destination-unreachable -j DROP
-A ufw-before-input -p icmp --icmp-type time-exceeded -j DROP
-A ufw-before-input -p icmp --icmp-type parameter-problem -j DROP
-A ufw-before-input -p icmp --icmp-type echo-request -j DROP

# Сохраняем конфигурацию через наш nano редактор (CTRL X + Y + ENTER)
# Перезагружаем UFW

sudo ufw reload

# Теперь все пинги блокируются извне

# Ниже представлена информация из документации v2.0 (Прошлая)

# Базовые настройки IPTables | Запрет пинга на ваш дедик | Запрет входа с других айпи по SSH (только ваш)

iptables -A INPUT -s IP/32 -p icmp -j DROP

# Или через FirewallD

sudo firewall-cmd --add-icmp-block=echo-request --permanent

# Разрешить свой айпи для входа через SSH,SFTP - ПУНКТ ДЕЛАТЬ ПЕРВЫМ ИЗ ВСЕХ!

iptables -A INPUT -p tcp --dport 22 -s YourIP -j ACCEPT

# Дропнуть порт 22 (aka SSH, SFTP). < ! > ДЕЛАТЬ ПОСЛЕ РАЗРЕШЕНИЯ < ! >

iptables -A INPUT -p tcp --dport 22 -j DROP

# Установка

apt-get install iptables-persistent

# Правила iptables необходимо создать, затем выполнить следующую команду

service iptables-persistent start

# Поскольку утилита является демоном — прекратить ее работу нельзя, однако можно очистить список правил

service iptables-persistent flush

# Обновить правила, если persistent уже был установлен

dpkg-reconfigure iptables-persistent

IPTables — Закрытие портов на нескольких серверных машинах

  • Здесь вы подробно узнаете как легко и быстро закрыть порты
# Представим что у нас есть два сервера VDS / VPS
# Первый сервер под маркой X - Это у нас будет Proxy сервер (Фильтр различных ботов, пакетов (TCP))
# Второй сервер под маркой Y - Это у нас будет Survival сервер (Основное выживание)
# На сервере X пропишите следующие команды в данном порядке, как они даны (Свреху вниз)

iptables -A INPUT -s <IP Y> -j ACCEPT
iptables -A INPUT -s 127.0.0.1 -j ACCEPT

# На сервере Y пропишите следующие команды в данном порядке, как они даны (Сверху вниз)

iptables -A INPUT -p tcp -s <IP X> --dport <PORT Y (Survival)> -j ACCEPT
iptables -A INPUT -p tcp --dport <PORT Y (Survival)> -j DROP

# После манипуляций на каждом VDS / VPS нужно ввести данную команду

apt install iptables-persistent

# Если у вас он уже был установлен, то просто обновите правила используя команду

dpkg-reconfigure iptables-persistent


# Если хотите можете также ознакомиться со списком ваших правил iptables на каждой из виртуальных машин

iptables --list

# Узнать номера всех правил

iptables -L --line-numbers

# Удалять правила можно следующим способом

iptables -D INPUT ЧИСЛО

«WinRar» — Известный архиватор, но для Linux (Лучше использовать tar)

# Установка
apt install zip unzip

# Там где нужно будет создать архив - у меня это папку /home
cd home

# Архивирование папки/файла | Можно находиться в любом пути (Вы указываете конкретно путь до папки/файла , который нужно заархивировать)
zip -r NAME.zip /home/BungeeCord

# Для примера в моем случае
# /home - дирректория папки с сервером
# /BungeeCord - сама папка с банджей, можно любую например: Survival, Anarchy, SkyBlock.

zip -r surv.zip /home/Survival

# Если имеется SkyBlock папка с сервером, то введите эту команду
# Указывать можно любой сервер, также вы можете например хранить сервер по пути /servers/BungeeCord
# Не обязательно использовать /home раздел для серверов!

zip -r sb.zip /home/SkyBlock

# Либо используйте встроенный tar

MySQL — Для Linux

# Установка MySQL
sudo apt install mysql-server

# Установка MariaDB (Лучше чем MySQL, является его форком)
sudo apt install mariadb-server

# Вход в саму БД
sudo mysql

# Создать БД
CREATE DATABASE IF NOT EXISTS DATABASE_NAME;

# Удалить БД
DROP DATABASE IF EXISTS DATABASE_NAME;

# Список БД
SHOW DATABASES;

# Создать юзера
CREATE USER IF NOT EXISTS 'DATABASE_USER'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'DATABASE_USER_NEW_PASSWORD';

# Создать юзера для MariaDB
CREATE USER IF NOT EXISTS 'DATABASE_USER'@'localhost' IDENTIFIED BY 'DATABASE_USER_NEW_PASSWORD';

# Изменить пароль юзеру
ALTER USER 'DATABASE_USER'@'localhost' IDENTIFIED WITH caching_sha2_password BY 'DATABASE_USER_NEW_PASSWORD';

# Изменить пароль юзеру для MariaDB
ALTER USER 'DATABASE_USER'@'localhost' IDENTIFIED BY 'DATABASE_USER_NEW_PASSWORD';

# Удалить юзера
DROP USER IF EXISTS 'DATABASE_USER'@'localhost';

# Список юзеров
SELECT user, host FROM mysql.user;

# Выдать права юзеру на определенную БД
GRANT ALL PRIVILEGES ON database_name.table_name TO 'DATABASE_USER'@'localhost';

# Выдать все права юзеру на все БД
GRANT ALL PRIVILEGES ON *.* TO 'DATABASE_USER'@'localhost';

# Список всех прав юзера БД
SHOW GRANTS FOR 'DATABASE_USER'@'localhost';

# Сделать дамп всей БД
mysqldump -uDATABASE_USER -pDATABASE_USER_PASSWORD --all-databases > DATABASE_CUSTOM_DUMP_NAME.sql

# Сделать дамп определенной БД
mysqldump -uDATABASE_USER -pDATABASE_USER_PASSWORD DATABASE_NAME > DATABASE_CUSTOM_DUMP_NAME.sql

# Если вы хотите скопировать ваш дамп БД в другое место
cp /var/lib/mysql/DATABASE_CUSTOM_DAMP_NAME.sql /home/testuser/

Отключение пароля для $ sudo {cmd}

# Для начала создайте файл (Делать от sudo или root пользователя)
# sudo visudo -f /etc/sudoers.d/sudowpass
sudo nano /etc/sudoers.d/sudowpass

# В файл sudowpass нужно вписать следующее:
YourUserHere ALL=(ALL) NOPASSWD:ALL

Передача файлов по SSH через scp

scp USER@IP:/путь_для_файла_на_другой_сервер название_файлика_на_вашем_сервере

# Использовать scp с кастомным портом SSH
# Для примера передадим файл test.dat на наш сервер Linux
scp -P 999 root@127.0.0.1:/home/path/to/path/test.dat /home/path/to/but/here/test.dat

# У вас запросит пароль, если вы используете ключи, то добавьте -i

Создание пользователя замена ~ root

# Замените '$name' вашим новым кастомным юзером
adduser $name

# Выдача прав на sudoers для нового юзера
# usermod -aG sudo $name
adduser $name sudo

# Забрать группу sudoers у $name
deluser $name sudo

Настройка языковых пакетов на Linux

sudo dpkg-reconfigure locales
# Выберите сначала Англ раскладку UTF=8
# После выберите пунктом выше C.UTF-8 и нажмите Ok
# Вы можете настроить языковые пакеты для себя.

Раздел главных настроек нашего серверного ядра

Защита вашего сервера

  • Не испульзуйте плагины с левых сайтов! Хотя я и использую плагины из других источников, однако я имею определенные знания для поиска дыр в них, поэтому использую в итоге плагины с чистым кодом. Вам определенно не рекомендую как новичкам (Возможно здесь имеются тоже люди, которые разбираются в этом, но всё таки статья ориентирована на начинающую категорию пользователей.
Если вы хотите сохранить безопасность вашего сервера и ваших игроков,
то лучше всего скачайте оригинальные плагины,
а если нужно купить, то лучше купите их.
  • Закрывайте все ненужные порты, а нужные открывайте!
Это конечно не критично, но лучше всего закрыть все порты. 
После установки UFW / FIREWALLD они автоматически закрываются, 
кроме 22*, однако мы рекомендуем вручную открыть SSH/SFTP порт.

Оптимизация сервера

  • Пожалуйста не используйте плагины на оптимизацию, в большинстве случаев они грузят сервер сильнее
> CleagLagg Plugin - Да, данный плагин может быть полезен для версий 1.12.2 и ниже, но для новых версий он вызывает
куда больше нагрузки, нежели помогает ( PaperMC давно имеет функционал данного плагина* )
Отключение или ограничение вагонеток, арморстендов через данный плагин не спасет вас от крашей.

> Стаккеры мобов в одного моба (Много подобных плагинов) - Даже от них нет особого смысла. 
Пейпер давно позволяет нормально оптимизировать мобов, а если мы используем форки по типу фуги или пурпура, 
то возможностей куда больше для оптимизации вашего игрового сервера

> АвтоОчистка лута на земле - Изучите файлики paper.yml / spigot.yml, но пожалуйста не используйте для этого плагины по типу ClearLagg

> Платный плагин / Самопис от себя или студий - Данное ПО не является аргументом для вашей производительности, 
лучше придерживаться уже известных плагинов, а по вашему желанию вы можете их дополнить, т.к большинство из них OpenSource

О создании игрового сервера в Minecraft

Рекомендуемое ПО для запуска сервера

  • Если вы планируете разрабатывать модовый сервер, то определенно рекомендую Fabric

Моды можно найти здесь или здесь

  • Если вы планируете разрабатывать обычный сервер, то определенно рекомендую PaperMC | PurPur

Рекомендуемое ПО Для разработки Proxy сервера Velocity | Velocity с сайта PaperMC

VDS/DEDICATED или PANEL HOSTING?

  • Автор данного поста не поддерживает панельные хосты из-за их ограничений для клиента. Если вы хотите создать качественный Проект, то пожалуйста придерживайтесь использовать выделенных или виртуальных серверо с полным доступом к SSH протоколу

Об авторе

Какое ПО используется для разработок игровых проектов (Сервер-сайд)

  • Я пользуюсь этими ПО: Fabric | PaperMC | PurPur | Velocity

Какое ПО используется для разработок игровых клиентов (Клиент-сайд)

  • Я пользуюсь этим ПО: Fabric

Какое ПО используется для подключения к серверу по SSH, SFTP

  • Я пользуюсь этими ПО на Windows: Termius (SSH Полностью бесплано, SFTP Пробная версия (Теперь бесплатно в FREE Plan :D), потом платно | WinSCP (SFTP)

Понравилась статья? Поделить с друзьями:
  • Как написать свастику на клавиатуре
  • Как написать свадебную клятву невесте
  • Как написать свадебному фотографу
  • Как написать свадебное приглашение
  • Как написать св читс