在应用中,作为客户端,可能会遇到服务端不回复心跳的情况,那么这个时候就需要客户端自发自收。
不必费力写一个定时器向自身发送心跳回复,直接在发送时调用其内部API就可以:
public class KeepAliveGatewayZigbee implements KeepAliveMessageFactory {
public static final String ROBOT_HEART = "robot_heart";
public static final String GATEWAY_HEART = "gateway_heart";
@Override
public boolean isRequest(IoSession ioSession, Object o) {
if (o instanceof String) {
String msg = (String) o;
if (ROBOT_HEART.equalsIgnoreCase(msg)) {
L.v("发送心跳");
//假装收到心跳
ioSession.getFilterChain().fireMessageReceived(GATEWAY_HEART);
return true;
}
}
return false;
}
@Override
public boolean isResponse(IoSession ioSession, Object o) {
if (o instanceof String) {
String msg = (String) o;
if (GATEWAY_HEART.equalsIgnoreCase(msg)) {
L.v("接收心跳");
return true;
}
}
return false;
}
@Override
public Object getRequest(IoSession ioSession) {
return ROBOT_HEART;
}
@Override
public Object getResponse(IoSession ioSession, Object o) {
return o;
}
}
是的,只需要在判断为发送心跳的isRequest方法中,调用一次fireMessageReceived传入应该回复的心跳即可。
究其原理,是因为我们清楚整个消息传递是怎样的。
这个可以从Mina源码看心跳超时机制略知一二,如同心跳超时调用sessionIdle一样,消息传递会回调一系列类似于sessionIdle的方法。
当我们实现心跳机制时,肯定都会使用类似
getFilterChain().addLast(HEARTBEAT, keepAliveFilter)
的代码添加一个KeepAliveFilter,KeepAliveFilter继承自IoFilterAdapter。
当这个Filter被添加到FilterChain中时,每次session的动作都会触发其相应的方法。而KeepAliveFilter正是对这些方法进行了覆写,实现了一些判断的逻辑。
比如每次发送消息后与接收消息后:
//KeepAliveFilter.java
@Override
public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
Object message = writeRequest.getMessage();
if (!isKeepAliveMessage(session, message)) {
nextFilter.messageSent(session, writeRequest);
}
}
@Override
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
try {
if (messageFactory.isRequest(session, message)) {
Object pongMessage = messageFactory.getResponse(session, message);
if (pongMessage != null) {
nextFilter.filterWrite(session, new DefaultWriteRequest(pongMessage));
}
}
if (messageFactory.isResponse(session, message)) {
resetStatus(session);
}
} finally {
if (!isKeepAliveMessage(session, message)) {
nextFilter.messageReceived(session, message);
}
}
}
每次发送消息后或接收消息后,KeepAliveFilter会使用isKeepAliveMessage方法判断发送的消息是否为心跳消息。而这个方法是交给开发者实现的:
private final KeepAliveMessageFactory messageFactory;
private boolean isKeepAliveMessage(IoSession session, Object message) {
return messageFactory.isRequest(session, message) || messageFactory.isResponse(session, message);
}
因为这个“||"逻辑或的原因,当isRequest为真时,isResponse就不会调用了,而当isRequest为假时,isResponse才会调用。
所以会有这么一种现象:
发送心跳消息时,因为经过messageSent,所以会调用一次isRequest方法;
收到心跳回复时,因为经过messageReceived,所以会将isRequest和isResponse先调用一次,然后再在isKeepAliveMessage中重复调用一次isRequest和isResponse方法。
在这么一种逻辑下,当服务端没有回复心跳时,完全可以模拟接收到心跳的回复,假装自己已经收到心跳回复,避免因心跳超时问题而被服务端断开或自行断开。