1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/magiclvzs-antnet

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
В этом репозитории не указан файл с открытой лицензией (LICENSE). При использовании обратитесь к конкретному описанию проекта и его зависимостям в коде.
Клонировать/Скачать
Внести вклад в разработку кода
Синхронизировать код
Отмена
Подсказка: Поскольку Git не поддерживает пустые директории, создание директории приведёт к созданию пустого файла .keep.
Loading...
README.md

Есть вопросы? Добро пожаловать в группу: 828958110.

Первое использование

Прежде чем представить antnet, давайте сначала попробуем его в действии и посмотрим, как он создаёт эхо-сервер.

package main

import (
    "antnet"
)

func main() {
    antnet.StartServer("tcp://:6666", antnet.MsgTypeCmd, &antnet.EchoMsgHandler{}, nil)
    antnet.WaitForSystemExit()
}

С помощью этого кода мы создали самый простой эхо-сервер. Теперь откройте командную строку, выполните команду telnet 127.0.0.1 6666, введите строку и нажмите Enter. Вы получите ответное сообщение, которое будет идентично отправленному вами.

Дизайн

В antnet код, связанный с определёнными функциями, организован в блоки, что позволяет быстро находить нужный код. Например, файлы с префиксом parser содержат код для парсера, а файлы с префиксом msgque — код для очереди сообщений. Кроме того, код в antnet тщательно продуман. Это касается, например, момента закрытия TCP-соединения и других аспектов. Я опубликую более подробную информацию на Zhihu:

Почему выбирают antnet?

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

Глобальные сообщения вызывают много вопросов, потому что подход antnet к ним отличается от подхода других фреймворков (см. часть 1 дизайна TCP). Многие считают, что глобальные сообщения должны обрабатываться в одном потоке, который перебирает все очереди сообщений и отправляет их с помощью цикла for. Это связано с тем, что такой подход используется во фреймворках на C++. Однако это может привести к проблемам с производительностью, так как поток будет часто блокироваться. Antnet решает эту проблему, позволяя каждой очереди обрабатывать свои собственные глобальные сообщения. Это позволяет использовать преимущества многоядерных процессоров и обеспечивает быструю отправку глобальных сообщений.

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

Кроме того, antnet учитывает множество сетевых деталей, которые часто игнорируются другими фреймворками из-за недостаточного понимания работы сетей. Я даже требую от кандидатов на работу написать полный граф состояний для TCP, хотя это требование обычно смягчается при реальном собеседовании.

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

Зависимости

  • github.com/golang/protobuf.
  • github.com/vmihailenco/msgpack.
  • github.com/go-redis/redis версии 6.
  • github.com/gorilla/websocket.

Производственная среда

Antnet обслуживает миллионы игроков по всему миру, и вот некоторые примеры коммерческих игр, использующих его:

Хотя antnet является базовым фреймворком, он не реализует RUDP, поскольку требования к RUDP могут различаться в зависимости от типа игры. Вместо этого я предпочитаю настраивать RUDP для каждого конкретного проекта.

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

  1. Порядок чтения и записи значений может повлиять на логику программы.
  2. Значение записывается наполовину, что может вызвать проблемы на 16-битных архитектурах при работе с 32-битными значениями. Однако современные серверы обычно используют 64-битные архитектуры, поэтому эта проблема не возникает.

Мобильные приложения

Antnet также поддерживает разработку H5-приложений, и он является единственным игровым продуктом H5, доступным для мобильных устройств. Вы можете попробовать его, поиграв в «美食大战老鼠2» или «街机三国3» на своём мобильном устройстве или отсканировав QR-код.

|---------------------------------------------------|

Приложение (пользовательская логика)
Обработка (IMsgHandler)
---------------------------------------------------
Парсинг (IMsgParser, IParser, IParserFactory)
---------------------------------------------------
Сеть (IMsgQue, Message)
---------------------------------------------------

Тестовый фреймворк

Wsserver — это тестовый фреймворк для веб-сокетов, демонстрирующий, как использовать antnet. Он содержит менее 60 строк логического кода и реализует чат-систему на основе веб-сокетов и protobuf. Union — официальный фреймворк antnet, но он пока не доступен для открытого использования по коммерческим причинам.

Архитектура
Приложение (пользовательская логика)
---------------------------------------------------
Обработка (IMsgHandler)
---------------------------------------------------
Парсинг (IMsgParser,IParser,IParserFactory)
---------------------------------------------------
Сеть (IMsgQue,Message)
---------------------------------------------------

Заголовок сообщения

Для сетевого сервера необходимо определить заголовок сообщения, который в antnet имеет длину 12 байт.

type MessageHead struct {
    Len   uint32 //длина данных
    Error uint16 //код ошибки
    Cmd   uint8  //команда
    Act   uint8  //действие
    Index uint16 //номер
    Flags uint16 //флаги
}

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

Сообщение

type Message struct {
    Head       *MessageHead //заголовок, может быть нулевым
    Data       []byte       //данные сообщения
    IMsgParser              //парсер
    User       interface{}  //пользовательские данные
}

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

Широковещательные и групповые сообщения

Antnet обрабатывает эти типы сообщений особым образом, обеспечивая высокую производительность. Функция Send используется для отправки глобальных сообщений, а функция SendGroup — для групповых сообщений. Один очередь сообщений может иметь несколько групповых идентификаторов, например, для комнат и клубов. Анализ

В запросе представлен текст на языке Go, который описывает архитектуру и принципы работы системы antnet. В тексте рассматриваются различные типы анализаторов (парсеров), их назначение и способы использования. Также описываются обработчики сообщений и процесс запуска сервера с использованием библиотеки antnet.

Перевод

Разбор

antnet в настоящее время имеет шесть типов анализаторов:

```go
type ParserType int

const (
    ParserTypePB      ParserType = iota //protobuf тип, используемый для взаимодействия с клиентом
    ParserTypeCmd                       //тип cmd, похожий на команды telnet, используется для прямого взаимодействия с программой
    ParserTypeJson                      //тип json, может использоваться для взаимодействия между клиентом и сервером
    ParserTypeMsgpack                   //тип msgpack, может использоваться для взаимодействия между клиентом и сервером
    ParserTypeCustom                    //пользовательский тип
    ParserTypeRaw                       //без разбора)
)

Все эти шесть типов анализаторов могут быть созданы с помощью antnet.Parser.

Каждый анализатор требует определения поля Type и поля ErrType. Поле Type указывает тип анализатора сообщений, а поле ErrType определяет поведение по умолчанию при сбое анализа сообщения. В настоящее время существует четыре способа обработки ошибок:

type ParserType int

const (
    ParseErrTypeSendRemind ParseErrType = iota //сообщение об ошибке отправляется клиенту
    ParseErrTypeContinue                       //сообщение об ошибке пропускается
    ParseErrTypeAlways                         //сообщение обрабатывается независимо от ошибки
    ParseErrTypeClose                          //сообщение об ошибке приводит к закрытию соединения)

По умолчанию анализатор имеет тип pb, и обработка ошибок заключается в отправке уведомления об ошибке клиенту.

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

pf := &antnet.Parser{Type: antnet.ParserTypeCmd}

Приведённый выше код определяет анализатор, основанный на типе cmd.

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

  1. На основе MsgTypeMsg, анализ выполняется на основе команд cmd и act, используя Register для регистрации.
  2. На основе MsgTypeCmd, такие сообщения обычно не имеют заголовка сообщения, и для регистрации используется RegisterMsg.

Функции регистрации определяются следующим образом:

Register(cmd uint8, act uint8, c2s interface{}, s2c interface{})
RegisterMsg(c2s interface{}, s2c interface{})

Анализатор командной строки

Анализатор командной строки используется для анализа ввода командных строк, подобно telnet. После запуска сервера я хочу иметь простой интерфейс обмена данными, предпочтительно основанный на telnet. Этот анализатор готов к приёму неполного ввода, пока вы в конечном итоге не введёте всё полностью, или к приёму неправильного ввода до тех пор, пока вы не введёте правильный ввод.

Анализатор командной строки в настоящее время поддерживает два тега:

  1. match:"k" означает, что достаточно сопоставить имя поля. Во время сопоставления имя поля по умолчанию будет считаться строчным.
  2. match:"kv" означает необходимость сопоставления имени поля и значения поля.

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

type GetGamerLevel struct {
    Get   string `match:"k"`
    Gamer int
    Level int `match:"k"`
}

Три поля объясняются следующим образом:

  1. Get — это метод, такой как get, set, reload. В этом случае мне нужно ввести только метод. Тег match:"k" указывает, что нужно сопоставлять только имя поля.
  2. Gamer — это идентификатор игрока. Без тега, для полей без тега анализатор предполагает, что необходимо сопоставить имя и значение поля. Например, ввод gamer 1 будет считаться допустимым, а gamer test — нет, поскольку test не является int.
  3. Level — это уровень игрока. Имеет тег, указывающий, что нужно только сопоставить level с именем поля.

Определив структуру данных, мы должны зарегистрировать её в анализаторе, используя следующий код:

pf.RegisterMsg(&GetGamerLevel{}, nil)

Таким образом, мы регистрируем сообщение в анализаторе.

Протокольный анализатор

Протокольный анализатор используется для анализа данных типа pb.

Пользовательский анализатор

Если существующие анализаторы не удовлетворяют требованиям, можно создать собственный анализатор, реализовав IParserFactory.

Обработчик

Обработчики используются для обработки сообщений. Обработчик должен реализовывать интерфейс IMsgHandler:

type IMsgHandler interface {
    OnNewMsgQue(msgque IMsgQue) bool                         //новое сообщение в очереди
    OnDelMsgQue(msgque IMsgQue)                              //очередь сообщений закрыта
    OnProcessMsg(msgque IMsq Que, msg *Message) bool          //функция обработки по умолчанию
    OnConnectComplete(msgque IMsgQue, ok bool) bool          //соединение успешно установлено
    GetHandlerFunc(msgque IMsgQue, msg *Message) HandlerFunc //получение функции обработки на основе сообщения)
}

Конечно, обычно нам не нужно полностью реализовывать вышеуказанный интерфейс. Достаточно добавить antnet.DefMsgHandler в наш обработчик.

Antnet.DefMsgHandler также определяет функции Register и RegisterMsg для различения различных входных данных. Если вы не зарегистрировали никаких функций обработки сообщений, система автоматически вызовет функцию OnProcessMsg, если она определена.

В приведённом примере получения уровня игрока мы определяем обработчик следующим образом:

type Handler struct {
    antnet.DefMsgHandler
}

Определив обработчик, мы создаём обработчик и регистрируем функции обработки, которые хотим использовать:

h := &Handler{}
h.RegisterMsg(&GetGamerLevel{}, func(msgque antnet.IMsgQue, msg *antnet.Message) bool {
    c2s := msg.C2S().(*GetGamerLevel)
    c2s.Level = 8
    msgque.SendStringLn(msg.C2SString())
    return true
})

Так мы создали обработчик и зарегистрировали функцию обработки.

Время вызова обработчика

Antnet создаёт два goroutine для каждого TCP-соединения: один для чтения, другой для записи. Обработка происходит в goroutine, отвечающем за чтение. Почему выбрана такая конструкция? Нужно ли обрабатывать следующее сообщение сразу после того, как предыдущее сообщение не было обработано?

Запуск службы

Чтобы запустить сетевой сервер с использованием antnet.StartServer, необходимо указать обработчик и анализатор.

В примере получения уровня игрока запуск службы выглядит следующим образом:

antnet.StartServer("tcp://:6666", antnet.MsgTypeCmd, h, pf)

После запуска службы мы ожидаем сигнала Ctrl+C для завершения работы службы, используя функцию WaitForSystemExit.

Полный пример:

package main

import "antnet"

type GetGamerLevel struct {
    Get   string `match:"k"`
    Gamer int
    Level int `match:"k"`
}

type Handler struct {
    antnet.DefMsgHandler
}
func test(msgque antnet.IMsgQue, msg *antnet.Message) bool {
    c2s := msg.C2S().(*GetGamerLevel)
    c2s.Level = 8
    msgque.SendStringLn(msg.C2SString())
    return true
}
func main() {
    pf := &antnet.Parser{Type: antnet.ParserTypeCmd}
    pf.RegisterMsg(&GetGamerLevel{}, nil)

    h
``` В данном тексте описывается создание основанного на командах сетевого приложения.

В примере кода показано, как можно создать и запустить сервер с помощью функции antnet.StartServer. Затем ожидается завершение работы программы с помощью вызова функции antnet.WaitForSystemExit.

Далее в тексте рассказывается о глобальных переменных и функциях, предоставляемых библиотекой antnet для удобства использования. Также описывается система логирования, основанная на константах LogLevelAllOn, LogLevelDebug, LogLevelInfo, LogLevelWarn, LogLevelError, LogLevelFatal и LogLevelAllOff. 

Кроме того, в тексте упоминается об обёртке над Redis, предоставляемой antnet, а также о встроенной системе таймеров и о модели данных, основанной на протобуферах.

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

Вы можете оставить комментарий после Вход в систему

Введение

Одна действительно использующая особенности Go сетевая библиотека. Развернуть Свернуть
Отмена

Обновления

Пока нет обновлений

Участники

все

Недавние действия

Загрузить больше
Больше нет результатов для загрузки
1
https://api.gitlife.ru/oschina-mirror/magiclvzs-antnet.git
git@api.gitlife.ru:oschina-mirror/magiclvzs-antnet.git
oschina-mirror
magiclvzs-antnet
magiclvzs-antnet
master