打卡日期(2019-07-05)
netty 读写检测机制(心跳机制)
学习要点
- 1.什么是心跳机制?
- 2.IdleStateHandler
1.什么是心跳机制
所谓心跳,即在TCP长链接中,客户端跟服务器端之间定期发送一种特殊的数据包(心跳包),通知对方自己还在线,以确保TCP链接的有效性。
注:心跳包还有另一个作用经常被忽略,即:一个连接如果长时间不用,防火墙或者路由器就会断开该链接
2.IdleStateHandler
netty自带的心跳监测实例,当连接的空闲时间(读或者写)太长时候,就会触发一个IdleStateEvent事件,然后服务端添加IdleStateHandler心跳监测处理器,并添加自定义处理器Handler类实现userEventTriggered()方法作为超时时间逻辑处理。
IdleStateHandler的三个实例化参数
- readerIdleTime:读空闲超时时间设置,如果channelRead0()方法超时readerIdleTime时间违背调用则会触发读超时事件,调用userEventTriggered()
- writerIdleTime:写超时时间设置,如果服务器向客户端在规定的writerIdleTime时间内没有写入数据,则会触发写超时事件,调用userEventTriggered()方法
- allIdleTimeSeconds:读写超时事件,所有类型的空闲时间超时设定,包括读和写
- unit:时间单位,默认为秒
服务端
package com.dragon.heart;
import com.dragon.netty.InitServer;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
/**
* @Description:
*/
public class MyHeartBeatServer {
public static void main(String[] args) throws InterruptedException {
//事件循环组 acceptorGroup用于接收请求
EventLoopGroup acceptorGroup = new NioEventLoopGroup();
//事件循环组 handlerGroup用于处理请求
EventLoopGroup handlerGroup = new NioEventLoopGroup();
try{
// sub-class which allows easy bootstrap of 用于轻松的启动bootstrap
ServerBootstrap serverBootstrap = new ServerBootstrap()
// group (acceptorGroup,handlerGroup)
// acceptorGroup 用于接收所有的请求
// handlerGroup 处理所有的事件和IO操作
.group(acceptorGroup,handlerGroup)
// NioSctpServerChannel 利用nio模式接收一个新的连接创建NioSctpChannel
.channel(NioServerSocketChannel.class)
// 服务端设置handler,只会对acceptorGroup 事件组生效
.handler(new LoggingHandler(LogLevel.INFO))
// 自定义处理器
.childHandler(new MyHeartBeatServerInit());
//绑定80端口,端口号可以自定义
ChannelFuture future = serverBootstrap.bind(8080).sync();
future.channel().closeFuture().sync();
}finally {
//友好关闭两个线程组
acceptorGroup.shutdownGracefully();
handlerGroup.shutdownGracefully();
}
}
}
这里需要备注一下:
serverBootstrap.handler(new LoggingHandler(LogLevel.INFO)),
这个日志处理器只会作用于服务端
serverBootstrap.handler()这个方法只针对服务端添加事件处理器
package com.dragon.heart;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
* @Description: 初始化器
*/
public class MyHeartBeatServerInit extends ChannelInitializer<SocketChannel>{
/**
* 一旦channel管道被注册这个方法就会被调用,这个方法返回一个之后实例将会从ChannelPipeline移除channel
*/
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// 在次SocketChannel管道的位置增追加一个通道处理器
ch.pipeline().addLast("idleStateHandler",new IdleStateHandler(8,8,10, TimeUnit.SECONDS));
// 在管道的最后一个位置追加自己的通道处理器
ch.pipeline().addLast("myHeartBeatHandler",new MyHeartBeatHandler());
}
}
package com.dragon.heart;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleStateEvent;
/**
* @Description:
*/
public class MyHeartBeatHandler extends SimpleChannelInboundHandler<Object> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
Channel channel = ctx.channel();
String eventType = null;
if(evt instanceof IdleStateEvent){
IdleStateEvent event = (IdleStateEvent) evt;
switch (event.state()){
case READER_IDLE:
eventType = "读事件";
break;
case WRITER_IDLE:
eventType = "写事件";
break;
case ALL_IDLE:
eventType = "读写事件";
break;
}
}
System.out.println("【服务器】 "+ channel.remoteAddress() +","+ eventType + " 超时");
}
}
客户端
package com.dragon.chart.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
/**
* @Description: 客户端
*/
public class MyChartClient {
public static void main(String[] args) throws InterruptedException, IOException {
EventLoopGroup clientGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
try{
bootstrap.group(clientGroup).channel(NioSocketChannel.class).handler(new MyChartClientInit());
Channel channel = bootstrap.connect("localhost",8080).sync().channel();
// 获取键盘输入的事件,通过通道channel将获取的内容写进通道
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
for(;;){
channel.writeAndFlush(br.readLine() + "\r\n");
}
}finally {
clientGroup.shutdownGracefully();
}
}
}
package com.dragon.chart.client;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
/**
* @Description:
*/
public class MyChartClientInit extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
/***
* @Param maxFrameLength 解帧码的最大长度
* @Param delimiters 分隔符
*/
pipeline.addLast(new DelimiterBasedFrameDecoder(4096, Delimiters.lineDelimiter()));
/***
* 解码器处理器
*/
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
/***
* 编码器处理器
*/
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
/***
* 自定义处理器
*/
pipeline.addLast(new MyChartClientHandler());
}
}
package com.dragon.chart.client;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
/**
* @Description:
*/
public class MyChartClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
/***
* 客户端不需要处理信息内容,能查看就行,所以不做任何处理,打印出来即可
*/
System.out.println(msg);
}
}
注:客户端着三个类全部采用子上一章节的代码
[《Netty学习打卡--从小白到放弃》----- 05 - netty实现简单的聊天功能](https://blog.csdn.net/u011291990/article/details/94722092)
分别运行服务端和客户端的代码,在客户端不输入任何信息的情况下会出现如下日志:
七月 05, 2019 3:43:22 下午 io.netty.handler.logging.LoggingHandler channelRegistered
信息: [id: 0x21f153de] REGISTERED
七月 05, 2019 3:43:22 下午 io.netty.handler.logging.LoggingHandler bind
信息: [id: 0x21f153de] BIND: 0.0.0.0/0.0.0.0:8080
七月 05, 2019 3:43:22 下午 io.netty.handler.logging.LoggingHandler channelActive
信息: [id: 0x21f153de, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
七月 05, 2019 3:43:29 下午 io.netty.handler.logging.LoggingHandler channelRead
信息: [id: 0x21f153de, L:/0:0:0:0:0:0:0:0:8080] READ: [id: 0xa93b9f73, L:/127.0.0.1:8080 - R:/127.0.0.1:9786]
七月 05, 2019 3:43:29 下午 io.netty.handler.logging.LoggingHandler channelReadComplete
信息: [id: 0x21f153de, L:/0:0:0:0:0:0:0:0:8080] READ COMPLETE
【服务器】 /127.0.0.1:9786,读事件 超时
【服务器】 /127.0.0.1:9786,写事件 超时
【服务器】 /127.0.0.1:9786,读写事件 超时
【服务器】 /127.0.0.1:9786,读事件 超时
【服务器】 /127.0.0.1:9786,写事件 超时
【服务器】 /127.0.0.1:9786,写事件 超时
【服务器】 /127.0.0.1:9786,读事件 超时
【服务器】 /127.0.0.1:9786,读写事件 超时
【服务器】 /127.0.0.1:9786,写事件 超时
【服务器】 /127.0.0.1:9786,读事件 超时