文章目录
- 1、Protocol Buffers定义接口
- 1.1、编写接口服务
- 1.2、Protobuf基础数据类型
- 2、服务器端实现
- 2.1、生成gRPC服务类
- 2.2、Java服务器端实现
- 3、java、go、php客户端实现
- 3.1、Java客户端实现
- 3.2、Go客户端实现
- 3.3、PHP客户端实现
- 4、运行效果
本文例子是在Window平台测试,Java编写的gRPC服务器端,同时使用Java、Go、PHP编写客户端调用。
1、Protocol Buffers定义接口
1.1、编写接口服务
// 定义protocol buffers版本(proto3)
syntax = "proto3";
// 定义包名,避免协议消息类型之间的命名冲突。
package helloworld;
// 定义java格式
option java_multiple_files = true;
option java_package = "com.penngo.grpc";
option java_outer_classname = "HelloWorldProto";
option objc_class_prefix = "HLW";
// 服务接口定义
service Greeter {
// 方法1定义
rpc SayHello (HelloRequest) returns (HelloReply) {}
// 方法2定义
rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}
// HelloRequest消息类型格式定义,
message HelloRequest {
// name为定义字段名,1为消息传输中的字段编号,使用后,不应该改编号。
string name = 1;
}
// HelloReply消息类型格式定义,
message HelloReply {
// message为定义字段名,1为消息传输中的字段编号,使用后,不应该改编号。
string message = 1;
}
1.2、Protobuf基础数据类型
<th>C++</th>
<th>Java/Kotlin</th>
<th>Python</th>
<th>Go</th>
<th>Ruby</th>
<th>C#</th>
<th>PHP</th>
<th>Dart</th>
</tr>
<tr>
<td>double</td>
<td>double</td>
<td>double</td>
<td>float</td>
<td>float64</td>
<td>Float</td>
<td>double</td>
<td>float</td>
<td>double</td>
</tr>
<tr>
<td>float</td>
<td>float</td>
<td>float</td>
<td>float</td>
<td>float32</td>
<td>Float</td>
<td>float</td>
<td>float</td>
<td>double</td>
</tr>
<tr>
<td>int32</td>
<td>int32</td>
<td>int</td>
<td>int</td>
<td>int32</td>
<td>Fixnum or Bignum (as required)</td>
<td>int</td>
<td>integer</td>
<td>int</td>
</tr>
<tr>
<td>int64</td>
<td>int64</td>
<td>long</td>
<td>int/long<sup>[4]</sup></td>
<td>int64</td>
<td>Bignum</td>
<td>long</td>
<td>integer/string<sup>[6]</sup></td>
<td>Int64</td>
</tr>
<tr>
<td>uint32</td>
<td>uint32</td>
<td>int<sup>[2]</sup></td>
<td>int/long<sup>[4]</sup></td>
<td>uint32</td>
<td>Fixnum or Bignum (as required)</td>
<td>uint</td>
<td>integer</td>
<td>int</td>
</tr>
<tr>
<td>uint64</td>
<td>uint64</td>
<td>long<sup>[2]</sup></td>
<td>int/long<sup>[4]</sup></td>
<td>uint64</td>
<td>Bignum</td>
<td>ulong</td>
<td>integer/string<sup>[6]</sup></td>
<td>Int64</td>
</tr>
<tr>
<td>sint32</td>
<td>int32</td>
<td>int</td>
<td>int</td>
<td>int32</td>
<td>Fixnum or Bignum (as required)</td>
<td>int</td>
<td>integer</td>
<td>int</td>
</tr>
<tr>
<td>sint64</td>
<td>int64</td>
<td>long</td>
<td>int/long<sup>[4]</sup></td>
<td>int64</td>
<td>Bignum</td>
<td>long</td>
<td>integer/string<sup>[6]</sup></td>
<td>Int64</td>
</tr>
<tr>
<td>fixed32</td>
<td>uint32</td>
<td>int<sup>[2]</sup></td>
<td>int/long<sup>[4]</sup></td>
<td>uint32</td>
<td>Fixnum or Bignum (as required)</td>
<td>uint</td>
<td>integer</td>
<td>int</td>
</tr>
<tr>
<td>fixed64</td>
<td>uint64</td>
<td>long<sup>[2]</sup></td>
<td>int/long<sup>[4]</sup></td>
<td>uint64</td>
<td>Bignum</td>
<td>ulong</td>
<td>integer/string<sup>[6]</sup></td>
<td>Int64</td>
</tr>
<tr>
<td>sfixed32</td>
<td>int32</td>
<td>int</td>
<td>int</td>
<td>int32</td>
<td>Fixnum or Bignum (as required)</td>
<td>int</td>
<td>integer</td>
<td>int</td>
</tr>
<tr>
<td>sfixed64</td>
<td>int64</td>
<td>long</td>
<td>int/long<sup>[4]</sup></td>
<td>int64</td>
<td>Bignum</td>
<td>long</td>
<td>integer/string<sup>[6]</sup></td>
<td>Int64</td>
</tr>
<tr>
<td>bool</td>
<td>bool</td>
<td>boolean</td>
<td>bool</td>
<td>bool</td>
<td>TrueClass/FalseClass</td>
<td>bool</td>
<td>boolean</td>
<td>bool</td>
</tr>
<tr>
<td>string</td>
<td>string</td>
<td>String</td>
<td>str/unicode<sup>[5]</sup></td>
<td>string</td>
<td>String (UTF-8)</td>
<td>string</td>
<td>string</td>
<td>String</td>
</tr>
<tr>
<td>bytes</td>
<td>string</td>
<td>ByteString</td>
<td>str (Python 2)<br>bytes (Python 3)</td>
<td>[]byte</td>
<td>String (ASCII-8BIT)</td>
<td>ByteString</td>
<td>string</td>
<td>List
<int></int>
</td>
</tr>
</tbody>
.proto |
---|
https://protobuf.dev/programming-guides/proto3/
2、服务器端实现
服务器端实例使用java编写
2.1、生成gRPC服务类
pom.xml
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.penngo</groupId>
<artifactId>grpc-helloworld</artifactId>
<version>1.0</version>
<properties>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.target>11</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty-shaded</artifactId>
<version>1.59.0</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>1.59.0</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>1.59.0</version>
</dependency>
<dependency> <!-- necessary for Java 9+ -->
<groupId>org.apache.tomcat</groupId>
<artifactId>annotations-api</artifactId>
<version>6.0.53</version>
<scope>provided</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.7.1</version>
</extension>
</extensions>
<plugins>
<!-- grpc代码生成插件 -->
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.6.1</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.24.0:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:1.59.0:exe:${os.detected.classifier}</pluginArtifact>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>alimaven</id>
<name>Maven Aliyun Mirror</name>
<url>https://maven.aliyun.com/repository/central</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>public</id>
<name>aliyun nexus</name>
<url>https://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>
</project>
命令行下执行gRPC的java代码
mvn compile
自动扫描代码目中src/main/proto/helloworld.proto下的proto文件,自动生成gRPC相关代码到target/generated-sources/protobuf目录下。
2.2、Java服务器端实现
HelloServer.java
package com.penngo;
import com.penngo.grpc.GreeterGrpc;
import com.penngo.grpc.HelloReply;
import com.penngo.grpc.HelloRequest;
import io.grpc.Grpc;
import io.grpc.InsecureServerCredentials;
import io.grpc.Server;
import io.grpc.stub.StreamObserver;
import java.io.IOException;
public class HelloServer {
public static void main(String[] args) throws IOException, InterruptedException {
int port = 50051;
Server server = Grpc.newServerBuilderForPort(port, InsecureServerCredentials.create())
.addService(new GreeterImpl())
.build()
.start();
Runtime.getRuntime().addShutdownHook(new Thread(()->{
System.err.println("*** shutting down gRPC server since JVM is shutting down");
stopServer(server);
System.err.println("*** server shut down");
}));
server.awaitTermination();
}
private static void stopServer(Server server) {
if (server != null) {
server.shutdown();
}
}
static class GreeterImpl extends GreeterGrpc.GreeterImplBase {
@Override
public void sayHello(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
System.out.println("收到客户端消息:" + req.getName());
String msg = "我是Java Server";
System.out.println("回复客户端消息:" + msg);
HelloReply reply = HelloReply.newBuilder().setMessage("你好!" + msg).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
@Override
public void sayHelloAgain(HelloRequest req, StreamObserver<HelloReply> responseObserver) {
System.out.println("再次收到客户端消息:" + req.getName());
String msg = "我是Java Server2";
System.out.println("再次回复客户端消息:" + msg);
HelloReply reply = HelloReply.newBuilder().setMessage(msg).build();
responseObserver.onNext(reply);
responseObserver.onCompleted();
}
}
}
https://github.com/protocolbuffers/protobuf/releases
3、java、go、php客户端实现
3.1、Java客户端实现
HelloClient.java
package com.penngo;
import com.penngo.grpc.GreeterGrpc;
import com.penngo.grpc.HelloReply;
import com.penngo.grpc.HelloRequest;
import io.grpc.Grpc;
import io.grpc.InsecureChannelCredentials;
import io.grpc.ManagedChannel;
import java.util.concurrent.TimeUnit;
public class HelloClient {
public static void main(String[] args) throws InterruptedException {
String host = "localhost";
int port = 50051;
ManagedChannel managedChannel = Grpc.newChannelBuilderForAddress(host, port, InsecureChannelCredentials.create()).build();
GreeterGrpc.GreeterBlockingStub blockingStub = GreeterGrpc.newBlockingStub(managedChannel);
String msg1 = "我是java client";
System.out.println("向服务器端发送消息:" + msg1);
HelloRequest helloRequest1 = HelloRequest.newBuilder().setName(msg1).build();
HelloReply reply1 = blockingStub.sayHello(helloRequest1);
System.out.println("收到服务器端消息:" + reply1.getMessage());
String msg2 = "我是java client2";
System.out.println("再次向服务器端发送消息:" + msg2);
HelloRequest helloRequest2 = HelloRequest.newBuilder().setName(msg2).build();
HelloReply reply2 = blockingStub.sayHelloAgain(helloRequest2);
System.out.println("再次收到服务器端消息:" + reply2.getMessage());
managedChannel.shutdownNow().awaitTermination(5, TimeUnit.SECONDS);
}
}
3.2、Go客户端实现
Go的代码生成需要使用protoc.exe来编译helloworld.proto服务文件,生成对应的服务调用代码
下载地址:https://github.com/protocolbuffers/protobuf/releases,当前最新版本为protoc-25.1-win64.zip
解压到目录D:\Program Files\protoc-25.1-win64\bin,需要把这个目录添加到环境变量PATH当中。
安装protocol编译器的Go插件
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2
执行下边命令生成Go的gRPC代码
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld/helloworld.proto
编写客户端实现代码
package main
import (
"context"
"flag"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
pb "grpctest/helloworld"
"log"
"time"
)
var (
addr = flag.String("addr", "localhost:50051", "the address to connect to")
)
func main() {
flag.Parse()
// Set up a connection to the server.
conn, err := grpc.Dial(*addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
msg1 := "我是go client"
log.Printf("向服务器端发送消息: %s", msg1)
r1, err1 := c.SayHello(ctx, &pb.HelloRequest{Name: msg1})
if err1 != nil {
log.Fatalf("could not greet: %v", err1)
}
log.Printf("收到服务器端消息:%s", r1.GetMessage())
msg2 := "我是go client2"
log.Printf("再次向服务器端发送消息: %s", msg1)
r2, err2 := c.SayHelloAgain(ctx, &pb.HelloRequest{Name: msg2})
if err2 != nil {
log.Fatalf("could not greet: %v", err2)
}
log.Printf("再次收到服务器端消息: %s", r2.GetMessage())
}
3.3、PHP客户端实现
安装gRPC的PHP扩展https://pecl.php.net/package/gRPC
当前测试php版本7.3,下载php_grpc-1.42.0-7.3-nts-vc15-x64.zip
php.ini这个文件加入
extension=php_grpc.dll
新建composer.json
{
"name": "xxs/grpc",
"require": {
"grpc/grpc": "^v1.4.0",
"google/protobuf": "^v3.3.0"
},
"autoload":{
"psr-4":{
"GPBMetadata\\":"GPBMetadata/",
"Helloworld\\":"Helloworld/"
}
}
}
在composer.json相同目录下执行命令下载依赖库
composer install
安装grpc的php插件,https://github.com/lifenglsf/grpc_for_windows
解压复制到项目下grpc_for_windows/x64/grpc_php_plugin.exe
执行命令生成gRPC代码
protoc --proto_path=. --php_out=. --grpc_out=. --plugin=protoc-gen-grpc=grpc_for_windows/x64/grpc_php_plugin.exe ./helloworld.proto
client.php实现
<?php
require dirname(__FILE__) . '/vendor/autoload.php';
//echo dirname(__FILE__) . '/vendor/autoload.php';
function greet($hostname)
{
$client = new Helloworld\GreeterClient($hostname, [
'credentials' => Grpc\ChannelCredentials::createInsecure(),
]);
$request = new Helloworld\HelloRequest();
$msg1 = "我是PHP client";
$request->setName($msg1);
echo "向服务器端发送消息: $msg1". PHP_EOL;
list($response, $status) = $client->SayHello($request)->wait();
if ($status->code !== Grpc\STATUS_OK) {
echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL;
exit(1);
}
echo "收到服务器端消息:".$response->getMessage() . PHP_EOL;
$msg2 = "我是PHP client2";
$request->setName($msg2);
echo "再次向服务器端发送消息: $msg2". PHP_EOL;
list($response, $status) = $client->SayHelloAgain($request)->wait();
if ($status->code !== Grpc\STATUS_OK) {
echo "ERROR: " . $status->code . ", " . $status->details . PHP_EOL;
exit(1);
}
echo "再次收到服务器端发送消息:".$response->getMessage() . PHP_EOL;
$client->close();
}
$hostname = !empty($argv[2]) ? $argv[2] : 'localhost:50051';
greet($hostname);
4、运行效果
服务器端