基于GoogleProtoBuffer+NIO实现RPC案例

1 篇文章 0 订阅
1 篇文章 0 订阅

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值