gRPC (2): Code generation and four service method rpc call examples

Overview

This article will use official examples to quickly get started with the construction of the gRPC project and its four request methods:

  1. simple RPC
  2. server-side streaming RPC
  3. client-side streaming RPC
  4. bidirectional streaming RPC 

Official example

Build gRPC project

I used gradle to build the project, and refer to the official example for the specific build process of maven.

  • Add project dependencies

implementation 'io.grpc:grpc-netty-shaded:1.32.1'
implementation 'io.grpc:grpc-protobuf:1.32.1'
implementation 'io.grpc:grpc-stub:1.32.1'
compileOnly 'org.apache.tomcat:annotations-api:6.0.53' // necessary for Java 9+
  • Generate code

Write the .proto file and place it in the corresponding location of the project. This location is as follows by default: src/main/proto and src/test/proto

At the same time you need to install a plug-in: protobuf-plugin

The gradle plugin is installed as follows:

plugins {
    id 'com.google.protobuf' version '0.8.8'
}

protobuf {
  protoc {
    artifact = "com.google.protobuf:protoc:3.12.0"
  }
  plugins {
    grpc {
      artifact = 'io.grpc:protoc-gen-grpc-java:1.32.1'
    }
  }
  generateProtoTasks {
    all()*.plugins {
      grpc {}
    }
  }
}

If you want to modify the location of the .proto file (that is , the path of the .proto file scanned before the plugin generates the code ), add the following configuration:

sourceSets {
  main {
    proto {
      // In addition to the default 'src/main/proto'
      srcDir 'src/main/protobuf'
      srcDir 'src/main/protocolbuffers'
      // In addition to the default '**/*.proto' (use with caution).
      // Using an extension other than 'proto' is NOT recommended,
      // because when proto files are published along with class files, we can
      // only tell the type of a file from its extension.
      include '**/*.protodevel'
    }
    java {
      ...
    }
  }
  test {
    proto {
      // In addition to the default 'src/test/proto'
      srcDir 'src/test/protocolbuffers'
    }
  }
}

When the gradle dependency is resolved, the following plugin appears in the idea gradle workspace, which means the installation is complete

Write .proto file

syntax = "proto3";

package com.leolee.protobuf;

option java_package = "com.leolee.protobuf.gRPC.Student";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;//是否生成多个问题,false的话,就是所有的对象,接口都是在一个文件中

//使用 gRPC插件命令:gradle generateProto,生成java code

//定义service层
service StudentService {

  //A simple RPC
  rpc GetRealNameByUserName (MyRequest) returns (MyResponse) {}

}

//定义请求和响应
message MyRequest {
  string username = 1;
}

message MyResponse {
  string realName = 1;
}

Note: Must use proto3 syntax

  1. Run the gradle clean command in the command line window of the current project to clean up the project build folder
  2. Execute the gRPC protobuf plug-in command: gradle generateProto  , when BUILD SUCCESSFUL appears, it means success

Java code will be generated in the /build directory:

simple RPC

PS. This demo is to simulate a simple client sending a request to the server via TCP, the server receives and processes the request and then returns the response message to the client

Since we .proto define java_package = "com.leolee.protobuf.gRPC.Student"

Copy all the code classes generated before to the corresponding java package

Write service implementation class

package com.leolee.grpc.sutdent;


import com.leolee.protobuf.gRPC.Student.MyRequest;
import com.leolee.protobuf.gRPC.Student.MyResponse;
import com.leolee.protobuf.gRPC.Student.StudentServiceGrpc;
import io.grpc.stub.StreamObserver;

/**
 * @ClassName StudentServiceImpl
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public void getRealNameByUserName(MyRequest request, StreamObserver<MyResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getUsername());

        //组织返回结果,需要注意的是gRPC的service 是通过StreamObserver参数来返回结果的,所以方法是 void
        responseObserver.onNext(MyResponse.newBuilder()
                .setRealName("LYL")
                .build()
        );

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }
}

Write the server according to the official example

package com.leolee.grpc.sutdent;

import io.grpc.Server;
import io.grpc.ServerBuilder;

import java.io.IOException;
import java.util.concurrent.TimeUnit;

/**
 * @ClassName GrpcServer
 * @Description: gRPC服务端,可参考官网在github中的示例
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class GrpcServer {

    private Server server;

    private void start () throws IOException {

        int port = 8899;
        this.server = ServerBuilder
                .forPort(port)
                .addService(new StudentServiceImpl())
                .build()
                .start();

        System.out.println("server started!");

        //调用JVM钩子,在server端推出之前,释放掉rpc调用
        Runtime.getRuntime().addShutdownHook(new Thread(() -> {
            System.out.println("shutting down gRPC server since JVM is shutting down");
            try {
                GrpcServer.this.stop();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("server shut down");
        }));

        System.out.println("start method execution finished");
    }

    private void stop () throws InterruptedException {

        if (server != null) {
            server.shutdown()
                    .awaitTermination(30, TimeUnit.SECONDS);//等待30秒后退出
        }
    }

    /**
     * 功能描述: <br> 官方demo解释:Await termination on the main thread since the grpc library uses daemon threads.
     * 〈〉   实际就是为了给gRPC的server提供阻塞的能力,保持不退出
     * @Param: []
     * @Return: void
     * @Author: LeoLee
     * @Date: 2020/9/12 16:38
     */
    private void blockUntilShutdown() throws InterruptedException {

        if (server != null) {
            server.awaitTermination();
        }
    }

    public static void main(String[] args) throws InterruptedException, IOException {

        GrpcServer server = new GrpcServer();
        server.start();
        server.blockUntilShutdown();
    }
}

What needs to be noted here is Runtime , which is a JVM callback hook, which will be introduced later. This article will not explain it in detail.

Write the client

package com.leolee.grpc.sutdent;

import com.leolee.protobuf.gRPC.Student.MyRequest;
import com.leolee.protobuf.gRPC.Student.MyResponse;
import com.leolee.protobuf.gRPC.Student.StudentServiceGrpc;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

/**
 * @ClassName GrpcClient
 * @Description: gRPC客户端
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class GrpcClient {

    private StudentServiceGrpc.StudentServiceBlockingStub studentServiceBlockingStub;

    public GrpcClient (ManagedChannel channel) {

        studentServiceBlockingStub = StudentServiceGrpc.newBlockingStub(channel);
    }

    public static void main(String[] args) {

        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("127.0.0.1", 8899)
                .usePlaintext()//Use of a plaintext connection to the server. By default a secure connection mechanism such as TLS will be used.
                .build();

        GrpcClient grpcClient = new GrpcClient(channel);
        MyResponse response = grpcClient.studentServiceBlockingStub.getRealNameByUserName(MyRequest
                .newBuilder()
                .setUsername("LeoLee")
                .build());

        System.out.println("server response:" + response.getRealName());
    }
}

Starting the server and the client sequentially will simulate the process in which the client  sends "LeoLee" to the server through the getRealNameByUserName method, and the server responds and returns "LYL" to the client.

server-side streaming RPC

Modify the .proto file

Add server-side streaming RPC service, pay attention to the modification of the stream keyword

syntax = "proto3";

package com.leolee.protobuf;

option java_package = "com.leolee.protobuf.gRPC.Student";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;//是否生成多个问题,false的话,就是所有的对象,接口都是在一个文件中

//使用 gRPC插件命令:gradle generateProto,生成java code

//定义service层
service StudentService {

  //A simple RPC
  rpc GetRealNameByUserName (MyRequest) returns (MyResponse) {}

  //A server-side streaming RPC
  rpc GetStudentByAge (StudentRequest) returns (stream StudentResponse) {}

}

//定义请求和响应
message MyRequest {
  string username = 1;
}

message MyResponse {
  string realName = 1;
}

message StudentRequest {
  int32 age = 1;
}

message StudentResponse {
  string name = 1;
  int32 age = 2;
  string city = 3;
}

Generate code

Use the gRPC protobuf plugin to generate code in the same way and copy it to the corresponding directory

Modify the service implementation class

Increase the implementation method of getStudentByAge

It should be noted that the so-called server-side stream RPC is essentially implemented by adding return objects to StreamObserver multiple times

package com.leolee.grpc.sutdent;


import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.stub.StreamObserver;

/**
 * @ClassName StudentServiceImpl
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public void getRealNameByUserName(MyRequest request, StreamObserver<MyResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getUsername());

        //组织返回结果,需要注意的是gRPC的service 是通过StreamObserver参数来返回结果的,所以方法是 void
        responseObserver.onNext(MyResponse.newBuilder()
                .setRealName("LYL")
                .build()
        );

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }

    @Override
    public void getStudentByAge(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getAge());

        //模拟返回都是某个相同年龄的同学
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee")
                .setCity("上海").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee2")
                .setCity("北京").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee3")
                .setCity("广东").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee4")
                .setCity("深圳").build());

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }
}

Modify the client code

Increase the request for getStudentByAge, it can be seen that since the server returns stream StudentResponse, according to the generated code, it needs to receive the response collection through Iterator

package com.leolee.grpc.sutdent;

import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;

import java.util.Iterator;


/**
 * @ClassName GrpcClient
 * @Description: gRPC客户端
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class GrpcClient {

    private StudentServiceGrpc.StudentServiceBlockingStub studentServiceBlockingStub;

    public GrpcClient (ManagedChannel channel) {

        //一种阻塞的Stub,必须等到请求响应返回的时候,才会往下继续执行
        studentServiceBlockingStub = StudentServiceGrpc.newBlockingStub(channel);
    }

    public static void main(String[] args) {

        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("127.0.0.1", 8899)
                .usePlaintext()//Use of a plaintext connection to the server. By default a secure connection mechanism such as TLS will be used.
                .build();
        GrpcClient grpcClient = new GrpcClient(channel);

        System.out.println("——————————————————————————————————————————————");

        MyResponse response = grpcClient.studentServiceBlockingStub.getRealNameByUserName(MyRequest
                .newBuilder()
                .setUsername("LeoLee")
                .build());

        System.out.println("server response:" + response.getRealName());

        System.out.println("——————————————————————————————————————————————");

        //流式的返回,会将每一个返回对象放入一个迭代器中
        Iterator<StudentResponse> studentResponseList = grpcClient.studentServiceBlockingStub
                .getStudentByAge(StudentRequest.newBuilder()
                                .setAge(25)
                                .build());
        while (studentResponseList.hasNext()) {
            StudentResponse studentResponse = studentResponseList.next();
            System.out.println("name:" + studentResponse.getName());
            System.out.println("age:" + studentResponse.getAge());
            System.out.println("city:" + studentResponse.getCity());
        }
    }
}

Start the server and the client sequentially, and you will see the print results of the four response objects

client-side streaming RPC

Modify the .proto file

Add  client-side streaming RPC

At this time, the RPC defines a streaming request object, uses stream to modify StudentRequest, and the return value is a collection of StudentResponse: StudentResponseList

syntax = "proto3";

package com.leolee.protobuf;

option java_package = "com.leolee.protobuf.gRPC.Student";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;//是否生成多个问题,false的话,就是所有的对象,接口都是在一个文件中

//使用 gRPC插件命令:gradle generateProto,生成java code

//定义service层
service StudentService {

  //A simple RPC
  rpc GetRealNameByUserName (MyRequest) returns (MyResponse) {}

  //A server-side streaming RPC
  rpc GetStudentByAge (StudentRequest) returns (stream StudentResponse) {}

  //A client-side streaming RPC
  rpc GetStudentWapperByAge (stream StudentRequest) returns (StudentResponseList) {}

}

//定义请求和响应
message MyRequest {
  string username = 1;
}

message MyResponse {
  string realName = 1;
}

message StudentRequest {
  int32 age = 1;
}

message StudentResponse {
  string name = 1;
  int32 age = 2;
  string city = 3;
}

message StudentResponseList {
  repeated StudentResponse studentResponse = 1;
}

Generate code

Use the gRPC protobuf plugin to generate code in the same way and copy it to the corresponding directory

Modify the service implementation class

Note: Please pay attention to the comments written in the getStudentWapperByAge method to help understand

package com.leolee.grpc.sutdent;


import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.stub.StreamObserver;

/**
 * @ClassName StudentServiceImpl
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public void getRealNameByUserName(MyRequest request, StreamObserver<MyResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getUsername());

        //组织返回结果,需要注意的是gRPC的service 是通过StreamObserver参数来返回结果的,所以方法是 void
        responseObserver.onNext(MyResponse.newBuilder()
                .setRealName("LYL")
                .build()
        );

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }

    @Override
    public void getStudentByAge(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getAge());

        //模拟返回都是某个相同年龄的同学
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee")
                .setCity("上海").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee2")
                .setCity("北京").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee3")
                .setCity("广东").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee4")
                .setCity("深圳").build());

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }

    /**
     * 功能描述: <br> 对于A client-side streaming RPC 来说,返回值很奇怪,以StudentRequest作为返回值,这里其实是通过一个回调来完成的返回
     * 〈〉换句话说:需要返回一个StreamObserver接口对象,我们来重写该接口的三个方法,当请求到服务端后,会回调这三个方法,所以在这三个方法内处理请求(不好理解啊!!!)
     * @Param: [responseObserver]
     * @Return: io.grpc.stub.StreamObserver<com.leolee.protobuf.gRPC.Student.StudentRequest>
     * @Author: LeoLee
     * @Date: 2020/9/13 16:08
     */
    @Override
    public StreamObserver<StudentRequest> getStudentWapperByAge(StreamObserver<StudentResponseList> responseObserver) {

        return new StreamObserver<StudentRequest>() {

            /**
             * 功能描述: <br> 当客户端流式请求发送的时候,每发送一个StudentRequest,该方法将会被回调一次
             * 〈〉
             * @Param: [value]
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 16:24
             */
            @Override
            public void onNext(StudentRequest value) {

                System.out.println("onNext request param [age]:" + value.getAge());
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            /**
             * 功能描述: <br> 当客户端将流式请求一个一个发送给服务端完成后,回调该方法
             * 〈〉
             * @Param: []
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 16:28
             */
            @Override
            public void onCompleted() {

                //构造StudentResponseList返回对象
                StudentResponse studentResponse1 = StudentResponse.newBuilder()
                        .setName("LeoLee1")
                        .setAge(25)
                        .setCity("上海").build();
                StudentResponse studentResponse2 = StudentResponse.newBuilder()
                        .setName("LeoLee2")
                        .setAge(25)
                        .setCity("北京").build();
                StudentResponseList studentResponseList = StudentResponseList.newBuilder()
                        .addStudentResponse(studentResponse1)
                        .addStudentResponse(studentResponse2)
                        .build();

                responseObserver.onNext(studentResponseList);
                responseObserver.onCompleted();
            }
        };
    }
}

Modify the client code

Note: Pay attention to the comments in the " A client-side streaming RPC " code block to help understand

package com.leolee.grpc.sutdent;

import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

import java.util.Iterator;


/**
 * @ClassName GrpcClient
 * @Description: gRPC客户端
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class GrpcClient {

    private StudentServiceGrpc.StudentServiceBlockingStub studentServiceBlockingStub;

    private StudentServiceGrpc.StudentServiceStub studentServiceStub;

    public GrpcClient (ManagedChannel channel) {

        //一种阻塞的Stub,必须等到请求响应返回的时候,才会往下继续执行
        studentServiceBlockingStub = StudentServiceGrpc.newBlockingStub(channel);

        //一种异步的stub,客户端是流式请求的时候使用
        studentServiceStub = StudentServiceGrpc.newStub(channel);
    }

    public static void main(String[] args) throws InterruptedException {

        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("127.0.0.1", 8899)
                .usePlaintext()//Use of a plaintext connection to the server. By default a secure connection mechanism such as TLS will be used.
                .build();
        GrpcClient grpcClient = new GrpcClient(channel);

        System.out.println("———————————————————————A simple RPC———————————————————————");

        MyResponse response = grpcClient.studentServiceBlockingStub.getRealNameByUserName(MyRequest
                .newBuilder()
                .setUsername("LeoLee")
                .build());

        System.out.println("server response:" + response.getRealName());

        System.out.println("——————————————————————A server-side streaming RPC ————————————————————————");

        //流式的返回,会将每一个返回对象放入一个迭代器中
        Iterator<StudentResponse> studentResponseList = grpcClient.studentServiceBlockingStub
                .getStudentByAge(StudentRequest.newBuilder()
                                .setAge(25)
                                .build());
        while (studentResponseList.hasNext()) {
            StudentResponse studentResponse = studentResponseList.next();
            System.out.println("name:" + studentResponse.getName());
            System.out.println("age:" + studentResponse.getAge());
            System.out.println("city:" + studentResponse.getCity());
        }

        System.out.println("——————————————————————A client-side streaming RPC ————————————————————————");

        StreamObserver<StudentResponseList> studentResponseListStreamObserver = new StreamObserver<StudentResponseList>() {

            //每当服务端响应的时候,会回调该方法一次
            @Override
            public void onNext(StudentResponseList value) {

                value.getStudentResponseList().forEach(studentResponse -> {
                    System.out.println("接收服务端响应");
                    System.out.println(studentResponse.getName());
                    System.out.println(studentResponse.getAge());
                    System.out.println(studentResponse.getCity());
                });
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            @Override
            public void onCompleted() {


            }
        };

        //发出对服务端的异步流请求
        StreamObserver<StudentRequest> studentRequestStreamObserver = grpcClient.studentServiceStub.getStudentWapperByAge(studentResponseListStreamObserver);
        //构造传递给服务端的参数,流式发送给服务端
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onCompleted();

        //由于studentServiceStub = StudentServiceGrpc.newStub(channel);中studentServiceStub是异步的(详见StudentServiceGrpc.newStub),
        //导致请求还没有发送完成,当前线程就执行完毕了,所以如果想要看到相关的打印,需要让主线程多等待一会
        Thread.sleep(5000);
    }
}

Start the server and client in order, the output is as follows

  • Server
onNext request param [age]:25
onNext request param [age]:25
onNext request param [age]:25
  • Client
——————————————————————A client-side streaming RPC ————————————————————————
接收服务端响应
LeoLee1
25
上海
接收服务端响应
LeoLee2
25
北京

bidirectional streaming RPC 

Modify the .proto file

Add two-way flow RPC service

syntax = "proto3";

package com.leolee.protobuf;

option java_package = "com.leolee.protobuf.gRPC.Student";
option java_outer_classname = "StudentProto";
option java_multiple_files = true;//是否生成多个问题,false的话,就是所有的对象,接口都是在一个文件中

//使用 gRPC插件命令:gradle generateProto,生成java code

//定义service层
service StudentService {

  //A simple RPC
  rpc GetRealNameByUserName (MyRequest) returns (MyResponse) {}

  //A server-side streaming RPC
  rpc GetStudentByAge (StudentRequest) returns (stream StudentResponse) {}

  //A client-side streaming RPC
  rpc GetStudentWapperByAge (stream StudentRequest) returns (StudentResponseList) {}

  //A bidirectional streaming RPC
  rpc BidirectionalStreamTalk (stream StreamRequest) returns (stream StreamResponse) {}
}

//定义请求和响应
message MyRequest {
  string username = 1;
}

message MyResponse {
  string realName = 1;
}

message StudentRequest {
  int32 age = 1;
}

message StudentResponse {
  string name = 1;
  int32 age = 2;
  string city = 3;
}

message StudentResponseList {
  repeated StudentResponse studentResponse = 1;
}

message StreamRequest {
  string request_info = 1;
}

message StreamResponse {
  string response_info = 1;
}

Generate code

Use the gRPC protobuf plugin to generate code in the same way and copy it to the corresponding directory

Modify the service implementation class

package com.leolee.grpc.sutdent;


import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.stub.StreamObserver;

import java.util.UUID;

/**
 * @ClassName StudentServiceImpl
 * @Description: TODO
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class StudentServiceImpl extends StudentServiceGrpc.StudentServiceImplBase {

    @Override
    public void getRealNameByUserName(MyRequest request, StreamObserver<MyResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getUsername());

        //组织返回结果,需要注意的是gRPC的service 是通过StreamObserver参数来返回结果的,所以方法是 void
        responseObserver.onNext(MyResponse.newBuilder()
                .setRealName("LYL")
                .build()
        );

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }

    @Override
    public void getStudentByAge(StudentRequest request, StreamObserver<StudentResponse> responseObserver) {

        System.out.println("接收到客户端信息:" + request.getAge());

        //模拟返回都是某个相同年龄的同学
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee")
                .setCity("上海").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee2")
                .setCity("北京").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee3")
                .setCity("广东").build());
        responseObserver.onNext(StudentResponse.newBuilder()
                .setAge(request.getAge())
                .setName("LeoLee4")
                .setCity("深圳").build());

        //返回结果,调用completed方法
        responseObserver.onCompleted();
    }

    /**
     * 功能描述: <br> 对于A client-side streaming RPC 来说,返回值很奇怪,以StudentRequest作为返回值,这里其实是通过一个回调来完成的返回
     * 〈〉换句话说:需要返回一个StreamObserver接口对象,我们来重写该接口的三个方法,当请求到服务端后,会回调这三个方法,所以在这三个方法内处理请求(不好理解啊!!!)
     * @Param: [responseObserver]
     * @Return: io.grpc.stub.StreamObserver<com.leolee.protobuf.gRPC.Student.StudentRequest>
     * @Author: LeoLee
     * @Date: 2020/9/13 16:08
     */
    @Override
        public StreamObserver<StudentRequest> getStudentWapperByAge(StreamObserver<StudentResponseList> responseObserver) {

        return new StreamObserver<StudentRequest>() {

            /**
             * 功能描述: <br> 当客户端流式请求发送的时候,每发送一个StudentRequest,该方法将会被回调一次
             * 〈〉
             * @Param: [value]
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 16:24
             */
            @Override
            public void onNext(StudentRequest value) {

                System.out.println("onNext request param [age]:" + value.getAge());
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            /**
             * 功能描述: <br> 当客户端将流式请求一个一个发送给服务端完成后,回调该方法
             * 〈〉
             * @Param: []
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 16:28
             */
            @Override
            public void onCompleted() {

                //构造StudentResponseList返回对象
                StudentResponse studentResponse1 = StudentResponse.newBuilder()
                        .setName("LeoLee1")
                        .setAge(25)
                        .setCity("上海").build();
                StudentResponse studentResponse2 = StudentResponse.newBuilder()
                        .setName("LeoLee2")
                        .setAge(25)
                        .setCity("北京").build();
                StudentResponseList studentResponseList = StudentResponseList.newBuilder()
                        .addStudentResponse(studentResponse1)
                        .addStudentResponse(studentResponse2)
                        .build();

                responseObserver.onNext(studentResponseList);
                responseObserver.onCompleted();
            }
        };
    }

    /**
     * 功能描述: <br> A bidirectional streaming RPC
     * 〈〉
     * @Param: [responseObserver]
     * @Return: io.grpc.stub.StreamObserver<com.leolee.protobuf.gRPC.Student.StreamRequest>
     * @Author: LeoLee
     * @Date: 2020/9/13 21:46
     */
    @Override
    public StreamObserver<StreamRequest> bidirectionalStreamTalk(StreamObserver<StreamResponse> responseObserver) {

        return new StreamObserver<StreamRequest>() {

            /**
             * 功能描述: <br> 当客户端流式请求发送的时候,每发送一个StudentRequest,该方法将会被回调一次
             * 〈〉
             * @Param: [value]
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 21:56
             */
            @Override
            public void onNext(StreamRequest value) {

                //接收客户端数据
                System.out.println("onNext request param [requestInfo]:" + value.getRequestInfo());

                //将响应信息通过一条独立于客户端的流返回给客户端
                responseObserver.onNext(StreamResponse.newBuilder()
                        .setResponseInfo("this is response from server to client[" + value.getRequestInfo() + "]:" + UUID.randomUUID().toString())
                        .build());
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            /**
             * 功能描述: <br> 虽然客户端和服务端互相传递数据的流是独立的,但是正常业务场景情况下客户端的流完毕后,服务端也是关闭的
             * 〈〉所以在这里当客户端的请求流关闭后,也关闭流
             * @Param: []
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 22:19
             */
            @Override
            public void onCompleted() {

                responseObserver.onCompleted();
            }
        };
    }
}

Modify the client code

Note: Compare the codes of the two request methods A client-side streaming RPC demo and A bidirectional streaming RPC, and experience this data receiving method with callback method

package com.leolee.grpc.sutdent;

import com.leolee.protobuf.gRPC.Student.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;

import java.util.Iterator;


/**
 * @ClassName GrpcClient
 * @Description: gRPC客户端
 * @Author LeoLee
 * @Date 2020/9/12
 * @Version V1.0
 **/
public class GrpcClient {

    private StudentServiceGrpc.StudentServiceBlockingStub studentServiceBlockingStub;

    private StudentServiceGrpc.StudentServiceStub studentServiceStub;

    public GrpcClient (ManagedChannel channel) {

        //一种阻塞的Stub,必须等到请求响应返回的时候,才会往下继续执行
        studentServiceBlockingStub = StudentServiceGrpc.newBlockingStub(channel);

        //一种异步的stub,客户端是流式请求的时候使用
        studentServiceStub = StudentServiceGrpc.newStub(channel);
    }

    public static void main(String[] args) throws InterruptedException {

        ManagedChannel channel = ManagedChannelBuilder
                .forAddress("127.0.0.1", 8899)
                .usePlaintext()//Use of a plaintext connection to the server. By default a secure connection mechanism such as TLS will be used.
                .build();
        GrpcClient grpcClient = new GrpcClient(channel);

        System.out.println("———————————————————————A simple RPC———————————————————————");

        MyResponse response = grpcClient.studentServiceBlockingStub.getRealNameByUserName(MyRequest
                .newBuilder()
                .setUsername("LeoLee")
                .build());

        System.out.println("server response:" + response.getRealName());

        System.out.println("——————————————————————A server-side streaming RPC————————————————————————");

        //流式的返回,会将每一个返回对象放入一个迭代器中
        Iterator<StudentResponse> studentResponseList = grpcClient.studentServiceBlockingStub
                .getStudentByAge(StudentRequest.newBuilder()
                                .setAge(25)
                                .build());
        while (studentResponseList.hasNext()) {
            StudentResponse studentResponse = studentResponseList.next();
            System.out.println("name:" + studentResponse.getName());
            System.out.println("age:" + studentResponse.getAge());
            System.out.println("city:" + studentResponse.getCity());
        }

        System.out.println("——————————————————————A client-side streaming RPC————————————————————————");

        StreamObserver<StudentResponseList> studentResponseListStreamObserver = new StreamObserver<StudentResponseList>() {

            //每当服务端响应的时候,会回调该方法一次
            @Override
            public void onNext(StudentResponseList value) {

                value.getStudentResponseList().forEach(studentResponse -> {
                    System.out.println("接收服务端响应");
                    System.out.println(studentResponse.getName());
                    System.out.println(studentResponse.getAge());
                    System.out.println(studentResponse.getCity());
                });
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            @Override
            public void onCompleted() {


            }
        };

        //发出对服务端的异步流请求
        StreamObserver<StudentRequest> studentRequestStreamObserver = grpcClient.studentServiceStub.getStudentWapperByAge(studentResponseListStreamObserver);
        //构造传递给服务端的参数,流式发送给服务端
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onNext(StudentRequest.newBuilder()
                .setAge(25).build());
        studentRequestStreamObserver.onCompleted();

        System.out.println("——————————————————————A bidirectional streaming RPC————————————————————————");

        StreamObserver<StreamRequest> responseStreamObserver = grpcClient.studentServiceStub.bidirectionalStreamTalk(new StreamObserver<StreamResponse>() {

            /**
             * 功能描述: <br> 接收服务端的流消息
             * 〈〉
             * @Param: [value]
             * @Return: void
             * @Author: LeoLee
             * @Date: 2020/9/13 22:46
             */
            @Override
            public void onNext(StreamResponse value) {

                System.out.println(value.getResponseInfo());
            }

            @Override
            public void onError(Throwable t) {

                System.out.println("onError:" + t.getMessage());
            }

            @Override
            public void onCompleted() {

                System.out.println("onCompleted");
            }
        });

        //模拟发送数据给服务端
        for (int i = 0; i < 5; i++) {
            responseStreamObserver.onNext(StreamRequest.newBuilder()
                    .setRequestInfo("this is request from client:" + i)
                    .build());
            Thread.sleep(1000);
        }

        //由于studentServiceStub = StudentServiceGrpc.newStub(channel);中studentServiceStub是异步的(详见StudentServiceGrpc.newStub),
        //导致请求还没有发送完成,当前线程就执行完毕了,所以如果想要看到相关的打印,需要让主线程多等待一会
        Thread.sleep(6000);
    }
}

Start the server and the client sequentially, and observe the output on both ends:

The server receives every streaming message and immediately returns the streaming message to the client

 

Those who need code come here to get it: demo project address

Guess you like

Origin blog.csdn.net/qq_25805331/article/details/108561100