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 {
@Override
public void sessionCreated(IoSession session) throws Exception {
Log.d(TAG, "sessionCreated");
}
@Override
public void sessionOpened(IoSession session) throws Exception {
//建立连接
Log.d(TAG, "sessionOpened");
}
@Override
public void sessionClosed(IoSession session) throws Exception {
//关闭连接
Log.d(TAG, "sessionClosed");
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
//空闲
Log.d(TAG, "sessionIdle status: " + status.toString());
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
//异常
Log.e(TAG, "exceptionCaught cause: " + cause);
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
//接收消息
Log.d(TAG, "messageReceived message: " + message.toString());
}
@Override
public 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;
@Override
public 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() {
@Override
public void sessionCreated(IoSession session) {
}
@Override
public void sessionOpened(IoSession session) {
ioSession = session; //连接上后获取IoSession,用于发送消息等
if (communicationListener != null) {
communicationListener.onConnected();
}
}
@Override
public void sessionClosed(IoSession session) {
if (communicationListener != null) {
communicationListener.onDisConnect();
}
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) {
if (session.isBothIdle()) {
session.closeOnFlush(); //读写超时后断开连接
}
}
@Override
public void messageReceived(IoSession session, final CommunicationData data) { //接收消息
ioSession = session;
if (communicationListener != null) {
communicationListener.onReceiveMsg(data);
}
}
@Override
public void messageSent(IoSession session, CommunicationData data) {
ioSession = session;
}
@Override
public 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(); //解码类
}
@Override
public ProtocolEncoder getEncoder(IoSession ioSession) throws Exception {
return transferEncoder;
}
@Override
public 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();
}
@Override
protected 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 {
@Override
public 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;
}
@Override
public void sessionCreated(IoSession session) throws Exception {
Log.d(TAG, "sessionCreated");
if (handlerListener != null) {
handlerListener.sessionCreated(session);
}
}
@Override
public void sessionOpened(IoSession session) throws Exception {
Log.d(TAG, "sessionOpened");
if (handlerListener != null) {
handlerListener.sessionOpened(session);
}
}
@Override
public void sessionClosed(IoSession session) throws Exception {
Log.d(TAG, "sessionClosed");
if (handlerListener != null) {
handlerListener.sessionClosed(session);
}
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
Log.d(TAG, "sessionIdle status: " + status.toString());
if (handlerListener != null) {
handlerListener.sessionIdle(session, status);
}
}
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
Log.e(TAG, "exceptionCaught cause: " + cause);
if (handlerListener != null) {
handlerListener.exceptionCaught(session, cause);
}
}
@Override
public 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);
}
}
@Override
public 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;
@Override
public 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() {
@Override
public void sessionCreated(IoSession session) {
}
@Override
public void sessionOpened(IoSession session) {
if (communicationListener != null) {
communicationListener.onConnected();
}
}
@Override
public void sessionClosed(IoSession session) {
if (communicationListener != null) {
communicationListener.onDisConnect();
}
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) {
}
@Override
public void messageReceived(IoSession session, final CommunicationData data) {
//接收消息if (communicationListener != null) {
communicationListener.onReceiveMsg(data);
}
}
@Override
public void messageSent(IoSession session, CommunicationData data) {
}
@Override
public void exceptionCaught(IoSession session, final Throwable cause) {
if (communicationListener != null) {
communicationListener.onException(cause);
}
}
};
}
好了,现在基于Mina框架 基本实现了一个完整的socket传送消息的功能,利用上述实现可以非常方便的发送和接收消息。