Netty-搭建一个简单的消息接收发送服务

Netty介绍

Netty是由JBOSS提供的一个java开源框架。Netty提供异步的、事件驱动的网络应用程序框架和工具,用以快速开发高性能、高可靠性的网络服务器和客户端程序。

也就是说,Netty 是一个基于NIO的客户、服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户,服务端应用。Netty相当简化和流线化了网络应用的编程开发过程,例如,TCP和UDP的socket服务开发。

官网地址:http://netty.io/

使用场景

Netty之所以能成为主流的NIO框架,是因为它有下面的优点:

  • NIO的类库和API使用难度较高,Netty进行了封装,容易上手
  • 高性能,功能强大,支持多种编解码功能,支持多种主流协议
  • 成熟,稳定,已经在多个大型框架中使用(dubbo,RocketMQ,Hadoop,mycat,Spring5)
  • …..

快速入门-Talk is cheap. Show me the code

添加依赖

   <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.21.Final</version>
        </dependency>
        <dependency>
            <groupId>cn.hutool</groupId>
            <artifactId>hutool-all</artifactId>
            <version>5.7.22</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>
    </dependencies>

服务端代码

public class EchoService {
    private final static Logger LOGGER = LoggerFactory.getLogger(EchoService.class);
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 54123;

    public static void main(String[] args) {
        ServerBootstrap b = new ServerBootstrap();
        //创建reactor 线程组
        EventLoopGroup bossLoopGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

        try {
            //1 设置reactor 线程组
            b.group(bossLoopGroup, workerLoopGroup);
            //2 设置nio类型的channel
            b.channel(NioServerSocketChannel.class);
            //3 设置监听端口
            b.localAddress(SERVER_PORT);
            //4 设置通道的参数
            /**
             *PooledByteBufAllocator 是 Netty 中默认的字节缓冲分配器实现。它使用池化的方式来管理字节缓冲,重用之前分配过的缓冲,从而减少内存分配和回收的开销。通过使用池化的方式,它能够在高并发情况下更高效地管理缓冲,适用于大量的、频繁的缓冲分配和回收操作。
             *PooledByteBufAllocator 适合处理长期运行的网络应用程序,特别是在高并发的情况下,它可以显著提高性能和内存利用率。
             */
            //b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            /**
             * UnpooledByteBufAllocator 是 Netty 中另一种字节缓冲分配器实现。它不使用池化机制,每次请求都会创建一个新的字节缓冲对象,每个缓冲对象在使用完后都会被单独回收。因此,它的开销较大,不具备池化的优势。
             * UnpooledByteBufAllocator 适合处理短期、简单的网络应用程序,特别是在并发较低的情况下,或者在需要临时创建大量缓冲时。
             */
            b.option(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT);
            b.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            b.childOption(ChannelOption.SO_KEEPALIVE, true);

            //5 装配子通道流水线
            b.childHandler(new ChannelInitializer<SocketChannel>() {
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(EchoServiceHandler.INSTANCE);
                }
            });

            // 6 开始绑定server
            // 通过调用sync同步方法阻塞直到绑定成功
            ChannelFuture channelFuture = b.bind();
            channelFuture.addListener((future)->{
                if(future.isSuccess())
                {
                    LOGGER.info(" ========》反应器线程 回调 服务器启动成功,监听端口: " +
                            channelFuture.channel().localAddress());

                }
            });
//            channelFuture.sync();
            LOGGER.info(" 调用线程执行的,服务器启动成功,监听端口: " +
                    channelFuture.channel().localAddress());

            // 7 等待通道关闭的异步任务结束
            // 服务监听通道会一直等待通道关闭的异步任务结束
            ChannelFuture closeFuture = channelFuture.channel().closeFuture();
            closeFuture.sync();
        } catch (Exception ex) {
            ExceptionUtil.stacktraceToString(ex);
        } finally {
            // 8 优雅关闭EventLoopGroup,
            // 释放掉所有资源包括创建的线程
            workerLoopGroup.shutdownGracefully();
            bossLoopGroup.shutdownGracefully();
        }
    }
}
@ChannelHandler.Sharable
public class EchoServiceHandler extends ChannelInboundHandlerAdapter {
    private final static Logger LOGGER = LoggerFactory.getLogger(EchoServiceHandler.class);
    public static final EchoServiceHandler INSTANCE = new EchoServiceHandler();

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        LOGGER.info("msg type: " + (in.hasArray()?"堆内存":"直接内存"));
        //因为Netty4.1+默认是使用直接内存的buffer来存储Channel读到的数据, Java要进行处理这些数据, 先要拷贝到自己的堆中
        //所以这里先建立一个对应长度的堆内数组
        int len = in.readableBytes();
        byte[] arr = new byte[len];
        in.getBytes(0, arr);
        LOGGER.info("server received: " + new String(arr, "UTF-8"));

        //写回数据,异步任务
        LOGGER.info("写回前,msg.refCnt:" + (in.refCnt()));//测试一下当前缓冲区的引用数量

        ChannelFuture f = ctx.writeAndFlush(msg);
//        ChannelFuture f = ctx.pipeline().writeAndFlush(msg);
//        ChannelFuture f = ctx.channel().pipeline().writeAndFlush(msg);


        f.addListener((ChannelFuture futureListener) -> {
            LOGGER.info("写回后,msg.refCnt:" + in.refCnt());
        });

        //传递到下一个Handler - 当前案例只有一个用户自定义的Handler, 其实不写也无所谓.
        //super.channelRead(ctx, msg);
    }
}

上面的示例中,我们创建了一个 ServerBootstrap 对象,然后配置了 bossGroupworkerGroup,设置了服务端的 Channel 类型为 NioServerSocketChannel,并添加了一个 ChannelInitializer 来配置处理器链。

ServerBootstrapgroup() 方法用于设置两个线程组,一个负责接收客户端连接(bossGroup),另一个负责处理客户端的读写请求(workerGroup)。

ServerBootstrapchannel() 方法用于指定服务端 Channel 的类型,NioServerSocketChannel.class 表示使用 NIO 的 ServerSocketChannel。

ServerBootstrapchildHandler() 方法用于设置处理器链。在上面的示例中,我们添加了一个自定义的 EchoServiceHandler 处理器。

最后,我们通过调用 bind() 方法绑定端口并启动服务器。在 ChannelFuture 中我们可以添加监听器来处理绑定结果和服务器关闭的操作。

 客户端代码

public class EchoClient {
    private final static Logger LOGGER = LoggerFactory.getLogger(EchoClient.class);
    public static final String SERVER_IP = "127.0.0.1";
    public static final int SERVER_PORT = 54123;

    public static void main(String[] args) {
        //Step1: 创建组装器 - 用于配置客户端的 - 事件轮询器 - 通道 - 处理器
        Bootstrap b = new Bootstrap();
        //Step2: 创建轮询器 - 封装了Selector, 用于选择数据传输事件
        EventLoopGroup workerLoopGroup = new NioEventLoopGroup();

        try {
            b.group(workerLoopGroup);
            //Step3.1:设置通道类型
            b.channel(NioSocketChannel.class);
            //Step3.2:设置监听端口
            b.remoteAddress(SERVER_IP, SERVER_PORT);
            //Step3.3:设置通道的参数
            b.option(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT);
            //默认是30s, 如果在给定的时间不能成功建立连接或者被丢弃掉,将抛出ConnectTimeoutException
            b.option(ChannelOption.CONNECT_TIMEOUT_MILLIS,10000);
            b.option(ChannelOption.SO_KEEPALIVE, true);;
            //Step4: 配置事件处理器
            b.handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel socketChannel) throws Exception {
                    socketChannel.pipeline().addLast(EchoClientHandler.INSTANCE);
                }
            });
            //Step5: 循环链接服务端
            ChannelFuture f = null;
            boolean connected = false;
            while (!connected) {
                f = b.connect();
                f.addListener((ChannelFuture futureListener) -> {
                    if (futureListener.isSuccess()) {
                        LOGGER.info("EchoClient客户端连接成功!");
                    } else {
                        LOGGER.info("EchoClient客户端连接失败!");
                    }
                });
                // sync作用: 因为上面的连接到服务器上以及监听都是异步操作, 执行后马上返回, 可能连接还未完全建立, 所以sync在此等待一下
                // f.sync(); 发生错误会抛异常
                f.awaitUninterruptibly();//发生错误不会抛异常
                if (f.isCancelled()) {
                    LOGGER.info("用户取消连接:");
                    return;
                    // Connection attempt cancelled by user
                } else if (f.isSuccess()) {
                    connected = true;
                }
            }

            //StepX - 业务操作: 在连接完成之后, 获取到通道, 往通道里面写一些数据
            //获取通道
            Channel channel = f.channel();
            Scanner scanner = new Scanner(System.in);
            LOGGER.info("请输入发送内容:");
            // 发送回调监听
            GenericFutureListener sendCallBack = future -> {
                if (future.isSuccess()) {
                    LOGGER.info("发送成功!");
                } else {
                    LOGGER.info("发送失败!");
                }
            };

            while (scanner.hasNext()) {
                //获取输入的内容
                String next = scanner.next();
                byte[] bytes = (DateUtil.now() + " >>" + next).getBytes(StandardCharsets.UTF_8);
                // 创建一个缓冲区, 用于存储待发送的信息
                ByteBuf buffer = channel.alloc().buffer();
                // 保存数据到直接内存的缓冲区
                buffer.writeBytes(bytes);
                // 通过通道将数据发送出去
                ChannelFuture writeAndFlushFuture = channel.writeAndFlush(buffer);
                writeAndFlushFuture.addListener(sendCallBack);
                LOGGER.info("请输入发送内容:");

            }
        } catch (Exception ex) {
            ExceptionUtil.stacktraceToString(ex);
        } finally {
            workerLoopGroup.shutdownGracefully();
        }
    }
}
@ChannelHandler.Sharable
public class EchoClientHandler extends ChannelInboundHandlerAdapter {
    private final static Logger LOGGER = LoggerFactory.getLogger(EchoClientHandler.class);
    public static final EchoClientHandler INSTANCE = new EchoClientHandler();
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf) msg;
        int len = in.readableBytes();
        byte[] arr = new byte[len];
        in.getBytes(0, arr);
        LOGGER.info("client received: " + new String(arr, StandardCharsets.UTF_8));
        //读取完成之后,要主动释放掉该buffer便于回收
        in.release();

    }
}

上面的示例中,我们创建了一个 Bootstrap 对象,然后配置了 workerGroup,设置了客户端的 Channel 类型为 NioSocketChannel,并添加了一个 ChannelInitializer 来配置处理器链。

Bootstrapgroup() 方法用于设置一个线程组,用于处理客户端的读写请求(workerGroup)。

Bootstrapchannel() 方法用于指定客户端 Channel 的类型,NioSocketChannel.class 表示使用 NIO 的 SocketChannel。

Bootstraphandler() 方法用于设置处理器链。在上面的示例中,我们添加了一个自定义的 EchiClientHandler 处理器。

最后,我们通过调用 connect() 方法连接到服务器,并通过 ChannelFuture 来处理连接的结果。在这个示例中,我们简单地使用 sync() 方法来等待连接完成。

最后通过 connect() 方法来连接到服务器。在连接建立后,我们可以通过 ChannelFuture 来添加监听器来处理连接的结果和客户端关闭的操作。

总之,Bootstrap 类提供了配置客户端的方法和启动客户端的方法,可以方便地创建和管理客户端的网络服务。

效果演示 

服务端启动效果

 客户端启动效果

发送消息效果 

 

 项目地址:https://github.com/YIminta/Netty-learning

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值