在实际的网络通信中,阻塞是很可能发生的。那我们可以设想如果我们的一条消息被分成了两截,分两次发来,那么我们的解码器中的doDecode方法被先后调用两次,在前一次中不足以解码出一条完整的信息,而在后一次调用中我们也不知道前面一次解码到了哪一步。这个问题我们通过引入一个上下文的概念来解决。
在doDecode方法中有一个IoSession类型的参数,它很类似于Web中的Session,拥有一个Map,用键值对的形式来存储参数。一个IoSession可以类比为阻塞IO中的一个连接,我们可以把解码过程中用到的所有变量都存在里面。为了程序的结构良好,我们把所有变量包装成一个Context对象,变量的初始化都放在Context的构造函数里,原来对变量的操作也就成了对Context的操作。改造后的解码器类如下所示
package mina.common; 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; /** * 解码器类 * @author 尹定宇 * @Email [email protected] * @version 2013-5-2 上午10:10:03 * @info */ public class CmccSipcDecoder extends CumulativeProtocolDecoder { //字符集 private final Charset charset; //上下文的key private final AttributeKey CONTEXT = new AttributeKey(getClass(),"context"); //构造方法 public CmccSipcDecoder(Charset charset) { this.charset = charset; } @Override protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception { //得到上下文 Context ctx = getContext(session); //设置缓冲区,初始长度为100,可以自动调整长度 IoBuffer buffer = ctx.getInnerBuffer(); //获得一个字符编码器 CharsetDecoder cd = charset.newDecoder(); //表示一行中第几个字节 int matchCount = ctx.getMatchCount(); //初始化几个字符串 String statusLine = ctx.getStatusLine(), sender = ctx.getSender(), receiver = ctx.getReceiver(), length = ctx.getLength(), sms = ctx.getSms(); //表示第几行 int line = ctx.getLine(); //从输入流中读取 while (in.hasRemaining()) { byte b = in.get(); //游标前移 matchCount++; buffer.put(b); //读到了换行符,而且不是第五行 if (b == 10 && line < 4) { //如果是第一行,得到状态行字符串 if (line == 0) { buffer.flip(); statusLine = buffer.getString(matchCount, cd); statusLine = statusLine.substring(0, statusLine.length() - 1); matchCount = 0; buffer.clear(); ctx.setStatusLine(statusLine); } //发送方 if (line == 1) { buffer.flip(); sender = buffer.getString(matchCount, cd); sender = sender.substring(0, sender.length() - 1); matchCount = 0; buffer.clear(); ctx.setSender(sender); } //接收方 if (line == 2) { buffer.flip(); receiver = buffer.getString(matchCount, cd); receiver = receiver.substring(0, receiver.length()-1); matchCount = 0; buffer.clear(); ctx.setReceiver(receiver); } //短信的长度 if (line == 3) { buffer.flip(); length = buffer.getString(matchCount, cd); length = length.substring(0, length.length() - 1); matchCount = 0; buffer.clear(); ctx.setLength(length); } //行数递增 line++; } else if (line == 4) { //如果是第五行,则读取短信内容 //当读到的字节数等于上一行所定义的短信长度时 if (matchCount == Long.parseLong(length.split(": ")[1])) { buffer.flip(); sms = buffer.getString(matchCount, cd); ctx.setSms(sms); ctx.setMatchCount(matchCount); ctx.setLine(line); break; } } ctx.setMatchCount(matchCount); ctx.setLine(line); } if(ctx.getLine()==4&&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 Context getContext(IoSession session){ Context context = (Context)session.getAttribute(CONTEXT); if(context == null){ context = new Context(); session.setAttribute(CONTEXT, context); } return context; } /** * 存储上下文的内部类 * @author 尹定宇 * @Email [email protected] * @version 2013-5-7 下午12:57:02 * @info */ private class Context{ private final IoBuffer innerBuffer; private String statusLine = ""; private String sender = ""; private String receiver = ""; private String length = ""; private String sms = ""; public Context(){ innerBuffer = IoBuffer.allocate(100).setAutoExpand(true); } private int matchCount = 0; private int line = 0; 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.matchCount = 0; this.line = 0; this.statusLine = ""; this.sender = ""; this.receiver = ""; this.length = ""; this.sms = ""; } public IoBuffer getInnerBuffer() { return innerBuffer; } } }
有一点特别需要我们注意,就是所有变量都来自Context的get方法,基本类型的变量改变之后别忘了把它们set回去,当然如果你把变量完全隐藏起来,只把外界可能用到的操作对外提供接口,那就更好了。