Mina 系列(四)之KeepAliveFilter -- 心跳检测
Mina 系列(四)之KeepAliveFilter -- 心跳检测
摘要: 心跳协议,对基于CS模式的系统开发来说是一种比较常见与有效的连接检测方式,最近在用MINA框架,原本自己写了一个心跳协议实现,后来突然发现MINA本身带有这样一个心跳实现,感于对框架的小小崇拜,在实践的同时研究了一下!
MINA 本身提供了一个过滤器类: org.apache.mina.filter.keepalive.KeepAliveFilter,该过滤器用于在 IO 空闲的时候发送并且反馈心跳包(keep-alive request/response)。
KeepAliveFilter
KeepAliveFilter 构造器
public KeepAliveFilter(KeepAliveMessageFactory messageFactory, IdleStatus interestedIdleStatus,
KeepAliveRequestTimeoutHandler policy) {
this(messageFactory, interestedIdleStatus, policy, 60, 30);
}
-
KeepAvlieMessageFactory
:该实例引用用于判断接受与发送的包是否是心跳包,以及心跳请求包的实现 -
IdleStatus
:该过滤器所关注的空闲状态,默认认为读取空闲。即当读取通道空闲的时候发送心跳包 -
KeepAliveRequestTimeoutHandler
:心跳包请求后超时无反馈情况下的处理机制,默认为 CLOSE,即关闭连接
首先需要实现接口 KeepAliveMessageFactory。该接口中的抽象方法有:
Modifier and Type | Method and Description |
---|---|
Object | getRequest(IoSession session) |
Returns | a (new) keep-alive request message. |
Object | getResponse(IoSession session, Object request) |
Returns | a (new) response message for the specified keep-alive request. |
boolean | isRequest(IoSession session, Object message) Returns true if and only if the specified message is a keep-alive request message. |
boolean | isResponse(IoSession session, Object message)Returns true if and only if the specified message is a keep-alive response message; |
一般来说心跳机制主要分为以下四类:
-
active 活跃型(活跃型心跳机制):当读取通道空闲的时候发送心跳请求,一旦该心跳请求被发送,那么需要在 keepAliveRequestTimeout 时间内接收到心跳反馈,否则 KeepAliveRequestTimeoutHandler 将会被调用,当一个心跳请求包被接受到后,那么心跳反馈也会立即发出。
KeepAliveMessageFactory 类的实现方法:
- getRequest(IoSession session) 必须反馈 non-null
- getResponse( IoSession session, Object request) 必须反馈 non-null
-
semi-active 半活跃型(半活跃型心跳机制):当读取通道空闲的时候发送心跳请求,然而并不在乎心跳反馈有没有,当一个心跳请求包被接收到后,那么心跳反馈也会立即发出。
KeepAliveMessageFactory 类的实现方法:
- getRequest(IoSession session) 必须反馈 non-null
- getResponse( IoSession session, Object request) 必须反馈 non-null
心跳包请求超时后的处理机制设置为 KeepAliveRequestTimeoutHandler.NOOP(不做任何处理),KeepAliveRequestTimeoutHandler.LOG(只输出警告信息不做其他处理)
-
passive 被动型(半活跃型心跳机制):当前 IO 不希望主动发送心跳请求,但是当接受到一个心跳请求后,那么该心跳反馈也会立即发出。
KeepAliveMessageFactory 类的实现方法:
- getRequest(IoSession session) 必须反馈 null
- getResponse( IoSession session, Object request) 必须反馈 non-null
-
deaf speaker 聋子型(聋子型心跳机制):当前IO会主动发送心跳请求,但是不想发送任何心跳反馈。
KeepAliveMessageFactory 类的实现方法:
- getRequest(IoSession session) 必须反馈 non-null
- getResponse( IoSession session, Object request) 必须反馈 null
心跳包请求超时后的处理机制设置为 KeepAliveRequestTimeoutHandler.DEAF_SPEAKER
-
sient-listener 持续监听型(持续监听型心跳机制):既不想发送心跳请求也不想发送心跳反馈。
KeepAliveMessageFactory 类的实现方法:
- getRequest(IoSession session) 必须反馈 null
- getResponse( IoSession session, Object request) 必须反馈 null
心跳包请求超时后的处理机制
接口 KeepAliveRequestTimeoutHandler,一般该处理主要是针对能够发送心跳请求的心跳机制。
- CLOSE: 关闭连接
- LOG:输出 警告信息
- NOOP:不做任何处理
- EXCEPTION:抛出异常
- DEAF_SPEAKER: 一个特殊的处理,停止当前过滤器对对心跳反馈监听,因此让过滤器丢失请求超时的侦测功能。(让其变成聋子)
- keepAliveRequestTimeout(KeepAliveFilter filter, IoSession session):自定义处理
KeepAliveFilter 配制
KeepAliveMessageFactoryImpl kamfi = new KeepAliveMessageFactoryImpl();
KeepAliveFilter kaf = new KeepAliveFilter(kamfi, IdleStatus.READER_IDLE, KeepAliveRequestTimeoutHandler.CLOSE);
// idle 事件回调
kaf.setForwardEvent(true);
// 心跳检测间隔时间
kaf.setRequestInterval(60);
// 心跳检测超时时间
kaf.setRequestTimeout(30);
-
setForwardEvent
使用了 KeepAliveFilter 之后,IoHandlerAdapter 中的 sessionIdle 方法默认是不会再被调用的! 所以必须加入这句话 sessionIdle 才会被调用 -
setRequestInterval
设置心跳包请求时间间隔,其实对于被动型的心跳机制来说,设置心跳包请求间隔貌似是没有用的,因为它是不会发送心跳包的,但是它会触发 sessionIdle 事件, 我们利用该方法,可以来判断客户端是否在该时间间隔内没有发心跳包,一旦 sessionIdle 方法被调用,则认为 客户端丢失连接并将其踢出。因此其中参数 heartPeriod 其实就是服务器对于客户端的 IDLE 监控时间。默认 60 s。 -
setRequestTimeout
超时时间,如果当前发出一个心跳请求后需要反馈。默认 30 s
下面对客户端与服务端和分别举个例子
服务器
以被动型心跳机制为例,服务器在接受到客户端连接以后被动接受心跳请求,当在规定时间内没有收到客户端心跳请求时 将客户端连接关闭。
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.keepalive.KeepAliveFilter;
import org.apache.mina.filter.keepalive.KeepAliveMessageFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Server {
private static final int PORT = 9123;
/** 30秒后超时 */
private static final int IDEL_TIMEOUT = 30;
/** 15秒发送一次心跳包 */
private static final int HEART_BEAT_RATE = 15;
/** 心跳包内容 */
private static final String HEART_BEAT_REQUEST = "0x11";
private static final String HEART_BEAT_RESPONSE = "0x12";
private static final Logger LOG = LoggerFactory.getLogger(Server.class);
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getSessionConfig().setReadBufferSize(1024);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, IDEL_TIMEOUT);
acceptor.getFilterChain().addLast("logger", new LoggingFilter());
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory()));
KeepAliveMessageFactory heartBeatFactory = new KeepAliveMessageFactoryImpl();
KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE);
// 设置是否forward到下一个filter
heartBeat.setForwardEvent(true);
// 设置心跳频率
heartBeat.setRequestInterval(HEART_BEAT_RATE);
acceptor.getFilterChain().addLast("heartbeat", heartBeat);
acceptor.setHandler(new IoHandlerAdapter());
acceptor.bind(new InetSocketAddress(PORT));
System.out.println("Server started on port: " + PORT);
}
/**
* 被动型心跳机制,服务器在接受到客户端连接以后被动接受心跳请求,当在规定时间内没有收到客户端心跳请求时将客户端连接关闭
* @ClassName KeepAliveMessageFactoryImpl
* @Description 内部类,实现 KeepAliveMessageFactory(心跳工厂)
*/
private static class KeepAliveMessageFactoryImpl implements KeepAliveMessageFactory {
/* 判断是否心跳请求包,是的话返回true */
@Override
public boolean isRequest(IoSession session, Object message) {
LOG.info("请求心跳包信息: " + message);
return message.equals(HEART_BEAT_REQUEST);
}
/* 由于被动型心跳机制,没有请求当然也就不关注反馈,因此直接返回 false */
@Override
public boolean isResponse(IoSession session, Object message) {
return false;
}
/* 被动型心跳机制无请求,因此直接返回 null */
@Override
public Object getRequest(IoSession session) {
return null;
}
/* 根据心跳请求 request,反回一个心跳反馈消息 non-null */
@Override
public Object getResponse(IoSession session, Object request) {
LOG.info("响应预设信息: " + HEART_BEAT_RESPONSE);
return HEART_BEAT_RESPONSE;
}
}
}
客户端
客户端会定时发送心跳请求(注意定时时间必须小于,服务器端的IDLE监控时间),同时需要监听心跳反馈,以此来判断是否与服务器丢失连接。对于服务器的心跳请求不给与反馈。
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.keepalive.KeepAliveFilter;
import org.apache.mina.filter.keepalive.KeepAliveMessageFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.net.InetSocketAddress;
public class Client {
private static final int PORT = 9123;
/** 30秒后超时 */
private static final int IDEL_TIMEOUT = 30;
/** 15秒发送一次心跳包 */
private static final int HEART_BEAT_RATE = 15;
/** 心跳包内容 */
private static final String HEART_BEAT_REQUEST = "0x11";
private static final String HEART_BEAT_RESPONSE = "0x12";
private static final Logger LOG = LoggerFactory.getLogger(Client.class);
public static void main(String[] args) throws IOException {
IoConnector connector = new NioSocketConnector();
connector.getSessionConfig().setReadBufferSize(1024);
connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, IDEL_TIMEOUT);
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory()));
KeepAliveMessageFactory heartBeatFactory = new KeepAliveMessageFactoryImpl();
KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE);
// 设置是否forward到下一个filter
heartBeat.setForwardEvent(true);
// 设置心跳频率
heartBeat.setRequestInterval(HEART_BEAT_RATE);
connector.getFilterChain().addLast("heartbeat", heartBeat);
connector.setHandler(new IoHandlerAdapter());
connector.connect(new InetSocketAddress("127.0.0.1", PORT));
System.out.println("Server started on port: " + PORT);
}
/**
* 被动型心跳机制,服务器在接受到客户端连接以后被动接受心跳请求,当在规定时间内没有收到客户端心跳请求时将客户端连接关闭
* @ClassName KeepAliveMessageFactoryImpl
* @Description 内部类,实现KeepAliveMessageFactory(心跳工厂)
* @author cruise
*
*/
private static class KeepAliveMessageFactoryImpl implements KeepAliveMessageFactory {
/* 服务器不会给客户端发送请求包,因此不关注请求包,直接返回 false */
@Override
public boolean isRequest(IoSession session, Object message) {
return false;
}
/* 客户端关注请求反馈,因此判断 mesaage 是否是反馈包 */
@Override
public boolean isResponse(IoSession session, Object message) {
LOG.info("响应预设信息: " + message);
return message.equals(HEART_BEAT_RESPONSE);
}
/* 获取心跳请求包 non-null */
@Override
public Object getRequest(IoSession session) {
LOG.info("请求预设信息: " + HEART_BEAT_REQUEST);
return HEART_BEAT_REQUEST;
}
/* 服务器不会给客户端发送心跳请求,客户端当然也不用反馈,该方法返回 null */
@Override
public Object getResponse(IoSession session, Object request) {
return null;
}
}
}
posted on 2018-03-03 13:37 binarylei 阅读(2211) 评论(0) 编辑 收藏 举报