Mina学习
IoConnector
NioDatagramConnector : 无阻塞的Socket 传输Connector,针对UDP
Idle : session至少在一个空闲周期(见配置)内没有处理过任何请求
一、断线重连的方式;
1. 在创建Mina客户端时增加一个监听器,或者增加一个拦截器,当检测到Session关闭时,自动进行重连。
2. 在第1种方式的基础上,增加客户端的读写通道空闲检查,当发生Session关闭或者读写空闲时,进行重连。
package com.yitop.feng.service; import org.apache.mina.core.filterchain.IoFilterAdapter; import org.apache.mina.core.future.ConnectFuture; import org.apache.mina.core.service.IoHandlerAdapter; import org.apache.mina.core.service.IoService; import org.apache.mina.core.service.IoServiceListener; import org.apache.mina.core.session.IdleStatus; import org.apache.mina.core.session.IoSession; import org.apache.mina.core.session.IoSessionConfig; import org.apache.mina.filter.codec.ProtocolCodecFilter; import org.apache.mina.filter.codec.textline.TextLineCodecFactory; import org.apache.mina.filter.logging.LoggingFilter; import org.apache.mina.transport.socket.SocketSessionConfig; import org.apache.mina.transport.socket.nio.NioSocketConnector; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.net.InetSocketAddress; import java.nio.charset.Charset; /** * @author fengzp * @date 16/8/23 * @email fengzp@gzyitop.com * @company 广州易站通计算机科技有限公司 */ public class MinaClient { private static final Logger LOGGER = LoggerFactory.getLogger(MinaClient.class); private NioSocketConnector connector; private IoSession session; private String hostname = "127.0.0.1"; private int port = 8899; public MinaClient(){ init(); } private void init(){ connector = new NioSocketConnector(); connector.getFilterChain().addLast("logger", new LoggingFilter()); connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("utf-8"), "]", "]"))); /** * 一、使用监听器或拦截器实现断线重连 * 二、在第1种方式的基础上,增加客户端的读写通道空闲检查,当发生Session关闭或者读写空闲时,进行重连 */ //使用拦截器实现断线重连 connector.getFilterChain().addFirst("reconnection", new IoFilterAdapter(){ @Override public void sessionClosed(NextFilter nextFilter, IoSession session) throws Exception { reconnect(); } }); //使用监听器实现断线重连 connector.addListener(new MyIoServiceListener()); connector.getSessionConfig().setReceiveBufferSize(10240); // 设置接收缓冲区的大小 connector.getSessionConfig().setSendBufferSize(10240);// 设置输出缓冲区的大小 connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30000); //读写都空闲时间:30秒 connector.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 40000);//读(接收通道)空闲时间:40秒 connector.getSessionConfig().setIdleTime(IdleStatus.WRITER_IDLE, 50000);//写(发送通道)空闲时间:50秒 //在数据处理器IoHandler中sessionIdle方法中加入Session会话关闭的代码,这样session关闭就能传递到拦截器或者监听器中,然后实现重连。 connector.setHandler(new MyIoHandlerConnector()); connect(); } private void connect(){ ConnectFuture future = connector.connect(new InetSocketAddress(hostname, port)); future.awaitUninterruptibly(10000); session = future.getSession(); } private void reconnect(){ while (true){ try { Thread.sleep(3000); connect(); if (session != null && session.isConnected()) { LOGGER.info("断线重连成功"); break; } }catch (Exception e){ LOGGER.error("断线重连失败", e); } } } class MyIoServiceListener implements IoServiceListener{ public void serviceActivated(IoService ioService) throws Exception { } public void serviceIdle(IoService ioService, IdleStatus idleStatus) throws Exception { } public void serviceDeactivated(IoService ioService) throws Exception { } public void sessionCreated(IoSession ioSession) throws Exception { } public void sessionDestroyed(IoSession ioSession) throws Exception { reconnect(); } } class MyIoHandlerConnector extends IoHandlerAdapter{ @Override public void sessionIdle(IoSession session, IdleStatus status) throws Exception { if(session != null){ //这里关闭session就会触发拦截器或者监听器,从而实现读写空闲时重连 session.close(true); } } @Override public void messageSent(IoSession session, Object message) throws Exception { } @Override public void messageReceived(IoSession session, Object message) throws Exception { String req = message==null?"":message.toString(); System.err.println("Client receive: "+req); } @Override public void sessionCreated(IoSession session) throws Exception { IoSessionConfig config = session.getConfig(); if (config instanceof SocketSessionConfig) { SocketSessionConfig sessionConfig = (SocketSessionConfig) config; sessionConfig.setKeepAlive(true);// 长连接 } System.out.println("Client session create"); } @Override public void sessionClosed(IoSession session) throws Exception { super.sessionClosed(session); System.out.println("Client session create"); } } public static void main(String[] args) { new MinaClient(); } }
实现mina心跳验证
简单介绍下keepAlive的机制:
首先,需要搞清楚TCP keepalive是干什么用的。从名字理解就能够知道,keepalive就是用来检测一个tcp connection是否还连接正常。当一个tcp connection建立好之后,如果双方都不发送数据的话,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本身提供了一个过滤器类:KeepAliveFilter ,该过滤器用于在IO空闲的时候发送并且反馈心跳包(keep-alive request/response)。
说到KeepAliveFilter这个类有必要先说一说其构造函数,即实例化该类需要些什么,该类构造函数中参数有三个分别是:
(1)KeepAvlieMessageFactory: 该实例引用用于判断接受与发送的包是否是心跳包,以及心跳请求包的实现
(2)IdleStatus: 该过滤器所关注的空闲状态,默认认为读取空闲。 即当读取通道空闲的时候发送心跳包
(3)KeepAliveRequestTimeoutHandler: 心跳包请求后超时无反馈情况下的处理机制 默认为CLOSE 即关闭连接
其实我们自己做下这两个接口的实现类就搞定了KeepAvlieMessageFactory、KeepAliveRequestTimeoutHandler
KeepAvlieMessageFactoryImpl中主要是定义心跳包的内容
KeepAliveRequestTimeoutHandlerImpl 主要是定义超时后的处理方式,通常是多次超时后就断开
二、测试例子
package com.yitop.feng.service; 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.core.session.IoSessionConfig; 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.SocketSessionConfig; import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.charset.Charset; /** * @author fengzp * @date 16/8/23 * @email fengzp@gzyitop.com * @company 广州易站通计算机科技有限公司 */ public class MinaServer { private NioSocketAcceptor acceptor; private int port = 8899; /** 30秒后超时 */ private static final int IDELTIMEOUT = 30; /** 15秒发送一次心跳包 */ private static final int HEARTBEATRATE = 15; public MinaServer() throws IOException { init(); } private void init() throws IOException { acceptor = new NioSocketAcceptor(); acceptor.getFilterChain().addLast("logger", new LoggingFilter()); acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("utf-8")))); KeepAliveFilter kaf = new KeepAliveFilter(new MyKeepAliveMessageFactory(), IdleStatus.BOTH_IDLE); kaf .setRequestInterval(HEARTBEATRATE);//设置心跳频率 kaf.setRequestTimeout(IDELTIMEOUT);//设置空闲超时时间 kaf.setForwardEvent(false); //idle事件回发, false表示当session进入idle状态的时候,不在调用handler中的sessionIdle方法 acceptor.getFilterChain().addLast("heart", kaf); acceptor.setHandler(new MyIoHandlerAdapter()); acceptor.getSessionConfig().setReadBufferSize(2048); acceptor.setReuseAddress(true);// 端口可重用 acceptor.bind(new InetSocketAddress(port)); } class MyKeepAliveMessageFactory implements KeepAliveMessageFactory{ String req = "+"; String resp = "-"; public MyKeepAliveMessageFactory(){ } public boolean isRequest(IoSession ioSession, Object o) { boolean isReq = (o!=null && req.equals(o.toString())); System.out.println(o.toString() + " : isRequest : " + isReq); return isReq; } public boolean isResponse(IoSession ioSession, Object o) { boolean isResp = (o!=null && resp.equals(o.toString())); System.out.println(o.toString() + " : isResponse : " + isResp); return isResp; } public Object getRequest(IoSession ioSession) { System.out.println("getRequest"); return req; } public Object getResponse(IoSession ioSession, Object o) { System.out.println("getResponse"); return resp; } } // class MyKeepAliveRequestTimeoutHandler implements KeepAliveRequestTimeoutHandler { // // public void keepAliveRequestTimedOut(KeepAliveFilter keepAliveFilter, IoSession ioSession) throws Exception { // // } // } class MyIoHandlerAdapter extends IoHandlerAdapter{ @Override public void messageReceived(IoSession session, Object message) throws Exception { String req = message==null?"":message.toString(); System.err.println("Server receive: "+req); session.write("hi client"); } @Override public void messageSent(IoSession session, Object message) throws Exception { String resp = message==null?"":message.toString(); System.err.println("Server sent: "+resp); } @Override public void sessionCreated(IoSession session) throws Exception { IoSessionConfig config = session.getConfig(); if (config instanceof SocketSessionConfig) { SocketSessionConfig sessionConfig = (SocketSessionConfig) config; sessionConfig.setKeepAlive(true);// 长连接 } System.out.println("Server session create"); } @Override public void sessionClosed(IoSession session) throws Exception { super.sessionClosed(session); System.out.println("Server session close"); } } public static void main(String[] args) throws IOException { new MinaServer(); } }
提供server后,分两种情况:
1.接受客户端心跳包,日志输出:
+ : isRequest : true
getResponse
+ : isResponse : false
+ : isRequest : true
14:14:29.262 [NioProcessor-2] INFO o.a.m.filter.logging.LoggingFilter - SENT: HeapBuffer[pos=0 lim=0 cap=0: empty]
- : isRequest : false
- : isResponse : true
2.主动发送心跳包到客户端,日志输出:
发送
getRequest
14:14:44.313 [NioProcessor-2] INFO o.a.m.filter.logging.LoggingFilter - SENT: HeapBuffer[pos=0 lim=0 cap=0: empty]
+ : isRequest : true
收到客户端响应
- : isRequest : false
- : isResponse : true
- : isRequest : false
- : isResponse : true