Netty 简单的入门使用案例

1. 组件

1.1 EventLoop

EventLoop 事件循环对象

EventLoop本质上是一个单线程执行器(同时维护了一个Selector),通过run方法来处理 Channel 上源源不断的 IO 事件。

它的继承关系是比较复杂的:

  • 继承了 j.u.c.ScheduledExecutorService 因此包含了线程池中所有的方法
  • 继承 Netty 自己的 io.netty.util.concurrent.OrderedEventExecutor
    • 提供了 boolean inEventLoop(Thread thread) 来判断一个线程是否属于当前 EventLoop
    • 提供了 EventExecutorGroup parent() 来获取自己归属的 EventLoopGroup

EventLoopGroup

EventLoopGroup (事件循环组) 绑定了一组的 EventLoop,Channel 通过调用 EventLoopGroup 的 register() 方法来绑定一个 EventLoop 来处理事件, 
绑定后的所有事件都将由这同一个 EventLoop 来处理。保证了 IO 事件处理时的线程安全问题。
  • 继承 io.netty.util.concurrent.EventExecutorGroup
    • 实现了 Iterable 可以使用 for 循环遍历 EventLoop
    • 可以使用 next() 方法获取集合中的下一个 EventLoop

1.1.1 简单地创建案例

// 内部创建了两个 EventLoop, 每个 EventLoop 维护一个线程
DefaultEventLoopGroup group = new DefaultEventLoopGroup(2);
System.out.println(group.next());
System.out.println(group.next());
System.out.println(group.next());

输出结果

io.netty.channel.nio.NioEventLoop@7085bdee
io.netty.channel.nio.NioEventLoop@1ce92674
io.netty.channel.nio.NioEventLoop@7085bdee

优雅的关闭EventLoopGroup
使用 shutdownGracefully() 可以实现优雅关闭, 当命令执行后 EventLoopGroup 会切换到关闭状态停止新任务的进入,
当现有任务都执行完毕后关闭,停止线程运行。从而保证应用整体是在正常有序的情况下退出的。

1.2 NioEventLoop 处理 IO 事件

Netty 创建服务端和客户端的基本步骤都是一样的,多写几遍熟练之后就不会觉得麻烦了

import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;

/**
 * @ClassName: EventLoopServer
 * TODO
 * @date: 8/28/2021 2:07 PM
 * @version: v1.0
 */
@Slf4j
public class EventLoopServer {

    public static void main(String[] args) {

        // 细分2: 非NIO Worker。如果某个 handler 执行时间比较长,可以抽离出 nio Worker,来防止nio性能受到损耗
        EventLoopGroup nonNioGroup = new DefaultEventLoopGroup(2);

        new ServerBootstrap()
            /* group 可以使用两个来更细致的区分职能
             * 细分1 : group(EventLoopGroup boss, EventLoopGroup worker)
             * boss 只负责 ServerSocketChannel 上的 accept 事件, worker 只负责 SocketChannel 的读写事件
             * * 一个EventLoop可以管理多个 channel,但是一个 channel 只能对应一个 EventLoop
             */
            .group(new NioEventLoopGroup(), new NioEventLoopGroup(2))
            .channel(NioServerSocketChannel.class)
            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast("handler1", new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            log.debug(buf.toString(StandardCharsets.UTF_8));
                            // 配置多个处理器需要手动触发下一个处理器
                            ctx.fireChannelRead(msg);
                        }
                    });
                    ch.pipeline().addLast(nonNioGroup, "handler2", new ChannelInboundHandlerAdapter() {
                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            ByteBuf buf = (ByteBuf) msg;
                            log.debug(buf.toString(StandardCharsets.UTF_8));
                        }
                    });
                }
            })
            .bind(8080);
    }

}
package org.example.netty.demo.eventloop;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;

import java.nio.charset.StandardCharsets;

/**
 * @ClassName: EventLoopClient
 * TODO
 * @date: 8/28/2021 2:24 PM
 * @version: v1.0
 */
@Slf4j
public class EventLoopClient {

    public static void main(String[] args) throws InterruptedException {

        Channel channel = new Bootstrap()
            .group(new NioEventLoopGroup())
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new StringEncoder(StandardCharsets.UTF_8));
                }
            })
            .connect("localhost", 8080)
            .sync()
            .channel();
        System.out.println(channel);
        System.out.println();
    }

}

1.2.1 Handler 是如何做到切换下一个执行的?

触发下一个Handler需要通过 ChannelHandlerContext 的 fireChannelRead(final Object msg) 方法触发

    @Override
    public ChannelHandlerContext fireChannelRead(final Object msg) {
        invokeChannelRead(findContextInbound(MASK_CHANNEL_READ), msg);
        return this;
    }
    
    static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) {
        final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next);
        // 获取下一个执行的 EventExecutor
        EventExecutor executor = next.executor();
        // 通过 inEventLoop() 方法判断当前线程和下一个执行线程是否是同一个
        if (executor.inEventLoop()) {
            // 如果是同一个,直接调用执行下一个
            next.invokeChannelRead(m);
        } else {
            // 不是,将执行函数交给下一个执行线程去处理。(换人执行)
            // EventLoop 继承了JUC线程池的方法,所以使用开一个新任务的方式执行
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    next.invokeChannelRead(m);
                }
            });
        }
    }

1.3 Channel

channel 的主要作用

  • close() 可以用来关闭 channel
  • closeFuture() 用来处理 channel 的关闭
    • sync 方法作用是同步等待 channel 关闭
    • 而 addListener 方法是异步等待 channel 关闭
  • pipeline() 方法添加处理器
  • write() 方法将数据写入
  • writeAndFlush() 方法将数据写入并刷出

Channel 连接和写出

public static void main(String[] args) throws InterruptedException {

    // 带有 future 或 promise 的类都是配合异步调用使用的
    NioEventLoopGroup singleGroup = new NioEventLoopGroup();
    ChannelFuture channelFuture = new Bootstrap()
        .group(singleGroup)
        .channel(NioSocketChannel.class)
        .handler(new ChannelInitializer<NioSocketChannel>() {
            @Override
            protected void initChannel(NioSocketChannel ch) throws Exception {
                ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                ch.pipeline().addLast(new StringEncoder());
            }
        })
        // connect() 是一个异步方法, 通过 main 线程去调用另一个线程来执行,所以不能马上得到正确结果
        .connect("localhost", 8080);
    /* 方案一:通过 sync() 方法让 main 线程阻塞,等待 connect 方法的线程执行完毕后再唤醒 */
    Channel channel = channelFuture.sync().channel();
    // write() 将数据写入缓冲区中, 所以目标在这步还收不到数据
    channel.write("msg");
    // flush() 将缓冲区中的数据刷出
    channel.flush();
    // channel.writeAndFlush() 写出并刷新
    
    /* 方案二: 通过 addListener() 方法,来异步处理结果 */
    channelFuture.addListener(new ChannelFutureListener() {
        @Override
        public void operationComplete(ChannelFuture future) throws Exception {
            Channel c = future.channel();
            c.writeAndFlush("hello");
        }
    });
}

Channel 关闭

    public static void main(String[] args) throws InterruptedException {
        // 带有 future 或 promise 的类都是配合异步调用使用的
        NioEventLoopGroup singleGroup = new NioEventLoopGroup();
        ChannelFuture channelFuture = new Bootstrap()
            .group(singleGroup)
            .channel(NioSocketChannel.class)
            .handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LoggingHandler(LogLevel.DEBUG));
                    ch.pipeline().addLast(new StringEncoder());
                }
            })
            // connect() 是一个异步方法, 通过 main 线程去调用另一个线程来执行,所以不能马上得到正确结果
            .connect("localhost", 8080);
        Channel channel = channelFuture.sync().channel();
        log.debug("{}", channel);
        new Thread(() -> {
            Scanner sc = new Scanner(System.in);
            while (true) {
                String str = sc.nextLine();
                if ("q".equals(str)) {
                    // close() 也是一个异步操作
                    channel.close();
                    break;
                }
                channel.writeAndFlush(str);
            }
        }).start();
        // 两种解决方案: 1). sync() 同步等待结果; 2). closeFuture() 获取future对象,addListener()
        ChannelFuture closeFuture = channel.closeFuture();
        closeFuture.addListener(new ChannelFutureListener() {
            @Override
            public void operationComplete(ChannelFuture future) throws Exception {
                log.debug("关闭Channel");
                /** channel 关闭了java线程还没有结束,是因为 NioEventLoopGroup 的线程还没有关闭 */
                // 优雅关闭 NioEventLoopGroup
                singleGroup.shutdownGracefully();
            }
        });
    }
posted @   thinkMIne  阅读(532)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
/*粒子线条,鼠标移动会以鼠标为中心吸附的特效*/
点击右上角即可分享
微信分享提示