项目开发中如何选择编解码器?如何解决TCP粘包问题?(Netty二)

2020-12-07 11:34:37 浏览数 (1)

​在使用Netty进行通信开发,如何选择编码器?在TCP粘包/拆包的问题如何解决?服务端在启动 流程是什么样的?连接服务流程是什么?

一 编解码器

1.1 什么叫编解码器

在网络传输的过程中,数据都是以字节流的方式进行传递。客户端在进行数据传递的时候 将原来的数据格式转化为字节,叫编码。服务端将字节转化为原来的格式,叫解码。统称 codec。 编解码器分为两部分-编码器和解码器,编码器负责出站,解码器负责入站。

1.2 解码器

1.2.1 概述

解码器负责入站操作,那么也一定要实现ChannelInboundHandler接口,所以解码器本质 上也是ChannelHandler。我们自定义编解码器只需要继承ByteToMessageDecoder(Netty提供抽象类,继承     ChannelInboundHandlerAdapter),实现decode()。Netty提供一些常用的解码器实现, 开箱即用。如下:

Netty也提供了MessageToMessageDecoder,将⼀种格式转化为另⼀种格式的解码器,也提供了⼀些 实现,如下:

1.2.2 将字节流转化为Intger类型(案例)

1 字节解码器

2 Handler

3 在pipeline中添加解码器

1.3 编码器

1.3.1 概述

将原来的格式转化为字节。我们要实现自定义解码器只要继承MessageToByteEncoder (实现了ChannelOutboundHandler接⼝),本质上也是ChannelHandler。 Netty中一些实现的编码器,如下:

Netty也提供了MessageToMessageEncoder,将⼀种格式转化为另⼀种格式的编码器,也提供了⼀些 实现:

1.3.2 将Integer类型编码为字节进⾏传递(案例)

  1. 自定义编码器
  2. Handler
  3. pipeline

二 开发Http服务器

通过Netty中提供的http的解码器,进行http服务器开发。

2.1 Netty配置

  1. server
  2. ServerHandler
  3. RequestParser
  4. 对象

2.2 服务端

  1. NettyObjectServer
  2. ServerHandler

2.3 客户端

  1. NettyObjectClient
  2. ClientHandler

2.4 JDK序列化的优化

JDK序列化使⽤是⽐较⽅便,但是性能较差,序列化后的字节⽐较⼤,所以⼀般在项⽬中不 会使⽤⾃带的序列化,⽽是会采⽤第三⽅的序列化框架Hessian编解码。

  1. 导入依赖
  2. User对象
  3. Hessian序列化⼯具类
  4. 编码器
  5. 解码器
  6. 服务端
  7. 客户端

二 TCP的粘包/拆包的问题以及解决

2.1 ReplayingDecoder

  1. 自定义解码器,将buf变为int

2. 使用ReplayingDecoder进行优化

  1. ReplayingDecoder使用说明
  2. 注意

继承ReplayingDecoder,错误示例和修改

ByteToIntegerDecoder2的实现

2.2 拆包和粘包问题重现(客户端向服务端发送十条数据)

  1. 客户端启动类
  2. 客户端ClientHandler
  3. 服务端NettyServer
  4. ServerHandler

2.2 什么是TCP的粘包和拆包问题

TCP是流传递的,所谓流,就是没有界限的数据。服务端接受客户端数据,并不知道是一条还是多条。 服务端如何拆包并不知道。

因此服务端和客户端进行数据传递的时候,要制定好拆包规则。客户端按照该规则进行粘包,服务端 按照该规则拆包。如果有任意违背该规则,服务端就不能拿到预期的数据。

  1. 解决思路(三种)

2.3 实战:解决TCP的粘包/拆包问题

  1. 自定义协议
  2. 编码器
  3. 解码器
  4. 客户端ClientHandler
  5. NettyClient
  6. ServerHandler
  7. NettyServer
  8. 测试

三 Netty核心源码解析

3.1 服务端启动过程刨析

  1. 创建服务端Channel
  2. 初始化服务端Channel
  3. 注册selector
  4. 绑定端口

什么时候进⾏更新selector的主从事件?最终在io.netty.channel.nio.AbstractNioChannel#doBeginRead()⽅法中完成的

3.2 连接请求过程源码刨析

  1. 新连接接入
  2. 注册读事件

四 使用Netty优化点

4.1 零拷贝

4.2 EventLoop的任务调度

而不是使用hannel.writeAndFlush(data);EventLoop的任务调度直接放入到channel所对应的EventLoop的执行队列,后者会导致线程切换。备注:在writeAndFlush的底层,如果没有通过eventLoop执行的话,就会启动新的线程。

4.3 减少ChannelPipline的调⽤⻓度

4.4 减少ChannelHandler的创建(基本上不会配置)

如果channelhandler是⽆状态的(即不需要保存任何状态参数),那么使⽤Sharable注解,并在 bootstrap时只创建⼀个实例,减少GC。否则每次连接都会new出handler对象。

注意:ByteToMessageDecoder之类的编解码器是有状态的,不能使⽤Sharable注解。

4.5 配置参数的设置

五 ByteBuf的api

  1. 顺序读api
  1. 顺序写操作

更多Java相关内容。请关注微信公众号 花花与Java

0 人点赞