使用Mina模拟短信的收发

来源:互联网 发布:java string kmp 编辑:程序博客网 时间:2024/05/20 14:40

完整版见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略*/}

下面是Mina编写的服务端主类MyServer.java

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");}}

下面是服务端的消息处理器ServerHandler.java

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 {@Overridepublic void messageReceived(IoSession session, Object message) throws Exception {SmsObject sms = (SmsObject)message;System.out.println("The message received from Client is [" + sms.getMessage() + "]");}@Overridepublic void sessionOpened(IoSession session) throws Exception{System.out.println("InComing Client:" + session.getRemoteAddress());}}

下面是Mina编写的客户端主类MyClient.java

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));}}

下面是客户端的消息处理器ClientHandler.java

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 {@Overridepublic 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);}}

下面是我们自定义的编解码工厂类CmccSipcCodecFactory.java

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);}@Overridepublic ProtocolDecoder getDecoder(IoSession arg0) throws Exception {return decoder;}@Overridepublic ProtocolEncoder getEncoder(IoSession arg0) throws Exception {return encoder;}}

重头戏:下面是我们自定义的编码器CmccSipcEncoder.java

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()方法输出字节流 */@Overridepublic 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);}}

重头戏:最后是我们自定义的解码器CmccSipcDecoder.java

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;}@Overrideprotected 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; //用于累积数据的IoBufferprivate 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;}}}
一句话总结:最为核心部分就是编码器和解码器

原创粉丝点击