小白也能看懂的 Netty 框架——实战(一)

小白也能看懂的 Netty 框架——实战(一)

一、架构回顾

简易Netty框架

image-20241203225612925

进一步查看BossGroup

image-20241203230347312

Netty的详细结构

image-20241204154932800

Netty抽象出了两组线程池:

  • BossGroup:专门负责接受客户端的连接。
  • WorkerGroup:负责网络的读写。

这两者都是 NIOEventLoopGroup 类型。

NIOEventLoopGroup 相当于一个事件循环组,它内部包含多个事件,每个事件都是 NIOEventLoop

NIOEventLoop 是一个持续循环执行处理任务的线程,每个线程都有一个 selector,用于监听绑定在上面的 Socket 网络通信。

NIOEventLoopGroup 可以有多个线程,也可以包含多个 NIOEventLoop

每个 Boss NIOEventLoop 的执行步骤:

  1. 轮询 accept 事件。
  2. 处理 accept 事件,建立与客户端的连接,生成 NIOSocketChannel,并将其注册到某个 Worker 的 NIOEventLoop 上的 selector
  3. 处理任务队列中的任务,即执行 runAllTasks

每个 Worker NIOEventLoop 的执行步骤:

  1. 轮询 readwrite 事件。
  2. 处理 I/O 事件,即处理 readwrite 操作,更新对应的 NIOSocketChannel
  3. 处理任务队列中的任务,即执行 runAllTasks

在处理业务时,每个 Worker NIOEventLoop 会使用 pipeline,其中包含一条管道。通过 pipeline,可以获取到处理器、拦截器等组件,从而实现定制化的业务处理。

这是对代码的分析及说明:

二、代码实战

服务端

nettyServer.java
package netty.simple;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class nettyServer {
    public static void main(String[] args) throws InterruptedException {
        // 创建两个group,分别用于boss和worker
        EventLoopGroup bossGroup = new NioEventLoopGroup();  // 处理连接请求
        EventLoopGroup workerGroup = new NioEventLoopGroup();  // 处理I/O操作

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();  // 创建服务端启动对象

            bootstrap.group(bossGroup, workerGroup)  // 设置两个线程组
                    .channel(NioServerSocketChannel.class)  // 使用 NioServerSocketChannel 作为通道实现
                    .option(ChannelOption.SO_BACKLOG, 128)  // 设置线程队列等待的连接个数
                    .childOption(ChannelOption.SO_KEEPALIVE, true)  // 保持活动连接状态
                    .childHandler(new ChannelInitializer<SocketChannel>() {  // 创建通道初始化器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyServerHandler());  // 添加自定义处理器
                        }
                    });

            System.out.println("服务器已准备好");

            // 绑定端口并启动服务器
            ChannelFuture cf = bootstrap.bind(6668).sync();  // 绑定端口并同步等待

            // 监听关闭事件
            cf.channel().closeFuture().sync();  // 阻塞直到关闭
        } finally {
            bossGroup.shutdownGracefully();  // 优雅关闭
            workerGroup.shutdownGracefully();  // 优雅关闭
        }
    }
}
说明:
  • 该服务端代码创建了两个事件循环组:bossGroup 用于接受客户端的连接请求,workerGroup 用于处理业务请求。
  • ServerBootstrap 是 Netty 服务端的启动引导类,配置了通道、选项、以及事件处理器。
  • ChannelInitializer<SocketChannel> 用于初始化每个客户端连接时创建的 SocketChannel,并将自定义的 NettyServerHandler 加入到通道的处理链中。
  • 最后,服务端绑定端口 6668,并通过 closeFuture().sync() 监听关闭事件。
NettyServerHandler.java
package netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("server ctx:" + ctx);
        // 将 msg 转为 ByteBuf
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("客户端发送消息:" + byteBuf.toString(CharsetUtil.UTF_8));
        System.out.println("客户端地址:" + ctx.channel().remoteAddress());
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 数据写入并刷新
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello client : miaowu", CharsetUtil.UTF_8));
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        ctx.channel().close();  // 发生异常时关闭通道
    }
}
说明:
  • channelRead 方法读取客户端发送的消息,并打印消息内容和客户端地址。
  • channelReadComplete 在读取完数据后写入数据并刷新,回应客户端的消息。
  • exceptionCaught 处理异常,并在发生错误时关闭通道。

客户端

nettyClient.java
package netty.simple;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class nettyClient {

    public static void main(String[] args) throws InterruptedException {
        EventLoopGroup eventExecutors = new NioEventLoopGroup();  // 创建事件循环组

        try {
            Bootstrap bootstrap = new Bootstrap();  // 创建客户端启动对象

            bootstrap.group(eventExecutors)  // 设置线程组
                    .channel(NioSocketChannel.class)  // 使用 NioSocketChannel 作为通道实现
                    .handler(new ChannelInitializer<SocketChannel>() {  // 配置通道处理器
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new NettyClientHandler());  // 添加自定义客户端处理器
                        }
                    });

            System.out.println("客户端启动成功");

            // 启动客户端并连接到服务端
            ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 6668).sync();  // 连接到服务端
            channelFuture.channel().closeFuture().sync();  // 阻塞直到连接关闭
        } finally {
            eventExecutors.shutdownGracefully();  // 优雅关闭
        }
    }
}
说明:
  • EventLoopGroup 用于管理线程。
  • Bootstrap 是客户端的启动引导类,配置了线程组、通道实现以及通道处理器。
  • 客户端通过 connect() 方法连接到服务端,使用 closeFuture().sync() 阻塞直到关闭连接。
NettyClientHandler.java
package netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("client " + ctx);
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello server : miaowu", CharsetUtil.UTF_8));  // 向服务器发送数据
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("服务器回复的消息:" + byteBuf.toString(CharsetUtil.UTF_8));  // 输出服务器返回的数据
        System.out.println("服务器地址:" + ctx.channel().remoteAddress());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();  // 发生异常时关闭通道
    }
}
说明:
  • channelActive 方法在连接建立后自动触发,客户端向服务端发送数据。
  • channelRead 方法用于读取服务端返回的数据,并打印消息内容和服务器地址。
  • exceptionCaught 处理异常,并在发生错误时关闭通道。

总结

  • 服务端:使用 ServerBootstrap 创建服务端,配置了 NioEventLoopGroup,分别负责接收连接和处理业务。
  • 客户端:使用 Bootstrap 创建客户端,配置了 NioSocketChannel 并与服务端建立连接。
  • Netty的异步模型:客户端和服务端之间的通信是异步的,通过 ChannelFuture 进行异步操作,确保不会阻塞主线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值