RPC

一、
RPC(Remote Procedure Call Protocol)——远程过程调用协议,它是一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议

二、

1.序列化与反序列化

序列化:将对象从内存中以二进制数据流的形式传递
反序列化:将二进制数据传递到内存中

持久化:内存中的数据是不能永久保存的,即瞬时数据,保存到文本中或其他介质中,永久化
反持久化:从持久化转化为内存存储

持久化的前提是序列化,但序列化的目的不一定是为了持久化

2.基本实现


public class Person implements Serializable{

/**
*
*/
private static final long serialVersionUID = -1170943594852852868L;
private String name ;
private Integer age ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}

}



/**
* 序列化:将对象以二进制的格式输出到文本文件中
* @throws IOException
*/
@Test
public void testSerializable() throws IOException{
/**
* 所有需要序列化的类均需要实现 Serializable 接口
* 标记接口,标识 此类允许序列化与反序列化
* java.io.NotSerializableException
*/
Person person = new Person();
person.setName("zhang");
person.setAge(Integer.valueOf(26));
// 序列化输出:文件内容为二进制格式,无法直接阅读
FileOutputStream out = new FileOutputStream("D:\\study\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(out);

oos.writeObject(person);
oos.flush();
oos.close();

}

/**
* 反序列化:读取文本中的二进制信息
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void testReverseSerializable() throws IOException, ClassNotFoundException{

FileInputStream in = new FileInputStream("D:\\study\\test.txt");
ObjectInputStream ois = new ObjectInputStream(in);
Person person = (Person) ois.readObject();
System.out.println(person.getName()+" "+person.getAge());
}


若:
(若实现Serializable ,生成的
private static final long serialVersionUID = 1L;

执行序列化后,修改了Object的属性,增加或删除,再次执行反序列化的时候,异常
解释:
序列化写入的类与反序列化读出的类是两个不同的类,
是因为作用域的原因,写入时,程序执行完毕,内存自动回收
可以认为 读入的类是写入的类的克隆,具有相同的内容

若属性的数量或内容对应不上,克隆就会出现异常

对象序列化即实现Serializable 后,会生成一个随机的serialVersionUID
若属性变化,则会生成新的 serialVersionUID,
进行序列化读出与写入的时候就会比较serialVersionUID是否是同一个
即 serialVersionUID 是序列化与反序列化时的唯一标识

其他:
RPC传输Object时,设置不同机器上的Person的serialVersionUID相同,
即使Person中的内容完全相同,也不一定认为是同一个,即修改serialVersionUID,保持一致

3.transient 不需序列化


public class Person implements Serializable{

/**
*
*/
private static final long serialVersionUID = -1170943594852852868L;
private String name ;
/**
* 若某个属性保密,不希望进行系列化处理,加关键字,transient
*/
private transient Integer age ;
private String gender ;

public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public Person(String name, Integer age) {
super();
this.name = name;
this.age = age;
}
public Person() {
super();
}
public Person(String name, Integer age, String gender) {
super();
this.name = name;
this.age = age;
this.gender = gender;
}
}




/**
* 序列化:将对象以二进制的格式输出到文本文件中
* @throws IOException
*/
@Test
public void testSerializable() throws IOException{
/**
* 所有需要序列化的类均需要实现 Serializable 接口
* 标记接口,标识 此类允许序列化与反序列化
* java.io.NotSerializableException
*/
Person person = new Person();
person.setName("zhang");
person.setAge(Integer.valueOf(26));
person.setGender("male");
// 序列化输出:文件内容为二进制格式,无法直接阅读
FileOutputStream out = new FileOutputStream("D:\\study\\test.txt");
ObjectOutputStream oos = new ObjectOutputStream(out);

oos.writeObject(person);
oos.flush();
oos.close();

}

/**
* 反序列化:读取文本中的二进制信息
* @throws IOException
* @throws ClassNotFoundException
*/
@Test
public void testReverseSerializable() throws IOException, ClassNotFoundException{

FileInputStream in = new FileInputStream("D:\\study\\test.txt");
ObjectInputStream ois = new ObjectInputStream(in);
Person person = (Person) ois.readObject();
System.out.println(person.getName()+" "+person.getAge()+" "+person.getGender());
}


输出:
zhang null male

age 属性不需序列化,所以无输出

4.
缺点:
1)只支持java语言,无法跨语言
2)序列化后的数据量比较大
3)执行序列化与反序列化的效率比较低


public class PersonObjectSeriable implements Serializable{

/**
*
*/
private static final long serialVersionUID = -7328657292302834890L;
private String name ;
private transient Integer age ;
private String gender ;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public PersonObjectSeriable() {
super();
}
}





import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.junit.Test;

public class SunSerializableTest {

@Test
public void testSerializable() throws IOException{
PersonObjectSeriable person = new PersonObjectSeriable();
person.setName("zhang");
person.setAge(26);
person.setGender("male");

FileOutputStream out = new FileOutputStream("D:\\study\\java\\PersonObjectSerializable.txt");
ObjectOutputStream stream = new ObjectOutputStream(out);

stream.writeObject(person);
stream.flush();
stream.close();
}

@Test
public void testReverseSeriable() throws IOException, ClassNotFoundException{

FileInputStream in = new FileInputStream("D:\\study\\java\\PersonObjectSerializable.txt");
ObjectInputStream stream = new ObjectInputStream(in);

PersonObjectSeriable person = (PersonObjectSeriable) stream.readObject();
System.out.println(person.getName()+" "+person.getAge()+" "+person.getGender());
}
}


三、第三方序列化与反序列化框架

Avro
GoogleProtoBuffer
Thrift


1.GoogleProtoBuffer

生成与执行步骤
1)数据写入 .proto文件
2)编译成类文件
3)导入类文件进行序列化与反序列化

1)PersonProto.proto

package cn.study;// 文件的包路径,便于其他文件引用使用

option java_package = "com.study.rpc.ser.day.one.test"; // 生成的JAVA的类的包名
option java_outer_classname = "PersonProtos"; // JAVA文件名称

message Person{ // 声明类
// required 表示必须,optional 表示可选
// int32 对应 int 类型
// 1,2,3 ... 类中属性的唯一标识,按顺序添加,一旦添加不可更改;否则无法实现序列化
required string name = 1;
optional int32 age = 2;
required string gender = 3;
}



2.编译类文件

解压:protoc.exe 安装包,见 下方

打开 win+r cmd --> cd .. 到安装包目录 --> 将上面的 PersonProto.proto 拷贝到该目录下
执行 protoc.ext --java_out . ./PersonProto.proto


[img]http://dl2.iteye.com/upload/attachment/0124/5052/c09b41c6-d91a-3602-8fdc-21917ab98614.png[/img]

在解压的目录中会出现相应的生成文件


[img]http://dl2.iteye.com/upload/attachment/0124/5054/6b1879c0-e2c3-34ef-a71f-d2279c9dee1e.png[/img]

拷贝文件至对应的包中
报错--因为未导入相应的JAR包 -- 见下面链接

3.调用API执行

@Test
public void testSerializable() throws IOException{

Person person = Person.newBuilder()
.setName("zhang").setAge(26).setGender("male").build();
FileOutputStream output = new FileOutputStream("D:\\study\\java\\PersonObjectProto.txt");
person.writeTo(output);

}


序列化后的文件大小对比

[img]http://dl2.iteye.com/upload/attachment/0124/5057/b3fc5410-f6eb-37ae-bb42-c26b30fc9393.png[/img]


[img]http://dl2.iteye.com/upload/attachment/0124/5059/3a3ab82d-a675-3cc7-8c5f-e883863305c3.png[/img]



@Test
public void testReverseSerializable() throws IOException{

FileInputStream input = new FileInputStream("D:\\study\\java\\PersonObjectProto.txt");
Person person = Person.parseFrom(input);
input.close();

System.out.println(person.getName()+" "+person.getAge()+" "+person.getGender());


}


4.扩展
若IO流改为网络流,
接收方以同样的方式对 PersonProto.proto 进行处理,语言可以不同,即不同语言间传递对象

三、RPC实现

描述:客户端发送数据,服务端接收,计算,返回结果给客户端

1.MathService.proto


package cn.study;

option java_package = "com.study.rpc.ser.day.one";
option java_outer_classname = "MathServiceProto";
option java_generic_services = true ; // 是否包含服务

message Req{ // 定义第一个类,接收两个参数
required int32 num1 = 1;
required int32 num2 = 2;
}

message Resp{ // 定义第二个类,返回值
required int32 result = 1;
}

service MathService{ // 定义接口,方法名称 add 参数 Req 返回值 Resp
rpc add(Req) returns (Resp);
}




生成对应的JAVA文件

2.


import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

import com.google.protobuf.BlockingRpcChannel;
import com.google.protobuf.Descriptors.MethodDescriptor;
import com.google.protobuf.Message;
import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
import com.study.rpc.ser.day.one.MathServiceProto.MathService;
import com.study.rpc.ser.day.one.MathServiceProto.MathService.BlockingInterface;
import com.study.rpc.ser.day.one.MathServiceProto.Req;
import com.study.rpc.ser.day.one.MathServiceProto.Resp;

public class MathClient {

/**
* @param args
* @throws ServiceException
*/
public static void main(String[] args) throws ServiceException {

//stu -- 存根 -- 远程服务在本地的一个代表
//-- 具有和远程服务相同的远程调用方法
//-- 如果本地需要调用远程方法 找到存根调用对应方法即可
//-- 存根的底层会自动调用远程的方法来执行并获取结果并返回
//即:去取钱,但附近没有银行,但有ATM
//ATM就相当于银行的一个远程服务在本地的代表;取钱 扣款 与去银行是一样的
BlockingInterface stu = MathService.newBlockingStub(new MyBlockingRPCchannel());
Req req = Req.newBuilder().setNum1(25).setNum2(26).build();
Resp resp = stu.add(null, req);
System.out.println(resp.getResult());
}

}

class MyBlockingRPCchannel implements BlockingRpcChannel{

public Message callBlockingMethod(MethodDescriptor methodDescriptor,
RpcController controller, Message req, Message resp)
throws ServiceException {

Socket socket = new Socket();
// 请求连接远程服务器
try {
socket.connect(new InetSocketAddress("127.0.0.1", 9999));
// 发送请求给服务器
OutputStream out = socket.getOutputStream();
req.writeTo(out);
socket.shutdownOutput();
// 接收服务器返回结果
InputStream in = socket.getInputStream();
resp = Resp.parseFrom(in);
socket.shutdownInput();

return resp;
} catch (IOException e) {
e.printStackTrace();
}finally{
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null ;
}

}




import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

import com.google.protobuf.RpcController;
import com.google.protobuf.ServiceException;
import com.study.rpc.ser.day.one.MathServiceProto.MathService.BlockingInterface;
import com.study.rpc.ser.day.one.MathServiceProto.Req;
import com.study.rpc.ser.day.one.MathServiceProto.Resp;

public class MathService {

/**
* @param args
* @throws IOException
*/
public static void main(String[] args) throws IOException {

ServerSocket server = null ;
Socket socket = null ;

try {
server = new ServerSocket();
server.bind(new InetSocketAddress(9999));
socket = server.accept();

InputStream input = socket.getInputStream();
Req req = Req.parseFrom(input);
socket.shutdownInput();

MyMathService mathService = new MyMathService();
Resp resp = mathService.add(null, req);

OutputStream out = socket.getOutputStream();
resp.writeTo(out);
socket.shutdownOutput();

} catch (ServiceException e) {
e.printStackTrace();
}finally{
socket.close();
server.close();
}

}

}

class MyMathService implements BlockingInterface{

public Resp add(RpcController controller, Req request)
throws ServiceException {
int num1 = request.getNum1();
int num2 = request.getNum2();
int result = num1 + num2 ;
Resp resp = Resp.newBuilder().setResult(result).build();
return resp;
}

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值