Netty入门学习笔记(三)

阶段三:高级特性

在这个阶段,我们将学习Netty的高级特性,包括自定义编解码器、入站/出站处理器、UDP通信和HTTP服务器。

项目 1-09:自定义编解码器

📌 项目目标

学习如何自定义编解码器,实现自定义协议的编解码。

🔑 核心知识点
  1. ByteToMessageDecoder:字节到消息的解码器基类

    • 重写decode()方法
    • 处理TCP粘包/拆包
  2. MessageToByteEncoder:消息到字节的编码器基类

    • 重写encode()方法
    • 将对象编码为字节
  3. 自定义协议:定义消息格式

    • 消息头:长度字段
    • 消息体:实际数据
💻 核心代码解析

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的双向处理机制。

🔑 核心知识点
  1. ChannelInboundHandler:入站处理器

    • 处理接收的数据(从Socket读取)
    • 方法:channelRead()、channelActive()等
  2. ChannelOutboundHandler:出站处理器

    • 处理发送的数据(写入Socket)
    • 方法:write()、flush()等
  3. Pipeline执行顺序

    • 入站:从前往后执行
    • 出站:从后往前执行
  4. 处理器链:多个处理器组成处理链

💻 核心代码解析

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通信(无连接的数据报协议)。

🔑 核心知识点
  1. UDP vs TCP

    • UDP:无连接、不可靠、快速
    • TCP:面向连接、可靠、慢
  2. NioDatagramChannel:UDP通信的Channel类型

  3. DatagramPacket:UDP数据包

    • 包含数据和目标地址
  4. 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对比
特性TCPUDP
连接面向连接无连接
可靠性可靠传输不可靠
速度较慢较快
顺序保证顺序不保证
应用场景文件传输、HTTP视频直播、游戏
💡 UDP应用场景
  • 视频直播:实时性要求高
  • 在线游戏:低延迟
  • DNS查询:快速响应
  • 语音通话:实时通信

项目 1-12:HTTP服务器实现

📌 项目目标

学习如何使用Netty实现一个简单的HTTP服务器,能够接收HTTP请求并返回HTTP响应。

🔑 核心知识点
  1. HttpRequestDecoder:HTTP请求解码器

    • 将字节流解码为HttpRequest和HttpContent对象
  2. HttpResponseEncoder:HTTP响应编码器

    • 将HttpResponse对象编码为字节流
  3. HTTP消息结构

    • HttpRequest:请求行 + 请求头
    • HttpContent:请求体
    • LastHttpContent:请求结束标记
  4. 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更强大
ChannelGroupChannel组管理多个Channel,实现群发
2. 编解码技术

内置编解码器:

  • LineBasedFrameDecoder:基于换行符
  • StringDecoder/StringEncoder:字符串编解码
  • HttpRequestDecoder/HttpResponseEncoder:HTTP编解码

自定义编解码器:

  • ByteToMessageDecoder:自定义解码器
  • MessageToByteEncoder:自定义编码器
  • 实现私有协议
3. 通信协议支持
协议Channel类型特点应用场景
TCPNioSocketChannel可靠、有序文件传输、HTTP
UDPNioDatagramChannel快速、无连接视频直播、游戏
HTTPNioSocketChannel + 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,开启网络编程的精彩之旅!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值