Mina是Apache提供的socket框架。Mina提供基于Java NIO的Reactor网络模型API,并且封装了会话层、表示层,减轻开发者开发编码器、解码器的负担,即便不需要NIO的特性也能很大程序的提高开发效率。通过Mina内置的API可以方便地解决粘包缺包问题,方便的将字节报文转换为应用层消息报文,通过IDLE事件可以轻松开发心跳协议,支持并发地处理应用层报文,满足于文件的并发随机读写需求。
官网地址:http://mina.apache.org/mina-project/index.html
在Android应用开发时,通过官网下载后解压,一般需要用到的包如下,导入到libs中:
mina-core-2.0.17.jar //核心包
slf4j-api-1.7.25.jar //辅助使用,提供如Log工具类等,必须
Mina的简单实用
服务端 MinaServer
try {NioSocketAcceptor acceptor = new NioSocketAcceptor();acceptor.setHandler(new ServerHandler); //设置消息处理acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));acceptor.bind(new InetSocketAddress(8989)); //指定端口号} catch (IOException e) {e.printStackTrace();}
消息处理过程 ServerHandler
public class ServerHandler extends IoHandlerAdapter {@Overridepublic void sessionCreated(IoSession session) throws Exception {Log.d(TAG, "sessionCreated");}@Overridepublic void sessionOpened(IoSession session) throws Exception {//建立连接Log.d(TAG, "sessionOpened");}@Overridepublic void sessionClosed(IoSession session) throws Exception {//关闭连接Log.d(TAG, "sessionClosed");}@Overridepublic void sessionIdle(IoSession session, IdleStatus status) throws Exception {//空闲Log.d(TAG, "sessionIdle status: " + status.toString());}@Overridepublic void exceptionCaught(IoSession session, Throwable cause) throws Exception {//异常Log.e(TAG, "exceptionCaught cause: " + cause);}@Overridepublic void messageReceived(IoSession session, Object message) throws Exception {//接收消息Log.d(TAG, "messageReceived message: " + message.toString());}@Overridepublic void messageSent(IoSession session, Object message) throws Exception {//发送消息Log.d(TAG, "messageSent message: " + message.toString());}}
客户端 MinaClient
NioSocketConnector connector = new NioSocketConnector();connector.setHandler(new ClientHandler());connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TextLineCodecFactory()));InetSocketAddress address = new InetSocketAddress(ip, 8989); //指定服务端IP和端口号ConnectFuture future = connector.connect(address);future.awaitUninterruptibly();ioSession = future.getSession();if (ioSession.isConnected()) {ioSession.write("Hi Server"); //发送消息}
ClientHandler的实现与Server端是一样的。
Mina实现Android消息发送的服务端和客户端
前一段时间由于项目需要,需要在两个android设备间实现一个socket消息传送的功能,一个作为服务端,一个作为客户端,建立连接后可以相互发送消息。
首先,看一下具体实现涉及的类:

服务端实现:
public class ConnectServer {private ConnectThread connectThread;private IoSession ioSession;private IConnectListener communicationListener;public ConnectServer() {connectThread = new ConnectThread();connectThread.start();}public void setCommunicationListener(IConnectListener listener) {this.communicationListener = listener;}public void closeConnectThread() {if (connectThread != null) {connectThread.interrupt();connectThread.disConnect();}}private class ConnectThread extends Thread {NioSocketAcceptor acceptor;@Overridepublic void run() {super.run();try {acceptor = new NioSocketAcceptor();TransferControlHandler handler = new TransferControlHandler();handler.setHandlerListener(handlerListener);acceptor.setHandler(handler); //指定消息处理类//指定消息编解码处理类acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TransferControlCodecFactory()));- //心跳检测
KeepAliveFilter keepAliveFilter = new KeepAliveFilter(new TransferKeepAliveFactory(), IdleStatus.BOTH_IDLE);keepAliveFilter.setRequestInterval(5); //设置心跳包请求时间间隔,单位秒keepAliveFilter.setRequestTimeout(10); //设置超时时间,单位秒acceptor.getFilterChain().addLast("heartbeat", keepAliveFilter);acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 11); //空闲时间间隔,单位秒//解决exception: bind failed: EADDRINUSE (Address already in use). 用户未正常退出server,client又很快请求连接时出现acceptor.setReuseAddress(true);acceptor.bind(new InetSocketAddress(CommunicationProtocol.PORT));} catch (IOException e) {e.printStackTrace();}}public void disConnect() { //断开服务端连接if (acceptor != null) {acceptor.unbind();acceptor.dispose();}}}public void sendMsg(CommunicationData data) {//发送消息if (ioSession != null && ioSession.isConnected()) {ioSession.write(data);}}private TransferControlHandler.HandlerListener handlerListener = new TransferControlHandler.HandlerListener() {@Overridepublic void sessionCreated(IoSession session) {}@Overridepublic void sessionOpened(IoSession session) {ioSession = session; //连接上后获取IoSession,用于发送消息等if (communicationListener != null) {communicationListener.onConnected();}}@Overridepublic void sessionClosed(IoSession session) {if (communicationListener != null) {communicationListener.onDisConnect();}}@Overridepublic void sessionIdle(IoSession session, IdleStatus status) {if (session.isBothIdle()) {session.closeOnFlush(); //读写超时后断开连接}}@Overridepublic void messageReceived(IoSession session, final CommunicationData data) { //接收消息ioSession = session;if (communicationListener != null) {communicationListener.onReceiveMsg(data);}}@Overridepublic void messageSent(IoSession session, CommunicationData data) {ioSession = session;}@Overridepublic void exceptionCaught(IoSession session, final Throwable cause) {if (communicationListener != null) {communicationListener.onException(cause);}}};}
编解码处理类 TransferControlCodecFactory 是在socket消息接收和发送时最先获取到的,对消息内容进行处理后才转发给Handler类。
public class TransferControlCodecFactory implements ProtocolCodecFactory {private TransferControlEncoder transferEncoder;private TransferControlDecoder transferDecoder;public TransferControlCodecFactory() {transferEncoder = new TransferControlEncoder(); //编码类transferDecoder = new TransferControlDecoder(); //解码类}@Overridepublic ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {return transferEncoder;}@Overridepublic ProtocolDecoder getDecoder(IoSession ioSession) throws Exception {return transferDecoder;}}
在socket通信时,如果消息内容比较复杂,需要定义通信协议,对收发的消息根据协议进行处理。协议格式可以自己约定,比如约定消息协议如下:
public class CommunicationProtocol {/*** 消息协议* Header 头部, 4 bytes {0xff,0xfe,0xff,0xfe}* Length 总长度, 4 bytes =(DataType length + DataContent length)* TypeLen 消息类型长度, 4 bytes* DataType 消息类型* DataContent 消息内容*/public static final byte[] COMMUNICATION_HEAD = {-1, -2, -1, -2};//0xff,0xfe,0xff,0xfe 报文头public static final int PORT = 7200; //端口号}
那么,对于接收到的消息,需要按照协议格式进行解析
public class TransferControlDecoder extends CumulativeProtocolDecoder {private CommunicationData data;public TransferControlDecoder(){data = new CommunicationData();}@Overrideprotected boolean doDecode(IoSession ioSession, IoBuffer ioBuffer, ProtocolDecoderOutput output) throws Exception {int start = ioBuffer.position();if (ioBuffer.remaining() < 12) {return false;}- //判断是否是协议头
for (int i = 0; i < 4; i++) {byte b = ioBuffer.get();if (b != CommunicationProtocol.COMMUNICATION_HEAD[i]) {return false;}}int dataLen = ioBuffer.getInt();int dataTypeLen = ioBuffer.getInt();if (ioBuffer.remaining() < dataLen) {//如果IoBuffer剩余字节数小于协议内容长度,重新定位到开始位置,并返回false(表示将数据缓存起来,下次和新数据一起合并处理)ioBuffer.position(start);return false;}byte[] dataTypeByte = new byte[dataTypeLen];ioBuffer.get(dataTypeByte, 0, dataTypeLen); //获取消息类型的内容data.type = new String(dataTypeByte);int dataContentLen = dataLen - dataTypeLen;byte[] dataContentByte = new byte[dataContentLen];ioBuffer.get(dataContentByte, 0, dataContentLen); //获取消息内容data.content = new String(dataContentByte);output.write(data); //将数据内容写出(类型是Object,因此可以是任意类型数据,此处我们将数据转化为CommunicationData类型,方便处理)return true;}}
相应地,对于要发送出的数据,也需要遵守协议格式,对数据进行处理
public class TransferControlEncoder extends ProtocolEncoderAdapter {@Overridepublic void encode(IoSession ioSession, Object message, ProtocolEncoderOutput output) throws Exception {if (message instanceof CommunicationData) { //判断是否是CommunicationData类型IoBuffer ioBuffer = wrapData((CommunicationData) message);output.write(ioBuffer);}}- //封装协议内容
private IoBuffer wrapData(CommunicationData data) {if (data.type == null) {return null;}int headLen = CommunicationProtocol.COMMUNICATION_HEAD.length;int dataTypeLen = data.type.getBytes().length;int dataContentLen = data.content != null ? data.content.getBytes().length : 0;int dataLen = dataTypeLen + dataContentLen;IoBuffer ioBuffer = IoBuffer.allocate(headLen + dataLen + 8);ioBuffer.setAutoExpand(true);ioBuffer.put(CommunicationProtocol.COMMUNICATION_HEAD); //协议头ioBuffer.putInt(dataLen); //消息总长度ioBuffer.putInt(dataTypeLen); //消息类型长度ioBuffer.put(data.type.getBytes()); //消息类型if (data.content != null) {ioBuffer.put(data.content.getBytes()); //消息内容}ioBuffer.flip();return ioBuffer;}}
下面实现消息处理类
public class TransferControlHandler extends IoHandlerAdapter {private static final String TAG = "TransferControl";private HandlerListener handlerListener;public TransferControlHandler() {}public void setHandlerListener(HandlerListener listener) {this.handlerListener = listener;}@Overridepublic void sessionCreated(IoSession session) throws Exception {Log.d(TAG, "sessionCreated");if (handlerListener != null) {handlerListener.sessionCreated(session);}}@Overridepublic void sessionOpened(IoSession session) throws Exception {Log.d(TAG, "sessionOpened");if (handlerListener != null) {handlerListener.sessionOpened(session);}}@Overridepublic void sessionClosed(IoSession session) throws Exception {Log.d(TAG, "sessionClosed");if (handlerListener != null) {handlerListener.sessionClosed(session);}}@Overridepublic void sessionIdle(IoSession session, IdleStatus status) throws Exception {Log.d(TAG, "sessionIdle status: " + status.toString());if (handlerListener != null) {handlerListener.sessionIdle(session, status);}}@Overridepublic void exceptionCaught(IoSession session, Throwable cause) throws Exception {Log.e(TAG, "exceptionCaught cause: " + cause);if (handlerListener != null) {handlerListener.exceptionCaught(session, cause);}}@Overridepublic void messageReceived(IoSession session, Object message) throws Exception {Log.d(TAG, "messageReceived message: " + message.toString());if (handlerListener != null) {CommunicationData data = null;if (message instanceof CommunicationData) { //经过Decoder后message类型已经是CommunicationData类型了data = (CommunicationData) message;}handlerListener.messageReceived(session, data);}}@Overridepublic void messageSent(IoSession session, Object message) throws Exception {Log.d(TAG, "messageSent message: " + message.toString());if (handlerListener != null) {CommunicationData data = null;if (message instanceof CommunicationData) {data = (CommunicationData) message;}handlerListener.messageSent(session, data);}}public interface HandlerListener {void sessionCreated(IoSession session);void sessionOpened(IoSession session);void sessionClosed(IoSession session);void sessionIdle(IoSession session, IdleStatus status);void messageReceived(IoSession session, CommunicationData data);void messageSent(IoSession session, CommunicationData data);void exceptionCaught(IoSession session, Throwable cause);}}
客户端实现
public class ConnectClient {private Context mContext;private ConnectThread connectThread;private IConnectListener communicationListener;public ConnectClient(Context context) {this.mContext = context;connectThread = new ConnectThread();connectThread.start();}public void setCommunicationListener(IConnectListener listener) {this.communicationListener = listener;}public void sendMsg(CommunicationData data) { //发送消息connectThread.sendMsg(data);}public void closeConnectThread() {if (connectThread != null) {connectThread.interrupt();connectThread.disConnect();}}private class ConnectThread extends Thread {private NioSocketConnector connector;private IoSession ioSession;@Overridepublic void run() {super.run();connector = new NioSocketConnector();TransferControlHandler handler = new TransferControlHandler();handler.setHandlerListener(handlerListener);connector.setHandler(handler); //指定消息处理类- //指定消息编解码处理类
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new TransferControlCodecFactory()));reConnect();}public void reConnect() {boolean bool = false;while (!bool) {bool = connect();try {Thread.sleep(5000); //5秒重连} catch (InterruptedException e) {e.printStackTrace();}}}private boolean connect() {if (connector == null) {return true;}String ip = WifiUtils.getInstance(mContext).getServerIPAddress();InetSocketAddress address = new InetSocketAddress(ip, CommunicationProtocol.PORT); //指定服务端IP和端口号try {ConnectFuture future = connector.connect(address);future.awaitUninterruptibly();ioSession = future.getSession(); //连接成功后的IoSession,可用于发送消息等} catch (Exception e) {return false;}return ioSession != null;}public void disConnect() {if (connector != null) {connector.dispose();connector = null;}}public void sendMsg(CommunicationData data) {if (ioSession != null && ioSession.isConnected()) {ioSession.write(data);}}}private TransferControlHandler.HandlerListener handlerListener = new TransferControlHandler.HandlerListener() {@Overridepublic void sessionCreated(IoSession session) {}@Overridepublic void sessionOpened(IoSession session) {if (communicationListener != null) {communicationListener.onConnected();}}@Overridepublic void sessionClosed(IoSession session) {if (communicationListener != null) {communicationListener.onDisConnect();}}@Overridepublic void sessionIdle(IoSession session, IdleStatus status) {}@Overridepublic void messageReceived(IoSession session, final CommunicationData data) {//接收消息if (communicationListener != null) {communicationListener.onReceiveMsg(data);}}@Overridepublic void messageSent(IoSession session, CommunicationData data) {}@Overridepublic void exceptionCaught(IoSession session, final Throwable cause) {if (communicationListener != null) {communicationListener.onException(cause);}}};}
好了,现在基于Mina框架 基本实现了一个完整的socket传送消息的功能,利用上述实现可以非常方便的发送和接收消息。
306

被折叠的 条评论
为什么被折叠?



