Apache mina是一个基于NIO(非阻塞IO)模型的网络应用框架。详细资料和下载地址为:http://mina.apache.org/
远程客户端通过IoService建立连接得到session,session将数据传送到IoFilterChain进行过滤,最后客户端在IoHandle中进行数据操作,其工作原理如图所示:
此处记录的是Android中Apache mina于服务器建立的长连接通信示例。长连接和http短连接底层都是基于TCP/IP协议,长连接通过Socket,ServerSocket与服务器保持连接,服务端一般使用ServerSocket建立监听,监听客户端的连接,客户端使用Socket,指定端口和IP地址与服务端进行连接。通过长连接,可以实现服务器主动向客户端推送消息,可以减少客户端对服务器的轮询,减少服务器的压力,而且通信效率高于http,okhttp等。
Apache mina框架核心类:
创建java项目,导入mina-core-2.0.21.jar和slf4j-api-1.7.26.jar两个库文件,如不加slf4j包则会出现java.lang.ClassNotFoundException: org.slf4j.LoggerFactory错误。
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 + " 启动成功");
}
}
/**
* 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) {
}
}
新建Android项目,拷贝mina-core-2.0.21.jar和slf4j-api-1.7.26.jar两个库文件到libs目录并添加到dependencies,app\build.gradle如图所示:
/**
* 封装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;
}
}
/**
* 连接选项配置
* 通过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; // 心跳请求超时时间
}
/**
* 业务处理类
*/
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);
}
}
/**
* 服务管理类
*/
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;
}
}
/**
* 创建线程类来调用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;
}
}
}
/**
* 会话管理类
*/
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);
}
}
/**
* 当心跳超时时的处理
*/
class KeepAliveRequestTimeoutHandlerImpl implements KeepAliveRequestTimeoutHandler {
@Override
public void keepAliveRequestTimedOut(KeepAliveFilter keepAliveFilter, IoSession ioSession) throws Exception {
ioSession.closeNow();
}
}
/**
* 我这里没做啥操作
*/
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;
}
}
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客户端启动时报错:
解决办法是:在app\build.gradle中增加includeCompileClasspath true。如图所示:
若报错信息为:
解决办法是:在app\build.gradle中增加java8的编译。如图所示:
通常为防止客户端连接不上服务端,可以关闭服务端的防火墙再试试。