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

Структура проекта
Структура проекта

Мы реализуем основные методы, которые были описаны в .proto. Обратите внимание, что serverStream разобьется на 2 метода, о чем я расскажу дальше.

Эти методы мы реализуем в ходе статьи
Эти методы мы реализуем в ходе статьи

Как и в прошлой статье нам необходимо добавить плагин для генерации .proto файлов. Если возникли сложности, можно посмотреть код примера в репозитории.

Для того, чтобы отправлять вызовы на удаленный сервер, нам необходимо создать клиента. gRPC предоставляет несколько возможностей создания клиента - это BlockingStub и Stub. В чем отличие этих “заглушек”? BlockingStub - создает новую блокирующую заглушку, которая поддерживает вызовы унарного и потокового вывода (unary and streaming output calls, офф. док.), Stub - cоздает новую асинхронную заглушку, которая поддерживает все типы вызовов. Мы рассмотрим оба варианта заглушек. Для создании заглушки нам необходим Channel. Channel - если коротко, то это виртуальное соединение с концептуальной конечной точкой для выполнения RPC. Для создания Channel нам потребуется хост и порт. 

// Access a service running on the local machine on port 7777
String target = "localhost:7777";

// Create a communication channel to the server, known as a Channel. Channels are thread-safe
// and reusable. It is common to create channels at the beginning of your application and reuse
// them until the application shuts down.
ManagedChannel channel = ManagedChannelBuilder.forTarget(target)
  // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
  // needing certificates.
  .usePlaintext()
  .build();

Затем мы можем создать newBlockingStub и newStub (создаем через Channel и ManagedChannel для примера)

// 'channel' here is a Channel, not a ManagedChannel, 
// so it is not this code's responsibility to
// shut it down.
// Passing Channels to code makes code easier to test 
// and makes it easier to reuse Channels.
blockingStub = ProfileServiceGrpc.newBlockingStub(channel);
asyncStub = ProfileServiceGrpc.newStub(ManagedChannelBuilder.forAddress(HOST, PORT).usePlaintext().build());

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

Реализация getCurrentProfile
Реализация getCurrentProfile

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

Main
Main

  Теперь если вызвать getCurrentProfile, то мы получим ответ профиля с именем “test” (сервер из прошлой статьи). 

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

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

// Блокирующая и асинхронная заглушки
private final ProfileServiceGrpc.ProfileServiceBlockingStub blockingStub;
private final ProfileServiceGrpc.ProfileServiceStub asyncStub;
// Создание заглушек в конструкторе
public GrpcProfileClientImpl() {
    // Create a communication channel to the server, known as a Channel. Channels are thread-safe
    // and reusable. It is common to create channels at the beginning of your application and reuse
    // them until the application shuts down.
    ManagedChannel channel = ManagedChannelBuilder.forTarget(TARGET)
            // Channels are secure by default (via SSL/TLS). For the example we disable TLS to avoid
            // needing certificates.
            .usePlaintext()
            .build();
    blockingStub = ProfileServiceGrpc.newBlockingStub(channel);
    asyncStub = ProfileServiceGrpc.newStub(channel);
}

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

А вот для не блокирующего принятия сообщений нам потребуется передать StreamObserver, в который сервер будет передавать ответы.

Не блокирующее получение ответов от сервера
Не блокирующее получение ответов от сервера

Так как у нас получение происходит асинхронно, я добавляю CountDownLatch с await, для того что бы текущий поток подождал ответов от сервера. 

И последний вариант клиент серверного взаимодействия - biDirectionalStream. Нам снова потребуется реализовать StreamObserver в который клиент будет передавать сообщения и получать ответы от сервера.

Если все запустить и проверить, то по логам клиента и сервера можно заметить, что сообщение передаются и наш клиент работает корректно. 

Подведем итоги

Мы написали клиент, для рассмотренного в прошлой статье сервера на gRPC. Рассмотрели и реализовали основные варианты клиент серверного взаимодействия с использованием gRPC. А вы используете gRPC для клиент серверного общения? Почему вы выбрали gRPC? 

   В прошлой статье мы разобрали, как писать сервер на gRPC. И протестировали его с помощью BloomRPC. Теперь, давайте разберем как пишется клиент. И попробуем отправлять запросы с клиента на сервер. Это будет наш первый шаг к созданию микросервисов на грпц. 

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