下面记录使用golang开发grpc的服务端程序,然后供java去调用grpc服务的过程,先看服务端proto文件内容
syntax = "proto3";
package proto;
option java_package = "com.test.rpc";
option java_multiple_files = false;
message SayHelloRequest{
bytes name=1;
}
message SayHelloResponse{
bytes result=1;
}
service SayHelloService{
rpc SayHello(SayHelloRequest) returns (SayHelloResponse);
}
以上文件命名为service.proto文件保存,grpc要求每一个方法调用都要有一个请求参数,一个响应参数,这里面的SayHelloRequest就是请求参数,具体来讲grpc远程方法调用时就只需要传递一个name参数,bytes对应的是字符串类型。相对应的SayHelloResponse就是grpc方法调用的返回结果,返回的也是个字符串,参数名称叫做result,具体的grpc方法名称是SayHello。
go语言端通过以下命令生成go代码(在service.proto所在的目录执行):
protoc --go_out=plugins=grpc:. service.proto
命令执行以后会产生一个Service.pb.go文件,里面会自动生成如下内容(部分):
上面自动生成的代码里面的interface就是等待我们去具体实现的grpc服务内容,具体来说就是去具体实现SayHello那个方法,随便找个地方去实现这个方法,例如:
package main
import (
"golang.org/x/net/context"
"google.golang.org/grpc"
"log"
"motorway/proto"
"net"
"runtime"
"strconv"
)
const (
port = "41005"
)
type Data struct{}
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
//起服务
lis, err := net.Listen("tcp", ":"+port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
data:=&Data{}
proto.RegisterSayHelloServiceServer(s,data)
log.Printf("grpc server in: %s", port)
s.Serve(lis)
}
func (t *Data) SayHello(ctx context.Context,in *proto.SayHelloRequest) (result *proto.SayHelloResponse, err error){
return &proto.SayHelloResponse{
Result:[] byte("hello :"+string(in.Name)),
},nil
}
在上面的代码中,Data对象的SayHello就是我们实现的接口方法(因为 go语言实现接口没有implements等关键字,只要写的方法名称,参数,返回值和interface里面的方法定义一样就认为是实现该方法了),上面的main方法里面实例化一个grpc的server,向server对象注册SayHello服务对象data,最后调用Serve方法就可以对外提供grpc服务了。
以上go语言端的grpc服务已经就绪,可以等待调用了,要是急于验证服务的可用性,go的客户端调用代码如下:
package main
import (
"fmt"
"motorway/proto"
"log"
"runtime"
"strconv"
"strings"
"sync"
"time"
"math/rand"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
var (
wg sync.WaitGroup
)
const (
networkType = "tcp"
server = "127.0.0.1" //"172.0.16.111" "172.0.16.105"//"127.0.0.1"
port = "41005"
parallel = 50 //连接并行度
times = 1000 //每连接请求次数
)
func main() {
runtime.GOMAXPROCS(runtime.NumCPU())
currTime := time.Now()
//并行请求
for i := 0; i < int(parallel); i++ {
wg.Add(1)
go func() {
defer wg.Done()
exe()
}()
}
wg.Wait()
log.Printf("time taken: %.2f s ", time.Now().Sub(currTime).Seconds())
}
func exe() {
//建立连接
conn, err := grpc.Dial(server + ":" + port,grpc.WithInsecure())
if nil!=conn{
defer conn.Close()
}
if (nil!=err){
fmt.Printf("创建连接失败!%s\n",err)
return
}
client2 := proto.NewSayHelloServiceClient(conn)
for i := 0; i < int(times); i++ {
testSayHello(client2)
}
}
func testSayHello(client proto.SayHelloServiceClient){
req:=&proto.SayHelloRequest{
Name:[]byte("张三"),
}
result,_:=client.SayHello(context.Background(),req)
if nil!=result{
//fmt.Printf("%s \n",string(result.Result))
}else{
fmt.Println("response is nil")
}
}
代码里面模拟了50个并发客户端,每个客户端请求调用1000次的情况,在我的i5 cpu机器上面差不多需要4.2s完成这5万次调用。
好了,言归正传,接着说java端怎么进行调用的问题,新建一个spring boot项目,在pom.xml里面增加maven依赖如下:
io.grpc
grpc-netty-shaded
1.20.0
io.grpc
grpc-protobuf
1.20.0
io.grpc
grpc-stub
1.20.0
然后继续在pom.xml的build里面增加plugin等如下(不要完全照搬,就是org.xolstice.maven.plugins那个还有下面那个extensions需要复制):
org.springframework.boot
spring-boot-maven-plugin
org.xolstice.maven.plugins
protobuf-maven-plugin
0.5.1
com.google.protobuf:protoc:3.7.1:exe:${os.detected.classifier}
grpc-java
io.grpc:protoc-gen-grpc-java:1.20.0:exe:${os.detected.classifier}
compile
compile-custom
kr.motd.maven
os-maven-plugin
1.5.0.Final
然后在项目里面建立proto目录,将go语言那边的service.proto文件复制进去,如下图:
然后打开maven工具,找到如下图所示的插件:
先执行protobuf:compile一遍,再执行protobuf:copile-custom,会自动生成如下文件:
正常情况下应该生成两个文件,一个是com.test.rpc.Service,另外一个是com.test.rpc.SayHelloServiceGrpc,回到spring boot application入口main方法,编写如下代码:
package com.grpc;
import com.google.protobuf.ByteString;
import com.test.rpc.SayHelloServiceGrpc;
import com.test.rpc.Service;
import io.grpc.netty.shaded.io.grpc.netty.NegotiationType;
import io.grpc.netty.shaded.io.grpc.netty.NettyChannelBuilder;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
@SpringBootApplication
public class DemoApplication {
private static final String host="127.0.0.1";
private static final int port=41005;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
io.grpc.Channel channel = NettyChannelBuilder.forAddress(host, port)
.negotiationType(NegotiationType.PLAINTEXT)
.build();
Service.SayHelloRequest req=Service.SayHelloRequest.newBuilder().setName(ByteString.copyFrom("测试",Charset.forName("utf-8"))).build();
Service.SayHelloResponse result= SayHelloServiceGrpc.newBlockingStub(channel).sayHello(req);
try {
System.out.println(result.getResult().toString("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
}
}
java这边请求参数是通过Service.XXXXX(请求对象).newBuilder().不断的set各个请求参数值(xxx).build()去构造出来的,然后是调用SayHelloServiceGrpc.newBlockingStub(channel).sayHello(req); 这种方式去调用的,grpc貌似有多种调用方式,还没有研究到那么深入,最后是调用结果的输出。
golang服务端程序正确的返回了grpc调用结果。