# Netty高级应用


<!--more-->


## 1.netty编解码器

### 1.1 java的编解码

- 编码（Encode）称为序列化， 它将对象序列化为字节数组，用于网络传输、数据持久化或者其它用途。

- 解码（Decode）称为反序列化，它把从网络、磁盘等读取的字节数组还原成原始对象（通常是原始对象的拷贝），以方便后续的业务逻辑操作。

java序列化对象只需要实现java.io.Serializable接口并生成序列化ID

Java序列化目的：1.网络传输。2.对象持久化。

Java序列化缺点：1.无法跨语言。 2.序列化后码流太大。3.序列化性能太低。

### 1.2 Netty编解码器

- 概念

  在网络应用中需要实现某种编解码器，将原始字节数据与自定义的消息对象进行互相转换。网络中都是以字节码的数据形式来传输数据的，服务器编码数据后发送到客户端，客户端需要对数据进行解码。

  对于Netty而言，编解码器由两部分组成：编码器、解码器。 

  > 解码器：负责将消息从字节或其他序列形式转成指定的消息对象。
  >
  > 编码器：将消息对象转成字节或其他序列形式在网络上传输。

  Netty 的编（解）码器实现了 **ChannelHandlerAdapter**，也是一种特殊的 ChannelHandler，所以依赖于 ChannelPipeline，可以将多个编（解）码器链接在一起，以实现复杂的转换逻辑。
  Netty里面的编解码： 解码器：负责处理“入站 InboundHandler”数据。  编码器：负责“出站 OutboundHandler” 数据。

- 解码器(Decoder)

  解码器负责 解码“入站”数据从一种格式到另一种格式，解码器处理入站数据是抽象ChannelInboundHandler的实现。需要将解码器放在ChannelPipeline中。对于解码器，Netty中主要提供了抽象基类**ByteToMessageDecoder**和**MessageToMessageDecoder**

  ![img_23](https://img.zhaojq.top/20260727091825550.png "img_23")

  - ByteToMessageDecoder: 用于将字节转为消息，需要检查缓冲区是否有足够的字节
  - ReplayingDecoder: 继承ByteToMessageDecoder，不需要检查缓冲区是否有足够的字节,但是 ReplayingDecoder速度略慢于ByteToMessageDecoder,同时不是所有的ByteBuf都支持。项目复杂性高则使用ReplayingDecoder，否则使用ByteToMessageDecoder 
  - MessageToMessageDecoder: 用于从一种消息解码为另外一种消息（例如POJO到POJO）

  示例代码：**netty-demo** codec

- 编码器(Encoder)

  与ByteToMessageDecoder和MessageToMessageDecoder相对应，Netty提供了对应的编码器实现**MessageToByteEncoder**和**MessageToMessageEncoder**，二者都实现ChannelOutboundHandler接口。

  ![img_24](https://img.zhaojq.top/20260727091825787.png "img_24")

  - MessageToByteEncoder: 将消息转化成字节
  - MessageToMessageEncoder: 用于从一种消息编码为另外一种消息（例如POJO到POJO）

  示例代码：**netty-demo** codec

- 编码解码器Codec

  同时具有编码与解码功能，特点同时实现了ChannelInboundHandler和ChannelOutboundHandler接口，因此在数据输入和输出时都能进行处理。

  ![img_25](https://img.zhaojq.top/20260727091826052.png "img_25")

  Netty提供提供了一个ChannelDuplexHandler适配器类,编码解码器的抽象基类 ByteToMessageCodec ,MessageToMessageCodec都继承与此类. 

  示例代码：**netty-demo** codec

  

## 2.netty安全-群聊天室

要求：

1. 编写一个 Netty  群聊系统，实现服务器端和客户端之间的数据简单通讯

2. 实现多人群聊

3. 服务器端：可以监测用户上线，离线，并实现消息转发功能

4. 客户端：可以发送消息给其它所有用户，同时可以接受其它用户发送的消息

**示例代码：netty-demo** chat



## 3.基于Netty的Http服务器开发

Netty的HTTP协议栈无论在性能还是可靠性上，都表现优异，非常适合在非Web容器的场景下应用，相比于传统的Tomcat、Jetty等Web容器，它更加轻量和小巧，灵活性和定制性也更好。

功能需求：

> 1. Netty 服务器在 8080 端口监听
>
> 2. 浏览器发出请求 "http://localhost:8080/ "
>
> 3. 服务器可以回复消息给客户端 "Hello! 我是Netty服务器 " ,并对特定请求资源进行过滤.

**示例代码：netty-demo** http



## 4.基于Netty的WebSocket开发网页版聊天室

### 4.1 websocket简介

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，客户端和服务器只需要完成一次握手，两者之间就直接可以创建持久性的连接，并进行双向数据传输。

> 应用场景
>
> 1. 社交订阅
>
> 2. 协同编辑/编程
>
> 3. 股票基金报价
>
> 4. 体育实况更新
>
> 5. 多媒体聊天
>
> 6. 在线教育

### 4.2 WebSocket和HTTP的区别

http协议是用在应用层的协议，他是基于tcp协议的，http协议建立连接也必须要有三次握手才能发送信息。 http连接分为短连接，长连接，短连接是每次请求都要三次握手才能发送自己的信息。即每一个request对应一个response。长连接是在一定的期限内保持连接。保持TCP连接不断开。客户端与服务器通信，必须要有客户端先发起,  然后服务器返回结果。客户端是主动的，服务器是被动的。 

**客户端要想实时获取服务端消息就得不断发送长连接到服务端.**

WebSocket实现了多路复用，他是**全双工**通信。在webSocket协议下服务端和客户端可以同时发送信息。 建立了WebSocket连接之后,  服务端可以主动发送信息到客户端。而且信息当中不必在带有head的部分信息了与http的长链接通信来说，这种方式，

**不仅能降低服务器的压力。而且信息当中也减少了部分多余的信息。**

**示例代码：netty-websocket-demo**



## 5.Netty中粘包和拆包的解决方案

### 5.1 粘包和拆包简介

粘包和拆包是TCP网络编程中不可避免的，无论是服务端还是客户端，当我们读取或者发送消息的时候，都需要考虑TCP底层的粘包/拆包机制。

TCP是个“流”协议，所谓流，就是没有界限的一串数据。TCP底层并不了解上层业务数据的具体含义，它会根据TCP缓冲区的实际情况进行包的划分，所以在业务上认为，一个完整的包可能会被TCP拆分成多个包进行发送，也有可能把多个小的包封装成一个大的数据包发送，这就是所谓的TCP粘包和拆包问题。

如图所示，假设客户端分别发送了两个数据包D1和D2给服务端，由于服务端一次读取到的字节数是不确定的，故可能存在以下4种情况。

![img_26](https://img.zhaojq.top/20260727091826254.png "img_26")

**TCP粘包和拆包产生的原因:**

数据从发送方到接收方需要经过操作系统的缓冲区，而造成粘包和拆包的主要原因就在这个缓冲区上。粘包可以理解为缓冲区数据堆积，导致多个请求数据粘在一起，而拆包可以理解为发送的数据大于缓冲区，进行拆分处理。



### 5.2 粘包拆包代码演示

- 粘包

  **示例代码：netty-demo** stickybag

  可以看到，在注释掉粘包拆包解码器后，客户端发送10次，但服务端一次读取了客户端发送过来的消息,应该读取10次. 因此发生粘包.

- 拆包

  **示例代码：netty-demo** unpacking

  可以看到，在注释掉粘包拆包解码器后, 当客户端发送的数据包比较大的时候, 客户端发送10次，但服务端读取了18次, 应该读取10次, 则发送拆包事件.

### 5.3 粘包和拆包的解决办法

- 业内解决方案

  由于底层的TCP无法理解上层的业务数据，所以在底层是无法保证数据包不被拆分和重组的，这个问题只能通过上层的应用协议栈设计来解决，根据业界的主流协议的解决方案，可以归纳如下。

  > - 消息长度固定，累计读取到长度和为定长LEN的报文后，就认为读取到了一个完整的信息
  > - 将换行符作为消息结束符
  > - 将特殊的分隔符作为消息的结束标志，回车换行符就是一种特殊的结束分隔符
  > - 通过在消息头中定义长度字段来标识消息的总长度

- netty中的粘包和拆包解决方案

  Netty提供了4种解码器来解决

  - 固定长度的拆包器 FixedLengthFrameDecoder，每个应用层数据包都拆分成固定长度的大小
  - 行拆包器 LineBasedFrameDecoder，每个应用层数据包，都以换行符作为分隔符，进行分割拆分
  - 分隔符拆包器 DelimiterBasedFrameDecoder，每个应用层数据包，都通过自定义的分隔符，进行分割拆分
  - 基于数据包长度的拆包器 LengthFieldBasedFrameDecoder，将应用层数据包的长度，作为接收端应用层数据包的拆分依据。按照应用层数据包的大小，拆包。这个拆包器，有一个要求，就是应用层协议中包含数据包的长度

- 代码实现

  > - LineBasedFrameDecoder解码器
  >
  >   ```java
  >   ch.pipeline().addLast(new LineBasedFrameDecoder(2048));
  >   
  >   ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客户端"+i+"\n", CharsetUtil.UTF_8));
  >   ```
  >
  > - DelimiterBasedFrameDecoder解码器
  >
  >   ```java
  >   ByteBuf byteBuf = Unpooled.copiedBuffer("$".getBytes(StandardCharsets.UTF_8));
  >   ch.pipeline().addLast(new DelimiterBasedFrameDecoder(2048, byteBuf));
  >   
  >   ctx.writeAndFlush(Unpooled.copiedBuffer("你好呀,我是Netty客户端"+i+"$", CharsetUtil.UTF_8));
  >   ```
  >
  >   










