Netty 是什么?
Netty是 一个异步事件驱动的网络应用程序框架,用于快速开发可维护的高性能协议服务器和客户端。Netty是一个NIO客户端服务器框架,可以快速轻松地开发网络应用程序,例如协议服务器和客户端。它极大地简化和简化了诸如TCP和UDP套接字服务器之类的网络编程。“快速简便”并不意味着最终的应用程序将遭受可维护性或性能问题的困扰。Netty经过精心设计,结合了许多协议(例如FTP,SMTP,HTTP以及各种基于二进制和文本的旧式协议)的实施经验。结果,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());
}
}
最终效果
由于服务代码过长,这里就不全部黏贴出来了,已经将以上代码放在Github上,并将做成了Springboot 的Starter 可以直接进行使用.