完整版见https://jadyer.github.io/2012/10/19/mina-packet/
This is Apache Mina 2.0.4, Let`s drink code....
下面是用于模拟短信协议内容的实体类
package com.mina.model;
/**
* 模拟短信协议内容的对象
* @see M sip:wap.fetion.com.cn SIP-C/2.0 //状态行,一般表示协议的名字、版本号等
* @see S: 1580101xxxx //短信的发送号码
* @see R: 1880202xxxx //短信的接收号码
* @see L: 21 //短信的字节数
* @see 你好!!Hello World!! //短信的内容
* @see 上面每行的末尾使用ASCII的10(\n)作为换行符
*/
public class SmsObject {
private String sender; //短信发送者
private String receiver; //短信接收者
private String message; //短信内容
/*三个属性的getter和setter略*/
}
package com.mina.server;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
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.transport.socket.nio.NioSocketAcceptor;
import com.mina.factory.CmccSipcCodecFactory;
public class MyServer {
public static void main(String[] args) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new CmccSipcCodecFactory(Charset.forName("UTF-8"))));
acceptor.setHandler(new ServerHandler());
acceptor.bind(new InetSocketAddress(9876));
System.out.println("Mina Server is Listing on := 9876");
}
}
package com.mina.server;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import com.mina.model.SmsObject;
public class ServerHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
SmsObject sms = (SmsObject)message;
System.out.println("The message received from Client is [" + sms.getMessage() + "]");
}
@Override
public void sessionOpened(IoSession session) throws Exception{
System.out.println("InComing Client:" + session.getRemoteAddress());
}
}
package com.mina.client;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import com.mina.factory.CmccSipcCodecFactory;
public class MyClient {
public static void main(String[] args) {
IoConnector connector = new NioSocketConnector();
connector.setConnectTimeoutMillis(3000);
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new CmccSipcCodecFactory(Charset.forName("UTF-8"))));
connector.setHandler(new ClientHandler());
connector.connect(new InetSocketAddress("localhost", 9876));
}
}
package com.mina.client;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import com.mina.model.SmsObject;
public class ClientHandler extends IoHandlerAdapter {
@Override
public void sessionOpened(IoSession session) throws Exception {
SmsObject sms = new SmsObject();
sms.setSender("15025302990");
sms.setReceiver("13716700602");
sms.setMessage("Hi Jadyer,这是我用Mina2.x发给你的消息....");
session.write(sms);
}
}
package com.mina.factory;
import java.nio.charset.Charset;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
import com.mina.codec.CmccSipcDecoder;
import com.mina.codec.CmccSipcEncoder;
/**
* 自定义编解码工厂
* @see 实际上这个工厂类就是包装了编码器、解码器
* @see 通过接口中的getEncoder()、getDecoder()方法向ProtocolCodecFilter过滤器返回编解码器实例
* @see 以便在过滤器中对数据进行编解码
*/
public class CmccSipcCodecFactory implements ProtocolCodecFactory {
private final CmccSipcEncoder encoder;
private final CmccSipcDecoder decoder;
public CmccSipcCodecFactory(){
this(Charset.defaultCharset());
}
public CmccSipcCodecFactory(Charset charset){
this.encoder = new CmccSipcEncoder(charset);
this.decoder = new CmccSipcDecoder(charset);
}
@Override
public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
return decoder;
}
@Override
public ProtocolEncoder getEncoder(IoSession arg0) throws Exception {
return encoder;
}
}
package com.mina.codec;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import com.mina.model.SmsObject;
/**
* 自定义编码器
* Mina中编写编码器可以实现ProtocolEncoder,其中有encode()、dispose()两个方法需要实现
* dispose()用于在销毁编码器时释放关联的资源,由于该方法一般我们并不关心,故通常直接继承适配器ProtocolEncoderAdapter
* @see ==============================================================================================================
* @see 相比较解码(字节转为JAVA对象,也叫拆包)来说,编码(Java对象转为字节,也叫做打包)就很简单了
* @see 我们只需要把Java对象转为指定格式的字节流,然后write()就可以了
* @see ==============================================================================================================
* @see 解码器的编写有以下几个步骤
* @see 1、将encode()方法中的message对象强制转换为指定的对象类型
* @see 2、创建IoBuffer缓冲区对象,并设置为自动扩展
* @see 3、将转换后的message对象中的各个部分按照指定的应用层协议进行组装,并put()到IoBuffer缓冲区
* @see 4、数据组装完毕后,调用flip()方法,为输出做好准备
* @see 切记在write()方法之前调用IoBuffer的flip()方法,否则缓冲区的position的后面是没有数据可以用来输出的
* @see 你必须调用flip()方法将position移至0,limit移至刚才的position。这个flip()方法的含义请参看java.nio.ByteBuffer
* @see 5、最后调用ProtocolEncoderOutput的write()方法输出IoBuffer缓冲区实例
* @see ==============================================================================================================
*/
public class CmccSipcEncoder extends ProtocolEncoderAdapter {
private final Charset charset;
public CmccSipcEncoder(Charset charset){
this.charset = charset;
}
/**
* 依据传入的字符集类型对message对象进行编码
* 编码的方式就是按照短信协议拼装字符串到IoBuffer缓冲区,然后调用ProtocolEncoderOutput的write()方法输出字节流
*/
@Override
public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
SmsObject sms = (SmsObject)message;
CharsetEncoder ce = charset.newEncoder();
IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
String statusLine = "M sip:wap.fetion.com.cn SIP-C/2.0";
String sender = sms.getSender();
String receiver = sms.getReceiver();
String smsContent = sms.getMessage();
buffer.putString(statusLine+'\n', ce);
buffer.putString("S: "+sender+'\n', ce);
buffer.putString("R: "+receiver+'\n', ce);
//使用String类与Byte[]类型之间的转换方法获得转为字节流后的字节数
buffer.putString("L: "+smsContent.getBytes(charset).length+'\n', ce);
buffer.putString(smsContent, ce);
buffer.flip();
out.write(buffer);
}
}
package com.mina.codec;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.AttributeKey;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import com.mina.model.SmsObject;
/**
* 自定义解码器
* Mina中编写解码器,可以实现ProtocolDecoder接口,其中有decode()、finishDecode()、dispose()三个方法
* finishDecode()用于处理IoSession关闭时剩余的未读取数据,该方法通常不会被用到,不过也可忽略处理剩余的数据
* 同样的,一般情况下,我们只需要继承适配器ProtocolDecoderAdapter,关注decode()方法即可
* @see =============================================================================================================
* @see 解码器相对编码器来说,最麻烦的是数据发送过来的规模。比如聊天室中每隔一段时间都会有聊天内容发送过来
* @see 此时 decode()方法会被往复调用,这样处理时就会非常麻烦,幸好Mina提供了CumulativeProtocolDecoder类
* @see 它是累积性的协议解码器。即只要有数据发送过来,该类就会去读取数据,然后累积到内部的IoBuffer缓冲区
* @see 但具体的拆包(把累积到缓冲区的数据解码为Java对象)则交由子类的doDecode()方法完成
* @see 实际上CumulativeProtocolDecoder就是在decode()中反复的调用暴漏给子类实现的doDecode()方法
* @see =============================================================================================================
* @see 具体执行过程如下所示
* @see 1、这里的doDecode()方法返回true时
* @see CumulativeProtocolDecoder的decode()方法首先会判断你是否在doDecode()方法中从内部的IoBuffer缓冲区读取了数据
* @see 如果没有,则会抛出非法的状态异常
* @see 即doDecode()返回true就表示你已经消费了本次数据(相当于聊天室中一个完整的消息已经读取完毕)
* @see 进一步说,也就是此时你必须已经消费过内部的IoBuffer缓冲区的数据(哪怕是消费了一个字节的数据)
* @see 如果验证通过,那么CumulativeProtocolDecoder会检查缓冲区内是否还有数据未读取
* @see 如果有就继续调用doDecode()方法,没有则停止对doDecode()方法的调用,直到有新的数据被缓冲
* @see 2、当doDecode()方法返回false时,CumulativeProtocolDecoder会停止对doDecode()的调用
* @see 但这时,若本次数据还有未读取完的,那么就将含有剩余数据的IoBuffer缓冲区保存到IoSession中
* @see 以便下一次数据到来时可以从IoSession中提取合并
* @see 若发现本次数据全都读取完毕,则清空IoBuffer缓冲区
* @see =============================================================================================================
* @see 简而言之,当你认为读取到的数据已经够解码了,那么就返回true,否则就返回false
* @see 这个CumulativeProtocolDecoder其实最重要的工作就是帮你完成了数据的累积,因为这个工作是很烦琐的
* @see =============================================================================================================
* @see 假如数据的发送被拆成了多次(譬如:短信协议的短信内容、消息报头被拆成了两次数据发送),那么上面的代码势必就会存在问题
* @see 因为当第二次调用doDecode()方法时,状态变量i、matchCount势必会被重置,也就是原来的状态值并没有被保存
* @see 那么我们如何解决状态保存的问题呢
* @see 答案就是将状态变量保存在IoSession中或者是Decoder实例自身,但推荐使用前者
* @see 因为虽然Decoder是单例的,其中的实例变量保存的状态在Decoder实例销毁前始终保持
* @see 但Mina并不保证每次调用doDecode()方法时都是同一个线程
* @see 这也就是说第一次调用doDecode()是IoProcessor-1线程,第二次有可能就是IoProcessor-2线程
* @see 这就会产生多线程中的实例变量的可视性(Visibility,具体请参考Java的多线程知识)问题
* @see 而IoSession中使用一个同步的HashMap保存对象,所以我们就不需要担心多线程带来的问题
* @see =============================================================================================================
* @see 使用IoSession保存解码器的状态变量通常的写法如下所示
* @see 1、在解码器中定义私有的内部类Context,然后将需要保存的状态变量定义在Context中存储
* @see 2、在解码器中定义方法获取这个Context的实例,这个方法的实现要优先从IoSession中获取Context
* @see =============================================================================================================
* @see 这里做了如下的几步操作
* @see 1、所有记录状态的变量移到了Context内部类中,包括记录读到短信协议的哪一行的line
* @see 每一行读取了多少个字节的matchCount,还有记录解析好的状态行、发送者、接受者、短信内容、累积数据的innerBuffer等
* @see 这样就可以在数据不能完全解码,等待下一次doDecode()方法的调用时,还能承接上一次调用的数据
* @see 2、在doDecode()方法中主要的变化是各种状态变量首先是从Context中获取,然后操作之后,将最新的值setXXX()到Context中保存
* @see 3、这里注意doDecode()方法最后的判断,当认为不够解码为一条短信息时,返回false,即在本次数据流解码中不要再调用doDecode()方法
* @see 当认为已解码出一条短信息时,输出消息并重置所有状态变量,返回true,即若本次数据流解码中还有没解码完的数据,则继续调用doDecode()
* @see =============================================================================================================
*/
//public class CmccSipcDecoder extends CumulativeProtocolDecoder {
// private final Charset charset;
// public CmccSipcDecoder(Charset charset){
// this.charset = charset;
// }
// @Override
// protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
// IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true);
// CharsetDecoder cd = charset.newDecoder();
// int i = 1; //记录解析到了短信协议中的哪一行(\n)
// int matchCount = 0; //记录在当前行中读取到了哪一个字节
// String statusLine="", sender="", receiver="", length="", sms="";
// while(in.hasRemaining()){
// byte b = in.get();
// buffer.put(b);
// if(10==b && 5>i){ //10==b表示换行:该短信协议解码器使用\n(ASCII的10字符)作为分解点
// matchCount++;
// if(1 == i){
// buffer.flip(); //limit=position,position=0
// statusLine = buffer.getString(matchCount, cd);
// statusLine = statusLine.substring(0, statusLine.length()-1); //移除本行的最后一个换行符
// matchCount = 0; //本行读取完毕,所以让matchCount=0
// buffer.clear();
// }
// if(2 == i){
// buffer.flip();
// sender = buffer.getString(matchCount, cd);
// sender = sender.substring(0, sender.length()-1);
// matchCount = 0;
// buffer.clear();
// }
// if(3 == i){
// buffer.flip();
// receiver = buffer.getString(matchCount, cd);
// receiver = receiver.substring(0, receiver.length()-1);
// matchCount = 0;
// buffer.clear();
// }
// if(4 == i){
// buffer.flip();
// length = buffer.getString(matchCount, cd);
// length = length.substring(0, length.length()-1);
// matchCount = 0;
// buffer.clear();
// }
// i++;
// }else if(5 == i){
// matchCount++;
// if(Long.parseLong(length.split(": ")[1]) == matchCount){
// buffer.flip();
// sms = buffer.getString(matchCount, cd);
// i++;
// break;
// }
// }else{
// matchCount++;
// }
// }
// SmsObject smsObject = new SmsObject();
// smsObject.setSender(sender.split(": ")[1]);
// smsObject.setReceiver(receiver.split(": ")[1]);
// smsObject.setMessage(sms);
// out.write(smsObject);
// return false; //告诉Mina:本次数据已全部读取完毕,故返回false
// }
//}
/**
* 以上注释的解码器,适用于客户端发送的数据是一次全部发送完整的情况
* 下面的这个解码器,适用于客户端发送的数据被拆分为多次后发送的情况
*/
public class CmccSipcDecoder extends CumulativeProtocolDecoder {
private final Charset charset;
private final AttributeKey CONTEXT = new AttributeKey(getClass(), "context");
public CmccSipcDecoder(Charset charset){
this.charset = charset;
}
private Context getContext(IoSession session){
Context context = (Context)session.getAttribute(CONTEXT);
if(null == context){
context = new Context();
session.setAttribute(CONTEXT, context);
}
return context;
}
@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
CharsetDecoder cd = charset.newDecoder();
Context ctx = this.getContext(session);
IoBuffer buffer = ctx.innerBuffer;
int matchCount = ctx.getMatchCount();
int line = ctx.getLine();
String statusLine = ctx.getStatusLine();
String sender = ctx.getSender();
String receiver = ctx.getReceiver();
String length = ctx.getLength();
String sms = ctx.getSms();
while(in.hasRemaining()){
byte b = in.get();
matchCount++;
buffer.put(b);
if(10==b && line<4){
if(0 == line){
buffer.flip();
statusLine = buffer.getString(matchCount, cd);
statusLine = statusLine.substring(0, statusLine.length()-1);
matchCount = 0;
buffer.clear();
ctx.setStatusLine(statusLine);
}
if(1 == line){
buffer.flip();
sender = buffer.getString(matchCount, cd);
sender = sender.substring(0, sender.length()-1);
matchCount = 0;
buffer.clear();
ctx.setSender(sender);
}
if(2 == line){
buffer.flip();
receiver = buffer.getString(matchCount, cd);
receiver = receiver.substring(0, receiver.length()-1);
matchCount = 0;
buffer.clear();
ctx.setReceiver(receiver);
}
if(3 == line){
buffer.flip();
length = buffer.getString(matchCount, cd);
length = length.substring(0, length.length()-1);
matchCount = 0;
buffer.clear();
ctx.setLength(length);
}
line++;
}else if(4 == line){
if(Long.parseLong(length.split(": ")[1]) == matchCount){
buffer.flip();
sms = buffer.getString(matchCount, cd);
ctx.setSms(sms);
ctx.setMatchCount(matchCount); //由于下面的break,这里需要调用else外面的两行代码
ctx.setLine(line);
break;
}
}
ctx.setMatchCount(matchCount);
ctx.setLine(line);
}
//判断本次是否已读取完毕:要求读取到最后一行,且读取的字节数与前一行指定的字节数相同
if(4==ctx.getLine() && Long.parseLong(ctx.getLength().split(": ")[1])==ctx.getMatchCount()){
SmsObject smsObject = new SmsObject();
smsObject.setSender(sender.split(": ")[1]);
smsObject.setReceiver(receiver.split(": ")[1]);
smsObject.setMessage(sms);
out.write(smsObject);
ctx.reset();
return true;
}else{
return false;
}
}
private class Context{
private final IoBuffer innerBuffer; //用于累积数据的IoBuffer
private String statusLine = ""; //记录解析好的状态行
private String sender = ""; //记录解析好的发送者
private String receiver = ""; //记录解析好的接受者
private String length = "";
private String sms = ""; //记录解析好的短信内容
private int matchCount = 0; //记录每一行读取了多少个字节
private int line = 0; //记录读到短信协议的哪一行
public Context(){
innerBuffer = IoBuffer.allocate(100).setAutoExpand(true);
}
public String getStatusLine() {
return statusLine;
}
public void setStatusLine(String statusLine) {
this.statusLine = statusLine;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getLength() {
return length;
}
public void setLength(String length) {
this.length = length;
}
public String getSms() {
return sms;
}
public void setSms(String sms) {
this.sms = sms;
}
public int getMatchCount() {
return matchCount;
}
public void setMatchCount(int matchCount) {
this.matchCount = matchCount;
}
public int getLine() {
return line;
}
public void setLine(int line) {
this.line = line;
}
public void reset(){
this.innerBuffer.clear();
this.statusLine = "";
this.sender = "";
this.receiver = "";
this.length = "";
this.sms = "";
this.matchCount = 0;
this.line = 0;
}
}
}
一句话总结:最为核心部分就是编码器和解码器