一、Protocol Buffer

我们还是先给出一个在实际开发中经常会遇到的系统场景。比如:我们的客户端程序是使用Java开发的,可能运行自不同的平台,如:Linux、Windows或者是Android,而我们的服务器程序通常是基于Linux平台并使用C++开发完成的。在这两种程序之间进行数据通讯时存在多种方式用于设计消息格式,如:
      1. 直接传递C/C++语言中一字节对齐的结构体数据,只要结构体的声明为定长格式,那么该方式对于C/C++程序而言就非常方便了,仅需将接收到的数据按照结构体类型强行转换即可。事实上对于变长结构体也不会非常麻烦。在发送数据时,也只需定义一个结构体变量并设置各个成员变量的值之后,再以char*的方式将该二进制数据发送到远端。反之,该方式对于Java开发者而言就会非常繁琐,首先需要将接收到的数据存于ByteBuffer之中,再根据约定的字节序逐个读取每个字段,并将读取后的值再赋值给另外一个值对象中的域变量,以便于程序中其他代码逻辑的编写。对于该类型程序而言,联调的基准是必须客户端和服务器双方均完成了消息报文构建程序的编写后才能展开,而该设计方式将会直接导致Java程序开发的进度过慢。即便是Debug阶段,也会经常遇到Java程序中出现各种域字段拼接的小错误。
      2. 使用SOAP协议(WebService)作为消息报文的格式载体,由该方式生成的报文是基于文本格式的,同时还存在大量的XML描述信息,因此将会大大增加网络IO的负担。又由于XML解析的复杂性,这也会大幅降低报文解析的性能。总之,使用该设计方式将会使系统的整体运行性能明显下降。
      对于以上两种方式所产生的问题,Protocol Buffer均可以很好的解决,不仅如此,Protocol Buffer还有一个非常重要的优点就是可以保证同一消息报文新旧版本之间的兼容性。

 Protocol Buffers是一个跨语言、跨平台的具有可扩展机制的序列化数据工具。也就是说,我在ubuntu下用python语言序列化一个对象,并使用http协议传输到使用java语言的android客户端,java使用对用的代码工具进行反序列化,也可以得到对应的对象。听起来好像跟json没有多大区别。。。其实区别挺多的。

Google说protobuf是smaller,faster,simpler,我们使用google规定的proto协议定义语言,之后使用proto的工具对代码进行“编译”,生成对应的各个平台的源代码,我们可以使用这些源代码进行工作。

二 、GRPC基本框架

gRPC就是其中的一种RPC框架。

如上图所示,在gRPC中,客户端应用程序可以就像调用本地对象方法一样直接调用不同服务器上的应用程序方法,使您更容易创建分布式应用程序和服务。与许多RPC系统一样,gRPC基于定义服务的思想,定义可以远程调用的方法,包括方法的参数和返回类型。在服务器端,服务器实现此接口并运行一个gRPC服务器来处理客户端调用。在客户端,客户端有一个“存根stub”(简称为某些语言的客户端),提供与服务器相同的方法。所有的数据传输都使用protobuf。

关于protobuf的格式,可以参考:http://www.cnblogs.com/stephen-liu74/archive/2013/01/02/2841485.html

三、grpc-java示例

该例子主要实现了,客户端向服务端添加手机号码的功能。

注意:经过实验中央maven仓库有些jar下载不下来,最好用阿里的仓库,添加方法见:

http://www.cnblogs.com/boshen-hzb/p/6590277.html

1、新建maven工程grpc-demo

添加grpc1.0 maven依赖

<repositories>
<repository>
<!-- Maven 自带的中央仓库使用的Id为central 如果其他的仓库声明也是用该Id 就会覆盖中央仓库的配置 -->
<id>nexus-aliyun</id>
<name>Nexus aliyun</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<layout>default</layout>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories> <modelVersion>4.0.0</modelVersion>
<groupId>com.grpc.addphone</groupId>
<artifactId>grpc-addphone</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>grpcAddPhone</name> <properties>
<grpc.version>1.0.3</grpc.version>
</properties> <dependencies>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-netty</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-protobuf</artifactId>
<version>${grpc.version}</version>
</dependency>
<dependency>
<groupId>io.grpc</groupId>
<artifactId>grpc-stub</artifactId>
<version>${grpc.version}</version>
</dependency>
</dependencies>

配置protobuf 插件,可以自动将.proto文件生成对应的java代码,一旦生成代码以后最好将protobuf插件注释掉,这样是为了,在以后mvn install等命令时,重复生成代码。

如下面加粗部分:

        <build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.Final</version>
</extension>
</extensions>

<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>com.google.protobuf:protoc:3.0.2:exe:${os.detected.classifier}</protocArtifact>
<pluginId>grpc-java</pluginId>
<pluginArtifact>io.grpc:protoc-gen-grpc-java:${grpc.version}:exe:${os.detected.classifier}</pluginArtifact>
<protoSourceRoot>${basedir}/src/main/proto</protoSourceRoot>
<!-- <outputDirectory>${basedir}/src/main/java</outputDirectory> -->
<!-- <attachProtoSources>true</attachProtoSources> -->
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>
</build>

2、定义proto,将文件放到/src/main/proto目录下

addphone.proto

syntax = "proto3";

option java_multiple_files = true;
option java_package = "com.grpc.addphone";
option java_outer_classname = "addphoneProto"; package addphone; enum PhoneType {
HOME = 0;
WORK = 1;
OTHER = 2;
} message ProtobufUser {
int32 id = 1;
string name = 2;
message Phone{
PhoneType phoneType = 1;
string phoneNumber = 2;
}
repeated Phone phones = 3;
} message AddPhoneToUserRequest{
int32 uid = 1;
PhoneType phoneType = 2;
string phoneNumber = 3;
} message AddPhoneToUserResponse{
bool result = 1;
} service PhoneService {
rpc addPhoneToUser(AddPhoneToUserRequest) returns (AddPhoneToUserResponse);
}

3、执行mvn install则会根据上面的proto文件定义在target下生成java文件,生成目录如下所示:

4、将生成的/target/generated-sources/protobuf/里面的grpc-java和java里面的com复制到src/main/java里面。

5、新建包com.service、com.server、com.client

6、在com.service里面新建PhoneServiceImp类,用来处理客户端发过来的请求。

package com.service;

import com.grpc.addphone.PhoneServiceGrpc;
import com.grpc.addphone.AddPhoneToUserRequest;
import com.grpc.addphone.AddPhoneToUserResponse; import io.grpc.stub.StreamObserver; public class PhoneServiceImp extends PhoneServiceGrpc.PhoneServiceImplBase{ @Override
public void addPhoneToUser(AddPhoneToUserRequest request, StreamObserver<AddPhoneToUserResponse> responseObserver) {
// TODO Auto-generated method stub
AddPhoneToUserResponse response = null;
if(request.getPhoneNumber().length() == 11 ){
System.out.printf("uid = %s , phone type is %s, nubmer is %s\n", request.getUid(), request.getPhoneType(), request.getPhoneNumber());
response = AddPhoneToUserResponse.newBuilder().setResult(true).build();
}else{
System.out.printf("The phone nubmer %s is wrong!\n",request.getPhoneNumber());
response = AddPhoneToUserResponse.newBuilder().setResult(false).build();
}
responseObserver.onNext(response);
responseObserver.onCompleted();
} }

代码很简单,我们只是检查手机号是不是11位,如果是把客户端的请求参数打印出来,给客户端返回true,如果不是11位,提示手机号错误,给客户端返回false。

7、在com.server包里面创建GRpcServer类,该类主要是负责启动服务端监听线程,而真正的处理类是PhoneServiceImp

package com.server;

import io.grpc.Server;
import io.grpc.ServerBuilder;
import java.io.IOException;
import java.util.logging.Logger;
import com.service.PhoneServiceImp; public class GRpcServer {
private static final Logger logger = Logger.getLogger(GRpcServer.class.getName()); private Server server; private void start() throws IOException {
/* The port on which the server should run */
int port = 50051;
server = ServerBuilder
.forPort(port)
.addService(new PhoneServiceImp())
.build()
.start();
logger.info("Server started, listening on " + port);
//关闭钩子本质是一个线程(也称为Hook线程),用来监听jvm的关闭。通过Runtime的addShutdownHook可以向JVM注册一个关闭钩子。Hook线程在JVM正常关闭才会执行,强制关闭时不会执行。
Runtime.getRuntime().addShutdownHook(new Thread() {
@Override
public void run() {
System.err.println("*** shutting down gRPC server since JVM is shutting down");
GRpcServer.this.stop();
System.err.println("*** server shut down");
}
});
} private void stop() {
if (server != null) {
server.shutdown();
}
} /**
* Await termination on the main thread since the grpc library uses daemon
* threads.
*/
private void blockUntilShutdown() throws InterruptedException {
if (server != null) {
server.awaitTermination();
}
} /**
* Main launches the server from the command line.
*/
public static void main(String[] args) throws IOException, InterruptedException {
final GRpcServer server = new GRpcServer();
server.start();
server.blockUntilShutdown();
}
}

8、在com.client包里面新建GRpcClient

package com.client;

import io.grpc.ManagedChannel;
import io.grpc.ManagedChannelBuilder;
import io.grpc.StatusRuntimeException; import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import java.util.logging.Logger; import com.grpc.addphone.PhoneServiceGrpc;
import com.grpc.addphone.AddPhoneToUserRequest;
import com.grpc.addphone.AddPhoneToUserResponse;
import com.grpc.addphone.PhoneType; public class GRpcClient { private static final Logger logger = Logger.getLogger(GRpcClient.class.getName()); private final ManagedChannel channel; private final PhoneServiceGrpc.PhoneServiceBlockingStub blockingStub; /** Construct client connecting to gRPC server at {@code host:port}. */
public GRpcClient(String host, int port) {
ManagedChannelBuilder<?> channelBuilder = ManagedChannelBuilder.forAddress(host, port).usePlaintext(true);
channel = channelBuilder.build();
blockingStub = PhoneServiceGrpc.newBlockingStub(channel);
} public void shutdown() throws InterruptedException {
channel.shutdown().awaitTermination(5, TimeUnit.SECONDS);
} /** add phone to user. */
public void addPhoneToUser(int uid, PhoneType phoneType, String phoneNubmer) {
logger.info("Will try to add phone to user " + uid);
AddPhoneToUserRequest request = AddPhoneToUserRequest.newBuilder().setUid(uid).setPhoneType(phoneType)
.setPhoneNumber(phoneNubmer).build();
AddPhoneToUserResponse response;
try {
response = blockingStub.addPhoneToUser(request);
} catch (StatusRuntimeException e) {
logger.log(Level.WARNING, "RPC failed: {0}", e.getStatus());
return;
}
logger.info("Result: " + response.getResult());
} public static void main(String[] args) throws Exception {
GRpcClient client = new GRpcClient("localhost", 50051);
try {
client.addPhoneToUser(1, PhoneType.WORK, "13888888888");
} finally {
client.shutdown();
}
}
}

java grpc实例分析的更多相关文章

  1. java IO 实例分析

    初学java,一直搞不懂java里面的io关系,在网上找了很多大多都是给个结构图草草描述也看的不是很懂.而且没有结合到java7 的最新技术,所以自己来整理一下,有错的话请指正,也希望大家提出宝贵意见 ...

  2. java基础学习05(面向对象基础01--类实例分析)

    面向对象基础01(类实例分析) 实现的目标 1.如何分析一个类(类的基本分析思路) 分析的思路 1.根据要求写出类所包含的属性2.所有的属性都必须进行封装(private)3.封装之后的属性通过set ...

  3. RPC原理及RPC实例分析

    在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服务消费方和服务提供方是本地调用关系. 1 2 3 4 5 6 public class ...

  4. RPC-原理及RPC实例分析

    还有就是:RPC支持的BIO,NIO的理解 (1)BIO: Blocking IO;同步阻塞: (2)NIO:Non-Blocking IO, 同步非阻塞; 参考:IO多路复用,同步,异步,阻塞和非阻 ...

  5. RPC原理及RPC实例分析(转)

    出处:https://my.oschina.net/hosee/blog/711632 在学校期间大家都写过不少程序,比如写个hello world服务类,然后本地调用下,如下所示.这些程序的特点是服 ...

  6. 从Java String实例来理解ANSI、Unicode、BMP、UTF等编码概念

    转(http://www.codeceo.com/article/java-string-ansi-unicode-bmp-utf.html#0-tsina-1-10971-397232819ff9a ...

  7. 常用 Java 静态代码分析工具的分析与比较

    常用 Java 静态代码分析工具的分析与比较 简介: 本文首先介绍了静态代码分析的基 本概念及主要技术,随后分别介绍了现有 4 种主流 Java 静态代码分析工具 (Checkstyle,FindBu ...

  8. Android Touch事件原理加实例分析

    Android中有各种各样的事件,以响应用户的操作.这些事件可以分为按键事件和触屏事件.而Touch事件是触屏事件的基础事件,在进行Android开发时经常会用到,所以非常有必要深入理解它的原理机制. ...

  9. JVM 字节码执行实例分析

    前言 最近在看<Java 虚拟机规范>和<深入理解JVM虚拟机>,对于字节码的执行有了进一步的了解.字节码就像是汇编语言,是 JVM 的指令集.下面我们先对 JVM 执行引擎做 ...

随机推荐

  1. linux下thinkphp取消调试模式后找不到网页解决方案

    1.最大嫌疑是Runtime目录权限不足,导致common~runtime.php文件无法生成, 解决:1.整个Runtime目录删除,让系统重新生成; 2.给Runtime及以下的所有文件足够权限0 ...

  2. soap,socket

    Socket 接口是访问 Internet 使用得最广泛的方法. 如果你有一台刚配好TCP/IP协议的主机,其IP地址是202.120.127.201, 此时在另一台主机或同一台主机上执行ftp 20 ...

  3. 关于sencha touch中给文本添加焦点无效的解决方案

    目前的解决方案是给你的执行代码加上一个timeout延迟100ms+ setTimeout(function(){ SoftKeyboard.isShowing(function(isShowing) ...

  4. 查看Android内存,cpu

    转自https://testerhome.com/topics/2583 一.查看内存 查看Android应用内存: adb shell dumpsys meminfo 1.查看详细的内存: adb ...

  5. 【汇总】C#数据类型及转换

    13.C# 16进制与字符串.字节数组之间的转换 https://www.cnblogs.com/seventeen/archive/2009/10/29/1591936.html C# 对象.文件与 ...

  6. C# 在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke [问题点数:40分

    注意:  this.DateTimeRun = true;            new Thread(jishi_kernel).Start(); 线程的启动,不能放在    public Form ...

  7. Spring、Springboot常用注解:@Qualifier(不定时更新)

    1.@Qualifier 出现场景: 老项目中有多个实现类实现同一个接口时,或者一个项目中有多个数据源时,spring容器不知道该注入哪个实现类或者使用哪个数据源,该注解就派上用场. 1)多实现类实现 ...

  8. 自定义tag标签的方法

    JSP1.0中可以通过继承TagSupport或者BodyTagSupport来实现自定义的tag处理方法. JSP2.0中也支持另外一种更为简单的自定tag的方法,那就是直接讲JSP代码保存成*.t ...

  9. Linux实战教学笔记40: Mha-Atlas-MySQL高可用方案实践(二)

    六,配置VIP漂移 主机名 IP地址(NAT) 漂移VIP 描述 mysql-db01 eth0:192.168.0.51 VIP:192.168.0.60 系统:CentOS6.5(6.x都可以) ...

  10. 两个不同vim之间复制内容

    常规想法是打开两个vim,然后进行yy和p操作,但是实践证明根本是不行的.此时,我们需要分割窗口,然后就可以复制粘贴了.步骤如下: 假设我要把srv.c文件的readline函数整体复制到cli.c文 ...