序列化

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. CentOS8安装jdk1.8

    安装方法 CentOS8上使用 yum 直接安装,环境变量自动配置好 查看是否已安装 看到下面结果,说明已经安装配置 jdk [root@iZ2ze8crquorxf6c7l0eluZ ~]# jav ...

  2. JavaWeb学习总结—Session

    转载自:https://www.cnblogs.com/xdp-gacl/p/3855702.html 一.Session简单介绍 在WEB开发中,服务器可以为每个用户浏览器创建一个会话对象(sess ...

  3. Servlet体系及方法

    时间:2016-11-11 15:07 --Servlet体系Servlet(interface):    实现类:GenericServlet.HttpServletServletConfig(in ...

  4. JOB状态与并发

    由于job每次被执行时都会创建一个新的实例, jobDetail实例时,要进行数据存储或者,特殊字段操作,需要每次schedul执行job时保留之前的数据, 那么就需要job在有状态下保持之前的数据信 ...

  5. jmeter实际场景应用之测试上传excel文件

    日常工作上测试的时候,会有一些场景是导入/上传文件.我们系统多是excel文件,这里就用excel文件为例,详述一下此次测试遇到的坑.最终结果是成功的,请看到最后! 1.获取接口的一些参数信息 先按F ...

  6. Kafka内外网访问

    本文介绍了Kafka内外网访问的设置. kafka的两个配置listeners和advertised.listeners listeners kafka监听的网卡的ip,假设你机器上有两张网卡,内网1 ...

  7. noip模拟17

    \(\color{white}{\mathbb{霞光划破暗淡天际,月影彷徨,鸡鸣仿佛,冀之以继往开来,名之以:黎明}}\) 今天似乎取得了有史以来最好的成绩~ 前两名都 A 掉了 \(t3\),然鹅 ...

  8. IPSec协议框架

    文章目录 1. IPSec简介 1.1 起源 1.2 定义 1.3 受益 2. IPSec原理描述 2.1 IPSec协议框架 2.1.1 安全联盟 2.1.2 安全协议 报文头结构 2.1.3 封装 ...

  9. [源码解析] 深度学习流水线并行 PipeDream(5)--- 通信模块

    [源码解析] 深度学习流水线并行 PipeDream(5)--- 通信模块 目录 [源码解析] 深度学习流水线并行 PipeDream(5)--- 通信模块 0x00 摘要 0x01 前言 0x02 ...

  10. 记录一下Vray5中文汉化版本中导出EXR或vrimg多通道文件的那些坑和解决方法

    最近在给一个培训机构代课,学生英语基础差,就安装了Vray5的中文版,噩梦从此开始. 做过合成的都知道,需要输出多通道到NUKE或者AE中进行合成,通常情况下把多个pass分成不同的文件对硬盘反复读写 ...