protobuf的安装,请参考之前的博客:mac上同时安装proto2和proto3并编译.proto文件
maven + protobuf 配置与使用, 请参考之前的博客: IDEA + maven + protobuf配置(on mac)
2. 基于protobuf实现数据存储 2.1 学以致用
直接使用protobuf官网的示例代码,稍作修改写入address_book.proto文件中
// 只修改了java_package option java_package = "com.sunrise.protos";
根据protobuf官方文档的介绍,message对应的Java类将由自己的序列化和反序列化的方法
自己的需求:
借助writeTo(output)方法,将通讯录(AddressBook)序列化到指定文件中借助parseFrom(input)方法,从文件中读取通讯录数据并反序列化为AddressBook对象 2.2 代码实现
代码中,将使用commons-lang随机生成用户名和电话号码
commons-lang commons-lang 2.6
具体的代码实现如下:
package com.sunrise.protos;
public class AddressBookTest {
private static final Random random = new Random();
private static int count = 0;
public AddressBook generateAddressBook(int n) {
// 构建n个person
AddressBook.Builder builder = AddressBook.newBuilder();
for (int i = 0; i < n; i++) {
builder.addPeople(generatePerson());
}
return builder.build();
}
// 构建一条通讯记录,也就是构建一个person实例
private Person generatePerson() {
int id = ++count;
String name = "user_" + RandomStringUtils.random(4, false, true);
String email = RandomStringUtils.random(12, true, true).toLowerCase() + "@qq.com";
int phoneNumberType = random.nextInt(3);
Person.PhoneNumber phoneNumber = Person.PhoneNumber.newBuilder()
.setNumber(RandomStringUtils.random(11, false, true))
.setType(Person.PhoneType.valueOf(phoneNumberType))
.build();
// 创建person
Person person = Person.newBuilder()
.setId(id)
.setName(name)
.setEmail(email)
.addPhones(phoneNumber)
.build();
return person;
}
public void writeToFile(AddressBook book, String file) {
try (FileOutputStream outputStream = new FileOutputStream(file)) {
book.writeTo(outputStream);
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
public AddressBook readFromFile(String file) {
try (FileInputStream in = new FileInputStream(file)) {
return AddressBook.parseFrom(in);
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
public void printAddressBook(AddressBook addressBook) {
for (Person person : addressBook.getPeopleList()) {
System.out.println(person.toString());
}
}
}
使用AddressBookTest,实现通讯录的创建和读取
public static void main(String[] args) {
AddressBookTest test = new AddressBookTest();
System.out.println("开始创建通讯录...");
AddressBook addressBook = test.generateAddressBook(1);
test.printAddressBook(addressBook);
test.writeToFile(addressBook, "address_book");
System.out.println("成功创建通讯录!");
System.out.println("开始读取通讯录...");
AddressBook read = test.readFromFile("address_book");
System.out.println("成功读取读取通讯录!");
test.printAddressBook(read);
}
执行结果如下:
本人才疏学浅,基于protobuf实现自己的rpc框架时,发现困难重重最大的困难就是:不知道如何实现相关接口,让client和server能建立通信欢迎大佬与我交流 3.1 定义一个service
大家都知道大名鼎鼎的gRPC,其实gRPC的实现就是基于protobuf实现的
server和client之间的RPC方法,由service进行定义
这是一个简单的service定义,方法名为大驼峰形式,请求参数为message Request,返回参数为message Response
syntax = "proto2";
package rpc_service;
option java_multiple_files = true;
option java_generic_services = true;
option java_package = "com.sunrise.service";
option java_outer_classname = "RpcService";
message Request {
required string token = 1;
required int32 id = 2;
optional string user = 3;
}
message Response {
required int32 status_code = 1;
message User {
required int32 id = 1;
optional string user = 2;
optional int32 age = 3;
optional string address = 4;
}
optional User user = 2;
optional string toast = 3;
}
service MyRpcService {
rpc SearchUser(Request) returns(Response);
}
编译生成的Java代码,相关接口和方法存放在抽象类MyRpcService,类名由service Xyz决定
编译时,如果java_generic_services 为false,将不会产生MyRpcService类,也就无法定义建立rpc通信框架
MyRpcService中,有一个抽象方法searchUser(),对应的就是service中定义的SearchUSer这个RPC方法
public abstract void searchUser(
com.google.protobuf.RpcController controller,
com.sunrise.service.Request request,
com.google.protobuf.RpcCallback done);
从方法定义中的RpcCallback可知,searchUser()是一个非阻塞的方法
除了这些方法,MyRpcService中还有一些实现com.google.protobuf.Servic接口的方法,方法介绍见官方文档或源码注释
有时,考虑到业务会继承Service类,实现自己RPC server,MyRpcService中还定义了一个Interface接口,里面只有自定义的PRC方法
public interface Interface {
public abstract void searchUser(
com.google.protobuf.RpcController controller,
com.sunrise.service.Request request,
com.google.protobuf.RpcCallback done);
}
通过实现Interface接口,然后通过newReflectiveService()方法创建Service
关于Service类,自己的理解:非阻塞状态,对应的就是MyRpcService,从newReflectiveService()方法的实现可以看出
public static com.google.protobuf.Service newReflectiveService(
final Interface impl) {
return new MyRpcService() {
@java.lang.Override
public void searchUser(
com.google.protobuf.RpcController controller,
com.sunrise.service.Request request,
com.google.protobuf.RpcCallback done) {
impl.searchUser(controller, request, done);
}
};
}
3.2.2 非阻塞的client
MyRpcService.Stub类,是留给RPC client的、用于实现client远程调用server端方法的stub(木桩,类似于一个通道)
基于RpcChannel创建Stub,就可以实现client和server之间的通信,从而可以调用RPC方法searchUser()
Stub的创建方法有两种:一是基于工厂方法newStub,而是直接new Stub
MyRpcService.Stub stub = MyRpcService.newStub(new MyRpcChannel(8080)); MyRpcService.Stub stub = new MyRpcService.Stub(new MyRpcChannel(8080);
创建好stub后,clientd就可以直接调用searchUser(),就像调用本地的服务一样
stub.searchUser(new MyRpcController(), Request.newBuilder()
.setId(20)
.setToken("client_token")
.build(), parameter -> System.out.println(parameter.toString()));
3.3 查看service中的阻塞接口
3.3.1 阻塞的server
既然存在非阻塞接口Interface,相应地,也存在阻塞接口BlockingInterface
public class BlockingServer implements MyRpcService.BlockingInterface {
@Override
public Response searchUser(RpcController controller, Request request) throws ServiceException {
// 省略具体实现
}
}
使用时,需要实现BlockingInterface接口,然后基于的newReflectiveBlockingService()创建对应的BlockingService
public static void main(String[] args) {
BlockingService blockingService = MyRpcService.newReflectiveBlockingService(new BlockingServer());
}
包名com.sunrise.protos由proto文件中的java_package进行设置,若未设置则由package xxx;中的包名替代代替
AddressBookProtos类是表示该proto文件的wrapper类,由java_outer_classname进行设置
之所以存在多个Java文件,而非只有一个指定的wrapper类AddressBookProtos,这是因为设置了java_multiple_files = true
3.3.2 阻塞的clientStub是非阻塞状态下,client访问server的stub;BlockingStub则是阻塞状态下,client访问server的stub与Stub不同的是,BlockingStub不能直接通过new BlockingStub()进行创建,因为其构造函数为private基于BlockingStub创建client并访问server的RPC方法的伪代码如下:
public static void main(String[] args) throws ServiceException {
MyRpcService.BlockingInterface blockingStub = MyRpcService.newBlockingStub(new MyBlockingRpcChannel());
blockingStub.searchUser(new MyRpcController(), Request.newBuilder().build());
}
3.4 后续规划
由于时间和分人能力问题,笔者暂时不打算基于protobuf实现一个自己的RPC框架如有需要,后续可能会学习gRPC通过搜索,自己在网上找到了一个大神基于protubuf实现的RPC框架代码如果感兴趣,可以拜读大神代码



