序列化

java 从零开始手写 RPC (01) 基于 socket 实现

java 从零开始手写 RPC (02)-netty4 实现客户端和服务端

java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

前面几节我们实现了最基础的客户端调用服务端,这一节来学习一下通讯中的对象序列化。

为什么需要序列化

netty 底层都是基于 ByteBuf 进行通讯的。

前面我们通过编码器/解码器专门为计算的入参/出参进行处理,这样方便我们直接使用 pojo。

但是有一个问题,如果想把我们的项目抽象为框架,那就需要为所有的对象编写编码器/解码器。

显然,直接通过每一个对象写一对的方式是不现实的,而且用户如何使用,也是未知的。

序列化的方式

基于字节的实现,性能好,可读性不高。

基于字符串的实现,比如 json 序列化,可读性好,性能相对较差。

ps: 可以根据个人还好选择,相关序列化可参考下文,此处不做展开。

json 序列化框架简介

实现思路

可以将我们的 Pojo 全部转化为 byte,然后 Byte 转换为 ByteBuf 即可。

反之亦然。

代码实现

maven

引入序列化包:

<dependency>
<groupId>com.github.houbb</groupId>
<artifactId>json</artifactId>
<version>0.1.1</version>
</dependency>

服务端

核心

服务端的代码可以大大简化:

serverBootstrap.group(workerGroup, bossGroup)
.channel(NioServerSocketChannel.class)
// 打印日志
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ch.pipeline()
.addLast(new RpcServerHandler());
}
})
// 这个参数影响的是还没有被accept 取出的连接
.option(ChannelOption.SO_BACKLOG, 128)
// 这个参数只是过一段时间内客户端没有响应,服务端会发送一个 ack 包,以判断客户端是否还活着。
.childOption(ChannelOption.SO_KEEPALIVE, true);

这里只需要一个实现类即可。

RpcServerHandler

服务端的序列化/反序列化调整为直接使用 JsonBs 实现。

package com.github.houbb.rpc.server.handler;

import com.github.houbb.json.bs.JsonBs;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.common.model.CalculateRequest;
import com.github.houbb.rpc.common.model.CalculateResponse;
import com.github.houbb.rpc.common.service.Calculator;
import com.github.houbb.rpc.server.service.CalculatorService; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; /**
* @author binbin.hou
* @since 0.0.1
*/
public class RpcServerHandler extends SimpleChannelInboundHandler { private static final Log log = LogFactory.getLog(RpcServerHandler.class); @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
final String id = ctx.channel().id().asLongText();
log.info("[Server] channel {} connected " + id);
} @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
final String id = ctx.channel().id().asLongText(); ByteBuf byteBuf = (ByteBuf)msg;
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes);
CalculateRequest request = JsonBs.deserializeBytes(bytes, CalculateRequest.class);
log.info("[Server] receive channel {} request: {} from ", id, request); Calculator calculator = new CalculatorService();
CalculateResponse response = calculator.sum(request); // 回写到 client 端
byte[] responseBytes = JsonBs.serializeBytes(response);
ByteBuf responseBuffer = Unpooled.copiedBuffer(responseBytes);
ctx.writeAndFlush(responseBuffer);
log.info("[Server] channel {} response {}", id, response);
} }

客户端

核心

客户端可以简化如下:

channelFuture = bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<Channel>(){
@Override
protected void initChannel(Channel ch) throws Exception {
channelHandler = new RpcClientHandler();
ch.pipeline()
.addLast(new LoggingHandler(LogLevel.INFO))
.addLast(channelHandler);
}
})
.connect(RpcConstant.ADDRESS, port)
.syncUninterruptibly();

RpcClientHandler

客户端的序列化/反序列化调整为直接使用 JsonBs 实现。

package com.github.houbb.rpc.client.handler;

import com.github.houbb.json.bs.JsonBs;
import com.github.houbb.log.integration.core.Log;
import com.github.houbb.log.integration.core.LogFactory;
import com.github.houbb.rpc.client.core.RpcClient;
import com.github.houbb.rpc.common.model.CalculateResponse; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler; /**
* <p> 客户端处理类 </p>
*
* <pre> Created: 2019/10/16 11:30 下午 </pre>
* <pre> Project: rpc </pre>
*
* @author houbinbin
* @since 0.0.2
*/
public class RpcClientHandler extends SimpleChannelInboundHandler { private static final Log log = LogFactory.getLog(RpcClient.class); /**
* 响应信息
* @since 0.0.4
*/
private CalculateResponse response; @Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf)msg;
byte[] bytes = new byte[byteBuf.readableBytes()];
byteBuf.readBytes(bytes); this.response = JsonBs.deserializeBytes(bytes, CalculateResponse.class);
log.info("[Client] response is :{}", response);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
// 每次用完要关闭,不然拿不到response,我也不知道为啥(目测得了解netty才行)
// 个人理解:如果不关闭,则永远会被阻塞。
ctx.flush();
ctx.close();
} public CalculateResponse getResponse() {
return response;
} }

小结

为了便于大家学习,以上源码已经开源:

https://github.com/houbb/rpc

希望本文对你有所帮助,如果喜欢,欢迎点赞收藏转发一波。

我是老马,期待与你的下次相遇。

java 从零开始手写 RPC (04) -序列化的更多相关文章

  1. java 从零开始手写 RPC (05) reflect 反射实现通用调用之服务端

    通用调用 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 java 从零开始手写 RPC (03) 如何 ...

  2. java 从零开始手写 RPC (07)-timeout 超时处理

    <过时不候> 最漫长的莫过于等待 我们不可能永远等一个人 就像请求 永远等待响应 超时处理 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RP ...

  3. java 从零开始手写 RPC (03) 如何实现客户端调用服务端?

    说明 java 从零开始手写 RPC (01) 基于 socket 实现 java 从零开始手写 RPC (02)-netty4 实现客户端和服务端 写完了客户端和服务端,那么如何实现客户端和服务端的 ...

  4. java 从零开始手写 RPC (01) 基于 websocket 实现

    RPC 解决的问题 RPC 主要是为了解决的两个问题: 解决分布式系统中,服务之间的调用问题. 远程调用时,要能够像本地调用一样方便,让调用者感知不到远程调用的逻辑. 这一节我们来学习下如何基于 we ...

  5. 手写RPC框架指北另送贴心注释代码一套

    Angular8正式发布了,Java13再过几个月也要发布了,技术迭代这么快,框架的复杂度越来越大,但是原理是基本不变的.所以沉下心看清代码本质很重要,这次给大家带来的是手写RPC框架. 完整代码以及 ...

  6. 看了这篇你就会手写RPC框架了

    一.学习本文你能学到什么? RPC的概念及运作流程 RPC协议及RPC框架的概念 Netty的基本使用 Java序列化及反序列化技术 Zookeeper的基本使用(注册中心) 自定义注解实现特殊业务逻 ...

  7. 手写RPC框架(六)整合Netty

    手写RPC框架(六)整合Netty Netty简介: Netty是一个基于NIO的,提供异步,事件驱动的网络应用工具,具有高性能高可靠性等特点. 使用传统的Socket来进行网络通信,服务端每一个连接 ...

  8. 从零开始手写 dubbo rpc 框架

    rpc rpc 是基于 netty 实现的 java rpc 框架,类似于 dubbo. 主要用于个人学习,由渐入深,理解 rpc 的底层实现原理. 前言 工作至今,接触 rpc 框架已经有很长时间. ...

  9. java - day015 - 手写双向链表, 异常(续), IO(输入输出)

    类的内存分配 加载到方法区 对象在堆内存 局部变量在栈内存 判断真实类型,在方法区加载的类 对象.getClass(); 类名.class; 手写双向链表 package day1501_手写双向链表 ...

随机推荐

  1. "排序二叉树"之探幽

    /*怎么理解排序二叉树呢?在二叉树的基本定义上增加两个基本条件: (1)所有左子树的节点数值都小于此节点的数值: (2)所有右节点的数值都大于此节点的数值. */ 1 /*************** ...

  2. CrackMe-CFF Crackme #3

    转载自:OllyDbg入门教程 我们先来运行一下这个 crackme(用 PEiD 检测显示是 Delphi 编的),界面如图: 这个 crackme 已经把用户名和注册码都输好了,省得我们动手^_^ ...

  3. ProjectEuler 008题

    题目: The four adjacent digits in the 1000-digit number that have the greatest product are 9 9 8 9 = 5 ...

  4. Go错误处理正确姿势

    1. panic 在什么情况下使用panic? 在程序启动的时候,如果有强依赖的服务出现故障时panic退出 在程序启动的时候,如果发现有配置明显不符合要求,可以panic退出(预防编程) 其他情况下 ...

  5. Go进阶--httptest

    目录 基本使用 扩展使用 接口context使用 模拟调用 测试覆盖率 参考 单元测试的原则,就是你所测试的函数方法,不要受到所依赖环境的影响,比如网络访问等,因为有时候我们运行单元测试的时候,并没有 ...

  6. ubuntu软件工具推荐

    时间:2019-04-11 记录:PangYuaner 标题:串口调试利器--Minicom配置及使用详解 地址:https://www.cnblogs.com/wonux/p/5897127.htm ...

  7. 板子题 Sol

    RT Cyber_Tree 出了一道板子题... 这题乍看之下貌似还不戳,但如果您做过类似的题,那么这就是一道板子题.... 首先明确要求的是什么,如果我们只考虑权值最大而不考虑最小距离,那么要求的显 ...

  8. SQL-Instead of 触发器

    定义及优点 INSTEAD OF触发器指定执行触发器而不是执行触发 的SQL 语句,从而替代触发语句的操作.        在表或视图上,每个 INSERT.UPDATE 或 DELETE 语句最多可 ...

  9. djang2.1教育平台02

    在次申明,之所以重做这个资料是因为原幕课教程漏洞太多,新手根本没有办法正常照些学习,我凭着老男孩python 课程基础,重做这个教程 ,更改版本为当前最新版本,为了方法以后的人学习,并不是照着原文照  ...

  10. 第25篇-虚拟机对象操作指令之putstatic

    之前已经介绍了getstatic与getfield指令的汇编代码执行逻辑,这一篇介绍putstatic指令的执行逻辑,putfield将不再介绍,大家可以自己去研究,相信大家有这个实力. putsta ...