# 【夜读源码】grpc-java 概览

gRPC 是 Google 开源的一套语言无关基于HTTP2协议的 RPC 框架，其中 Java 的实现称为 grpc-java，代码地址在 https://github.com/grpc/grpc-java。

RPC （remote process call）的含义是程序执行本地的一个方法（看上去像本地执行），实际上会在远程程序代码内执行，这个过程会涉及到网络通讯和传输协议。gRPC作为 RPC 一种实现，传输的网络协议是HTTP2，传输的内容按照 Protocol Buffer 进行编码，并且支持各种语言的实现。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660231145754/TDYnQjZAb.png align="left"  )


# 概述
grpc-java 是一个 RPC 库和框架，运行在 JDK 8 上，项目包括两部分：代码生成和运行库。
代码生成由C++编写，用于生成一部分Java 代码, 生成的部分，称为 stub，包括 client stub 和 server stub。
运行库由Java 编写，Java 项目可以引用这些运行库。


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660231754973/BCuYW_Ki5.png align="left")

在了解 stub 和 运行库的内容之前，运行一个完整的例子，把它运行起来。


# gRPC 实例
创建一个 Maven 作为项目管理工具的 Java 项目。

## 添加依赖
把 grpc 的 运行库 都加进来
```
<dependencies>
  <dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-netty-shaded</artifactId>
    <version>1.48.1</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-protobuf</artifactId>
    <version>1.48.1</version>
  </dependency>
  <dependency>
    <groupId>io.grpc</groupId>
    <artifactId>grpc-stub</artifactId>
    <version>1.48.1</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>
```
## 代码生成插件
为了能 生成代码，也就是生成Stub, 添加 maven 插件
```
<build>
  <extensions>
    <extension>
      <groupId>kr.motd.maven</groupId>
      <artifactId>os-maven-plugin</artifactId>
      <version>1.6.2</version>
    </extension>
  </extensions>
  <plugins>
    <plugin>
      <groupId>org.xolstice.maven.plugins</groupId>
      <artifactId>protobuf-maven-plugin</artifactId>
      <version>0.6.1</version>
      <configuration>
        <protocArtifact>com.google.protobuf:protoc:3.21.1:exe:${os.detected.classifier}</protocArtifact>
        <pluginId>grpc-java</pluginId>
        <pluginArtifact>io.grpc:protoc-gen-grpc-java:1.48.1:exe:${os.detected.classifier}</pluginArtifact>
      </configuration>
      <executions>
        <execution>
          <goals>
            <goal>compile</goal>
            <goal>compile-custom</goal>
          </goals>
        </execution>
      </executions>
    </plugin>
  </plugins>
</build>
```
## grpc 接口描述
需要写一个描述文件 `src/main/proto/hello.proto`，按照protocol buffer的语法，定义 方法的名称和参数的数据格式。
代码生成插件，读取 hello.proto，生成 stub

```
syntax = "proto3";

option java_package = "org.shalk.grpc";

message Info {
  int32 id = 1;
  string name = 2;
  bool ready = 3;
}

service Hello{
  rpc echo(Info) returns (Info);
  rpc clientStream (Info) returns (stream Info);
  rpc serverStream (stream Info) returns (Info);
  rpc biStream (stream Info) returns (stream Info);
}
```

## 生成代码

```
mvn compile
```

## 编写服务端代码

服务端需要写两部分代码，server stub implement （服务端 stub 实现）和 server start （服务端启动）
服务端 stub 实现，就是对`  rpc echo(Info) returns (Info);` 的实现。
服务端启动，是启动一个端口，对外提供网络服务，这样客户端就可以通过网络请求把请求数据发过来，服务端在执行完请求后，把执行结果发送回去。
```
//  服务端 stub 实现
public class HelloImpl extends HelloGrpc.HelloImplBase {
    @Override
    public void echo(HelloOuterClass.Info request, StreamObserver<HelloOuterClass.Info> responseObserver) {
        System.out.printf("request info Id:%d\n", request.getId());
        responseObserver.onNext(request);
        responseObserver.onCompleted();
    }
}
```
服务端启动
```
import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;

public class SimpleServer {
    public static void main(String[] args) throws IOException, InterruptedException {
        Server server = ServerBuilder.forPort(8080).addService(new HelloImpl()).build();
        server.start();
        System.out.println("server started");
        server.awaitTermination();
        System.out.println("server stoped");

    }
}

```


## 编写客户端代码
客户端需要些两部分代码，client stub initial (客户端 stub 初始化） 和 client stub call （客户端 stub 调用）

```

import io.grpc.Channel;
import io.grpc.ManagedChannelBuilder;

public class SimpleClient {
    public static void main(String[] args) {
        // 客户端 stub  初始化
        Channel channel = ManagedChannelBuilder.forAddress("localhost", 8080).usePlaintext().build();
        HelloGrpc.HelloBlockingStub helloBlockingStub = HelloGrpc.newBlockingStub(channel);
        // 客户端 stub 调用
        for (int i = 0; i < 100; i++) {
            HelloOuterClass.Info request = HelloOuterClass.Info.newBuilder().setId(i).setName("hi").setReady(true).build();
            HelloOuterClass.Info response = helloBlockingStub.echo(request);
            System.out.printf("get response A:%d\n", response.getId());
        }
    }

}
```

## 运行

服务端先启动，客户端运行可以看到结果
```
get response A:96
get response A:97
get response A:98
get response A:99
```



# 整体架构 
整体架构，纵向上，gRPC可以分为 服务端和客户端，横向上，可以划分为传输层，通道层 和 Stub 层三层。第一层，Stub 层 是提供给开发者的接口和数据模型，这个和proto 文件描述的是一致的。第二层，通道层（Channel）是在传输层之上，提供给Stub 层用的。主要作用是做一些拦截器和装饰器，一般通信传输会做一些拦截，可以做 日志，权限和监控等等。第三层，传输层是用于接收和发送数据的层次，它的接口一般不对外暴露。



![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660574982821/TrCPQoXj_.png align="left")


# 一次调用的时序

回顾前面的示例代码，客户端 `helloBlockingStub` 的一次调用，服务端响应请求。第一步，请求从客户端的Stub层，依次穿过客户端的通道层，传输层，到达网络上。其次，数据通过网络到达服务端的传输层，依次穿过服务端的通道层，最后到达Stub 层 ，也就是服务端的实现。最后，服务端的实现中Stub 响应结果，逆向把数据传输到客户端的Stub 层。

## 客户端发送请求
1. stub 执行echo 方法
2. echo 方法 执行静态方法blockingUnarycall
3. blockingUsnaryCall中，构造了一个ClientCall，ClientCall 准备了了一个listenablefuture，等Listenablefuture 状态是Done，就把结果从ListenableFuture 中返回。
4. 准备listenableFuture时，DelayedClientCall  里准备了 四个pendingRunnables 的任务等待执行。
5 等待状态时Done的时候，exector.waitAndDrain  把这四个任务拿到，分别执行一下。
6. 任务1 realCall.start(finalListener, headers);
7. 任务2  realCall.request(numMessages);
8. 任务3 realCall.sendMessage(message);
9. 任务4 realCall.halfClose();
10.  DelayedClientCall 代理 realCall， realCall 是ClientCallImpl
11  realCall 代理 ClientStream ， clientStream 有接口 `void start(ClientStreamListener listener);` `void request(int numMessages);` `void writeMessage(InputStream message);` 和 `halfClose()`
12  ClientCallImpl 中又把这四个方法，代理给 Stream，Stream的类型是 RetriableStream
13 RetriableStream 又执行过程创建DelayStream，代理给DelayStream
14 DelayStream 又创建四个任务。 （request，writeMessage，flush 和 halfCLose）
15 DelayStream 把任务代理给realStream，这个realStream 是 NettyClientStream

整理来看 Stub 把请求传给了ClientCall， ClientCall 传给了 ClientStream

ClientStream 有Netty 的实现，内部会把数据传输到网络上。

这里面Transport 和 Framer 的逻辑还没明白，第一篇大概就先看到这里。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660667983311/LS3EJ2EP-.png align="left")

## 服务端接收请求
类似的，服务端也有 ServerCall 和ServerStream, 并且把deramer 解数据的任务扔到线程池。
deframer 把解开的数据，找到listener，执行到 Stub 的实现里，数据送达。

ServerCall => ServerStream => Deframer


## 服务端回复响应
类似于客户端发请求

## 客户端接收响应
类似于服务端收请求


# Next
一次调用中，客户端和服务端处理请求的过程，可以按照层次和时许，把过程梳理更清晰一点。
下一篇文章，将深入`客户端发送请求`，把客户端中 ClientCall 的作用，ClientStream的作用，时序线程池，以及Netty 怎么把数据发送出去，以及数据包的格式 细致的展开写一写。
