博主近端时间在做一个项目,里面有个需求需要实现点对点可选择聊天,就像一个QQ一样。但是需求又没那么高大尚。似乎就只是一个简单的聊天。网上找了很多信息,最后决定使用MINA框架来实现。现在的IM通讯协议有一个叫XMPP的,博主一看,好像项目需求不需要那么复杂,于是博主根据实际需要自己定了一个简单的传输协议来实现。项目之前用的是openfire,但是聊天只是一个其中一个基础功能,还有其他需求,而且openfire有各种限制,不支持JDK1.7等。于是只能放弃使用它,自己来写一个通信服务器了。博主是做Android开发的,服务器端理应该是做服务器的人负责,无奈公司人手不够,项目又催的紧,服务器端还有很多其他的业务需要那个专业的人去做,于是,博主只好硬着头皮上了。查看了一下好像OPENFIRE好像就是在MINA的框架上基于XMPP协议写出来的,于是博主果断就去研究了MINA框架,搞了那么2天,还真被博主搞出来了。好了,废话不说了,言归正传。
MINA框架,是Apache组织应用程序(Multipurpose Infrastructure for Network Applications) 是 Apache 组织一个较新的项目,它为开发高性能和高可用性的网络应用程序提供了非常便利的框架。在这不在详细描述MINA框架的底层工作原理,直接进入实例开发,如果想看MINA框架的底层实现原理,请转接(博主就是看这篇文章学的):http://blog.csdn.net/w13770269691/article/details/8614584
一、MINA框架服务端开发
博主的项目的服务端是一个web项目,还有的业务,在这里博主将MINA框架部分剥离了出来,重新新建了一个项目。
1、MINA服务器编写:
package com.freedom.mina;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
/**
* @ClassName: Server
* @author victor_freedom ([email protected])
* @createddate Dec 14, 2014 11:25:40 PM
* @Description: TODO
*/
public class Server implements Runnable {
private final Logger logger = Logger.getLogger(Server.class);
public Thread start;
public Server() {
start = new Thread(this);
}
public void startService() {
System.out.println("MINA启动");
start.start();
}
public void stopService(){
System.out.println("MINA停止");
start.interrupt();
}
public void run() {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset
.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
LineDelimiter.WINDOWS.getValue())));
acceptor.setHandler(new ServerHandler());
acceptor.getSessionConfig().setReadBufferSize(1024);
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
try {
acceptor.bind(new InetSocketAddress(9090));
} catch (IOException e) {
logger
.error("Server start failed, Please check the network or port!");
}
logger.info("Server start up!");
}
}
2、MINA服务消息Handler编写(这里需要大家注意看,在android配合MINA框架使用的时候,在中文消息的处理上,需要特别的注意,详情请注意看代码注释)
package com.freedom.mina;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Date;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
/**
* @ClassName: ServerHandler
* @author victor_freedom ([email protected])
* @createddate Dec 15, 2014 12:34:44 AM
* @Description:
*/
public class ServerHandler extends IoHandlerAdapter {
/**
* 用于存放用户信息的集合
*/
private final UserSessionMap userSessionMap = UserSessionMap.getInstance();
@Override
public void messageSent(IoSession session, Object message) throws Exception {
super.messageSent(session, message);
}
@Override
public void sessionOpened(IoSession session) throws Exception {
}
@Override
public void sessionClosed(IoSession session) throws Exception {
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
super.exceptionCaught(session, cause);
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
// 将收到的信息解码
String data = URLDecoder.decode(message.toString(), "UTF-8").trim();
// 这是博主自定义的一个传输协议,采用json数据格式传输
ChatMessage receiveData = GsonUtils.jsonToBean(data, ChatMessage.class);
if ("LOGIN".equals(receiveData.getType())) {
userSessionMap.put(receiveData.getSender(), session);
for (String key : userSessionMap.keySet()) {
if (key.equals(receiveData.getSender()))
continue;
IoSession ioSession = userSessionMap.get(key);
ChatMessage sendData = new ChatMessage();
sendData.setContent(receiveData.getSender() + "上线了");
sendData.setSender(receiveData.getSender());
sendData.setType("LOGIN");
sendData.setReceiver(key);
sendData.setSendTime(new Date().getTime());
sendData.setReceiveTime(new Date().getTime());
String datas = GsonUtils.beanToJson(sendData).trim();
// System.out.println(datas);
//在Android端,涉及到中文传输的时候,总会出现乱码或者是收不到消息的问题,试了各种办法,最后只能通过URLEncoder转码的方式来实现中文传输,
//其他方式统统都不行,在服务端,记得转码完了之后加上换行符,不然android端收不到消息。
ioSession.write(URLEncoder.encode(datas, "UTF-8") + "\r\n");
}
} else if ("OFFLINE".equals(receiveData.getType())) {
userSessionMap.remove(receiveData.getSender());
for (String key : userSessionMap.keySet()) {
IoSession ioSession = userSessionMap.get(key);
ChatMessage sendData = new ChatMessage();
sendData.setContent(receiveData.getSender() + "下线了");
sendData.setSender(receiveData.getSender());
sendData.setType("OFFLINE");
sendData.setReceiver(key);
sendData.setSendTime(new Date().getTime());
sendData.setReceiveTime(new Date().getTime());
String datas = GsonUtils.beanToJson(sendData).trim();
// System.out.println(datas);
ioSession.write(URLEncoder.encode(datas, "UTF-8") + "\r\n");
}
} else {
System.out.println(data);
IoSession ioSession = userSessionMap.get(receiveData.getReceiver());
ioSession.write(URLEncoder.encode(data, "UTF-8") + "\r\n");
}
}
@Override
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
super.sessionIdle(session, status);
}
}
二、MINA框架客户端开发
1、Android后台接受消息服务编写:
/**
* @Company: Batways
* @Project:Tnt
* @Title: UpdateService.java
* @Package com.batways.tnt.service
* @Description: TODO
* @author victor_freedom ([email protected])
* @date 2014年9月6日 下午2:16:08
* @version V1.0
*/
package com.example.minatest.service;
import java.net.InetSocketAddress;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.Charset;
import java.util.Date;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.IBinder;
import com.example.minatest.LoggerUtils;
import com.example.minatest.bean.ChatMessage;
import com.example.minatest.gloable.ConstantValues;
import com.example.minatest.utils.GsonUtils;
/**
* @ClassName: UpdateService
* @author victor_freedom ([email protected])
* @createddate 2014年9月6日 下午2:16:08
* @Description: TODO
*
*/
public class UpdateService extends Service {
private DataHandler dataHandler;
private IoSession session;
private String TAG = "UpdateService";
private BroadcastReceiver sendMessage = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
try {
ChatMessage messge = new ChatMessage();
messge.setType("NORMAL");
String data = intent.getStringExtra("data");
messge.setContent(data);
messge.setSendTime(new Date().getTime());
messge.setReceiveTime(new Date().getTime());
//发送者
messge.setSender("justice");
//接受者
messge.setReceiver("freedom");
//这里发送消息的时候需要特别注意,这里和服务端有点不同,换行符需要和实体内容一起进行编码,不然服务端接受不到消息。
String info = GsonUtils.beanToJson(messge) + "\r\n";
session.write(URLEncoder.encode(info, "UTF-8"));
} catch (Exception e) {
e.printStackTrace();
}
}
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onCreate() {
IntentFilter filter = new IntentFilter();
filter.addAction(ConstantValues.SENDMESSGE);
filter.addCategory(Intent.CATEGORY_DEFAULT);
dataHandler = new DataHandler();
registerReceiver(sendMessage, filter);
new Thread(new Runnable() {
@Override
public void run() {
IoConnector connector = new NioSocketConnector();
connector.getFilterChain().addLast(
"textProtocol",
new ProtocolCodecFilter(new TextLineCodecFactory(
Charset.forName("UTF-8"), LineDelimiter.WINDOWS
.getValue(), LineDelimiter.WINDOWS
.getValue())));
connector.getSessionConfig().setReadBufferSize(1024);
connector.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE,
10);
connector.setHandler(dataHandler);
// 这里是异步操作 连接后立即返回
ConnectFuture future = connector.connect(new InetSocketAddress(
"192.168.1.111", 9090));
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();
session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
connector.dispose();
}
}).start();
super.onCreate();
}
@Override
public void onDestroy() {
unregisterReceiver(sendMessage);
sendMessage = null;
super.onDestroy();
}
private class DataHandler extends IoHandlerAdapter {
@Override
public void sessionCreated(IoSession session) throws Exception {
}
@Override
public void sessionOpened(IoSession session) throws Exception {
//上线与服务器建立连接之后,告诉服务器上线了
ChatMessage messge = new ChatMessage();
messge.setType("LOGIN");
//发送者
messge.setSender("justice");
messge.setContent("login");
//接受者(这里接受者可以不需要设置)
messge.setReceiver("freedom");
messge.setSendTime(new Date().getTime());
messge.setReceiveTime(new Date().getTime());
//这里发送消息的时候需要特别注意,这里和服务端有点不同,换行符需要和实体内容一起进行编码,不然服务端接受不到消息。
session.write(URLEncoder.encode(GsonUtils.beanToJson(messge)
+ "\r\n", "UTF-8"));
}
@Override
public void sessionClosed(IoSession session) throws Exception {
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
//处理从服务端接收到的消息
String result = URLDecoder.decode(message.toString(), "UTF-8");
LoggerUtils.i(TAG, result);
BroadcastHelper.sendBroadCast(getBaseContext(),
ConstantValues.RECEIVEMESSGE, "data", result);
}
@Override
public void messageSent(IoSession session, Object message)
throws Exception {
}
}
}
package com.example.minatest;
import android.app.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.example.minatest.bean.ChatMessage;
import com.example.minatest.gloable.ConstantValues;
import com.example.minatest.service.BroadcastHelper;
import com.example.minatest.service.UpdateService;
import com.example.minatest.utils.GsonUtils;
/**
* @ClassName: MainActivity
* @author victor_freedom ([email protected])
* @createddate 2014-12-15 上午12:49:29
* @Description: TODO
*/
public class MainActivity extends Activity {
private Button sendMessage;
private TextView recevier;
private EditText content;
private Intent intent;
protected String TAG = "MainActivity";
// 接受消息广播
private BroadcastReceiver contentRecevier = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String data = intent.getStringExtra("data");
ChatMessage receiveData = GsonUtils.jsonToBean(data,
ChatMessage.class);
// 如果接受到空消息时过滤掉
if (null == receiveData) {
return;
}
// 将消息展现出来。
recevier.setText(receiveData.getSender() + ":"
+ receiveData.getContent());
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
intent = new Intent(MainActivity.this, UpdateService.class);
startService(intent);
IntentFilter filter = new IntentFilter();
filter.addAction(ConstantValues.RECEIVEMESSGE);
filter.addCategory(Intent.CATEGORY_DEFAULT);
registerReceiver(contentRecevier, filter);
sendMessage = (Button) findViewById(R.id.send);
content = (EditText) findViewById(R.id.content);
recevier = (TextView) findViewById(R.id.recevier);
recevier.setText("消息");
sendMessage.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
String data = content.getText().toString();
if (data.isEmpty())
return;
// 点击发送消息到服务器
BroadcastHelper.sendBroadCast(MainActivity.this,
ConstantValues.SENDMESSGE, "data", data);
content.setText("");
}
});
}
@Override
protected void onDestroy() {
//退出处理,这里应该在发个下线的消息,时间太晚了,博主就不写了。
unregisterReceiver(contentRecevier);
contentRecevier = null;
stopService(intent);
intent = null;
super.onDestroy();
}
}
终于写完了,今晚不错,写了两篇,前一篇JAVA文件IO流详解也写了两个小时,这个从11点开始写,到现在瞬间就到1点了。
希望这篇博客可以帮到所有运用MINA框架做Android开发的人。
DEMO下载地址(服务端加客户端):http://download.csdn.net/detail/victorfreedom/8259969