同步非阻塞NIO框架Netty示例,以及性能测试数据

最近有一个高并发的项目,经过反复思考,决定采用Netty框架,最后也给出了普通的springboot接口和Netty框架性能的对比。

springboot框架集成Netty框架比较简单,直接上代码
加入依赖

        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.25.Final</version>
        </dependency>

追加java代码

package com.jndj.platform.netty;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.InetSocketAddress;

/**
 * 实现客户端发送请求,服务器端会返回Hello Netty
 */
public class NettyServer {
    private static final Logger logger = LoggerFactory.getLogger(NettyServer.class);

    public void start(InetSocketAddress socketAddress) {

        // 定义一对线程组(两个线程池)
        // 主线程组,用于接收客户端的链接,但不做任何处理
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        // 定义从线程组,主线程组会把任务转给从线程组进行处理
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            // 服务启动类,任务分配自动处理
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            // 需要去针对一个之前的线程模型(上面定义的是主从线程)
            serverBootstrap.group(bossGroup, workerGroup)
                    // 设置NIO的双向通道
                    .channel(NioServerSocketChannel.class)
                    //子处理器,用于处理workerGroup
                    // 设置chanel初始化器
                    // 每一个chanel由多个handler共同组成管道(pipeline)
                    .childHandler(new NettyServerInitializer())
                    //.localAddress(socketAddress)
                    // 设置队列大小
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    // 两小时内没有数据的通信时,TCP会自动发送一个活动探测数据报文
                    .childOption(ChannelOption.SO_KEEPALIVE, true);

            // 启动
            // 绑定端口,并设置为同步方式,是一个异步的chanel
            ChannelFuture future = serverBootstrap.bind(8888).sync();
            logger.info("服务器启动开始监听端口: {}", socketAddress.getPort());

            // 关闭
            // 获取某个客户端所对应的chanel,关闭并设置同步方式
            future.channel().closeFuture().sync();
        }catch (Exception e) {
            e.printStackTrace();
        }finally {
            //使用一种优雅的方式进行关闭
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

package com.jndj.platform.netty;

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;

/**
 * 这是一个初始化器,chanel注册后会执行里面相应的初始化方法(也就是将handler逐个进行添加)
 */
public class NettyServerInitializer extends ChannelInitializer<SocketChannel>{
    // 对chanel进行初始化
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        // 通过socketChannel去获得对应的管道
        ChannelPipeline channelPipeline = socketChannel.pipeline();
        // pipeline中会有很多handler类(也称之拦截器类)
        // 获得pipeline之后,可以直接.add,添加不管是自己开发的handler还是netty提供的handler
        // 一般来讲添加到last即可,添加了一个handler并且取名为HttpServerCodec
        // 当请求到达服务端,要做解码,响应到客户端做编码
        channelPipeline.addLast("HttpServerCodec", new HttpServerCodec());
//        channelPipeline.addLast("decoder", new StringDecoder(CharsetUtil.UTF_8));
//        channelPipeline.addLast("encoder", new StringEncoder(CharsetUtil.UTF_8));
        // 添加自定义的CustomHandler这个handler,返回Hello Netty
        channelPipeline.addLast("NettyServerHandler", new NettyServerHandler());
    }
}

package com.jndj.platform.netty;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderNames;
import io.netty.handler.codec.http.HttpObject;
import io.netty.handler.codec.http.HttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.util.CharsetUtil;

/**
 * 创建自定义助手类
 * 处理的请求是:客户端向服务端发起送数据,先把数据放在缓冲区,服务器端再从缓冲区读取,类似于[ 入栈, 入境 ]
 */
public class NettyServerHandler extends SimpleChannelInboundHandler<HttpObject>{//Http请求,所以使用HttpObject

    @Override
    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("channel注册");
        super.channelRegistered(ctx);
    }

    @Override
    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("channel注册");
        super.channelUnregistered(ctx);
    }

    /**
     * 客户端连接会触发
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("channel活跃状态");
        super.channelActive(ctx);
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("客户端与服务端断开连接之后");
        super.channelInactive(ctx);
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channel读取数据完毕");
        super.channelReadComplete(ctx);
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
//        System.out.println("用户事件触发");
        super.userEventTriggered(ctx, evt);
    }

    @Override
    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("channel可写事件更改");
        super.channelWritabilityChanged(ctx);
    }

    /**
     * 发生异常触发
     * channel发生异常,若不关闭,随着异常channel的逐渐增多,性能也就随之下降
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//        System.out.println("捕获channel异常");
        super.exceptionCaught(ctx, cause);
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("助手类添加");
        super.handlerAdded(ctx);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
//        System.out.println("助手类移除");
        super.handlerRemoved(ctx);
    }

    /**
     * ChannelHandlerContext:上下文对象
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
        //获取当前channel
        Channel currentChannel = ctx.channel();

        //判断msg是否为一个HttpRequest的请求类型
        if(msg instanceof HttpRequest) {

            // 客户端远程地址
//            System.out.println(currentChannel.remoteAddress());
            /**
             *
             * 未加判断类型,控制台打印的远端地址如下:
             *
             /0:0:0:0:0:0:0:1:5501
             /0:0:0:0:0:0:0:1:5501
             /0:0:0:0:0:0:0:1:5502
             /0:0:0:0:0:0:0:1:5502
             *
             * 原因是接收的MSG没有进行类型判断
             *
             * 增加了判断,为何还会打印两次?
             *
             /0:0:0:0:0:0:0:1:5605
             /0:0:0:0:0:0:0:1:5605
             *
             * 打开浏览器的network会发现,客户端对服务端进行了两次请求:
             * 	1、第一次是所需的
             *  2、第二次是一个icon
             *  因为没有加路由(相当于Springmvc中的requestMapping),只要发起请求,就都会到handler中去
             *
             */

            //在Linux中也可以通过CURL 本机Ip:端口号 发送请求(只打印一次,干净的请求)

            // 定义发送的消息(不是直接发送,而是要把数据拷贝到缓冲区,通过缓冲区)
            // Unpooed:是一个专门用于拷贝Buffer的深拷贝,可以有一个或多个
            // CharsetUtil.UTF_8:Netty提供
            ByteBuf content = Unpooled.copiedBuffer("Hello Netty", CharsetUtil.UTF_8);

            // 构建一个HttpResponse,响应客户端
            // HttpVersion.HTTP_1_1:默认开启keep-alive
            FullHttpResponse response =
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
            // 设置当前内容长度、类型等
            response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
            // readableBytes:可读长度
            response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());

            // 通过长下文对象,把响应刷到客户端
            ctx.writeAndFlush(response);
        }
    }
}

package com.jndj.platform;

import com.jndj.platform.netty.NettyServer;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

import java.net.InetSocketAddress;

@SpringBootApplication
@EnableScheduling
public class PlatformApplication {

    public static void main(String[] args) {
        SpringApplication.run(PlatformApplication.class, args);
        //启动服务端
        NettyServer nettyServer = new NettyServer();
        nettyServer.start(new InetSocketAddress("127.0.0.1", 8888));
    }
}

运行后结果
在这里插入图片描述
使用postman测试一下高并发下的性能,并且也写了一个简单的springboot的api,进行了对比测试
10000次请求,间隔1ms的并发

Netty的性能结果,大部分响应时间在2ms,3ms,速度还是挺快的。
在这里插入图片描述
普通的springboot接口,接口响应速度大体在5ms到10ms之间
在这里插入图片描述

最后结论,明显看出使用了NIO的Netty框架速度更快

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值