一、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 文件权限详细说明

    在本章前部,当你试图转换到根用户的登录目录时,你收到了以下消息: cd /root bash: /root: Permission denied 这是 Linux 安全功能的一个演示.Linux 和 ...

  2. cx_Oracle.DatabaseError: ORA-12541: TNS:no listener

    问题:利用Python连接Oracle时报错,完整过程如下 import cx_Oracle conn = cx_Oracle.connect('testma/dingjia@192.168.88.1 ...

  3. Linux学习笔记 -- 文件包含

    简述 简单来讲,shell 中的文件包含指的是在一个文件中引用另外一个文件.通过这种方式,我们可以将一些公用的代码封装为一个独立的文件,并在需要的时候引用它即可. 语法 . filename # 注意 ...

  4. Java实例变量初始化

    由一道面试题所想到的--Java实例变量初始化 时间:2015-10-07 16:08:38      阅读:23      评论:0      收藏:0      [点我收藏+] 标签:java   ...

  5. oracle同义词详解

    在Oracle中对用户的管理是使用权限的方式来管理的,也就是说,如果我们想使用数据库,我们就必须得有权限,但是如果是别人将权限授予了我们, 我们也是能对数据库进行操作的,但是我们必须要已授权的表的名称 ...

  6. git submodule一些操作

    checkout指定tag cd /path/to/yoursubmodule git checkout yourTag cd .. git add yoursubmodule git commit ...

  7. scrapy核心组件工作流程和post请求

    一 . 五大核心组件的工作流程 引擎(Scrapy)用来处理整个系统的数据流处理, 触发事务(框架核心) 调度器(Scheduler)用来接受引擎发过来的请求, 压入队列中, 并在引擎再次请求的时候返 ...

  8. 由于安装Android设备驱动异常,ADB无法识别安卓设备的解决方案

    体验更优排版请移步原文:http://blog.kwin.wang/programming/android-driver-exception-solution.html 最近换了台新电脑,在Andro ...

  9. Balls(poj 3783)

    The classic Two Glass Balls brain-teaser is often posed as: “Given two identical glass spheres, you ...

  10. iOS 上的蓝牙框架 - Core Bluetooth for iOS

    原文: Core Bluetooth for iOS 6 Core Bluetooth 是在iOS5首次引入的,它允许iOS设备可以使用健康,运动,安全,自动化,娱乐,附近等外设数据.在iOS 6 中 ...