接上一节:springboot+mina框架服务端的实现(一) ------ pom依赖、mina配置类、自定义协议以及编解码器的实现
参考博客:矢落叶の博客
四、 心跳包的实现
先简单介绍下keepAlive的机制:
首先,需要搞清楚TCP keepalive是干什么用的。从名字理解就能够知道,keepalive就是用来检测一个tcp connection是否还连接正常。当一个tcpconnection建立好之后,如果双方都不发送数据的话,tcp协议本身是不会发送其它的任何数据的,也就是说,在一个idle的connection上,两个socket之间不产生任何的数据交换。从另一个方面讲,当一个connection建立之后,链接双方可以长时间的不发送任何数据,比如几天,几星期甚至几个月,但该connection仍然存在。
所以,这就可能出现一个问题。举例来说,server和client建立了一个connection,server负责接收client的request。当connection建立好之后,client由于某种原因机器停机了。但server端并不知道,所以server就会一直监听着这个connection,但其实这个connection已经失效了。
keepalive就是为这样的场景准备的。当把一个socket设置成了keepalive,那么这个socket空闲一段时间后,它就会向对方发送数据来确认对方仍然存在。放在上面的例子中,如果client停机了,那么server所发送的keepalive数据就不会有response,这样server就能够确认client完蛋了(至少从表面上看是这样)。
MINA本身提供了一个过滤器类: org.apache.mina.filter.keepalive.KeepAliveFilter ,该过滤器用于在IO空闲的时候发送并且反馈心跳包(keep-alive request/response)。
该类构造函数中参数有三个分别是:
(1)KeepAvlieMessageFactory: 该实例引用用于判断接受与发送的包是否是心跳包,以及心跳请求包的实现
(2)IdleStatus: 该过滤器所关注的空闲状态,默认认为读取空闲。 即当读取通道空闲的时候发送心跳包
(3)KeepAliveRequestTimeoutHandler: 心跳包请求后超时无反馈情
先看KeepAliveMessageFactory
接口:
public interface KeepAliveMessageFactory {
boolean isRequest(IoSession session, Object message);
boolean isResponse(IoSession session, Object message);
Object getRequest(IoSession session);
Object getResponse(IoSession session, Object request);
}
实现这个接口:
public class KeepAliveFactoryImpl implements KeepAliveMessageFactory {
static final Logger logger = LoggerFactory.getLogger(KeepAliveFactoryImpl.class);
@Resource
private BaseHandler keepAliveHandler;
// 用来判断接收到的消息是不是一个心跳请求包,是就返回true[接收端使用]
@Override
public boolean isRequest(IoSession session, Object message) {
if (message instanceof MyPack) {
MyPack pack = (MyPack) message;
if (Const.HEART_BEAT == pack.getModule()) {
return true;
}
}
return false;
}
// 用来判断接收到的消息是不是一个心跳回复包,是就返回true[发送端使用]
@Override
public boolean isResponse(IoSession session, Object message) {
// TODO Auto-generated method stub
return false;
}
// 在需要发送心跳时,用来获取一个心跳请求包[发送端使用]
@Override
public Object getRequest(IoSession session) {
// TODO Auto-generated method stub
return null;
}
// 在需要回复心跳时,用来获取一个心跳回复包[接收端使用]
@Override
public Object getResponse(IoSession session, Object request) {
MyPack attendPack = (MyPack) request;
if (null == session.getAttribute(Const.SESSION_KEY)) {
// 需要先进行登录
return new MyPack(Const.AUTHEN, attendPack.getSeq(), "fail");
}
// 将超时次数置为0
session.setAttribute(Const.TIME_OUT_KEY, 0);
return new MyPack(Const.HEART_BEAT, attendPack.getSeq(), "success");
}
}
超时后的处理放在业务处理类的sessionIdle
方法中实现,稍后详细介绍
五、 自定义Session类及其管理类
方便对session会话进行管理,方便对session会话集合获取和删除
服务端接收到新的Session后,构造一个封装类,实现session 的部分方法,并额外实现方法
5.1 MySession 类
就一个处理业务的方法,使用自定义Session代替IoSession
public class MySession implements Serializable {
private static final long serialVersionUID = 1L;
// 不参与序列化
private transient IoSession session;
// session在本机器的ID
private Long nid;
// session绑定的服务ip
private String host;
// 访问端口
private int port;
// session绑定的设备
private String account;
public MySession() {
}
public MySession(IoSession session) {
this.session = session;
this.host = ((InetSocketAddress) session.getRemoteAddress()).getAddress().getHostAddress();
this.port = ((InetSocketAddress) session.getRemoteAddress()).getPort();
this.nid = session.getId();
}
/**
* 将key-value自定义属性,存储到IO会话中
*/
public void setAttribute(String key, Object value) {
if (null != session) {
session.setAttribute(key, value);
}
}
/**
* 从IO的会话中,获取key的value
*/
public Object getAttribute(String key) {
if (null != session) {
return session.getAttribute(key);
}
return null;
}
/**
* 在IO的会话中,判断是否存在包含key-value
*/
public boolean containsAttribute(String key) {
if (null != session) {
return session.containsAttribute(key);
}
return false;
}
/**
* 从IO的会话中,删除key
*/
public void removeAttribute(String key) {
if (null != session) {
session.removeAttribute(key);
}
}
/**
* 获取IP地址
*/
public SocketAddress getRemoteAddress() {
if (null != session