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

Почему это все таки сервер(или все-таки прокси)

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

Как устроенна локальная сессия игры?

Когда мы запускаем локальную сессию игры, клиент Minecraft запускает встроенный сервер:

Интересно что сервер доступен как по 19132 порту, так и по 50968
Интересно что сервер доступен как по 19132 порту, так и по 50968

Это создает возможность подключаться к нашему миру как через локальную сеть, так и через xbox live(который тут выступает в виде сервиса публичного туннеля). Вероятнее всего, основной клиент общается с миром точно так же, как с ним общаются все остальные клиенты, через UDP протокол RakNet. На основе этого предположения, мы можем сделать вывод, что клиентская часть игры существует обособлено от управляющей и основное ядро Minecraft заложенно в серверной части.Написать свой сервер с нуля, это написать свой Minecraft. Возможно ли это? Возможно, протоколы полностью описаны, существуют реализации протокола на разных языках. Нужно ли нам это для модификации игрового процесса? Давайте разбираться.

Можно ли называть наш сервер прокси?

Является ли сервер, прокси между клиентом и базой данных? Является ли база данных прокси между сервером и файловой системой? Наша прослойка общается с оригинальным сервером и клиентом по тому-же протоколу, по которому оригинальный сервер и клиенты могли бы общаться сами со себе. Но делает ли это нашу прослойку просто прокси? Оригинальный сервер в нашем случае, выступает в роли готового движка, который предоставляет api интерфейс для нас, через udp, когда как наш сервер содержит всю дополнительную логику:

Зачем нам реализовывать ожидаемую от Minecraft логику с нуля, когда мы можем подмешивать к уже существующей свою? Нужна ли нам та гибкость, которую мы получим при нативном обращении к коду движка?

Так все выглядит на самом деле
Так все выглядит на самом деле
Так это должно быть у нас в голове
Так это должно быть у нас в голове

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

Как расширять функциональность нашего сервера?

Думая о том, как можно расширять функциональность нашего сервера, так чтобы различные части функциональности могли работать как синхронно, так и асинхронно, я не смог придумать варианта лучше, чем разделение функциональности на модули, последовательность выполнения которых будет настраиваться в callback структуры сервера:

server/server.go

package server

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"log"
)

type Server struct {
	listener          *minecraft.Listener
	ConnectionHandler func(*minecraft.Conn, *minecraft.Listener)
}

func (server *Server) Start() {
	p, _ := minecraft.NewForeignStatusProvider(":19131")

	listener, err := minecraft.ListenConfig{
		StatusProvider:         p,
		AuthenticationDisabled: true,
	}.Listen("raknet", ":19130")
	if err != nil {
		panic(err)
	}

	server.listener = listener
	server.acceptConnections()
}

func (server *Server) acceptConnections() {
	for {
		c, err := server.listener.Accept()
		if err != nil {
			log.Println(err)
		}

		go server.ConnectionHandler(c.(*minecraft.Conn), server.listener)
	}
}

modules/init_dialer.go

package modules

import "github.com/sandertv/gophertunnel/minecraft"

type InitDialer struct{}

func (InitDialer) Run(conn *minecraft.Conn) (*minecraft.Conn, error) {
	return minecraft.Dialer{
		ClientData: conn.ClientData(),
	}.Dial("raknet", ":19131")
}

modules/spawner.go

package modules

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"sync"
)

type Spawner struct{}

func (Spawner) Run(conn *minecraft.Conn, dialer *minecraft.Conn) error {
	var err error

	g := sync.WaitGroup{}

	g.Add(2)

	go func() {
		err = conn.StartGame(dialer.GameData())

		if err != nil {
			return
		}

		g.Done()
	}()

	go func() {
		err := dialer.DoSpawn()

		if err != nil {
			return
		}

		g.Done()
	}()

	g.Wait()

	return err
}

modules/proxy.go

package modules

import "github.com/sandertv/gophertunnel/minecraft"

type Proxy struct{}

func (Proxy) Run(conn *minecraft.Conn, dialer *minecraft.Conn, listener *minecraft.Listener) {
	go func() {
		defer dialer.Close()
		defer listener.Disconnect(conn, "connection lost")

		for {
			pk, err := conn.ReadPacket()
			if err != nil {
				return
			}

			err = dialer.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()

	go func() {
		defer dialer.Close()
		defer listener.Disconnect(conn, "connection lost")

		for {
			pk, err := dialer.ReadPacket()
			if err != nil {
				return
			}

			err = conn.WritePacket(pk)
			if err != nil {
				return
			}
		}
	}()
}

main.go

package main

import (
	"github.com/sandertv/gophertunnel/minecraft"
	"minecraft-server/modules"
	"minecraft-server/server"
)

func main() {
	s := server.Server{
		ConnectionHandler: handleConnection,
	}
	s.Start()
}

func handleConnection(conn *minecraft.Conn, listener *minecraft.Listener) {
	dialer, err := modules.InitDialer{}.Run(conn)

	if err != nil {
		listener.Disconnect(conn, "Connect error")

		return
	}

	err = modules.Spawner{}.Run(conn, dialer)

	if err != nil {
		listener.Disconnect(conn, "Spawn error")

		return
	}

	modules.Proxy{}.Run(conn, dialer, listener)
}

В нашем случае, мы обрабатываем ошибки синхронных модулей, чтобы иметь возможность завершить выполнение функции, когда как асинхронные модули берут эту ответственность на себя. Недостатком такой организации очевидны. Если какой-то асинхронный модуль сделает что с conn, dialer, listener, остальные асинхронные модули использующие их, посыпятся. Модуль в нашем случае понятие не имеющее никакого четкого определения и не подчиняющиеся никаким конкретным правилам. Мы можем позволить синхронным модулям обрабатывать ошибки, вызывать модули из других модулей, позволять им быть ассинхронными и синхронными одновременно.

Баним пользователя

modules/ban.go

package modules

import (
	"errors"
	"fmt"
	"github.com/sandertv/gophertunnel/minecraft"
)

type Ban struct{}

func (Ban) Run(conn *minecraft.Conn) error {
	if conn.IdentityData().DisplayName == "Steve" {
		return errors.New("player is banned")
	}

	return nil
}
func handleConnection(conn *minecraft.Conn, listener *minecraft.Listener) {
	err := modules.Ban{}.Run(conn)

	if err != nil {
		listener.Disconnect(conn, err.Error())

		return
	}
}

Заключение

Исходный код приведенный в публикации доступен здесь. Буду очень рад, если вы предложите свои варианты организации добавления функциональности и/или приведете аргументы против той, что используется в статье.

Комментарии (17)


  1. DaemonGloom
    17.01.2022 13:56

    Можете назвать это wrapper/обёртка, можете прокси — но это не сервер. Точно так же, как и forge не является сервером/клиентом майнкрафт, хоть и работает по похожему принципу.


    1. DavidNadejdin Автор
      17.01.2022 14:41

      Можно очень легко тут манипулировать тем, что мы понимаем под сервером Minecraft. Я бы хотел узнать ваше определенне. Но как мне кажется, то что описанно в статье, вполне подходит. Оно принимает, обрабатывает и отдает запросы, реализует определенный протокол, может фактически существовать без оригинального движка Minecraft. Если пытаться спорить о том чем все это дело является, нужно для начала четко разложить что такое клиент, сервер и ядро Minecraft. К сожалению ядро Minecraft спаяно с сервером. Но представьте что у вас появилась возможность обращаться к движку не через оригинальный сервер. Как изменится ваша цепочка действий для реализации определенного рода задач? Nukkit например, не заявляет о себе как о полноценном сервере Minecraft, скорее как о наборе инструментов, для реализации своего сервера или тула. Мне всегда казалось, что написание собственных игровых серверов, уже под готовые игры, подразумевает введение дополнительной функциональности, поверх существующей. Или можно поставить вопрос по другому. Если я начал бы выкладывать цикл статей - "пишем Minecraft ядро на Go", было бы такое название корректно? Тогда что под собой подразумевает написание сервера?


      1. saboteur_kiev
        17.01.2022 15:46
        +1

        может фактически существовать без оригинального движка Minecraft

        Вот давайте об этом подробнее. Убираем сервер майнкрафта, и что можно делать на вашем "сервере", чтобы туда было интересно заходить клиентам?

        Вы прицепились к слову "прокси" и "сервер" и почему-то считаете, что назвать ваш код "сервером" это очень важно.

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

        Мне всегда казалось, что написание собственных игровых серверов, уже под готовые игры, подразумевает введение дополнительной функциональности, поверх существующей

        Есть много вариантов, как можно расширять функционал. Можно с нуля писать весь движок (как например писали java версию Lineage2), можно дополнять функционал основного сервера, если есть возможность раздобыть исходники или рабочий вариант основного сервера (так вроде когда-то модифицировали gta на мультиплеер). Даже тот же pvpgn это все-таки был именно полноценным сервером, хотя его функционал был минимален.

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


        1. DavidNadejdin Автор
          17.01.2022 16:05

          Я как раз и говорю о том, что то что описано в статье, не является движком и делегирует задачу по получению основной логики, оригинальному серверу. Давайте представим что мы убираем оригинальный сервер, убираем модуль "proxy". Остается наш "сервер" способным принимать и отправлять коннекты? Да. Можем ли мы клиенту "нарисовать" что угодно? Можем. Вопрос в том, насколько интересно это будет для клиента, стоит вопросом написания ядра игры как мне кажется. Конкретно я не цепляюсь к терминам, так как мне кажется они тут довольно смазаны. Скорее чувствую обвинение в кликбейте. Да, мы делегируем в данный момент огромный пласт задачи оригинальному серверу. А если наш сервер в теории будет реализовывать больше логики, чем оригинальный сервер будет брать на себя? Оригинальный сервер будет счиаться уже микросервисом?


          1. saboteur_kiev
            18.01.2022 02:23

            Да. Можем ли мы клиенту "нарисовать" что угодно? Можем.

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


            1. DavidNadejdin Автор
              18.01.2022 02:31

              Он может взять и нарисовать клиенту что угодно, в рамках протокола RakNet. По вашему модуль прокси работает как какойнибудь симлинк? Вопросы "может ли" и "руководствуясь какой логикой", стоят раздельно


            1. DavidNadejdin Автор
              18.01.2022 02:36

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


            1. DavidNadejdin Автор
              18.01.2022 02:41

              Просто проблема спора, в том что вы то аппелируете к технической разнице между прокси сервером и сервером, то к user usability, говоря что так как описаный сервер не способен отдавать ожидаемую от него функциональность, сервером его называть нельзя. И вот это уже выглядит, как конкретный троллинг и издевательство. Вот техническое определение сервера, прочтите его пожалуйста. А задаваясь вопросом user usability, клиенту до лампочки, как именно вы реализуете для него логику ожидаемую от minecraft и как вы подмешиваете в нее свою


            1. DavidNadejdin Автор
              18.01.2022 03:12

              Оригинальный сервер и движок ествественно могут обращаться к друг другу нативно. Точно также как функция бана в нашем сервере, не вызывается где то там из вне, а говорит серверу, отправь сообщение что пользователь забанен и разорви коннент. Но это никак не размывает границы между сервером и движком. И пытаясь докопаться что технически описаный сервер выступает в роли прокси, не стоит говорить о том какую логику результатов сервер предоставляет для пользователя. Сервер описаный в статье, как технически является сервером, так и для пользователя это полноценный север minecraft, который просто делегирует пласт задач оригинальному серверу. Раздобудь мы исходники, цепочка действий для решения определенных задач, ну никак бы не изменилась. У нас бы также было ядро отвечающее за логику(в статье это модули) и сервер который допускает общение между ядром и клиетами


        1. DavidNadejdin Автор
          18.01.2022 10:23

          Вот давайте об этом подробнее. Убираем сервер майнкрафта, и что можно делать на вашем "сервере", чтобы туда было интересно заходить клиентам?

          И тут я дам последней комментарий. Задайте сами себе вопрос, чем является мой "сервер", если выкинуть оригинальный сервер minecraft. Он уже не может называться прокси/врапером/оберткой, но и сервером minecraft он называтся тоже не может по вашему мнению. Тогда что он из себя начинает представлять?


    1. DavidNadejdin Автор
      17.01.2022 14:52

      или же можно поставить вопрос таким образом - Что из себя представляет net? TCP, UDP сервер или все таки интерфейс для реализации сервера? В какой момент мы можем сказать что написали UDP сервер? В тот момент когда он начал принимать пакеты?

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

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


      1. saboteur_kiev
        17.01.2022 15:50

        Там же ясно написано что это такое.

        Package net provides a portable interface for network I/O, including TCP/IP, UDP, domain name resolution, and Unix domain sockets.

        Переведу. Это пакет API для работы с сетью, при помощи которого вы можете быстро написать клиент или сервер, или и то и другое сразу.

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

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

        Что тогда в вашем понимании, тот самый заветный сервер Minecraft с большой буквы?

        Как только перестанете заниматься троллингом и задумаетесь, поймете. Основной сервер майнкрафт - это игровой движок игры, который позволяет игрокам взаимодействовать в едином мире.


        1. DavidNadejdin Автор
          17.01.2022 16:28

          Вы можете взять и выкинуть модуль "proxy" из "сервера". Вы можете подцепить туда какую-угодно логику. С возможностью принимать и отдавать соотвествующие данные, своими силами, оно справляется. Можно ли это назвать только прокси? Почему я привел пример с nginx? Nginx может отдавать статику сам или направлять запросы, например в php-fmp. Почему я привел пример с net? Потому что gophertunnel в нашем случае, выступает интерфейсом который позволяет нам быстро написать RackNet клиент или сервер. Насколько они будут функциональны, ведь совсем другой вопрос


        1. DavidNadejdin Автор
          17.01.2022 16:37

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


        1. DavidNadejdin Автор
          18.01.2022 02:24

          Просто еще раз подумал над тем, что может быть я все таки ошибаюсь и заслужено получил столько негодования в свою сторону. Давайте попробуем разобрать пример с nginx. Чем является nginx? Веб сервером/Прокси. Что это он дает нам в первом случае? Интерфейс к нашим приложениями, которые написаны например на php или python. Что во 2? Реверс прокси например к приложению на node.js.

          Давайте представим что внезапно, приложение на php или python, отвечающее за всю внутренюю логику, ожидаемую при запросах к вашему приложению через nginx, исчезло. Перестал ли nginx от этого быть web сервером?

          Давайте представим что код описаный в статье, это просто реверс прокси. Насколько вы уверены, что когда клиент обращается к ngnix, ngnix создает http клиент, который в свою очередь посылает такой же запрос к проксируемому серверу?

          Давайте немного забудем ngnix и вспомним net. Что он представляет из себя? Интерфейс для быстрой реализации tcp/udp сервера/клиента. Может ли такой самописный сервер, называться сервером? Может. Насколько он функционален? Это совсем другой вопрос.

          И тут давате вспомним gophertunnel(большое спасибо его автору) и зададимся вопросом, а что это вообще такое? Как мне кажется, это net, только для racknet. Может ли server.go приведенный в статье, называть себя сервером? Да, он способен принимать и отдавать пакеты, соответсвующие протоколу.

          Давайте вернемся к тому, как устроенна локальная сессия игры(вы можете почитать об это в статье)

          это игровой движок игры, который позволяет игрокам взаимодействовать в едином мире.

          Если бы minecraft не умел в мультиплеер, но локальный клиент продолжал бы обращаться к движку minecraft через сервер, перестал бы быть сервер, сервером? Как в вашем представлении, устроен движок игры? Когда движок игры, хочет передать клиенту,что сущность x переместилась, происходит какая то магия? Нет, движок говорит серверу "передай клиенту y, что сущность x переместилась". Сущностью x в данном случае может как быть моб, управляемый внутреней логикой движка, так и пользователь который общается с движком, через сервер, отсылая понятные серверу данные, которые тот в свою очередь преобразует в понятные для нативного кода значения.

          Что мы сделали в 2 части статьи? Мы написали сервер, который готов принимать и отдавать пакеты, вы только дайте ему нужные. Как? Вопрос конкретной задачи. Ничего не мешает вам отказатся от того чтобы делегировать запросы в оригинальный сервер minecraft. Если вы смотрели приведенный в статье код, "модуля" proxy, то с виду он просто берет и перенаправляет запросы от сервера, к клиенту и наоборот. Практически это так, фактически ответ от нашего сервера, клиенту и от оригинального сервера Minecraft, нашему серверу, это не одно и тоже. По вашей логике, Nukkit например, это полноценный сервер minecraft, потому что он не использует оригинальный сервер. Но я повторюсь что Nukkit не реализует ожидаемую логику от Minecraft полностью. Получается чтобы либо я реализую, хотябы мельчайшую часть логики ожидамой от Minecraft и уже могу называть свой сервер сервером, либо Nukkit это не сервер, а прокси к плагинам? Как раз таки оригинальный сервер minecraft, в нашем случае можно назвать сервисом, который и вправду берет на себя большую часть задач. Но когда сервисом стало являтся чтото меньшего размера, а сервером чтото большего?

          И nginx в первую очередь является веб-серверов, в котором уже реализованы разные штуки.

          Как только перестанете заниматься троллингом и задумаетесь, поймете. Основной сервер майнкрафт - это игровой движок игры, который позволяет игрокам взаимодействовать в едином мире.

          Я искренне недоумеваю от обвинений в троллинге, когда аргументы содержат "разные штуки" или же когда ваше утверждение, противоречит само себе. Может быть, такое недопонимание сложилось из-за разных ожиданий? Насколько глубоко вы пытались разобрать тему написания сервера Minecraft? Кто из нас на самом деле пытается упорно назвать мой код сервером или прокси? Мне правда не принципиально, но как мне кажется, то что описано в статье, все таки подходит под определенние сервера.

          Повторюсь еще раз. Написали ли мы в статье Minecraft сервер? Я считаю что да. Использует ли наш сервер, движок написанный нами? Очевидно что нет. Готов ли он использовать движок, который мы можем написать сами? Да, однозначно


          1. DaemonGloom
            18.01.2022 09:29

            Поймите. Сервер Minecraft — это ПО, которое реализует логику игрового мира И взаимодействие с клиентами. Ссылаться здесь на статью википедии про web-сервер — просто некорректно.
            Вас сейчас пинают не за то, что вы написали некий софт, а за заголовок статьи и попытку называть этот софт "сервером Майнкрафт".


            1. DavidNadejdin Автор
              18.01.2022 09:44

              Еще раз. Технически это сервер minecraft, для пользователя, как бы это не было удивительно, это тоже сервер minecraft. Как он реализует логикику игрового мира, это только вопрос реализации. Это вы поймите, что если вы ожидали тут увидеть реализацию ядра, реверс инжеренринг существующих механик, то это проблема конкретно ваших ожиданий. Назови я статью "пишем некий софт для оригинального сервера minecraft" или "пишем прокси для оригинального сервера", по моим ощущениям это вызывало бы еще больший десонанс. Просто вы прыгаете от попыток предъявить мне, что это технически прокси, к попыткам что сервер не реализует полностью логику игрового мира. Даже если вы определитесь, какой версии вы придерживаетесь, сервер не является прокси на 100% технически и при этом на 100% реализует игровую логику. Да, он делегирует 99% логики оригинальному движку. Но задам вам вопрос снова. Если сервер который не сервер, будет реализовывать дополнительной логики, номинально больше чем оригинальный сервер, то оригинальный сервер может считаться просто внешним сервисом? Не нужно фраз по типу "вы поймте что я прав, потому что прав, а все остальное не корректно ". Хотите объективного спора? Приводите аргументы, примеры пожалуйста. Назовите хоть один кастомный сервер Minecraft может быть. Мне приводили в прошлой части в пример Nukkit. Но наверное 3 раз повторюсь, что он не реализует логику ожидаемую от Minecraft на 100%. Да, он поддерживает плагины, а сервер описанный в статье "поддерживает" "модули". Это последний комментарий который я дам по этому поводу, потому что я устал на огромные попытки объяснить чтото, получать в ответ короткое "да ты дурачек, мы лучше знаем что это не сервер". Вы изначально предвзяты и заряжены целью убедить меня назвать статью по другому. Пусть дальше это остается на суд читателей