Netty 入门

netty

Netty 核心组件

1. Channel

​ Channel是 Java NIO 的一个基本构造。可以看作是传入或传出数据的载体。因此,它可以被打开或关闭,连接或者断开连接。

2. EventLoop 与 EventLoopGroup

​ EventLoop 定义了Netty的核心抽象,用来处理连接的生命周期中所发生的事件,在内部,将会为每个Channel分配一个EventLoop。

​ EventLoopGroup 是一个 EventLoop 池,包含很多的 EventLoop。

​ Netty 为每个 Channel 分配了一个 EventLoop,用于处理用户连接请求、对用户请求的处理等所有事件。EventLoop 本身只是一个线程驱动,在其生命周期内只会绑定一个线程,让该线程处理一个 Channel 的所有 IO 事件。

​ 一个 Channel 一旦与一个 EventLoop 相绑定,那么在 Channel 的整个生命周期内是不能改变的。一个 EventLoop 可以与多个 Channel 绑定。即 Channel 与 EventLoop 的关系是 n:1,而 EventLoop 与线程的关系是 1:1。

3. ServerBootstrap 与 Bootstrap

​ Bootstarp 和 ServerBootstrap 被称为引导类,指对应用程序进行配置,并使他运行起来的过程。Netty处理引导的方式是使的应用程序和网络层相隔离。

​ Bootstrap 是客户端的引导类,Bootstrap 在调用 bind()(连接UDP)和 connect()(连接TCP)方法时,会新创建一个 Channel,仅创建一个单独的、没有父 Channel 的 Channel 来实现所有的网络交换。

​ ServerBootstrap 是服务端的引导类,ServerBootstarp 在调用 bind() 方法时会创建一个 ServerChannel 来接受来自客户端的连接,并且该 ServerChannel 管理了多个子 Channel 用于同客户端之间的通信。

4. ChannelHandler 与 ChannelPipeline

​ ChannelHandler 是对 Channel 中数据的处理器,这些处理器可以是系统本身定义好的编解码器,也可以是用户自定义的。这些处理器会被统一添加到一个 ChannelPipeline 的对象中,然后按照添加的顺序对 Channel 中的数据进行依次处理。

5. ChannelFuture

​ Netty 中所有的 I/O 操作都是异步的,即操作不会立即得到返回结果,所以 Netty 中定义了一个 ChannelFuture 对象作为这个异步操作的“代言人”,表示异步操作本身。如果想获取到该异步操作的返回值,可以通过该异步操作对象的addListener() 方法为该异步操作添加监 NIO 网络编程框架 Netty 听器,为其注册回调:当结果出来后马上调用执行。

​ Netty 的异步编程模型都是建立在 Future 与回调概念之上的。

6. Netty 线程模型

Boss线程(也称为Accept线程):负责接收客户端的连接请求,并将接收到的连接注册到Worker线程中,以便进行后续的I/O操作。在Netty中,可以通过创建一个NioEventLoopGroup作为Boss线程组来实现。
Worker线程(也称为I/O线程):负责处理与客户端的连接,包括读取数据、写入数据等操作。Worker线程通常是一个线程池,可以处理多个客户端的连接请求。在Netty中,可以通过创建另一个NioEventLoopGroup作为Worker线程组来实现。

Netty ChannelHandler 执行顺序

ChannelHandler执行顺序

使用 Netty 编写 Server

1. Netty Server

package com.lcx.server;

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 java.util.HashSet;
import java.util.Set;

/**
 * @author : lichangxin
 * @create : 2024/5/15 16:08
 * @description
 */

public class NettyServer {

    public static Set<Channel> channelSet = new HashSet<>();

    public static void main(String[] args) throws Exception {
        int port = 9999; // 监听端口
        // 配置服务端NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new NettyServerHandler());
                        }
                    });

            // 开始监听并接受连接
            ChannelFuture f = b.bind(port).sync();

            // 等待服务器套接字关闭
            f.channel().closeFuture().sync();
        } finally {
            // 关闭所有EventLoopGroup以终止所有线程
            workerGroup.shutdownGracefully();
            bossGroup.shutdownGracefully();
        }
    }

}

2. Netty Server Handler

package com.lcx.server;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @author : lichangxin
 * @create : 2024/5/15 16:10
 * @description
 */
public class NettyServerHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端发送的消息是:" + msg);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.err.println("有新的客户端与服务器发生连接,客户端地址:" + channel.remoteAddress());
    }

    /**
     * 当有客户端与服务器断开连接时执行此方法,此时会自动将此客户端从 channelGroup 中移除
     * 1.打印提示信息
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.err.println("有客户端与服务器断开连接,客户端地址:" + channel.remoteAddress());
        channel.close();
        ctx.close();
    }

    /**
     * 表示channel 处于活动状态
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + " 处于活动状态");
        ctx.channel().writeAndFlush("test");
    }

    /**
     * 表示channel 处于不活动状态
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + " 处于不活动状态");
    }

}

3. Netty Client

package com.lcx.server;

import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.concurrent.GlobalEventExecutor;
import lombok.extern.slf4j.Slf4j;

/**
 * @author : lichangxin
 * @create : 2024/5/15 16:11
 * @description
 */
@Slf4j
public class NettyClient {

    public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    public static String host = "127.0.0.1";

    private static int port = 9999;

    public static void main(String[] args) {
        NioEventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap bootstrap = new Bootstrap()
                    .group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder());
                            pipeline.addLast(new StringEncoder());
                            pipeline.addLast(new NettyClientHandler());
                        }
                    });
            ChannelFuture future = bootstrap.connect(host, port).sync();


            log.info("TCPClient Start , Connect host:"+host+":"+port);
//                future.channel().closeFuture().sync();
            future.channel().writeAndFlush(createMessage());

        } catch (Exception e) {
            try {
                log.error("TCPClient Error", e);
                log.error("TCPClient Retry Connect host:\"+host+\":\"+port");
            } catch (Exception ex) {
                throw new RuntimeException(ex);
            }
        }
    }
    private static ByteBuf createMessage() {
        // 创建一个ByteBuf,并写入数据
        ByteBuf buffer = Unpooled.buffer();
        buffer.writeInt(12345); // 写入一个整数
        buffer.writeBytes("Hello, Server!".getBytes()); // 写入一个字符串
        return buffer;
    }
}

4. Netty Client Handler

package com.lcx.server;

import io.netty.channel.*;

/**
 * @author : lichangxin
 * @create : 2024/5/15 16:11
 * @description
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<Object> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("客户端收到消息:" + msg);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.err.println("有新的客户端与服务器发生连接,客户端地址:" + channel.remoteAddress());
        NettyClient.channelGroup.add(channel);
    }

    /**
     * 当有客户端与服务器断开连接时执行此方法,此时会自动将此客户端从 channelGroup 中移除
     * 1.打印提示信息
     */
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        System.err.println("有客户端与服务器断开连接,客户端地址:" + channel.remoteAddress());
    }

    /**
     * 表示channel 处于活动状态
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("Are you good?", CharsetUtil.UTF_8));
        System.out.println(ctx.channel().remoteAddress() + " 处于活动状态");
        NettyServer.channelSet.add(ctx.channel());
        System.out.println("添加 channel 完成");
    }

    /**
     * 表示channel 处于不活动状态
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(ctx.channel().remoteAddress() + " 处于不活动状态");
    }
}

API

1. Netty中ctx.writeAndFlush与ctx.channel().writeAndFlush的区别

在Netty中,ctx.writeAndFlush()ctx.channel().writeAndFlush() 都用于向通道(Channel)写入数据并立即刷新(flush)数据,但它们之间有一个关键的上下文(Context)差异。

  1. ctx.writeAndFlush():

ctx.writeAndFlush(): 这个方法是在ChannelHandler中调用的,ctx表示ChannelHandlerContext,它包含了ChannelHandler和ChannelPipeline的信息。当你在ChannelHandler中调用ctx.writeAndFlush()时,消息会被写入到当前ChannelHandlerContext所关联的Channel,并且会沿着ChannelPipeline进行处理。

  1. ctx.channel().writeAndFlush():

这个方法直接通过Channel对象调用,而不是通过ChannelHandlerContext。它会将消息写入到Channel所关联的最后一个ChannelHandlerContext,并且会从最后一个ChannelHandlerContext开始沿着ChannelPipeline处理消息。这意味着,如果你在一个ChannelHandler之外的地方调用ctx.channel().writeAndFlush(),消息会从ChannelPipeline的最后一个ChannelHandler开始处理,而不是从当前ChannelHandler开始。

2. ChannelInboundHandler 生命周期

package com.lcx.nettyHandler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;

/**
 * @author : lichangxin
 * @create : 2024/5/20 13:25
 * @description
 */
public class TestChannelInboundHandler extends SimpleChannelInboundHandler {
    @Override
    public void channelRegistered(ChannelHandlerContext channelHandlerContext) throws Exception { // 2
        System.out.println("channelRegistered");
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("channelUnregistered");
    }

    @Override
    public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception { // 3
        System.out.println("channelActive");
    }

    @Override
    public void channelInactive(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("channelInactive");
    }

    @Override
    public void channelRead(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("channelRead");
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("channelRead0");
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("channelReadComplete");
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("userEventTriggered");
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("channelWritabilityChanged");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception { // 1
        System.out.println("handlerAdded");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("handlerRemoved");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
        System.out.println("exceptionCaught");
    }
}

  1. handlerAdded
  2. channelRegistered
  3. channelActive
  4. channelRead
  5. channelReadComplete
  6. channelInactive
  7. channelUnregistered
  8. handlerRemoved

3. ChannelOutBoundHandler 生命周期

package com.lcx.nettyandler;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandler;
import io.netty.channel.ChannelPromise;

import java.net.SocketAddress;

/**
 * @author : lichangxin
 * @create : 2024/5/20 13:26
 * @description
 */
public class TestChannelOutboundHandler implements ChannelOutboundHandler {
    @Override
    public void bind(ChannelHandlerContext channelHandlerContext, SocketAddress socketAddress, ChannelPromise channelPromise) throws Exception {
        System.out.println("bind");
    }

    @Override
    public void connect(ChannelHandlerContext channelHandlerContext, SocketAddress socketAddress, SocketAddress socketAddress1, ChannelPromise channelPromise) throws Exception {
        System.out.println("connect");
    }

    @Override
    public void disconnect(ChannelHandlerContext channelHandlerContext, ChannelPromise channelPromise) throws Exception {
        System.out.println("disconnect");
    }

    @Override
    public void close(ChannelHandlerContext channelHandlerContext, ChannelPromise channelPromise) throws Exception {
        System.out.println("close");
    }

    @Override
    public void deregister(ChannelHandlerContext channelHandlerContext, ChannelPromise channelPromise) throws Exception {
        System.out.println("deregister");
    }

    @Override
    public void read(ChannelHandlerContext channelHandlerContext) throws Exception { // 2
        System.out.println("read");
    }

    @Override
    public void write(ChannelHandlerContext channelHandlerContext, Object o, ChannelPromise channelPromise) throws Exception {
        System.out.println("write");
    }

    @Override
    public void flush(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("flush");
    }

    @Override
    public void handlerAdded(ChannelHandlerContext channelHandlerContext) throws Exception {  // 1
        System.out.println("handlerAdded");
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext channelHandlerContext) throws Exception {
        System.out.println("handlerRemoved");
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) throws Exception {
        System.out.println("exceptionCaught");
    }
}

  1. handlerAdded
  2. read
  3. write
  4. flush

Channel 常用配置

Netty Channel常用的一些配置参数包括但不限于以下几种:

  1. CONNECT_TIMEOUT_MILLIS:这是Netty的连接超时参数,表示连接建立的超时时间,单位为毫秒。默认值为30000毫秒,即30秒。
  2. MAX_MESSAGES_PER_READ:这个参数表示在一次Loop读取操作中能够读取的最大消息数。对于ServerChannel或者NioByteChannel,默认值为16;对于其他类型的Channel,默认值为1。
  3. WRITE_SPIN_COUNT:这是Netty中用于控制一个Loop写操作执行的最大次数的参数。默认值为16。
  4. ALLOCATOR:这是ByteBuf的分配器参数。在Netty 4.0版本中,默认值为UnpooledByteBufAllocator;在Netty 4.1版本中,默认值为PooledByteBufAllocator。
  5. RCVBUF_ALLOCATOR:这个参数用于配置接收缓冲区的分配器。可选值包括FixedRecvByteBufAllocator(固定大小的接收缓冲区分配器)等。默认值为AdaptiveRecvByteBufAllocator.DEFAULT,这是一个可以根据实际接收到的数据大小动态调整接收缓冲区大小的分配器。
  6. WRITE_BUFFER_WATER_MARK:这是写缓冲区的水位线参数。当写缓冲区中的数据量达到高水位线时,Netty会触发ChannelWritabilityChanged事件;当写缓冲区中的数据量低于低水位线时,Netty也会触发ChannelWritabilityChanged事件。

除了上述参数外,Netty Channel还有其他一些配置参数,这些参数可以根据具体的应用场景和需求进行配置。在Netty中,可以通过ChannelConfig接口来访问和修改这些参数。例如,可以通过Channel.config()方法来获取当前Channel的配置对象,然后调用配置对象的方法来修改相应的参数。

请注意,以上参数的具体含义和用法可能会随着Netty版本的更新而有所变化,因此建议查阅最新的Netty文档以获取最准确的信息。

Netty 常用编码器和解码器

Netty 提供了许多常用的 decoder 和 encoder,这些编解码器可以帮助开发者在网络通信中方便地处理数据的编码和解码。以下是一些 Netty 中常用的 decoder 和 encoder:

Decoder(解码器)

  1. ByteToMessageDecoder:这是一个基础的解码器,用于将字节数据(ByteBuf)解码为消息对象。开发者可以继承这个类并重写 decode() 方法来实现自定义的解码逻辑。
  2. StringDecoder:将接收到的字节数据解码为字符串。它基于指定的字符集(默认为 UTF-8)进行解码。
  3. LineBasedFrameDecoder:这是一个基于行的解码器,它按行切分数据帧。当从网络读取数据时,它会根据换行符(如 “\n” 或 “\r\n”)来切分数据,然后将每一行数据作为一个独立的消息对象进行解码。
  4. DelimiterBasedFrameDecoder:这是一个基于特定分隔符的解码器。开发者可以指定一个或多个分隔符,当从网络读取数据时,它会根据这些分隔符来切分数据帧。
  5. RedisDecoder:基于 Redis 协议的解码器,用于解析 Redis 服务器发送的命令和数据。
  6. HttpObjectDecoder:基于 HTTP 协议的解码器,用于解析 HTTP 请求和响应消息。
  7. JsonObjectDecoder:基于 JSON 数据格式的解码器,用于将 JSON 格式的字节数据解码为 Java 对象。

Encoder(编码器)

  1. MessageToByteEncoder:这是一个基础的编码器,用于将消息对象编码为字节数据(ByteBuf)。开发者可以继承这个类并重写 encode() 方法来实现自定义的编码逻辑。
  2. StringEncoder:将字符串编码为字节数据。它基于指定的字符集(默认为 UTF-8)进行编码。
  3. ObjectEncoder:用于将 Java 对象序列化为字节数据。这通常与 ObjectDecoder 配合使用,以实现 Java 对象的网络传输。
  4. RedisEncoder:基于 Redis 协议的编码器,用于将 Redis 命令和数据编码为字节数据,以便发送给 Redis 服务器。
  5. HttpObjectEncoder:基于 HTTP 协议的编码器,用于将 HTTP 请求和响应消息编码为字节数据,以便发送给 HTTP 服务器或客户端。

这些编解码器可以根据需要组合使用,以实现复杂的网络通信协议。例如,在 HTTP 协议的客户端实现中,可能会使用 HttpObjectDecoderHttpObjectEncoder 来处理 HTTP 请求和响应的编码和解码;而在 Redis 协议的客户端实现中,可能会使用 RedisDecoderRedisEncoder 来处理 Redis 命令和数据的编码和解码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_CX_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值