RPC简介:RPC即java的远程方法调用,是java在分布式环境下,分布式程序的各个部分之间进行互相调用的机制.传统的方法调用都是在一台JVM虚拟机中运行,直接在内存中完成方法的调用,但是在分布式的环境下,方法的调用可能需要跨JVM虚拟机 跨进程 跨网络来进行,此时无法直接在内存中完成,需要通过序列化反序列化机制 网络通信机制 动态代理机制 等等的机制来实这种远程的方法的调用,这个过程称之为java的远程方法调用,即RPC.RPC并不是一项独立的技术,而是基于若干种技术,针对分布式环境下远程的方法调用提出的一种解决方案,本案例基于当下比较流行的序列化框架Google ProtoBuffer+非阻塞式IO NIO实现RPC(求和运算)
addService.proto代码
package cn.tedu.gp;
option java_package = "cn.tedu.gp";
option java_outer_classname = "GP_AddService";
option java_generic_services = true;
message Request{
required int32 num1 = 1;
required int32 num2 = 2;
}
message Response{
required int32 result = 1;
}
service AddService {
rpc add (Request) returns (Response);
}
客户端代码(方法调用者)
/**
* 基于GoogleProtoBuffer+NIO实现RPC(java远程方法调用)案例
*
* @author lisifan 2018/08/21 客户端代码(方法调用者)
*/
public class OOS {
public static void main(String[] args) throws Exception {
BlockingInterface stub = AddService.newBlockingStub(new BlockingRpcChannel() {
@Override
public Message callBlockingMethod(MethodDescriptor md, RpcController controller, Message req, Message resp)
throws ServiceException {
// >>利用NIO实现同步非阻塞信息传输
try {
// 创建选择器
Selector s = Selector.open();
// 创建客户端
SocketChannel sc = SocketChannel.open();
// 开启非阻塞
sc.configureBlocking(false);
// 连接服务器
sc.connect(new InetSocketAddress("127.0.0.1", 44444));
// 在选择器上进行注册,关注连接事件
sc.register(s, SelectionKey.OP_CONNECT);
while (true) {
// 选择器进行选择就绪线程
s.select();// 这步相当于选择
// 必须要写上一步,因为线程在上一步是阻塞的,也就是说如果没有就绪线程,直接keys接下来的操作会空指针
Set<SelectionKey> keys = s.selectedKeys();// 返回结果
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isConnectable()) {
// 连接事件就绪
// 完成连接事件(但有可能已经连接,需要判断)
// 从key中获取sc对象
SocketChannel scx = (SocketChannel) key.channel();
if (!scx.isConnected()) {
while (!scx.finishConnect()) {
}
}
// 注册写事件
scx.register(s, SelectionKey.OP_WRITE);
} else if (key.isWritable()) {
// 写事件就绪
// 完成写事件
SocketChannel scx = (SocketChannel) key.channel();
// 使用协议传输数据
byte[] reqByte = req.toByteArray();
String reqData = new String(reqByte);
String data = reqByte.length + "\r\n" + reqData;
ByteBuffer buf = ByteBuffer.wrap(data.getBytes());
scx.write(buf);
// 注册读事件
scx.register(s, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 读事件就绪
SocketChannel scx = (SocketChannel) key.channel();
String lenStr = "";
while (!lenStr.endsWith("\r\n")) {
ByteBuffer buf = ByteBuffer.allocate(1);
scx.read(buf);
lenStr += new String(buf.array());
buf.clear();
}
int len = Integer.parseInt(lenStr.substring(0, lenStr.length() - 2));
ByteBuffer buf = ByteBuffer.allocate(len);
while (buf.hasRemaining()) {
scx.read(buf);
}
Response respx = Response.parseFrom(buf.array());
return respx;
} else {
throw new RuntimeException("O_O");
}
}
it.remove();
}
} catch (Exception e) {
e.printStackTrace();
throw new RuntimeException(e);
}
}
});
Request req = Request.newBuilder().setNum1(64).setNum2(5).build();
Response resp = stub.add(null, req);
int result = resp.getResult();
System.out.println(result);
}
}
服务端代码(方法执行者)
/**
* 基于GoogleProtoBuffer+NIO实现RPC(java远程方法调用)案例
*
* @author lisifan 2018/08/21 服务端代码(方法执行者)
*/
public class OIS {
public static void main(String[] args) throws Exception {
// 创建选择器
Selector sel = Selector.open();
// 创建客户端
ServerSocketChannel ssc = ServerSocketChannel.open();
// 设置非阻塞
ssc.configureBlocking(false);
// 接收连接
ssc.bind(new InetSocketAddress(44444));
// 注册连接事件
ssc.register(sel, SelectionKey.OP_ACCEPT);
while (true) {
sel.select();
Set<SelectionKey> keys = sel.selectedKeys();
Iterator<SelectionKey> it = keys.iterator();
while (it.hasNext()) {
SelectionKey key = it.next();
if (key.isAcceptable()) {
// 监听到接收事件
ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
// 完成连接
SocketChannel sc = sscx.accept();
sc.configureBlocking(false);
// 注册读事件
sc.register(sel, SelectionKey.OP_READ);
} else if (key.isReadable()) {
// 监听到读事件
SocketChannel sc = (SocketChannel) key.channel();
// 设置非阻塞
sc.configureBlocking(false);
// 读取数据
String lenStr = "";
while (!lenStr.endsWith("\r\n")) {
ByteBuffer buf = ByteBuffer.allocate(1);
sc.read(buf);
lenStr += new String(buf.array());
buf.clear();
}
int len = Integer.parseInt(lenStr.substring(0, lenStr.length() - 2));
ByteBuffer buf = ByteBuffer.allocate(len);
while (buf.hasRemaining()) {
sc.read(buf);
}
Request req = Request.parseFrom(buf.array());
BlockingInterface bif = new BlockingInterface() {
@Override
public Response add(RpcController controller, Request request) throws ServiceException {
int n1 = request.getNum1();
int n2 = request.getNum2();
Response resp = Response.newBuilder().setResult(n1 + n2).build();
return resp;
}
};
Response resp = bif.add(null, req);
// 注册写事件
sc.register(sel, SelectionKey.OP_WRITE, resp);
} else if (key.isWritable()) {
// 监听到写事件
SocketChannel sc = (SocketChannel) key.channel();
Response resp = (Response) key.attachment();
byte[] respByte = resp.toByteArray();
String respData = new String(respByte);
String data = respByte.length + "\r\n" + respData;
ByteBuffer buf = ByteBuffer.wrap(data.getBytes());
sc.write(buf);
key.cancel();
}
}
it.remove();
}
}
}
使用GoogleProtoBuffer的步骤:
a. 编写proto文件,利用GPB自定义的语法来声明要进行序列化的类的结构
b. 通过GPB提供的编译器将proto文件编译为需要的语言的类文件
c. 将生成的类导入项目中,利用GPB提供的API实现序列化反序列化
.proto文件编译命令:(前提是将proto文件和exe指令文件方法统一目录,并且当前命令窗口正处于这个目录)
protoc.exe --java_out=./ ./addService.proto