Netty 是什么?

Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。
Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了诸如TCP和UDP套接字服务器之类的网络编程。
“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP以及各种基于二进制和文本的旧式协议)的实施经验。结果,Netty成功地找到了一种无需妥协即可轻松实现开发,性能,稳定性和灵活性的方法。

springboot netty容器 springboot netty主要应用场景_springboot netty容器

概念性的在这里就不做过多的说明了,本文将围绕实战进行

Spring boot + Netty

模块介绍

  • javayh-health-monitor-server
  • 心跳检测的服务中心
  • javayh-health-monitor-client
  • 心跳检测的客户端,需要作为jar 进行引入
  • javayh-health-monitor-demo
  • 示例的demo

服务端创建

  • 创建启动类
public class HeartBeatServer {

	/**
	 * NioEventLoopGroup是一个处理I / O操作的多线程事件循环。 Netty为不同类型的传输提供各种EventLoopGroup实现。
	 * 我们在此示例中实现了服务器端应用程序,因此将使用两个NioEventLoopGroup。 第一个,通常称为“老板”,接受传入连接。第二个,通常称为“工人”,
	 * 一旦老板接受连接并将接受的连接注册到工作人员,就处理被接受连接的流量。
	 * 使用了多少个线程以及它们如何映射到创建的Channels取决于EventLoopGroup实现,甚至可以通过构造函数进行配置。
	 */
	private EventLoopGroup boss = new NioEventLoopGroup();

	private EventLoopGroup work = new NioEventLoopGroup();



	/**
	 * 启动 Netty
	 * @return
	 * @throws InterruptedException
	 * @PostConstruct
	 */
	public void start(int port) throws InterruptedException {
		ServerBootstrap bootstrap = new ServerBootstrap().group(boss, work)
				.channel(NioServerSocketChannel.class)
				.localAddress(new InetSocketAddress(port))
				// 保持长连接
				.childOption(ChannelOption.SO_KEEPALIVE, true)
				.childHandler(new HeartbeatInitializer());
		// 绑定并开始接受传入的连接。
		ChannelFuture future = bootstrap.bind(port).sync();
		if (future.isSuccess()) {
			log.info("启动 Java有货 Health Monitor Server 成功");
		}
		future.channel().closeFuture().sync();
	}

	/**
	 * 销毁
	 * @PreDestroy
	 */
	public void destroy() {
		boss.shutdownGracefully().syncUninterruptibly();
		work.shutdownGracefully().syncUninterruptibly();
		log.info("关闭 Java有货 Health Monitor Server 成功");
	}

}
  • 创建Handler
public class HeartBeatSimpleHandler extends SimpleChannelInboundHandler<MessageBody> {

    private static final ByteBuf HEART_BEAT = Unpooled.unreleasableBuffer(
            Unpooled.copiedBuffer(
                    MessageBody.builder()
                            .msgId(20200202L)
                            .msg("pong")
                            .appName("Health-Monitor-Server")
                            .createDate(new Date()).build().toString(),
                    CharsetUtil.UTF_8));

    private static final String YMS = "yyyy-MM-dd HH:mm:ss";

    /**
     * <p>
     * 链接验证
     * </p>
     *
     * @param context
     * @param messageBody
     * @return void
     * @version 1.0.0
     * @author Dylan-haiji
     * @since 2021/4/13
     */
    @Override
    protected void channelRead0(ChannelHandlerContext context, MessageBody messageBody)
            throws Exception {
        StringBuilder sb = new StringBuilder();
        sb.append("服务端消息接收成功==>").append(messageBody);
        log.info(sb.toString());
        // 保存客户端与 Channel 之间的关系
        NettySocketHolder.put(messageBody.getAppName(),(NioSocketChannel) context.channel());
    }

    /**
     * <p>
     * 取消绑定
     * </p>
     *
     * @param ctx
     * @return void
     * @version 1.0.0
     * @author Dylan-haiji
     * @since 2021/4/13
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        NettySocketHolder.remove((NioSocketChannel) ctx.channel());
    }

    /**
     * <p>
     * 尝试建立链接
     * </p>
     *
     * @param ctx
     * @param evt
     * @return void
     * @version 1.0.0
     * @author Dylan-haiji
     * @since 2021/4/13
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
            throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                log.info("已经5秒没有收到信息!");
                // 向客户端发送消息
                ctx.writeAndFlush(HEART_BEAT)
                        .addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
            }
        }
        super.userEventTriggered(ctx, evt);
    }

    /**
     * <p>
     *      有服务上线通知
     * </p>
     * @version 1.0.0
     * @since 2021/4/14
     * @param ctx
     * @return void
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        log.info("服务上线时间为: {}, IP 为:{}", DateFormatUtils.format(new Date(),YMS), ctx.channel().remoteAddress());
    }

    /**
     * <p>
     * 异常的处理机制
     * </p>
     *
     * @param ctx
     * @param cause
     * @return void
     * @version 1.0.0
     * @since 2021/4/14
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        throw new HealthMonitorException(cause.getMessage(), cause);
    }
}

客户端创建

  • 启动类
public class HeartbeatClient {

	private final static Logger LOGGER = LoggerFactory.getLogger(HeartbeatClient.class);

	private EventLoopGroup group = new NioEventLoopGroup();

	private SocketChannel socketChannel;

	/**
	 * 启动类
	 * @throws InterruptedException
	 *
	 * @PostConstruct
	 */
	public void start(HeartbeatClientProperties properties) throws InterruptedException {
		Bootstrap bootstrap = new Bootstrap();
		/*
		 * NioSocketChannel用于创建客户端通道,而不是NioServerSocketChannel。
		 * 请注意,我们不像在ServerBootstrap中那样使用childOption(),因为客户端SocketChannel没有父服务器。
		 */
		bootstrap.group(group).channel(NioSocketChannel.class)
				.handler(new ClientHandleInitializer());
		/*
		 * 启动客户端 我们应该调用connect()方法而不是bind()方法。
		 */
		ChannelFuture future = bootstrap
				.connect(properties.getHost(), properties.getPort()).sync();
		if (future.isSuccess()) {
			LOGGER.info("启动 Java有货 Health Monitor Client 成功");
		}

		socketChannel = (SocketChannel) future.channel();
		future.channel().closeFuture().sync();
	}
	/**
	 * 销毁	@PreDestroy
	 */
	public void destroy() {
		group.shutdownGracefully().syncUninterruptibly();
		LOGGER.info("关闭 Java有货 Health Monitor Client 成功");
	}

}
  • 创建Handler
@Slf4j
public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
			throws Exception {
		if (evt instanceof IdleStateEvent) {
			IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
			if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
				log.info("已经10秒没收到消息了");
				// 向服务端发送消息
				MessageBody heartBeat = SpringUtils.getBean("heartBeat",
						MessageBody.class);
				ctx.writeAndFlush(heartBeat)
						.addListener(ChannelFutureListener.CLOSE_ON_FAILURE);
			}

		}
		super.userEventTriggered(ctx, evt);
	}

	/**
	 * 每当从服务端接收到新数据时,都会使用收到的消息调用此方法 channelRead0(),在此示例中,接收消息的类型是ByteBuf。
	 * @param channelHandlerContext
	 * @param byteBuf
	 * @throws Exception
	 */
	@Override
	protected void channelRead0(ChannelHandlerContext channelHandlerContext,
			ByteBuf byteBuf) throws Exception {
		// 从服务端收到消息时被调用
        StringBuilder sb = new StringBuilder();
        sb.append("客户端收到消息==>")
                .append(byteBuf.toString(CharsetUtil.UTF_8));
		log.info(sb.toString());
	}

}

最终效果

springboot netty容器 springboot netty主要应用场景_网络_02

springboot netty容器 springboot netty主要应用场景_客户端_03


由于服务代码过长,这里就不全部黏贴出来了,已经将以上代码放在Github上,并将做成了Springboot 的Starter 可以直接进行使用.