springboot 集成 grpc 和 protobuf(二) | 在实际项目中使用 grpc 和 protobuf
不熟悉grpc和protobuf的请参考第一篇:
springboot 集成 grpc 和 protobuf(一) | grpc和protobuf 的简介及本地使用.
注意:下面的测试代码写在同一个项目中!
一、项目结构:如下图
二、项目搭建
2.1 引入pom依赖:代码比较长,请耐心
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.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-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.45</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.0</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.38</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<scope>test</scope>
</dependency>
<!--使用grpc和protobuf-->
<!-- protobuf依赖-->
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.5.1</version>
</dependency>
<!--grpc依赖-->
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-all</artifactId>
<version>1.11.0</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.25.Final</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-tcnative-boringssl-static</artifactId>
<version>2.0.8.Final</version>
</dependency>
<!--grpc客户端-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-client-spring-boot-starter</artifactId>
<version>1.3.0-RELEASE</version>
</dependency>
<!--grpc服务端-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>1.3.0-RELEASE</version>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.6.0</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<!--跳过test测试-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.5.1:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.11.0:exe:${os.detected.classifier}</pluginArtifact>
<!--默认值-->
<protoSourceRoot>${project.basedir}/src/main/proto</protoSourceRoot>
<!--默认值-->
<!--<outputDirectory>${project.build.directory}/generated-sources/protobuf/java</outputDirectory>-->
<outputDirectory>${project.basedir}/src/main/java</outputDirectory>
<!--设置是否在生成java文件之前清空outputDirectory的文件,默认值为true,设置为false时也会覆盖同名文件-->
<clearOutputDirectory>false</clearOutputDirectory>
</configuration>
<executions>
<execution>
<!--在执行mvn compile的时候会执行以下操作-->
<phase>compile</phase>
<goals>
<!--生成OuterClass类-->
<goal>compile</goal>
<!--生成Grpc类-->
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
2.2 proto 文件介绍,第一篇也有介绍
文件名:data.proto
syntax = "proto3";
package example; //编译出来的文件存放目录
service FormatData {
rpc DoFormat(Data) returns (Data){} //这里后面会用到,注意!!!
// DoForma 客户端调用服务端使用的方法
// Data 消息体,跟实体类类似,传参和返回都是一个实体接收
}
message Data {
string text = 1; // 1:代表第一个参数
string flag = 2; // 2:代表第二个参数
}
2.3 项目搭建好后,使用 maven 编译一下
2.4 然后会在java目录下生成:以Grpc结尾的文件才是连接的主要
2.4.1 DataOuterClass 文件是生成上面 data.proto 里面的message下面的 text 和 flag 参数的set和get方法,相当于实体Bean
2.4.2 FormatDataGrpc 文件是 grpc 的连接服务,下面会用这个调用
2.5 编写客户端:
三、代码编写:下面是先写的客户端代码,再写的服务端代码
客户端
3.1 application.yml文件配置(注意,yml文件中最下面url和端口根据启动项目打印的来,服务端有启动时有介绍)
server:
port: 8078
servlet:
context-path: /huangtu-timer
# mysql
spring:
# 环境 dev|test|prod
profiles:
active: test
logging:
level: debug
level.com.huangtu: debug
path: logs/
file: timer.log
# Grpc配置
grpc:
client:
local-grpc-server:
#测试,大写转小写
host: 0.0.0.0 #grpc服务端的 url
port: 9090 #grpc服务端的端口
enableKeepAlive: true
keepAliveWithoutCalls: true
3.2 客户端代码如下:一边参考 data.proto 文件一边理解更好
使用自动注入只需要配置yml中的即可,手动配置url和端口的话就注释上面的代码,放开下面代码的注释,然后直接用构造方法调用即可
import example.DataOuterClass;
import example.FormatDataGrpc;
import io.grpc.Channel;
import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import net.devh.springboot.autoconfigure.grpc.client.GrpcClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import types.Common;
import types.Unlock;
import types.WalletServiceGrpc;
import java.util.concurrent.TimeUnit;
/**
* CreateBy: huangtu
*/
@Component
public class GrpcClientService {
private Logger logger = LoggerFactory.getLogger(GrpcClientService.class);
/** yml文件配置自动注入grpc的url和端口 */
// @GrpcClient("local-grpc-server")
// private Channel serverChannel;
/**
* 测试方法
* @param letter 字母大小写转换
*/
// public void sendMessage(String letter) {
//将yml文件中的url和端口封装进连接服务 --
//这里大部分都是一样的,proto文件里面的server是什么方法,这里就会自带 xxxxxBlockingStub
// FormatDataGrpc.FormatDataBlockingStub stub = FormatDataGrpc.newBlockingStub(serverChannel);
//调用连接方法,封装参数,进行传输和接收
//自己自定义的 proto 文件直接参考替换类和方法就行
// DataOuterClass.Data data = stub.doFormat(DataOuterClass.Data.newBuilder().setText(letter).build());
// logger.info(data.getText()+"=======");
// }
/** 手动配置grpc的url和端口 */
private final ManagedChannel channel;
private final FormatDataGrpc.FormatDataBlockingStub blockingStub;
public GrpcClientService(String host, int port) {
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true);
channel = channelBuilder.build();
blockingStub = FormatDataGrpc.newBlockingStub(channel);
}
/**
* 停止连接---遇到异常可关闭连接----一般不用,因为连接是一直在的,报错看日志即可,关闭则需要重启服务端
*/
public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
}
/**
* 测试方法
* @param letter 字母:大小写转换
*/
public void sendMessage(String letter) {
//将yml文件中的url和端口封装进连接服务 --
//这里大部分都是一样的,proto文件里面的server是什么方法,这里就会自带 xxxxxBlockingStub
FormatDataGrpc.FormatDataBlockingStub stub = FormatDataGrpc.newBlockingStub(serverChannel);
//调用连接方法,封装参数,进行传输和接收
//自己自定义的 proto 文件直接参考替换类和方法就行
DataOuterClass.Data data = stub.doFormat(DataOuterClass.Data.newBuilder().setText(letter).build());
logger.info(data.getText()+"=======");
}
}
服务端----
3.3 java服务端代码:
3.3.1 加入pom依赖:这里的服务端依赖已经在上面加了,所以可以不用重复加
<!--grpc服务端-->
<dependency>
<groupId>net.devh</groupId>
<artifactId>grpc-server-spring-boot-starter</artifactId>
<version>1.3.0-RELEASE</version>
</dependency>
3.3.2 服务端代码
import example.DataOuterClass;
import example.FormatDataGrpc;
import io.grpc.stub.StreamObserver;
import net.devh.springboot.autoconfigure.grpc.server.GrpcService;
/**
* CreateBy: huangtu
*/
@GrpcService(DataOuterClass.class)
public class GrpcServerService extends FormatDataGrpc.FormatDataImplBase {
//注意,这里的 doFormat 方法,要跟dat.proto文件的server代码块里面的方法一样,注意大小写
//因为我这里message 代码块 是Data。我自己既用它做传参也用它做返回
//request是客户端传过来的参数,responseObserver是返回给客户端响应的参数
public void doFormat(DataOuterClass.Data request, StreamObserver<DataOuterClass.Data> responseObserver) {
String text = request.getText();
StringBuffer stringBuffer = new StringBuffer();
//大小写转换
for(int i=0;i<text.length();i++) {
//如果是小写
if (text.substring(i, i + 1).equals(text.substring(i, i + 1).toLowerCase())) {
stringBuffer.append(text.substring(i, i + 1).toUpperCase());
System.out.print(text.substring(i, i + 1).toUpperCase());
} else {
stringBuffer.append(text.substring(i, i + 1).toLowerCase());
System.out.print(text.substring(i, i + 1).toLowerCase());
}
}
DataOuterClass.Data.Builder abc = DataOuterClass.Data.newBuilder().setText(stringBuffer.toString());
//客户端每传一个消息过来都会调用一次onNext方法,当客户端发送完毕后,会执行onCompleted来返回一个对象给客户端
responseObserver.onNext(abc.build());
responseObserver.onCompleted();
}
}
3.4 启动项目:
在用一个项目中即在控制台可以看到客户端需要访问服务端的URL和端口,如果是两个单独的项目,根据自己的配置来。
3.5 方法调用
注意:自己写个测试类,用main方法的话直接仿照我的即可。使用yml配置的方式的话就放开客户端 GrpcClientService 代码上面的注释,把下面的代码注释
main方法测试:
public static void main(String[] args) {
//同一个项目启动,这里的端口和url跟日志上的一样。不同项目自己配置
GrpcClientService client = new GrpcClientService ("0.0.0.0", 9090);
try {
client.sendMessage("aaBBcc======");
} catch (Exception e) {
client.shutdown();
}
}
使用yml方式如下:
@Autowired
private GrpcClientService grpcClientService;
public void test(){
grpcClientService.sendMessage("aaBBcc======");
}
3.6 返回结果
AAbbCC=============
四、扩展,遇到的bug
1. springboot集成grpc报错:io.grpc.StatusRuntimeException: UNAVAILABLE.
2. springboot集成grpc报错:io.grpc.StatusRuntimeException: INTERNAL: Connection closed with unknown cause.
欢迎关注公众号:慌途L
后面会慢慢将文章迁移至公众号,也是方便在没有电脑的情况下可以进行翻阅,更新的话会两边同时更新,大家不用担心!