参考网站:http://blog.csdn.net/jadyer/article/details/8088928
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 {
- @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());
- }
- }
下面是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 {
- @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);
- }
- }
下面是我们自定义的编解码工厂类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);
- }
- @Override
- public ProtocolDecoder getDecoder(IoSession arg0) throws Exception {
- return decoder;
- }
- @Override
- public 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()方法输出字节流
- */
- @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);
- }
- }
重头戏:最后是我们自定义的解码器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;
- }
- @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;
- }
- }
- }