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; } } }一句话总结:最为核心部分就是编码器和解码器