Apache mina使用入门

  Apache mina是一个基于NIO(非阻塞IO)模型的网络应用框架。详细资料和下载地址为:http://mina.apache.org/
  远程客户端通过IoService建立连接得到session,session将数据传送到IoFilterChain进行过滤,最后客户端在IoHandle中进行数据操作,其工作原理如图所示:

Apache mina使用入门_第1张图片

  此处记录的是Android中Apache mina于服务器建立的长连接通信示例。长连接和http短连接底层都是基于TCP/IP协议,长连接通过Socket,ServerSocket与服务器保持连接,服务端一般使用ServerSocket建立监听,监听客户端的连接,客户端使用Socket,指定端口和IP地址与服务端进行连接。通过长连接,可以实现服务器主动向客户端推送消息,可以减少客户端对服务器的轮询,减少服务器的压力,而且通信效率高于http,okhttp等。

Apache mina框架核心类:

  • IoService接口相关类:监听管理、session管理等
  • IoAcceptor及其相关的类:继承于IoService接口,实现TCP或UDP协议的监听器
  • IoConnector及其相关的类:继承于IoService接口,支持TCP或UDP协议与服务器建立连接
  • LoggingFilter 记录mina所有日志
  • ProtocolCodecFilter 数据转化过滤器
  • CompressionFilter 数据压缩过滤器
  • SSLFilter 数据加密与解密传输过滤器
  • IoSession类:通信管理类
  • Handler类:session事件的监听,消息收发监听,异常监听等

mina简单服务端建立

  创建java项目,导入mina-core-2.0.21.jar和slf4j-api-1.7.26.jar两个库文件,如不加slf4j包则会出现java.lang.ClassNotFoundException: org.slf4j.LoggerFactory错误。

  1. MinaServer类
public class MinaServer {
    static final int PORT = 55555;
    public static void main(String[] args) {
        IoAcceptor acceptor = new NioSocketAcceptor();
        //设置过滤器,主要设置协议的编码解码
        acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(
                new TextLineCodecFactory(
                        StandardCharsets.UTF_8,
                        LineDelimiter.WINDOWS.getValue(),
                        LineDelimiter.WINDOWS.getValue()
                )
        ));
        //添加日志过滤器
        acceptor.getFilterChain().addLast("logger", new LoggingFilter());
        //设置读缓存大小
        acceptor.getSessionConfig().setReadBufferSize(2048);
        //设置读写空闲时间相同,都为10s
        acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
        //注册处理器
        acceptor.setHandler(new MyHandler());
        // 绑定端口开始进行监听
        try {
            acceptor.bind(new InetSocketAddress(PORT));
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("端口 " + PORT + " 启动成功");
    }
}
  1. MyHandler类
/**
 * session对象的创建、消息收发等事件监听
 */
public class MyHandler extends IoHandlerAdapter {
    @Override
    public void sessionCreated(IoSession session) {
        System.out.println("session被创建,sessionId: " + session.getId());
    }

    @Override
    public void sessionOpened(IoSession session) {
        String address = session.getRemoteAddress().toString();
        System.out.println("session被打开,address is : " + address);
    }

    @Override
    public void sessionClosed(IoSession session) {
        System.out.println("session客户端关闭,");
    }

    /**
     * 消息的接收
     *
     * @param session
     * @param message
     */
    @Override
    public void messageReceived(IoSession session, Object message) {
        //假如是string类型消息
        String strMsg = message.toString();
        //向客户端返回消息
        session.write("服务端已收到消息: " + new Date().toString());
        System.out.println("接收到消息:" + strMsg);
    }

    /**
     * 消息的发送
     *
     * @param session
     * @param message
     * @throws Exception
     */
    @Override
    public void messageSent(IoSession session, Object message) {
    }
}

mina客户端建立

  新建Android项目,拷贝mina-core-2.0.21.jar和slf4j-api-1.7.26.jar两个库文件到libs目录并添加到dependencies,app\build.gradle如图所示:
Apache mina使用入门_第2张图片

  1. ConnectManage类
/**
 * 封装connection和disconnection方法给外部调用
 */
public class ConnectManage {
    private ConnectConfig mConfig;
    private WeakReference<Context> mContext;
    private NioSocketConnector mConnector;
    private IoSession mSession;
    private InetSocketAddress mAddress;
    private ConnectFuture mConnectFuture;

    public ConnectManage(ConnectConfig config) {
        this.mConfig = config;
        mContext = new WeakReference<>(mConfig.getContext());
        init();
    }
    /**
     * 初始化连接配置
     */
    private void init() {
        // 创建连接对象
        mConnector = new NioSocketConnector();
        // 设置连接地址
        mAddress = new InetSocketAddress(mConfig.getIp(), mConfig.getPort());
        mConnector.setDefaultRemoteAddress(mAddress);
        // 设置缓冲大小
        mConnector.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
        // 设置过滤
        mConnector.getFilterChain().addLast("logger", new LoggingFilter());
        mConnector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ObjectSerializationCodecFactory()));
        // 设置心跳包,断线重连
        KeepAliveMessageFactory heartBeatFactory = new KeepAliveMessageFactoryImpl();
        KeepAliveRequestTimeoutHandler heartBeatHandler = new KeepAliveRequestTimeoutHandlerImpl();
        KeepAliveFilter heartBeat = new KeepAliveFilter(heartBeatFactory, IdleStatus.BOTH_IDLE, heartBeatHandler);
        // 是否回发
        heartBeat.setForwardEvent(true);
        // 设置当连接的读取通道空闲的时候,心跳包请求时间间隔
        heartBeat.setRequestInterval(mConfig.getHeartInterval());
        // 设置心跳包请求后,若超过该时间后则调用KeepAliveRequestTimeoutHandler.CLOSE_NOW
        heartBeat.setRequestTimeout(mConfig.getHeartTimeOut());
        mConnector.getFilterChain().addLast("heartbeat", heartBeat);
        mConnector.getSessionConfig().setUseReadOperation(true);
        // 设置连接监听
        mConnector.setHandler(new ConnectHandler(mContext.get(), this));
        // 超时设置
        mConnector.setConnectTimeoutMillis(mConfig.getConnectionTimeout());
    }
    /**
     * 是否关闭Session
     *
     * @return
     */
    public boolean isCloseSession() {
        if (mSession != null) return mSession.isClosing();
        return false;
    }
    /**
     * 与服务器建立连接
     *
     * @return
     */
    public boolean connect() {
        try {
            mConnectFuture = mConnector.connect(mAddress);
            //一直等到连接为止
            mConnectFuture.awaitUninterruptibly();
            //获取session对象
            mSession = mConnectFuture.getSession();
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return mSession != null;
    }
    /**
     * 断开与服务器的连接
     */
    public void disConnect() {
        if (mConnector != null) mConnector.dispose();
        if (mConnectFuture != null) mConnectFuture.cancel();
        mConnector = null;
        mSession = null;
        mAddress = null;
        mContext = null;
        mConnectFuture = null;
    }
}
  1. ConnectConfig类
/**
 * 连接选项配置
 * 通过lombok+构建者模式实现config
 */
@Getter
@Setter
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
public class ConnectConfig {
    private Context context;       //内容
    private String ip;             //IP地址
    private int port;              //端口
    private int readBufferSize;    // 缓存大小
    private int connectionTimeout; // 连接超时时间
    private int heartInterval;     // 心跳间隔时间
    private int heartTimeOut;      // 心跳请求超时时间
}
  1. ConnectHandler类
/**
 * 业务处理类
 */
public class ConnectHandler extends IoHandlerAdapter {
    private Context context;
    private ConnectManage connectionManage;
    private static final String BROADCAST_ACTION = "mina";
    private static final String MESSAGE = "Message";

    ConnectHandler(Context context, ConnectManage connectionManage) {
        this.context = context;
        this.connectionManage = connectionManage;
    }

    @Override
    public void sessionCreated(IoSession session) throws Exception {
        super.sessionCreated(session);
        System.out.println("---创建连接---");
    }

    /**
     * 将session保存到session manage中,从而可以发送消息到服务器端
     */
    @Override
    public void sessionOpened(IoSession session) throws Exception {
        // 当与服务器连接成功时,将我们的session保存到我们的session manager类中,从而可以发送消息到服务器
        SessionManage.getInstance().setIoSession(session);
        System.out.println("---打开连接---");
    }

    @Override
    public void sessionClosed(IoSession session) throws Exception {
        super.sessionClosed(session);
        System.out.println("---关闭连接---");
        if (!SessionManage.getInstance().isCloseSession()) {// 关闭后自动重连
            if (connectionManage != null && connectionManage.isCloseSession())
                connectionManage.connect();
        } else {
            if (connectionManage != null) connectionManage.disConnect();
            connectionManage = null;
        }
    }

    @Override
    public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
        super.sessionIdle(session, status);
        System.out.println("---空闲---");
    }

    @Override
    public void messageReceived(IoSession session, Object message) throws Exception {
        if (context != null) {
            //通过Android局部广播LocalBroadcastManager机制发送消息
            Intent intent = new Intent(BROADCAST_ACTION);
            intent.putExtra(MESSAGE, message.toString());
            LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
        }
        System.out.println("---接收消息--- " + message);
    }

    @Override
    public void messageSent(IoSession session, Object message) throws Exception {
        super.messageSent(session, message);
        System.out.println("---发送消息--- " + message);
    }
}
  1. ConnectService类
/**
 * 服务管理类
 */
public class ConnectService extends Service {

    //负责调用connect manage完成于服务器的连接
    private ConnectThread connectThread;

    @Override
    public void onCreate() {
        super.onCreate();
        //初始化线程并启动
        connectThread = new ConnectThread("mina");
        connectThread.start();

    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return super.onStartCommand(intent, flags, startId);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        //断开连接并释放线程
        connectThread.disConnection();
        connectThread = null;
    }
}
  1. ConnectThread类
/**
 * 创建线程类来调用connection manage完成于服务器的连接
 */
public class ConnectThread extends HandlerThread {

    private ConnectManage mConnectManage;
    private boolean isConnection;

    ConnectThread(String name) {
        super(name);
        ConnectConfig connectConfig = new ConnectConfig.ConnectConfigBuilder()
                .ip("192.168.1.55")
                .port(55555)
                .readBufferSize(10240)
                .connectionTimeout(10000)
                .heartInterval(20)
                .heartTimeOut(5).build();

        mConnectManage = new ConnectManage(connectConfig);
    }

    @Override
    protected void onLooperPrepared() {
        // 利用循环请求连接
        while (!isConnection) {
            if (mConnectManage == null) break;
            //是否于服务器连接成功
            boolean isConnection = mConnectManage.connect();
            if (isConnection) {
                // 当请求成功的时候,跳出循环
                System.out.println("----------连接成功");
                break;
            }
            System.out.println("---------连接失败");
            try {
                Thread.sleep(2500);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 销毁服务器的连接
     */
    public void disConnection() {
        isConnection = true;
        if (getLooper() != null) getLooper().quit();
        if (mConnectManage != null) {
            mConnectManage.disConnect();
            mConnectManage = null;
        }
    }
}
  1. SessionManage类
/**
 * 会话管理类
 */
class SessionManage {
    private static SessionManage mSessionManage = null;
    private IoSession mSession;
    private boolean isCloseSession;

    //单例加锁机制
    public static SessionManage getInstance() {
        if (mSessionManage == null) {
            synchronized (SessionManage.class) {
                if (mSessionManage == null)
                    mSessionManage = new SessionManage();
            }
        }
        return mSessionManage;
    }

    public void setIoSession(IoSession ioSession) {
        this.mSession = ioSession;
    }

    public boolean isCloseSession() {
        return isCloseSession;
    }

    private void setCloseSession(boolean closeSession) {
        isCloseSession = closeSession;
    }

    /**
     * 将对象写到服务器
     */
    public void writeToServer(Object msg) {
        if (mSession != null) {
            mSession.write(msg.toString());
        }
    }

    /**
     * 关闭连接
     */
    public void closeSession() {
        if (mSession != null) {
            mSession.closeNow();
            mSession.closeOnFlush();
            mSession.getService().dispose();
        }
        mSession = null;
        setCloseSession(false);
    }
}
  1. KeepAliveRequestTimeoutHandlerImpl类
/**
 * 当心跳超时时的处理
 */
class KeepAliveRequestTimeoutHandlerImpl implements KeepAliveRequestTimeoutHandler {
    @Override
    public void keepAliveRequestTimedOut(KeepAliveFilter keepAliveFilter, IoSession ioSession) throws Exception {
        ioSession.closeNow();
    }
}
  1. KeepAliveMessageFactoryImpl类
/**
 * 我这里没做啥操作
 */
class KeepAliveMessageFactoryImpl implements KeepAliveMessageFactory {
    @Override
    public boolean isRequest(IoSession ioSession, Object o) {
        return false;
    }

    @Override
    public boolean isResponse(IoSession ioSession, Object o) {
        return false;
    }

    @Override
    public Object getRequest(IoSession ioSession) {
        return null;
    }

    @Override
    public Object getResponse(IoSession ioSession, Object o) {
        return null;
    }
}
  1. MainActivity测试类
public class MainActivity extends AppCompatActivity {
    
    private Button bt1, bt2;
    private TextView view3;
    //自定义广播接收器
    private MessageBroadcastReceiver receiver;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        receiver = new MessageBroadcastReceiver();
        registBroadcast();
    }

    private void initView() {
        bt1 = findViewById(R.id.view);
        bt2 = findViewById(R.id.view2);
        view3 = findViewById(R.id.textView3);

        bt1.setOnClickListener(v -> SessionManage.getInstance().writeToServer("这是客户端发送的消息"));
        bt2.setOnClickListener(v -> {
            Intent intent = new Intent(MainActivity.this, ConnectService.class);
            startService(intent);
        });
    }

    private void registBroadcast() {
        IntentFilter filter = new IntentFilter("mina");
        LocalBroadcastManager.getInstance(this).registerReceiver(receiver, filter);
    }

    private void unregistBroadcast() {
        LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
    }
    /**
     * 接受数据并更新UI
     */
    private class MessageBroadcastReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            view3.setText(intent.getStringArrayExtra("Message").toString());
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        stopService(new Intent(this, ConnectService.class));
        unregistBroadcast();
    }
}

若Android客户端启动时报错:
Apache mina使用入门_第3张图片
解决办法是:在app\build.gradle中增加includeCompileClasspath true。如图所示:
Apache mina使用入门_第4张图片
若报错信息为:
Apache mina使用入门_第5张图片
解决办法是:在app\build.gradle中增加java8的编译。如图所示:
Apache mina使用入门_第6张图片
通常为防止客户端连接不上服务端,可以关闭服务端的防火墙再试试。


References:

  • MINA 2.0 User Guide
  • 长连接利器—网络框架解析之mina篇

你可能感兴趣的:(Java/Android,android)