在應用中,作爲客戶端,可能會遇到服務端不回覆心跳的情況,那麼這個時候就需要客戶端自發自收。
不必費力寫一個定時器向自身發送心跳回復,直接在發送時調用其內部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方法。
在這麼一種邏輯下,當服務端沒有回覆心跳時,完全可以模擬接收到心跳的回覆,假裝自己已經收到心跳回復,避免因心跳超時問題而被服務端斷開或自行斷開。