gRPC介绍
gRPC 基于 HTTP/2 标准设计,带来诸如双向流、流控、头部压缩、单 TCP 连接上的多复用请求等特性。这些特性使得其在移动设备上表现更好,更省电和节省空间占用。
gRPC有四中服务方法:
- Unary RPCs,一元RPC。客户端发送一个请求到服务端,服务端响应一个请求。
- rpc getUser (User) returns (User) {}
- Server streaming RPCs,服务端流RPC。客户端发送一个请求到服务端,获取到一个流去连续读取返回的消息,直到消息全部获取。gRPC保证单个请求的消息顺序。
- rpc getUsers (User) returns (stream User) {}
- Client streaming RPCs,客户端流RPC。客户端给服务器通过流写入连续的消息,一旦客户端完成了消息写入,就等待服务端读取完成然后返回一个响应。同时gRPC也会保证单个请求的消息顺序。
- rpc saveUsers (stream User) returns (User) {}
- Bidirectional streaming RPCs,双向流。客户端和服务端都可以通过 read-write流发送一个连续的消息。两个流之间的操作是相互独立的。所以,客户端和服务端可以同时进行流的读写。
- rpc saveUsers (stream User) returns (stream User) {}
protobuf插件安装
协议编写
message.proto
syntax = "proto3";package protocol;import "file.proto";option go_package = "protocol";option java_multiple_files = true;option java_package = "com.kone.pbdemo.protocol";message User { reserved 6 to 7; reserved "userId2"; int32 userId = 1; string username = 2; oneof msg { string error = 3; int32 code = 4; } string name = 8; UserType userType = 9; repeated int32 roles = 10; protocol.File file = 11; map<string, string> hobbys = 12;}enum UserType { UNKNOW = 0; ADMIN = 1; BUSINESS_USER = 2;};service UserService { rpc getUser (User) returns (User) {} rpc getUsers (User) returns (stream User) {} rpc saveUsers (stream User) returns (User) {}}service FileService { rpc getFile(User) returns(File) {}}
file.proto
syntax = "proto3";package protocol;option go_package = "protocol";option java_package = "com.kone.pbdemo.protocol";message File { string name = 1; int32 size = 2;}
maven插件
<build>
<!-- os系统信息插件, protobuf-maven-plugin需要获取系统信息下载相应的protobuf程序 -->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!-- proto文件目录 -->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!-- 生成的Java文件目录 -->
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
注意添加os系统信息插件,否则会提示如下错误。
添加os系统信息插件
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
生成代码
双击插件中的compile和compile-custom,分别编译bean和service。
也可以执行mvn clean compile生成文件。
生成的代码:
代码实现
方式一
不和springboot进行集成,自己手动通过ServerBuilder将服务进行启动。
添加需要的maven依赖
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
完整的maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kone</groupId>
<artifactId>pb-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pb-demo</name>
<description>Demo project for Protocbuf</description>
<properties>
<java.version>1.8</java.version>
<protobuf.version>3.19.4</protobuf.version>
<grpc.version>1.26.0</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- os系统信息插件, protobuf-maven-plugin需要获取系统信息下载相应的protobuf程序 -->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!-- proto文件目录 -->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!-- 生成的Java文件目录 -->
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 实现服务端
实现UserService
package com.kone.pbdemo.service;
import com.kone.pbdemo.protocol.User;
import com.kone.pbdemo.protocol.UserServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
/**
* @author Kone
* @date 2022/1/29
*/
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(User request, StreamObserver<User> responseObserver) {
System.out.println(request);
User user = User.newBuilder()
.setName("response name")
.build();
responseObserver.onNext(user);
responseObserver.onCompleted();
}
@Override
public void getUsers(User request, StreamObserver<User> responseObserver) {
System.out.println("get users");
System.out.println(request);
User user = User.newBuilder()
.setName("user1")
.build();
User user2 = User.newBuilder()
.setName("user2")
.build();
responseObserver.onNext(user);
responseObserver.onNext(user2);
responseObserver.onCompleted();
}
@Override
public StreamObserver<User> saveUsers(StreamObserver<User> responseObserver) {
return new StreamObserver<User>() {
@Override
public void onNext(User user) {
System.out.println("get saveUsers list ---->");
System.out.println(user);
}
@Override
public void onError(Throwable throwable) {
System.out.println("saveUsers error " + throwable.getMessage());
}
@Override
public void onCompleted() {
User user = User.newBuilder()
.setName("saveUsers user1")
.build();
responseObserver.onNext(user);
responseObserver.onCompleted();
}
};
}
}
添加服务并启动:
package com.kone.pbdemo.test;
import com.kone.pbdemo.service.FileServiceImpl;
import com.kone.pbdemo.service.MessageServiceImpl;
import com.kone.pbdemo.service.UserServiceImpl;
import io.grpc.Server;
import io.grpc.ServerBuilder;
/**
* @author Kone
* @date 2022/2/7
*/
public class PbServer {
public static void main(String[] args) throws Exception {
int port = 9091;
Server server = ServerBuilder
.forPort(port)
.addService(new UserServiceImpl())
.build()
.start();
System.out.println("server started, port : " + port);
server.awaitTermination();
}
}
- 客户端发起请求
package com.kone.pbdemo.test;
import com.google.protobuf.InvalidProtocolBufferException;
import com.kone.pbdemo.protocol.*;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.stub.StreamObserver;
import java.util.Iterator;
/**
* @author Kone
* @date 2022/1/29
*/
public class PbTest {
public static void main(String[] args) throws InterruptedException {
FileOuterClass.File file = FileOuterClass.File.newBuilder()
.setName("fileName")
.setSize(200)
.build();
User user = User.newBuilder()
.setUsername("zhangsan")
.setUserId(100)
.putHobbys("pingpong", "play pingpong")
.setCode(200)
.setFile(file)
// .setError("error string")
.build();
System.out.println(user);
System.out.println(user.getMsgCase().getNumber());
// 测试反序列化
try {
FileOuterClass.File fileNew = FileOuterClass.File.parseFrom(file.toByteArray());
System.out.println(fileNew);
} catch (InvalidProtocolBufferException e) {
e.printStackTrace();
}
String host = "127.0.0.1";
int port = 9091;
ManagedChannel channel = ManagedChannelBuilder.forAddress(host, port).usePlaintext().build();
// client接收一个对象
UserServiceGrpc.UserServiceBlockingStub userServiceBlockingStub = UserServiceGrpc.newBlockingStub(channel);
User responseUser = userServiceBlockingStub.getUser(user);
System.out.println(responseUser);
// client接收一个列表
Iterator<User> users = userServiceBlockingStub.getUsers(user);
while (users.hasNext()) {
System.out.println(users.next());
}
// client发送一个列表到Server端
UserServiceGrpc.UserServiceStub userServiceStub = UserServiceGrpc.newStub(channel);
StreamObserver<User> responseObserver = new StreamObserver<User>() {
@Override
public void onNext(User user) {
System.out.println("responseObserver -------> ");
System.out.println(user);
}
@Override
public void onError(Throwable throwable) {
System.out.println("responseObserver onError " + throwable.getMessage());
}
@Override
public void onCompleted() {
System.out.println("responseObserver onCompleted");
}
};
StreamObserver<User> requestObserver = userServiceStub.saveUsers(responseObserver);
for (int i = 0; i < 3; i++) {
requestObserver.onNext(user);
}
requestObserver.onCompleted();
Thread.sleep(2000);
channel.shutdown();
}
}
方式二
通过和springboot进行集成,让spring管理服务,自动启动服务。
需要的依赖
<!-- grpc server和spring-boot集成框架 -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<!-- grpc client和spring-boot集成框架 -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
完整的maven依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.6.3</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.kone</groupId>
<artifactId>pb-demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>pb-demo</name>
<description>Demo project for Protocbuf</description>
<properties>
<java.version>1.8</java.version>
<protobuf.version>3.19.4</protobuf.version>
<grpc.version>1.26.0</grpc.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>${protobuf.version}</version>
</dependency>
<!-- grpc server和spring-boot集成框架 -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<!-- grpc client和spring-boot集成框架 -->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>2.13.0.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<!-- os系统信息插件, protobuf-maven-plugin需要获取系统信息下载相应的protobuf程序 -->
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.2</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:${protobuf.version}:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<!-- proto文件目录 -->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!-- 生成的Java文件目录 -->
<outputDirectory>${project.basedir}/src/main/java/</outputDirectory>
<clearOutputDirectory>false</clearOutputDirectory>
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf</outputDirectory>-->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
- 服务端
实现服务端的接口,和方式一差不多,唯一不同的地方是添加@GrpcService,让spring进行管理。
package com.kone.pbdemo.service;
import com.kone.pbdemo.protocol.User;
import com.kone.pbdemo.protocol.UserServiceGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.boot.grpc.server.service.GrpcService;
/**
* @author Kone
* @date 2022/1/29
*/
@GrpcService
public class UserServiceImpl extends UserServiceGrpc.UserServiceImplBase {
@Override
public void getUser(User request, StreamObserver<User> responseObserver) {
System.out.println(request);
User user = User.newBuilder()
.setName("response name")
.build();
responseObserver.onNext(user);
responseObserver.onCompleted();
}
@Override
public void getUsers(User request, StreamObserver<User> responseObserver) {
System.out.println("get users");
System.out.println(request);
User user = User.newBuilder()
.setName("user1")
.build();
User user2 = User.newBuilder()
.setName("user2")
.build();
responseObserver.onNext(user);
responseObserver.onNext(user2);
responseObserver.onCompleted();
}
@Override
public StreamObserver<User> saveUsers(StreamObserver<User> responseObserver) {
return new StreamObserver<User>() {
@Override
public void onNext(User user) {
System.out.println("get saveUsers list ---->");
System.out.println(user);
}
@Override
public void onError(Throwable throwable) {
System.out.println("saveUsers error " + throwable.getMessage());
}
@Override
public void onCompleted() {
User user = User.newBuilder()
.setName("saveUsers user1")
.build();
responseObserver.onNext(user);
responseObserver.onCompleted();
}
};
}
}
- 在application.yml中添加gRPC端口配置
grpc:
client:
userClient:
negotiationType: PLAINTEXT
address: static://localhost:9090
server:
port: 9090
server:
port: 8080
grpc.client下的address是gRPC客户端调用远程的端口,项目自己启动调用自己,所以后下面的gRPC服务端一致。
grpc.server.port是gRPC服务启动的端口。
server.port是spring web自己启动的端口,即平时请求的http接口端口。
- 客户端使用
通过@GrpcClient("userClient"),将spring管理的服务注入,然后调用。
package com.kone.pbdemo;
import com.kone.pbdemo.protocol.User;
import com.kone.pbdemo.protocol.UserServiceGrpc;
import net.devh.boot.grpc.client.inject.GrpcClient;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.Iterator;
@SpringBootTest(classes = PbDemoApplication.class)
class PbDemoApplicationTests {
@GrpcClient("userClient")
private UserServiceGrpc.UserServiceBlockingStub userService;
@Test
void contextLoads() {
User user = User.newBuilder()
.setUserId(100)
.putHobbys("pingpong", "play pingpong")
.setCode(200)
.build();
System.out.println("get response-------->");
System.out.println(userService.getUser(user));
Iterator<User> users = this.userService.getUsers(user);
while (users.hasNext()) {
System.out.println(users.next());
}
}
}