在网络通信中,有时候需要保持长连接,一般的处理方式是使用socket,但是socket是阻塞式的,并且自己拼写socket的时候,步骤也挺繁杂,很容易出错。因此就想到了使用底层封装socket的框架---MINA框架。MINA框架的客户端是很好处理的,但是服务端网上大多是main函数来实现的。实际项目中,通常有很多需要tomcat来启动,因此就需要整合tomcat和MINA框架。本篇主要介绍MINA框架的获取以及客户端的使用。下一遍主要讲在服务端的使用,以及如何整合到tomcat中。
这个是MINA框架的下载地址 http://mina.apache.org/downloads-mina.html
打开后界面如下:
一个是当前版本的下载,一个是历史版本的下载。点击当前版本下载以后,会跳抓到下一个界面
点击红框处即可下载。当下载完成以后是个zip的压缩包,我们需要的jar包就放在这个压缩包里。网上有很多demo,用到了MINA的三四个jar包。但是,如果只需要满足基本的通信功能,两个jar包就行了。
把压缩包解压出来,会出现三个文件夹,其中dist文件夹里有个mina-core-2.0.16.jar,lib文件夹里有个slf4j-api-1.7.21.jar。这两个jar包就是我们需要的jar。如果是不同的版本,版本号会不一样。即mina-core-版本号.jar和slf4j-api-版本号.jar。我们把这两个jar放入我们的工程就可以开始编码了。
下面直接上代码:
==============================================================================
主界面MainActivity很简单,三个按钮,分别是开启服务,关闭服务,和点击发送消息的按钮。这里把与服务器的通信放到了service中进行处理。
package com.testdog.activity;
import android.content.Intent;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import com.dog.minaclient.R;
import com.testdog.manager.SessionManager;
import com.testdog.service.TestService;
public class MainActivity extends BaseActivity {
@Override
protected void init() {
// 在main里面把后台服务开启,然后主动连接服务器
setContentView(R.layout.activity_main);
Button btn_start = (Button) findViewById(R.id.btn_start);
Button btn_end = (Button) findViewById(R.id.btn_end);
Button btn_send = (Button) findViewById(R.id.btn_send);
btn_start.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
// TODO Auto-generated method stub
Intent intent = new Intent(MainActivity.this, TestService.class);
startService(intent);
}
});
btn_end.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, TestService.class);
stopService(intent);
}
});
btn_send.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
SessionManager.getInstance().writeToServer("hello123");
}
});
}
}
===========================================================================
主界面的布局:
android:layout_height="match_parent"
android:orientation="vertical" >
android:layout_height="wrap_content"
android:text="这是主界面"
/>
===========================================================================
BaseActivity中主要是用于广播事件的注册,接收从服务器发过来的消息进行处理
package com.testdog.activity;
import android.app.Activity;
import android.content.IntentFilter;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import com.testdog.broadcast.MessageBroadcastReceiver;
public abstract class BaseActivity extends Activity {
private MessageBroadcastReceiver receiver;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
registerBroadcast();
init();
}
protected abstract void init();
private void registerBroadcast() {
receiver = new MessageBroadcastReceiver();
IntentFilter filter = new IntentFilter();
filter.addAction("com.commonlibrary.mina.broadcast");
LocalBroadcastManager.getInstance(this).registerReceiver(receiver,filter);
}
private void unregisterBroadcast(){
LocalBroadcastManager.getInstance(this).unregisterReceiver(receiver);
}
@Override
protected void onDestroy() {
super.onDestroy();
unregisterBroadcast();
}
}
===========================================================================
广播接收者类:
package com.testdog.broadcast;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;
public class MessageBroadcastReceiver extends BroadcastReceiver{
@Override
public void onReceive(Context context, Intent intent) {
// 接受到广播以后进行判断,更具操作进行相应的跳转
String a = (String) intent.getExtras().get("message");
Toast.makeText(context, a, 0).show();
}
}
====================================================================
服务器的访问ip配置类
package com.testdog.config;
import android.content.Context;
/**
* Description:构建者模式
*/
public class ConnectionConfig {
private Context context;
private String ip;
private int port;
private int readBufferSize;
private long connectionTimeout;
public Context getContext() {
return context;
}
public String getIp() {
return ip;
}
public int getPort() {
return port;
}
public int getReadBufferSize() {
return readBufferSize;
}
public long getConnectionTimeout() {
return connectionTimeout;
}
public static class Builder{
private Context context;
private String ip = "192.168.168.20";
private int port = 9226;
private int readBufferSize = 10240;
private long connectionTimeout = 10000;
public Builder(Context context){
this.context = context;
}
public Builder setIp(String ip){
this.ip = ip;
return this;
}
public Builder setPort(int port){
this.port = port;
return this;
}
public Builder setReadBufferSize(int readBufferSize){
this.readBufferSize = readBufferSize;
return this;
}
public Builder setConnectionTimeout(long connectionTimeout){
this.connectionTimeout = connectionTimeout;
return this;
}
private void applyConfig(ConnectionConfig config){
config.context = this.context;
config.ip = this.ip;
config.port = this.port;
config.readBufferSize = this.readBufferSize;
config.connectionTimeout = this.connectionTimeout;
}
public ConnectionConfig builder(){
ConnectionConfig config = new ConnectionConfig();
applyConfig(config);
return config;
}
}
}
==========================================================================
链接服务器的管理类,主要是对MINA框架的一些配置,以及编解码规则。
package com.testdog.manager;
import android.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.prefixedstring.PrefixedStringCodecFactory;
import org.apache.mina.filter.codec.serialization.ObjectSerializationCodecFactory;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import com.testdog.config.ConnectionConfig;
import java.lang.ref.WeakReference;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
/**
* Description:
*/
public class ConnectionManager {
private static final String BROADCAST_ACTION = "com.commonlibrary.mina.broadcast";
private static final String MESSAGE = "message";
private ConnectionConfig mConfig;
private WeakReference
private NioSocketConnector mConnection;
private IoSession mSession;
private InetSocketAddress mAddress;
public ConnectionManager(ConnectionConfig config) {
this.mConfig = config;
this.mContext = new WeakReference
init();
}
private void init() {
mAddress = new InetSocketAddress(mConfig.getIp(), mConfig.getPort());
try {
mConnection = new NioSocketConnector();
mConnection.getSessionConfig().setReadBufferSize(mConfig.getReadBufferSize());
mConnection.getFilterChain().addLast("logging", new LoggingFilter());
mConnection.getFilterChain().addLast("codec",new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue())));
mConnection.setHandler(new DefaultHandler(mContext.get()));
mConnection.setDefaultRemoteAddress(mAddress);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 与服务器连接
*
* @return
*/
public boolean connnect() {
Log.e("tag", "准备连接");
try {
ConnectFuture future = mConnection.connect();
future.awaitUninterruptibly();
mSession = future.getSession();
SessionManager.getInstance().setSeesion(mSession);
} catch (Exception e) {
e.printStackTrace();
Log.e("tag", "连接失败");
return false;
}
return mSession == null ? false : true;
}
/**
* 断开连接
*/
public void disContect() {
mConnection.dispose();
mConnection = null;
mSession = null;
mAddress = null;
mContext = null;
Log.e("tag", "断开连接");
}
private static class DefaultHandler extends IoHandlerAdapter {
private Context mContext;
private DefaultHandler(Context context) {
this.mContext = context;
}
@Override
public void sessionOpened(IoSession session) throws Exception {
super.sessionOpened(session);
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
Log.e("tag", "接收到服务器端消息:" + message.toString());
if (mContext != null) {
Intent intent = null;
// 在这里进行判断服务器发过来的消息类别
if("出库".equals("出库")){
intent = new Intent(BROADCAST_ACTION);
} else if("入库".equals("")){
intent = new Intent(BROADCAST_ACTION);
} else {
//......
}
intent.putExtra(MESSAGE, message.toString());
LocalBroadcastManager.getInstance(mContext).sendBroadcast(intent);
}
}
}
}
messageReceived方法用来接收服务器发来的消息,然后把消息通过广播发送出去并做相应的处理。我这里是出库入库事件,而实际应用过程中,具体逻辑需要根据具体的需求去写。
==============================================================================
session管理器。session对象是客户端和服务器端进行通信的基础。通过session对象可以写入或者写出数据。每一个客户端链接服务器都会生成一个session对象。如果多个客户端同时连接服务器,可以使用Map集合把session对象存起来。
package com.testdog.manager;
import android.util.Log;
import org.apache.mina.core.session.IoSession;
/**
* Description: User: chenzheng Date: 2016/12/9 0009 Time: 17:50
*/
public class SessionManager {
private static SessionManager mInstance = null;
private IoSession mSession;
public static SessionManager getInstance() {
if (mInstance == null) {
synchronized (SessionManager.class) {
if (mInstance == null) {
mInstance = new SessionManager();
}
}
}
return mInstance;
}
private SessionManager() {
}
public void setSeesion(IoSession session) {
this.mSession = session;
}
public void writeToServer(String msg) {
if (mSession != null) {
Log.e("tag", "客户端准备发送消息");
mSession.write(msg);
}
}
public void closeSession() {
if (mSession != null) {
mSession.closeOnFlush();
}
}
public void removeSession() {
this.mSession = null;
}
}
===================================================================================
后台服务类代码
package com.testdog.service;
import com.testdog.thread.MyThread;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
/**
* 后台运行的服务,用来处理一些后台逻辑,比如接收到来自服务器端的请求以后,进行一些界面上的处理
*
* @author Administrator
*
*/
public class TestService extends Service {
private MyThread myThread;
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
super.onCreate();
// 在onCreate方法里连接服务器
myThread = new MyThread("mina", getApplicationContext());
// 启动线程
myThread.start();
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return super.onStartCommand(intent, flags, startId);
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
super.onDestroy();
myThread.disConnect();
// 把进程杀死
myThread.interrupt();
}
}
===================================================================
后台运行的线程自定义线程
package com.testdog.thread;
import com.testdog.config.ConnectionConfig;
import com.testdog.manager.ConnectionManager;
import android.content.Context;
import android.os.HandlerThread;
import android.util.Log;
public class MyThread extends HandlerThread {
private Context mContext;
boolean isConnection;
ConnectionManager mManager;
public MyThread(String name, Context context) {
super(name);
mContext = context;
// 主动连接服务器
// 连接服务器的配置类
ConnectionConfig config = new ConnectionConfig.Builder(context)
.setIp("192.168.1.190").setPort(10086).setReadBufferSize(2048)
.setConnectionTimeout(10000).builder();
// 连接服务器的管理类
mManager = new ConnectionManager(config);
}
/**
* loop循环,用来处理与服务器的连接
*/
@Override
protected void onLooperPrepared() {
// 死循环,如果连不上服务器,会一直在后台自动检测连接服务器
while (true) {
isConnection = mManager.connnect();
if (isConnection) {
// 说明连接服务器成功
Log.e("tag", "连接成功");
break;
}
try {
Log.e("tag", "尝试重新连接");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void disConnect(){
mManager.disContect();
}
}
=======================================================================================
清单文件中添加的权限
清单文件中添加服务
好了,这就是demo全部代码,只要全部粘贴过去就能使用了,以上亲测可用。服务端整合tomcat由于篇幅太长了,将在下篇中贴出来。