阶段三:高级特性
在这个阶段,我们将学习Netty的高级特性,包括自定义编解码器、入站/出站处理器、UDP通信和HTTP服务器。
项目 1-09:自定义编解码器
📌 项目目标
学习如何自定义编解码器,实现自定义协议的编解码。
🔑 核心知识点
-
ByteToMessageDecoder:字节到消息的解码器基类
- 重写decode()方法
- 处理TCP粘包/拆包
-
MessageToByteEncoder:消息到字节的编码器基类
- 重写encode()方法
- 将对象编码为字节
-
自定义协议:定义消息格式
- 消息头:长度字段
- 消息体:实际数据
💻 核心代码解析
MyDecoder.java - 自定义解码器
public class MyDecoder extends ByteToMessageDecoder {
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
// 1. 判断是否有足够的数据(至少4字节的长度字段)
if (in.readableBytes() < 4) {
return;
}
// 2. 标记当前读取位置
in.markReaderIndex();
// 3. 读取消息长度
int dataLength = in.readInt();
// 4. 判断是否有足够的数据
if (in.readableBytes() < dataLength) {
// 数据不完整,重置读取位置,等待更多数据
in.resetReaderIndex();
return;
}
// 5. 读取消息体
byte[] data = new byte[dataLength];
in.readBytes(data);
// 6. 将解码后的消息添加到输出列表
String message = new String(data, Charset.forName("GBK"));
out.add(message);
}
}
MyEncoder.java - 自定义编码器
public class MyEncoder extends MessageToByteEncoder<String> {
@Override
protected void encode(ChannelHandlerContext ctx, String msg, ByteBuf out) {
// 1. 将消息转换为字节数组
byte[] data = msg.getBytes(Charset.forName("GBK"));
// 2. 写入消息长度(4字节)
out.writeInt(data.length);
// 3. 写入消息体
out.writeBytes(data);
}
}
MyChannelInitializer.java - 使用自定义编解码器
@Override
protected void initChannel(SocketChannel channel) {
// 自定义解码器
channel.pipeline().addLast(new MyDecoder());
// 自定义编码器
channel.pipeline().addLast(new MyEncoder());
// 业务处理器
channel.pipeline().addLast(new MyServerHandler());
}
🎯 运行效果
- 使用自定义协议进行通信
- 消息格式:[长度(4字节)][消息体]
- 自动处理粘包/拆包问题
📊 自定义协议格式
+--------+----------------+
| Length | Data |
| 4 bytes| N bytes |
+--------+----------------+
💡 自定义编解码器的应用
- 实现私有协议
- 提高传输效率
- 增强安全性
- 支持复杂的数据结构
项目 1-10:入站/出站处理器
📌 项目目标
学习Netty的入站(Inbound)和出站(Outbound)处理器,理解Pipeline的双向处理机制。
🔑 核心知识点
-
ChannelInboundHandler:入站处理器
- 处理接收的数据(从Socket读取)
- 方法:channelRead()、channelActive()等
-
ChannelOutboundHandler:出站处理器
- 处理发送的数据(写入Socket)
- 方法:write()、flush()等
-
Pipeline执行顺序:
- 入站:从前往后执行
- 出站:从后往前执行
-
处理器链:多个处理器组成处理链
💻 核心代码解析
MyInMsgHandler.java - 入站处理器
public class MyInMsgHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("【入站处理器1】接收到消息:" + msg);
// 将消息传递给下一个入站处理器
ctx.fireChannelRead(msg);
}
}
MyOutMsgHandler.java - 出站处理器
public class MyOutMsgHandler extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
System.out.println("【出站处理器1】发送消息:" + msg);
// 将消息传递给下一个出站处理器
ctx.write(msg, promise);
}
}
MyChannelInitializer.java - 配置处理器链
@Override
protected void initChannel(SocketChannel channel) {
// 入站处理器(从前往后执行)
channel.pipeline().addLast("inHandler1", new MyInMsgHandler());
channel.pipeline().addLast("inHandler2", new MyInMsgHandler());
// 出站处理器(从后往前执行)
channel.pipeline().addLast("outHandler1", new MyOutMsgHandler());
channel.pipeline().addLast("outHandler2", new MyOutMsgHandler());
// 业务处理器
channel.pipeline().addLast(new MyClientHandler());
}
🎯 运行效果
接收数据时(入站):
【入站处理器1】接收到消息:Hello
【入站处理器2】接收到消息:Hello
发送数据时(出站):
【出站处理器2】发送消息:World
【出站处理器1】发送消息:World
📊 Pipeline执行流程
接收数据(入站):
Socket → 入站处理器1 → 入站处理器2 → 业务处理器
发送数据(出站):
业务处理器 → 出站处理器2 → 出站处理器1 → Socket
💡 应用场景
- 入站处理器:解码、日志记录、权限验证
- 出站处理器:编码、加密、压缩
- 处理器链:实现复杂的业务逻辑
项目 1-11:UDP通信实现
📌 项目目标
学习如何使用Netty实现UDP通信(无连接的数据报协议)。
🔑 核心知识点
-
UDP vs TCP:
- UDP:无连接、不可靠、快速
- TCP:面向连接、可靠、慢
-
NioDatagramChannel:UDP通信的Channel类型
-
DatagramPacket:UDP数据包
- 包含数据和目标地址
-
UDP特点:
- 不需要建立连接
- 不保证数据到达
- 适合实时性要求高的场景
💻 核心代码解析
NettyServer.java - UDP服务端
private void bing(int port) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioDatagramChannel.class) // 使用UDP Channel
.option(ChannelOption.SO_BROADCAST, true) // 支持广播
.handler(new MyChannelInitializer());
// UDP使用bind()而不是connect()
ChannelFuture f = b.bind(port).sync();
System.out.println("UDP服务端启动成功,端口:" + port);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
MyServerHandler.java - UDP消息处理
public class MyServerHandler extends SimpleChannelInboundHandler<DatagramPacket> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) {
// 1. 读取UDP数据包
ByteBuf buf = packet.content();
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String message = new String(data, Charset.forName("GBK"));
System.out.println("收到UDP消息:" + message);
System.out.println("发送方地址:" + packet.sender());
// 2. 回复UDP消息
String response = "服务端收到:" + message;
ByteBuf responseBuf = Unpooled.copiedBuffer(
response.getBytes(Charset.forName("GBK"))
);
// 3. 创建UDP数据包并发送
DatagramPacket responsePacket = new DatagramPacket(
responseBuf,
packet.sender() // 发送到原地址
);
ctx.writeAndFlush(responsePacket);
}
}
🎯 运行效果
- UDP服务端能够接收和发送数据报
- 不需要建立连接,直接收发数据
- 适合实时性要求高的场景
📊 TCP vs UDP对比
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 速度 | 较慢 | 较快 |
| 顺序 | 保证顺序 | 不保证 |
| 应用场景 | 文件传输、HTTP | 视频直播、游戏 |
💡 UDP应用场景
- 视频直播:实时性要求高
- 在线游戏:低延迟
- DNS查询:快速响应
- 语音通话:实时通信
项目 1-12:HTTP服务器实现
📌 项目目标
学习如何使用Netty实现一个简单的HTTP服务器,能够接收HTTP请求并返回HTTP响应。
🔑 核心知识点
-
HttpRequestDecoder:HTTP请求解码器
- 将字节流解码为HttpRequest和HttpContent对象
-
HttpResponseEncoder:HTTP响应编码器
- 将HttpResponse对象编码为字节流
-
HTTP消息结构:
- HttpRequest:请求行 + 请求头
- HttpContent:请求体
- LastHttpContent:请求结束标记
-
FullHttpResponse:完整的HTTP响应
- 包含响应行、响应头、响应体
💻 核心代码解析
MyChannelInitializer.java - HTTP编解码器配置
@Override
protected void initChannel(SocketChannel channel) {
// 1. HTTP响应编码器(出站)
channel.pipeline().addLast(new HttpResponseEncoder());
// 2. HTTP请求解码器(入站)
channel.pipeline().addLast(new HttpRequestDecoder());
// 3. 业务处理器
channel.pipeline().addLast(new MyServerHandler());
}
MyServerHandler.java - HTTP请求处理
public class MyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 处理HTTP请求行和请求头
if (msg instanceof HttpRequest) {
DefaultHttpRequest request = (DefaultHttpRequest) msg;
System.out.println("URI:" + request.getUri());
System.out.println("请求方法:" + request.method());
}
// 处理HTTP请求体
if (msg instanceof HttpContent) {
LastHttpContent httpContent = (LastHttpContent) msg;
ByteBuf byteData = httpContent.content();
if (!(byteData instanceof EmptyByteBuf)) {
byte[] msgByte = new byte[byteData.readableBytes()];
byteData.readBytes(msgByte);
System.out.println("请求体:" + new String(msgByte, Charset.forName("UTF-8")));
}
}
// 构造HTTP响应
String responseContent = "Hello from Netty HTTP Server!";
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.OK,
Unpooled.wrappedBuffer(responseContent.getBytes(Charset.forName("UTF-8")))
);
// 设置响应头
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain;charset=UTF-8");
response.headers().set(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes());
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE);
// 发送响应
ctx.write(response);
ctx.flush();
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
cause.printStackTrace();
}
}
NettyServer.java - HTTP服务器启动
private void bing(int port) {
EventLoopGroup parentGroup = new NioEventLoopGroup();
EventLoopGroup childGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(parentGroup, childGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true) // HTTP长连接
.childHandler(new MyChannelInitializer());
ChannelFuture f = b.bind(port).sync();
System.out.println("HTTP服务器启动成功,端口:" + port);
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
childGroup.shutdownGracefully();
parentGroup.shutdownGracefully();
}
}
🎯 运行效果
- 启动HTTP服务器后,在浏览器访问:
http://localhost:7397 - 浏览器显示:
Hello from Netty HTTP Server! - 服务端控制台打印请求信息
📊 HTTP消息处理流程
客户端请求:
浏览器 → HTTP请求字节流 → HttpRequestDecoder → HttpRequest对象 → 业务处理器
服务端响应:
业务处理器 → FullHttpResponse对象 → HttpResponseEncoder → HTTP响应字节流 → 浏览器
💡 HTTP服务器应用
- RESTful API服务
- Web应用后端
- 微服务网关
- 静态文件服务器
总结与展望
🎓 知识体系总结
通过这12个项目的学习,我们完整地掌握了Netty的核心知识体系:
1. 核心组件掌握
| 组件 | 作用 | 关键点 |
|---|---|---|
| EventLoopGroup | 事件循环组 | Boss线程组 + Worker线程组 |
| Bootstrap | 启动辅助类 | ServerBootstrap(服务端)、Bootstrap(客户端) |
| Channel | 网络通信载体 | NioServerSocketChannel、NioSocketChannel、NioDatagramChannel |
| ChannelPipeline | 处理器链 | 入站从前往后,出站从后往前 |
| ChannelHandler | 业务处理器 | Inbound(入站)、Outbound(出站) |
| ByteBuf | 字节缓冲区 | 比ByteBuffer更强大 |
| ChannelGroup | Channel组 | 管理多个Channel,实现群发 |
2. 编解码技术
内置编解码器:
- LineBasedFrameDecoder:基于换行符
- StringDecoder/StringEncoder:字符串编解码
- HttpRequestDecoder/HttpResponseEncoder:HTTP编解码
自定义编解码器:
- ByteToMessageDecoder:自定义解码器
- MessageToByteEncoder:自定义编码器
- 实现私有协议
3. 通信协议支持
| 协议 | Channel类型 | 特点 | 应用场景 |
|---|---|---|---|
| TCP | NioSocketChannel | 可靠、有序 | 文件传输、HTTP |
| UDP | NioDatagramChannel | 快速、无连接 | 视频直播、游戏 |
| HTTP | NioSocketChannel + HTTP编解码器 | 应用层协议 | Web服务、API |
4. 学习路径回顾
阶段一:服务端基础
├─ 1-01:理解Netty启动流程
├─ 1-02:手动解码(理解底层原理)
├─ 1-03:内置解码器(简化开发)
├─ 1-04:手动编码(理解底层原理)
├─ 1-05:内置编码器(简化开发)
└─ 1-06:ChannelGroup(群发消息)
阶段二:客户端实现
├─ 1-07:客户端启动流程
└─ 1-08:完整的收发功能
阶段三:高级特性
├─ 1-09:自定义编解码器(私有协议)
├─ 1-10:入站/出站处理器(Pipeline机制)
├─ 1-11:UDP通信(无连接协议)
└─ 1-12:HTTP服务器(应用层协议)
📈 知识演进图
项目1-01:最基础的服务端(只能监听连接)
↓
项目1-02:手动解码接收数据(理解ByteBuf)
↓
项目1-03:使用内置解码器(简化开发)
↓
项目1-04:手动编码发送数据(理解编码原理)
↓
项目1-05:使用内置编码器(代码最优雅)
↓
项目1-06:群发消息(ChannelGroup)
↓
项目1-07:实现客户端(理解客户端与服务端区别)
↓
项目1-08:完整的客户端(双向通信)
↓
项目1-09:自定义编解码器(私有协议)
↓
项目1-10:入站/出站处理器(Pipeline机制)
↓
项目1-11:UDP通信(无连接协议)
↓
项目1-12:HTTP服务器(应用层协议)
🎯 结语
Netty是一个强大的网络编程框架,通过这12个项目的学习,我们从零开始,逐步掌握了Netty的核心知识和实战技能。
希望这份学习笔记能够帮助你快速入门Netty,开启网络编程的精彩之旅!
1107

被折叠的 条评论
为什么被折叠?



