前面我们分析了MINA框架的源码,大家可以从这里进行查看,这篇博客我们使用MINA来完成项目中经常会用到的长连接、短连接、断线重连功能,直接开始了;
实现思路是:
(1):为MINA客户端设置监听器,具体来讲的话就是实现MINA框架为我们提供的IoServiceListener接口,当客户端与服务端的Session会话关闭的时候,客户端会回调这个接口里面的sessionDestroyed方法,我们便可以在这个方法进行断线重连的操作了,我们设置断线重连的次数为5次,每个连接的超时时间是5s,也就是说如果1次连接在5s内没有得到响应的话,我们就认为这次连接失败了,进行重连,重连次数超过5次就认为服务器端出现了严重问题,或者是网络环境实在是太差了,结束断点重连;
(2):除此之外,因为MINA底层实现是NIO,是有用到Buffer缓存的,因此我们使用MINA中的提供给我们的空闲检测机制进行了连接预关闭,客户端重连的操作,什么意思呢?就是说如果客户端在一段时间内没有向服务端发送数据的话,那么我们就会预关闭客户端服务端之间的Session会话,这样的话就可以清空该Session会话对应的Buffer缓存了,避免了如果下次操作需要更大缓存出现的缓存不足现象;具体实现是在客户端一段时间内没有向服务端传递数据的话,会回调服务端IoHandlerAdapter的sessionIdle方法,我们在这个方法里面调用IoSession的close方法,这样的话,客户端服务端之间的连接就断开了,相应的就会回调前面为客户端设置的sessionDestroyed方法,在这个方法里面进行重连就可以啦!
(3):实际中,客户端在第一次连接服务器的时候一般是没有连接次数限制的,也就是一直尝试连接,直到你连接成功,当然你也可以设置连接次数限制;
下面我附上实现主要代码,具体代码可以在文章末尾点击连接下载:
服务端:
public class HeartBeatServer {
public static void main(String[] args) {
HeartBeatServer server = new HeartBeatServer();
server.initServerMina(10000);
}
/**
* 初始化服务端MINA
* @param port 绑定的端口号
*/
public void initServerMina(int port)
{
NioSocketAcceptor acceptor = new NioSocketAcceptor();
HeartBeatHandler handler = new HeartBeatHandler();//创建一个Handler对象用于处理业务逻辑
acceptor.setHandler(handler);
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//添加Filter对象
acceptor.getSessionConfig().setReadBufferSize(2048);//设置读取数据缓存区的大小
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, AcceptorUtils.IDEL_TIME);//设置IDEL_TIME时间没有发送消息的话则处于空闲状态
try {
acceptor.bind(new InetSocketAddress(port));//绑定IP地址与端口号
} catch (IOException e) {
e.printStackTrace();
}
}
}
客户端:
public class MainActivity extends Activity {
public Button mButton;
public Intent intent = null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mButton = (Button) findViewById(R.id.button);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View arg0) {
//开启连接Service
intent = new Intent(MainActivity.this, HeartBeatService.class);
startService(intent);
}
});
}
@Override
protected void onDestroy() {
stopService(intent);
super.onDestroy();
}
}
public class HeartBeatService extends Service{
@Override
public void onCreate() {
//开启单独的线程,因为Service是位于主线程的,为了避免主线程被阻塞
HeartBeatThread thread = new HeartBeatThread();
thread.start();
super.onCreate();
}
class HeartBeatThread extends Thread
{
@Override
public void run() {
initClientMina(ConnectUtils.HOST, ConnectUtils.PORT);
}
}
/**
* 初始化客户端MINA
* @param host
* @param port
*/
public void initClientMina(String host,int port)
{
NioSocketConnector connector = null;
try {
connector = new NioSocketConnector();
HeartBeatHandler handler = new HeartBeatHandler();//创建handler对象,用于业务逻辑处理
connector.setHandler(handler);
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));//添加Filter对象
} catch (Exception e2) {
e2.printStackTrace();
System.out.println(e2.toString());
}
connector.setConnectTimeout(ConnectUtils.TIMEOUT);//设置连接超时时间
int count = 0;//记录连接次数
while(true)
{
try {
count++;
//执行到这里表示客户端刚刚启动需要连接服务器,第一次连接服务器的话是没有尝试次数限制的,但是随后的断线重连就有次数限制了
ConnectFuture future = connector.connect(new InetSocketAddress(ConnectUtils.HOST, ConnectUtils.PORT));
future.awaitUninterruptibly();//一直阻塞,直到连接建立
IoSession session = future.getSession();//获取Session对象
if(session.isConnected())
{
//表示连接成功
System.out.println(ConnectUtils.stringNowTime()+" : 客户端连接服务器成功.....");
break;
}
} catch (RuntimeIoException e) {
System.out.println(ConnectUtils.stringNowTime()+" : 第"+count+"次客户端连接服务器失败,因为"+ConnectUtils.TIMEOUT+"s没有连接成功");
try {
Thread.sleep(2000);//如果本次连接服务器失败,则间隔2s后进行重连操作
System.out.println(ConnectUtils.stringNowTime()+" : 开始第"+(count+1)+"次连接服务器");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
//为MINA客户端添加监听器,当Session会话关闭的时候,进行自动重连
connector.addListener(new HeartBeatListener(connector));
}
@Override
public IBinder onBind(Intent arg0) {
return null;
}
}
public class HeartBeatListener implements IoServiceListener {
public NioSocketConnector connector;
public HeartBeatListener(NioSocketConnector connector)
{
this.connector = connector;
}
@Override
public void serviceActivated(IoService arg0) throws Exception {
}
@Override
public void serviceDeactivated(IoService arg0) throws Exception {
}
@Override
public void serviceIdle(IoService arg0, IdleStatus arg1) throws Exception {
}
@Override
public void sessionClosed(IoSession arg0) throws Exception {
System.out.println("hahahaha");
}
@Override
public void sessionCreated(IoSession arg0) throws Exception {
}
@Override
public void sessionDestroyed(IoSession arg0){
repeatConnect("");
}
/*
* 断线重连操作
* @param content
*/
public void repeatConnect(String content)
{
// 执行到这里表示Session会话关闭了,需要进行重连,我们设置每隔3s重连一次,如果尝试重连5次都没成功的话,就认为服务器端出现问题,不再进行重连操作
int count = 0;// 记录尝试重连的次数
while (true) {
try {
count++;// 重连次数加1
ConnectFuture future = connector.connect(new InetSocketAddress(
ConnectUtils.HOST, ConnectUtils.PORT));
future.awaitUninterruptibly();// 一直阻塞住等待连接成功
IoSession session = future.getSession();// 获取Session对象
if (session.isConnected()) {
// 表示重连成功
System.out.println(content + ConnectUtils.stringNowTime() + " : 断线重连" + count
+ "次之后成功.....");
count = 0;
break;
}
} catch (Exception e) {
if (count == ConnectUtils.REPEAT_TIME) {
System.out.println(content + ConnectUtils.stringNowTime() + " : 断线重连"
+ ConnectUtils.REPEAT_TIME + "次之后仍然未成功,结束重连.....");
break;
} else
{
System.out.println(content + ConnectUtils.stringNowTime() + " : 本次断线重连失败,3s后进行第" + (count + 1) + "次重连.....");
try {
Thread.sleep(3000);
System.out.println(content + ConnectUtils.stringNowTime() + " : 开始第"+(count + 1)+"次重连.....");
} catch (InterruptedException e1) {
e1.printStackTrace();
}
}
}
}
}
}
我们来运行下程序,查看到客户端输出结果截图:
(1):红色部分表示我们还没在启动服务端时候,客户端会一直尝试建立连接,连接超时时间设置为5s,连接失败之后,暂停2s进行连接,因为是第一次连接嘛,所以我们没有对尝试连接次数做出限制;
(2):绿色部分是我们连接成功后,但是客户端10s内没有向服务端发送数据,那么这时候服务端就会调用sessionIdle方法来预关闭连接了,客户端回调IoServiceListener的sessionDestroyed方法在它里面进行重连操作,因为这时候服务端没挂且网络环境比较好,每次重连一次就成功了;
(3):蓝色部分是我们将服务器关闭又开启之后的输出,可以看到尝试建立4次之后连接建立成功;
(4):紫色部分是我们关闭服务器之后没有重新开启的输出,可以发现客户端在尝试重连5次之后不再进行连接操作;
注意几点:
(1):我们上面是通过在Service开启线程的方式来在后台运行长连接的建立和断线重连操作的,注意不能直接在Service里面进行这些操作,因为Service是位于主线程的,可能会带来ANR异常;
(2):不要忘记添加网络权限:
(3):如果你的应用是在已启动的时候就要进行长连接的建立,比如接收服务端的推送之类的功能,那么建议你通过闹钟服务来开启你的Service,这样的话能够保证你的长连接尽可能的存在;
还有一点就是MINA也是支持短连接的,也就是请求/响应模式,具体来讲我们可以在服务端回应客户端之后关闭Session就可以了;
点击下载源代码