Итак, на днях я нашел интересный проект Anycable, который позволяет использовать собственный сервер WebSocket в вашем приложении ruby. Я сразу подсел, начал читать об этом, и первое, что я о нем никогда не слышал, это Grpc.

Grpc — это инфраструктура RPC с открытым исходным кодом, разработанная Google, которая использует протокольные буферы. RPC (удаленный вызов процедуры) заключается в том, что мы можем вызывать метод на сервере так же, как вызывали локальный объект.

Итак, первое, что я сделал, это зашел на официальный сайт Grpc и сразу перешел в рубиновый раздел. То, что я нашел, — это учебник, который не соответствует коду из репозитория на Github, и мне было немного сложно следовать, поэтому я решил просто извлечь учебник в свой репозиторий и объяснить обзор того, что я научился.

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

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

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

Вы даже можете использовать уже определенные типы внутри других типов.

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

Теперь, когда мы определили наши основные типы, мы собираемся создать наш сервис; вы можете думать об этом как о нашем API на нашем сервере.

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

Внутри нашего сервиса мы определяем различные вызовы rpc.

Клиент может общаться с сервером четырьмя способами:

Клиент отправляет запрос, а сервер отправляет ответ, это самое простое.

rpc GetLocation(Coordinate) returns (Location) {}

Клиент отправляет запрос, а сервер отправляет в ответ поток сообщений, после чего клиент их читает.

rpc ListLocations(Area) returns (stream Location) {}

Клиент отправляет поток сообщений, затем клиент ждет, пока сервер прочитает их все и отправит ответ.

rpc RecordRoute(stream Coordinate) returns (RouteSummary) {}

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

rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}

Это все, что нам нужно знать. Вот полный файл

Теперь мы собираемся начать работу над реализацией сервера и клиента на Ruby.

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

Благодаря grpc-tools мы можем использовать команду для преобразования нашего файла proto в файл ruby.

Это создает файлы со всеми объектами, которые нам нужны для работы.

Создание сервера

Давайте объясним, что делает наш сервер; У него есть хэш-база данных местоположений, где он хранит широту и долготу в качестве ключей, а значением является имя этого местоположения.

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

Чтобы создать сервер, нам нужно сначала создать Handler, он имеет тот же контракт, который мы ранее определили в нашем прото-файле, и мы описали выше, поэтому у него есть соответствующие методы для всех вызовов rpc, которые мы ранее определили.

Благодаря сгенерированным файлам у нас есть доступ к некоторым классам, которые помогают определить наш Handler. Итак, мы собираемся начать с создания класса, который расширяет класс RouteGuide::Service, сгенерированный из файла proto.

Давайте реализуем контракт GetLocation.

DB — это база данных Hash, о которой мы упоминали ранее. Итак, если мы прочитаем метод, становится совершенно ясно, что он делает: получает координату в качестве аргумента, ищет внутри DB имя этой координаты и возвращает новое Location. Помните, что Location — это то, что мы создали из файла proto.

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

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

Давайте рассмотрим пример, когда клиент отправляет поток точек на сервер. Когда сервер получает поток, он получает Перечислитель, где считывает все данные.

Для объекта call из аргументов мы можем использовать метод each_remote_yield, который выдает каждое сообщение, отправленное клиентом.

Полный код вы можете найти здесь.

Вот весь код класса Handler.

Запуск сервера

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

Создание клиента

При работе с клиентским кодом нам нужно создать то, что называется stub, в нем есть все методы, которые мы определили в нашей службе route_guide, такие как get_location, list_locations, record_route, в основном это то, как мы общаемся с сервером.

Давайте свяжемся с сервером, чтобы получить функцию на основе координаты; нам нужно отправить Coordinate на сервер.

При этом мы можем вызвать get_location, передав координату, и ожидать ответа от сервера.

Давайте рассмотрим пример, когда сервер возвращает поток данных. Сервер возвращает Enumerator, и мы можем перебрать его, читая несколько ответов, это похоже на выполнение method из локального объекта.

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

Наш класс RandomRoute инкапсулирует логику создания Enumerator с использованием метода ядра enum_for есть отличная статья, в которой это объясняется более подробно.

Со всем этим мы только что создали сервер Grpc и клиент, которые взаимодействуют друг с другом.

Я создал репозиторий со всем кодом, чтобы вы могли посмотреть.

Если у вас есть какие-либо мысли или вопросы, пожалуйста, поделитесь, и я буду рад ответить в комментариях.

Спасибо за чтение, я знаю, что это был долгий путь, но я надеюсь, что сегодня вы узнали что-то новое.

Первоначально опубликовано наhttp://gustavocaso.github.io/.