本文纯手工制作,请用心观看。
在详细讲解mina的心跳机制前,读者需要已经具备了mina的心跳机制的基础知识。
如果不够了解,请查看一下链接自行脑补。
http://www.cnblogs.com/pricks/p/3832882.html
http://wandejun1012.iteye.com/blog/2065941
本文主要讲解的是Mina开源框架的心跳机制是如何实现的,以及对心跳机制的一个不解。
最常见的不解就是对KeepAliveMessageFactory 这个接口中的四个函数
boolean isRequest(IoSession session, Object message);
boolean isResponse(IoSession session, Object message);
Object getRequest(IoSession session);
Object getResponse(IoSession session, Object request);
感到困惑,感到纠结,他们到底有什么用处,在心跳机制里面,他们到底起到了什么样的作用。
以及如下网友的困惑
http://blog.csdn.net/h121baby/article/details/50810110
尤其是文中提到的第三条
明明客户端只发送了一次心跳包,服务端却判断了两次?
还有就是我们如何才能利用Mina系统的KeepAliveFilter来实现我们想要的心跳机制。
下面我们就来看一下Mina框架是如何实现这个KeepAliveFilter的,只有这样我们才能彻底掌握Mina的心跳机制。
在Mina源码下面,路径为apache-mina-2.0.10_\src\org\apache\mina\filter\keepalive的路径下面就是keepAliveFilter的实现
在这个目录下面一共有4个java文件
KeepAliveFilter.java 心跳机制的主要实现类
KeepAliveMessageFactory.java 一个接口文件,也就是我们主要需要去实现的类,里面有4个主要的方法
KeepAliveRequestTimeoutException .java 从名字可以看出是一个请求超时的异常类,我们不用处理
KeepAliveRequestTimeoutHandler.java 这个也是我们需要去实现的一个接口,来处理我们的超时逻辑的
由上述可以看出,Mina框架对心跳机制的主要实现是在KeepAliveFilter.java里面,下面我们重点分析一下这个类。
这个类 主要的可以被框架调用的复写的方法有三个:
@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));
}
}
//System.out.println("=======message================="+message);
if (messageFactory.isResponse(session, message)) {
//System.out.println("=======message is response");
resetStatus(session);
}
} finally {
if (!isKeepAliveMessage(session, message)) {
nextFilter.messageReceived(session, message);
}
}
}
@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 sessionIdle(
NextFilter nextFilter, IoSession session, IdleStatus status) throws Exception {
//System.out.println("idle status" + status);
if (status == interestedIdleStatus) {
if (!session.containsAttribute(WAITING_FOR_RESPONSE)) {
Object pingMessage = messageFactory.getRequest(session);
if (pingMessage != null) {
//System.out.println("send ping message");
// If policy is OFF, there's no need to wait for
// the response.
if (getRequestTimeoutHandler() != KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
markStatus(session);
if (interestedIdleStatus == IdleStatus.BOTH_IDLE) {
//System.out.println(" set IGNORE_READER_IDLE_ONCE ");
session.setAttribute(IGNORE_READER_IDLE_ONCE);
}
} else {
resetStatus(session);
}
nextFilter.filterWrite(
session,
new DefaultWriteRequest(pingMessage));
}
} else {
//System.out.println(" ping timeout11111111111111..........");
handlePingTimeout(session);
}
} else if (status == IdleStatus.READER_IDLE) {
if (session.removeAttribute(IGNORE_READER_IDLE_ONCE) == null) {
//System.out.println("IGNORE_READER_IDLE_ONCE is not null");
if (session.containsAttribute(WAITING_FOR_RESPONSE)) {
handlePingTimeout(session);
//System.out.println(" ping timeout22222..........");
}
}
}
if (forwardEvent) {
nextFilter.sessionIdle(session, status);
}
}
三个方法分别是:messageReceived,messageSent,sessionIdle。
从名字,我们大概可以看出,他们分别被调用的场景是:
messageReceived,当接收到消息的时候,
messageSent,当需要发送消息的时候,
sessionIdle,空闲的时候。
对于什么是心跳机制还不明白的同学请自行百度。
下面我们就可以猜测他们的调用过程,以及我将会使用断点查看堆栈信息的方式才证明自己的部分观点的正确性。
首先,肯定是session空闲的sessionIdle被调用了,作为服务器检查客户端是否还能正常工作而发出的心跳包的起点。
从上述代码可以看出,经过状态检查之后,会调用
Object pingMessage = messageFactory.getRequest(session);
而这里的getRequest就是我们自己想要去实现的KeepAliveMessageFactory这个接口的其中的一个方法。
从这里,可以看出,这个方法的意思是,需要获取一个心跳包。
从堆栈信息可以看出,确实是先在先调用sessionIdle,然后在调用了getRequest这个函数。
之后,经过几个检查之后,调用了markStatus(session);这个方法。
private void markStatus(IoSession session) {
session.getConfig().setIdleTime(interestedIdleStatus, 0);
session.getConfig().setReaderIdleTime(getRequestTimeout());
session.setAttribute(WAITING_FOR_RESPONSE);
}
这个方法主要是设置了状态,读空闲时间,以及最关键的session状态WAITING_FOR_RESPONSE,等待客户端的回复这个状态。
最后发送了刚才获取的心跳包。
nextFilter.filterWrite(session,new DefaultWriteRequest(pingMessage));
要发送数据,就会调用 messageSent这个方法。
而在这个messageSent方法中会判断 发送的这个包是否是心跳包isKeepAliveMessage,
private boolean isKeepAliveMessage(IoSession session, Object message) {
return messageFactory.isRequest(session, message) ||
messageFactory.isResponse(session, message);
}
这个方法会先调用
isRequest然后调用
isResponse。用来检测是否为心跳包。不管是请求包还是回复包,而这两个方法也是我们需要自己实现的。
从截图可以看出,确实是,先调用了messageSent方法,然后调用了isKeepAliveMessage,最后调用了isRequest,由于这里是获取了一个请求的心跳包,因此isRequest返回true,所以,后面的isResponse这个检测不会被调用。主要原因是在这个地方,使用了|| 这个运算符。如果messageFactory.isRequest(session, message) 返回true,那么后面的判断就不执行了。如果返回false,就会执行后面的判断。
这样,服务器端向客户端发送一个心跳包的全部过程就完成了。
此时,就是客户端如何处理了。
这个时候,分两种情况,第一种,客户端不返回数据,也就是超时了。第二种情况就是客户端返回了数据。
第一种情况,如果客户端不返回数据。
时间到了之后,进入session空闲,sessionIdle再次被调用了。
首先检测 session.containsAttribute(WAITING_FOR_RESPONSE)
在上次请求的时候,这个数据已经被设置了,因此它将返回true
将会进入handlePingTimeout(session);
private void handlePingTimeout(IoSession session) throws Exception {
resetStatus(session);
KeepAliveRequestTimeoutHandler handler = getRequestTimeoutHandler();
if (handler == KeepAliveRequestTimeoutHandler.DEAF_SPEAKER) {
return;
}
handler.keepAliveRequestTimedOut(this, session);
}
可以看出这个函数,resetStatus之后,就调用我们自己实现的一个超时处理的方法:
keepAliveRequestTimedOut,在这个方法里面,我们需要实现自己的逻辑,比如超过次数,需要关闭连接工作。
从这里,可以看出,系统是不会主动帮助我们关闭session的,只会在每一次超时之后调用keepAliveRequestTimedOut方法而已。
接下来,我们讨论第二种情况:
第二种情况就是客户端返回了数据。
如果有数据返回,那么肯定首先会调用messageReceived()这个函数,
从上面的代码我们可以知道,在这个函数中,
首先会调用messageFactory.isRequest(session, message) 函数,检查是否是请求的心跳包信息,
如果是,就messageFactory.getResponse(session, message);获取一个回复的心跳包,并且发送出去
其次,就是检查这个包是否是客户端回复的心跳包。如果是,就调用resetStatus(session);方法重置数据,主要清除超时的次数信息。
最后,调用isKeepAliveMessage()函数检查是否是心跳包,(包括请求的心跳包以及回复的心跳包)
,如果都不是,则把这个 nextFilter.messageReceived(session, message);的消息,传递给心跳过滤器的下一个过滤器。
至此,Mina的心跳过滤器就讲解完毕。大家是不是对Mina的心跳机制已经彻底掌握了呢?
那么,下面就来解答一下本文开头的3个问题。
第一个问题
最常见的误区就是对KeepAliveMessageFactory 这个接口中的四个函数
boolean isRequest(IoSession session, Object message);
boolean isResponse(IoSession session, Object message);
Object getRequest(IoSession session);
Object getResponse(IoSession session, Object request);
他们到底有什么用处,在心跳机制里面,他们到底起到了什么样的作用。
isRequest,这个接口就是用来判断接收到的消息是不是一个心跳请求包。isResponse,这个接口就是用来判断接收到的消息是不是一个心跳回复包。
getResponse,这个接口是用来获取一个心跳回复包。
就这几个操作而已。具体的心跳机制的逻辑部分是在KeepAliveFilter里面实现的。也就是上面讲解的部分。
第二个问题:明明客户端只发送了一次心跳包,服务端却判断了两次?
服务器判断了两次,也就是服务器调用了两次isRequest函数。
关于这个问题,从上面的messageReceived方法可以知道,
第一次,是在if (messageFactory.isRequest(session, message))
这个地方调用的,
第二次,是在 if (!isKeepAliveMessage(session, message))
这个地方,isKeepAliveMessage函数中的第一个判断
messageFactory.isRequest(session, message)
中调用过来一次。
对于这个问题,其实,当你明白了整个Mina的心跳实现机制之后,就会知道,其实系统调用几次isRequest或其他的接口,根本无所谓。
不要把关注点放在这个地方。
最后一个问题就是我们如何才能利用Mina系统的KeepAliveFilter来实现我们想要的心跳机制。
我们只需要正确的写出KeepAliveMessageFactory 的四个接口以及超时处理的KeepAliveRequestTimeoutHandler接口即可。
其他的事情,都交给系统框架处理了。
Demo下载地址:
http://download.csdn.net/detail/kkk0526/9556423