Mina 断线重连

定义:这里讨论的Mina 断线重连是指使用mina作为客户端软件,连接其他提供Socket通讯服务的服务器端。Socket服务器可以是Mina提供的服务器,也可以是C++提供的服务器。

一、断线重连的方式;

1. 在创建Mina客户端时增加一个监听器,或者增加一个拦截器,当检测到Session关闭时,自动进行重连。

2. 在第1种方式的基础上,增加客户端的读写通道空闲检查,当发生Session关闭或者读写空闲时,进行重连。

第一种方式比较传统,优点是简单方便,适合网络稳定、数据量不大(1M带宽以下)的环境;不过缺点是不能对系统级的连接断开阻塞进行捕获。

第二种方式更加精细,基本上能捕获到应用、网络、系统级的断连。

二、重连目的:

在使用Mina做为客户端时,往往因为网络、服务器、应用程序出现问题而导致连接断开,而自动重连,就是解决连接断开的唯一方式。如果网线断开、服务器宕机、应用程序挂了,都是断线的原因,这个时候,通过增加一个监听器或者拦截器,就能实现重连。但是生产环境中,断线的原因可能更复杂:网络不稳定、延时、服务器负载高、服务器或者应用程序的发送或者接收缓冲区满等等问题都可能导致数据传输过程出现类似于断线的情况,这个时候,光检测Session关闭是远远不够的,这个时候就需要一种重连机制,比如读写空闲超过30秒,就进行重连。对于数据不间断、实时性高、数据量大的应用场景,更是实用。

三、实例:

第一种:监听器方式

创建一个监听器实现mina的IoServiceListener接口,里面的方法可以不用写实现

  1. <span style="font-family: 'Microsoft YaHei',微软雅黑,SimHei,tahoma,arial,helvetica,sans-serif;">import org.apache.mina.core.service.IoService;
  2. import org.apache.mina.core.service.IoServiceListener;
  3. import org.apache.mina.core.session.IdleStatus;
  4. import org.apache.mina.core.session.IoSession;
  5. public class IoListener implements IoServiceListener{
  6. @Override
  7. public void serviceActivated(IoService arg0) throws Exception {
  8. // TODO Auto-generated method stub
  9. }
  10. @Override
  11. public void serviceDeactivated(IoService arg0) throws Exception {
  12. // TODO Auto-generated method stub
  13. }
  14. @Override
  15. public void serviceIdle(IoService arg0, IdleStatus arg1) throws Exception {
  16. // TODO Auto-generated method stub
  17. }
  18. @Override
  19. public void sessionCreated(IoSession arg0) throws Exception {
  20. // TODO Auto-generated method stub
  21. }
  22. @Override
  23. public void sessionDestroyed(IoSession arg0) throws Exception {
  24. // TODO Auto-generated method stub
  25. }
  26. }</span>

再创建客户端时加入监听

  1. <span style="font-family: 'Microsoft YaHei',微软雅黑,SimHei,tahoma,arial,helvetica,sans-serif;">        NioSocketConnector connector = new NioSocketConnector();  //创建连接客户端
  2. connector.setConnectTimeoutMillis(30000); //设置连接超时
  3. connector.getSessionConfig().setReceiveBufferSize(10240);   // 设置接收缓冲区的大小
  4. connector.getSessionConfig().setSendBufferSize(10240);// 设置输出缓冲区的大小
  5. //      加入解码器
  6. TextLineCodecFactory factory = new TextLineCodecFactory(Charset.forName("GBK"), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue());
  7. factory.setDecoderMaxLineLength(10240);
  8. factory.setEncoderMaxLineLength(10240);
  9. connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(factory));
  10. connector.setDefaultRemoteAddress(new InetSocketAddress(host, port));// 设置默认访问地址
  11. //添加处理器
  12. connector.setHandler(new IoHandler());
  13. // 添加重连监听
  14. connector.addListener(new IoListener() {
  15. @Override
  16. public void sessionDestroyed(IoSession arg0) throws Exception {
  17. for (;;) {
  18. try {
  19. Thread.sleep(3000);
  20. ConnectFuture future = connector.connect();
  21. future.awaitUninterruptibly();// 等待连接创建成功
  22. session = future.getSession();// 获取会话
  23. if (session.isConnected()) {
  24. logger.info("断线重连[" + connector.getDefaultRemoteAddress().getHostName() + ":" + connector.getDefaultRemoteAddress().getPort() + "]成功");
  25. break;
  26. }
  27. } catch (Exception ex) {
  28. logger.info("重连服务器登录失败,3秒再连接一次:" + ex.getMessage());
  29. }
  30. }
  31. }
  32. });
  33. for (;;) {
  34. try {
  35. ConnectFuture future = connector.connect();
  36. future.awaitUninterruptibly(); // 等待连接创建成功
  37. session = future.getSession(); // 获取会话
  38. logger.info("连接服务端" + host + ":" + port + "[成功]" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
  39. break;
  40. } catch (RuntimeIoException e) {
  41. logger.error("连接服务端" + host + ":" + port + "失败" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ", 连接MSG异常,请检查MSG端口、IP是否正确,MSG服务是否启动,异常内容:" + e.getMessage(), e);
  42. Thread.sleep(5000);// 连接失败后,重连间隔5s
  43. }
  44. }
  45. </span>

第一种:拦截器方式

  1. <span style="font-family: 'Microsoft YaHei',微软雅黑,SimHei,tahoma,arial,helvetica,sans-serif;">        connector = new NioSocketConnector();  //创建连接客户端
  2. connector.setConnectTimeoutMillis(30000); //设置连接超时
  3. //      断线重连回调拦截器
  4. connector.getFilterChain().addFirst("reconnection", new IoFilterAdapter() {
  5. @Override
  6. public void sessionClosed(NextFilter nextFilter, IoSession ioSession) throws Exception {
  7. for(;;){
  8. try{
  9. Thread.sleep(3000);
  10. ConnectFuture future = connector.connect();
  11. future.awaitUninterruptibly();// 等待连接创建成功
  12. session = future.getSession();// 获取会话
  13. if(session.isConnected()){
  14. logger.info("断线重连["+ connector.getDefaultRemoteAddress().getHostName() +":"+ connector.getDefaultRemoteAddress().getPort()+"]成功");
  15. break;
  16. }
  17. }catch(Exception ex){
  18. logger.info("重连服务器登录失败,3秒再连接一次:" + ex.getMessage());
  19. }
  20. }
  21. }
  22. });
  23. TextLineCodecFactory factory = new TextLineCodecFactory(Charset.forName(encoding), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue());
  24. factory.setDecoderMaxLineLength(10240);
  25. factory.setEncoderMaxLineLength(10240);
  26. //加入解码器
  27. connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(factory));
  28. //添加处理器
  29. connector.setHandler(new IoHandler());
  30. connector.getSessionConfig().setReceiveBufferSize(10240);   // 设置接收缓冲区的大小
  31. connector.getSessionConfig().setSendBufferSize(10240);          // 设置输出缓冲区的大小
  32. connector.setDefaultRemoteAddress(new InetSocketAddress(host, port));// 设置默认访问地址
  33. for (;;) {
  34. try {
  35. ConnectFuture future = connector.connect();
  36. // 等待连接创建成功
  37. future.awaitUninterruptibly();
  38. // 获取会话
  39. session = future.getSession();
  40. logger.error("连接服务端" + host + ":" + port + "[成功]" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
  41. break;
  42. } catch (RuntimeIoException e) {
  43. logger.error("连接服务端" + host + ":" + port + "失败" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ", 连接MSG异常,请检查MSG端口、IP是否正确,MSG服务是否启动,异常内容:" + e.getMessage(), e);
  44. Thread.sleep(5000);// 连接失败后,重连间隔5s
  45. }
  46. }</span>

第二种:加入空闲检测机制

空闲检测机制需要在创建客户端时,加入空闲超时,然后在处理器handler端的sessionIdle方法中加入一个预关闭连接的方法。让Session关闭传递到监听器或者拦截器的sessionClose方法中实现重连。

以拦截器方式为例,在创建客户端时,加入读写通道空闲检查超时机制。

  1. <span style="font-family: 'Microsoft YaHei',微软雅黑,SimHei,tahoma,arial,helvetica,sans-serif;">        connector = new NioSocketConnector();  //创建连接客户端
  2. connector.setConnectTimeoutMillis(30000); //设置连接超时
  3. //      断线重连回调拦截器
  4. connector.getFilterChain().addFirst("reconnection", new IoFilterAdapter() {
  5. @Override
  6. public void sessionClosed(NextFilter nextFilter, IoSession ioSession) throws Exception {
  7. for(;;){
  8. try{
  9. Thread.sleep(3000);
  10. ConnectFuture future = connector.connect();
  11. future.awaitUninterruptibly();// 等待连接创建成功
  12. session = future.getSession();// 获取会话
  13. if(session.isConnected()){
  14. logger.info("断线重连["+ connector.getDefaultRemoteAddress().getHostName() +":"+ connector.getDefaultRemoteAddress().getPort()+"]成功");
  15. break;
  16. }
  17. }catch(Exception ex){
  18. logger.info("重连服务器登录失败,3秒再连接一次:" + ex.getMessage());
  19. }
  20. }
  21. }
  22. });
  23. connector.getFilterChain().addLast("mdc", new MdcInjectionFilter());
  24. TextLineCodecFactory factory = new TextLineCodecFactory(Charset.forName(encoding), LineDelimiter.WINDOWS.getValue(), LineDelimiter.WINDOWS.getValue());
  25. factory.setDecoderMaxLineLength(10240);
  26. factory.setEncoderMaxLineLength(10240);
  27. //加入解码器
  28. connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(factory));
  29. connector.getSessionConfig().setReceiveBufferSize(10240);   // 设置接收缓冲区的大小
  30. connector.getSessionConfig().setSendBufferSize(10240);// 设置输出缓冲区的大小
  31. connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 30000);  //读写都空闲时间:30秒
  32. connector.getSessionConfig().setIdleTime(IdleStatus.READER_IDLE, 40000);//读(接收通道)空闲时间:40秒
  33. connector.getSessionConfig().setIdleTime(IdleStatus.WRITER_IDLE, 50000);//写(发送通道)空闲时间:50秒
  34. //添加处理器
  35. connector.setHandler(new IoHandler());
  36. connector.setDefaultRemoteAddress(new InetSocketAddress(host, port));// 设置默认访问地址
  37. for (;;) {
  38. try {
  39. ConnectFuture future = connector.connect();
  40. // 等待连接创建成功
  41. future.awaitUninterruptibly();
  42. // 获取会话
  43. session = future.getSession();
  44. logger.error("连接服务端" + host + ":" + port + "[成功]" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
  45. break;
  46. } catch (RuntimeIoException e) {
  47. System.out.println("连接服务端" + host + ":" + port + "失败" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ", 连接MSG异常,请检查MSG端口、IP是否正确,MSG服务是否启动,异常内容:" + e.getMessage());
  48. logger.error("连接服务端" + host + ":" + port + "失败" + ",,时间:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) + ", 连接MSG异常,请检查MSG端口、IP是否正确,MSG服务是否启动,异常内容:" + e.getMessage(), e);
  49. Thread.sleep(5000);// 连接失败后,重连10次,间隔30s
  50. }
  51. }</span>

然后在数据处理器IoHandler中sessionIdle方法中加入Session会话关闭的代码,这样session关闭就能传递到拦截器或者监听器中,然后实现重连。

  1. <span style="font-family: 'Microsoft YaHei',微软雅黑,SimHei,tahoma,arial,helvetica,sans-serif;">import org.apache.mina.core.service.IoHandlerAdapter;
  2. import org.apache.mina.core.session.IdleStatus;
  3. import org.apache.mina.core.session.IoSession;
  4. public class IoHandler extends IoHandlerAdapter {
  5. //部分代码忽略...
  6. @Override
  7. public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
  8. logger.info("-客户端与服务端连接[空闲] - " + status.toString());
  9. if(session != null){
  10. session.close(true);
  11. }
  12. }
  13. //部分代码忽略...
  14. }</span>

总结-最佳实践:

以上两种方式我个人认为最好是使用第二种。在实际的生产环境,对于数据量比较少的情况下,需要加一个线程专门发送心跳信息,然后在服务器端进行回应心跳,这样就保证读写通道不出现空闲。如果数据量比较大,大到24小时都有数据,那么就不需要心跳线程,可以直接在IoHandler处理器端中messageReceived方法中定时发送心跳到服务器。由于读写监控还可以处理服务器、网络、应用等等方面的不确定因素,所以建议使用第二种方式。

Mina 断线重连的更多相关文章

  1. Mina.Net实现的断线重连

    using Mina.Filter.Codec; using Mina.Filter.Codec.TextLine; using System; using System.Collections.Ge ...

  2. 关于socket tcp 断线重连

    这个问题困扰过我几次,都没有来得及研究,今天研究一下. 首先写一个最简易的socket tcp程序,连接成功后再关闭服务器然后再用客户端各种操作看是什么情况 测试表明 (1)客户端已经连接,当服务端关 ...

  3. netty4 断线重连

    转载:http://www.tuicool.com/articles/B7RzMbY 一 实现心跳检测 原理:当服务端每隔一段时间就会向客户端发送心跳包,客户端收到心跳包后同样也会回一个心跳包给服务端 ...

  4. Netty 客户端断线重连

    client 关闭后会执行 finally 代码块,可以在这里可以进行重连操作 public class NettyClient implements Runnable { private final ...

  5. ActiveMQ的断线重连机制

    断线重连机制是ActiveMQ的高可用性具体体现之一.ActiveMQ提供failover机制去实现断线重连的高可用性,可以使得连接断开之后,不断的重试连接到一个或多个brokerURL. 默认情况下 ...

  6. ADOConnection断线重连

    问题: ADOConnection断线重连问题描述: 使用ADOConnection连接oracle数据库,开始正常,当网络断开时数据库连接失败(此时查询ADOConnection.connected ...

  7. 发现电脑上装着liteide,就用golang做一个TCP通讯测试(支持先启动client端和断线重连)

    1.参考https://www.cnblogs.com/yin5th/p/9274495.html server端 main.go package main import ( "fmt&qu ...

  8. Netty断线重连

    Netty断线重连 最近使用Netty开发一个中转服务,需要一直保持与Server端的连接,网络中断后需要可以自动重连,查询官网资料,实现方案很简单,核心思想是在channelUnregistered ...

  9. 浅谈IM软件client的断线重连、心跳和长在线

    版权声明:原创文章,未经博主同意禁止转载.欢迎点击头像上方"郭晓东的专栏"查看专栏 https://blog.csdn.net/hherima/article/details/27 ...

随机推荐

  1. 读书笔记 effective C++ Item 33 避免隐藏继承而来的名字

    1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...

  2. 利用CSS函数calc(...)实现Web页面左右布局

    前言 因为自己的网站需要,想要做一个左右布局的页面: 左边是导航菜单之类的东西.右边是文档内容(因为最近看的一些软件的文档页面都是这么布局的): 左边固定宽度——300像素.右边使用剩余的宽度: 左边 ...

  3. day07作业

    import java.util.Scanner; class SsqGame { public static void main(String[] args) { int total = 0;//买 ...

  4. MySQL学习笔记:like和regexp的区别

    一.like关键字 like有两个模式:_和% _:表示单个字符,用来查询定长的数据 select name from table where name like '陈__'; %:表示0个或多个任意 ...

  5. JQuery中Table标签页和无缝滚动

    HTML代码: <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <t ...

  6. 一步一步学习IdentityServer3 (6)

    上一个章节提到了数据持久化 下面说一说自定义登录界面,Idr3提供了很多服务接口,其中一个就是 ViewService,我们只需要去注册 IViewService 这个接口的实现 提供了一个泛型视图服 ...

  7. CF 586B 起点到终点的最短路和次短路之和

    起点是右下角  终点是左上角 每次数据都是两行的点  输入n 表示有n列 接下来来的2行是 列与列之间的距离 最后一行是  行之间的距离 枚举就行   Sample test(s) input 41 ...

  8. Spark(三)RDD与广播变量、累加器

    一.RDD的概述 1.1 什么是RDD RDD(Resilient Distributed Dataset)叫做弹性分布式数据集,是Spark中最基本的数据抽象,它代表一个不可变.可分区.里面的元素可 ...

  9. 数据迁移之Sqoop

    一 简介 Apache Sqoop(TM)是一种用于在Apache Hadoop和结构化数据存储(如关系数据库)之间高效传输批量数据的工具 . 官方下载地址:http://www.apache.org ...

  10. GMM与EM算法

    用EM算法估计GMM模型参数 参考  西瓜书 再看下算法流程