下面我们举一个模拟电信运营商短信协议的编解码器实现,假设通信协议如下所示:
M sip:wap.fetion.com.cn SIP-C/2.0
S: 1580101xxxx
R: 1889020xxxx
L: 21
Hello World!
这里的第一行表示状态行,一般表示协议的名字、版本号等,第二行表示短信的发送号码,第三行表示短信接收的号码,第四行表示短信的字节数,最后的内容就是短信的内容。上面的每一行的末尾使用ASC II 的10(\n)作为换行符,因为这是纯文本数据,协议要求双方使用UTF-8 对字符串编解码。实际上如果你熟悉HTTP 协议,上面的这个精简的短信协议和HTTP 协议的组成是非常像
的,第一行是状态行,中间的是消息报头,最后面的是消息正文。在解析这个短信协议之前,你需要知晓TCP 的一个事项,那就是数据的发送没有规模性,所谓的规模性就是作为数据的接收端,不知道到底什么时候数据算是读取完毕,所以应用层协议在制定的时候,必须指定数据读取的截至点。一般来说,有如下三种方式设置数据读取的长度:
(1.)使用分隔符,譬如:TextLine 编解码器。你可以使用\r、\n、NUL 这些ASC II 中的特殊的字符来告诉数据接收端,你只要遇见分隔符,就表示数据读完了,不用在那里傻等着不知道还有没有数据没读完啊?我可不可以开始把已经读取到的字节解码为指定的数据类型了啊?
(2.)定长的字节数,这种方式是使用长度固定的数据发送,一般适用于指令发送,譬如:数据发送端规定发送的数据都是双字节,AA 表示启动、BB 表示关闭等等。
(3.)在数据中的某个位置使用一个长度域,表示数据的长度,这种处理方式最为灵活,上面的短信协议中的那个L 就是短信文字的字节数,其实HTTP 协议的消息报头中的Content-Length 也是表示消息正文的长度,这样数据的接收端就知道我到底读到多长的字节数就表示不用再读取数据了。相比较解码(字节转为JAVA 对象,也叫做拆包)来说,编码(JAVA 对象转为字节,也叫做打包)就很简单了,你只需要把JAVA 对象转为指定格式的字节流,write()就可以了。
下面我们开始对上面的短信协议进行编解码处理。
<span style="font-weight: normal;">public class SmsObject { private String sender;// 短信发送者 private String receiver;// 短信接受者 private String message;// 短信内容 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 getMessage() { return message; } public void setMessage(String message) { this.message = message; } }</span>
public class CmccSipcEncoder extends ProtocolEncoderAdapter { private final Charset charset; public CmccSipcEncoder(Charset charset) { this.charset = charset; } @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); buffer .putString("L: " + (smsContent.getBytes(charset).length) + "\n", ce); buffer.putString(smsContent, ce); buffer.flip(); out.write(buffer); } }
public class CmccSipcDecoder extends CumulativeProtocolDecoder { <span style="white-space:pre"> </span>private final Charset charset; <span style="white-space:pre"> </span>public CmccSipcDecoder(Charset charset) { <span style="white-space:pre"> </span>this.charset = charset; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>protected boolean doDecode(IoSession session, IoBuffer in,ProtocolDecoderOutput out) throws Exception { <span style="white-space:pre"> </span>IoBuffer buffer = IoBuffer.allocate(100).setAutoExpand(true); <span style="white-space:pre"> </span>CharsetDecoder cd = charset.newDecoder(); <span style="white-space:pre"> </span>int matchCount = 0; <span style="white-space:pre"> </span>String statusLine = "", sender = "", receiver = "", length = "", <span style="white-space:pre"> </span>sms = ""; <span style="white-space:pre"> </span>int i = 1; <span style="white-space:pre"> </span>while (in.hasRemaining()) { <span style="white-space:pre"> </span>byte b = in.get(); <span style="white-space:pre"> </span>buffer.put(b); <span style="white-space:pre"> </span>if (b == 10 && i < 5) { <span style="white-space:pre"> </span>matchCount++; <span style="white-space:pre"> </span>if (i == 1) { <span style="white-space:pre"> </span>buffer.flip(); <span style="white-space:pre"> </span>statusLine = buffer.getString(matchCount, cd); <span style="white-space:pre"> </span>statusLine = statusLine.substring(0, <span style="white-space:pre"> </span>statusLine.length() - 1); <span style="white-space:pre"> </span>matchCount = 0; <span style="white-space:pre"> </span>buffer.clear(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (i == 2) { <span style="white-space:pre"> </span>buffer.flip(); <span style="white-space:pre"> </span>sender = buffer.getString(matchCount, cd); <span style="white-space:pre"> </span>sender = sender.substring(0, sender.length() - 1); <span style="white-space:pre"> </span>matchCount = 0; <span style="white-space:pre"> </span>buffer.clear(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (i == 3) { <span style="white-space:pre"> </span>buffer.flip(); <span style="white-space:pre"> </span>receiver = buffer.getString(matchCount, cd); <span style="white-space:pre"> </span>receiver = receiver.substring(0, receiver.length() -1); <span style="white-space:pre"> </span>matchCount = 0; <span style="white-space:pre"> </span>buffer.clear(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>if (i == 4) { <span style="white-space:pre"> </span>buffer.flip(); <span style="white-space:pre"> </span>length = buffer.getString(matchCount, cd); <span style="white-space:pre"> </span>length = length.substring(0, length.length() - 1); <span style="white-space:pre"> </span>matchCount = 0; <span style="white-space:pre"> </span>buffer.clear(); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>i++; <span style="white-space:pre"> </span>} else if (i == 5) { <span style="white-space:pre"> </span>matchCount++; <span style="white-space:pre"> </span>if (matchCount == Long.parseLong(length.split(": ")[1])){ <span style="white-space:pre"> </span>buffer.flip(); <span style="white-space:pre"> </span>sms = buffer.getString(matchCount, cd); <span style="white-space:pre"> </span>i++; <span style="white-space:pre"> </span>break; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} else { <span style="white-space:pre"> </span>matchCount++; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>SmsObject smsObject = new SmsObject(); <span style="white-space:pre"> </span>smsObject.setSender(sender.split(": ")[1]); <span style="white-space:pre"> </span>smsObject.setReceiver(receiver.split(": ")[1]); <span style="white-space:pre"> </span>smsObject.setMessage(sms); <span style="white-space:pre"> </span>out.write(smsObject); <span style="white-space:pre"> </span>return false; <span style="white-space:pre"> </span>} }
public class XXXDecoder extends CumulativeProtocolDecoder{ <span style="white-space:pre"> </span>private final AttributeKey CONTEXT =new AttributeKey(getClass(), "context" ); <span style="white-space:pre"> </span>public Context getContext(IoSession session){ <span style="white-space:pre"> </span>Context ctx=(Context)session.getAttribute(CONTEXT); <span style="white-space:pre"> </span>if(ctx==null){ <span style="white-space:pre"> </span>ctx=new Context(); <span style="white-space:pre"> </span>session.setAttribute(CONTEXT,ctx); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>private class Context { <span style="white-space:pre"> </span>//状态变量 <span style="white-space:pre"> </span>} }注意这里我们使用了Mina 自带的AttributeKey 类来定义保存在IoSession 中的对象的键值,这样可以有效的防止键值重复。另外,要注意在全部处理完毕之后,状态要复位,譬如:聊天室中的一条消息读取完毕之后,状态变量要变为初始值,以便下次处理时重新使用。
public class CmccSipcCodecFactory implements ProtocolCodecFactory { <span style="white-space:pre"> </span>private final CmccSipcEncoder encoder; <span style="white-space:pre"> </span>private final CmccSipcDecoder decoder; <span style="white-space:pre"> </span>public CmccSipcCodecFactory() { <span style="white-space:pre"> </span>this(Charset.defaultCharset()); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>public CmccSipcCodecFactory(Charset charSet) { <span style="white-space:pre"> </span>this.encoder = new CmccSipcEncoder(charSet); <span style="white-space:pre"> </span>this.decoder = new CmccSipcDecoder(charSet); <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public ProtocolDecoder getDecoder(IoSession session) throws <span style="white-space:pre"> </span>Exception { <span style="white-space:pre"> </span>return decoder; <span style="white-space:pre"> </span>} <span style="white-space:pre"> </span>@Override <span style="white-space:pre"> </span>public ProtocolEncoder getEncoder(IoSession session) throws <span style="white-space:pre"> </span>Exception { <span style="white-space:pre"> </span>return encoder; <span style="white-space:pre"> </span>} }
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(new CmccSipcCodecFactory(Charset.forName("UTF-8")))); connector.getFilterChain().addLast("codec",new ProtocolCodecFilter(newCmccSipcCodecFactory(Charset.forName("UTF-8"))));
public void sessionOpened(IoSession session) { <span style="white-space:pre"> </span>SmsObject sms = new SmsObject(); <span style="white-space:pre"> </span>sms.setSender("15801012253"); <span style="white-space:pre"> </span>sms.setReceiver("18869693235"); <span style="white-space:pre"> </span>sms.setMessage("你好!Hello World!"); <span style="white-space:pre"> </span>session.write(sms); }
public void messageReceived(IoSession session, Object message) <span style="white-space:pre"> </span>throws Exception { <span style="white-space:pre"> </span>SmsObject sms = (SmsObject) message; <span style="white-space:pre"> </span>log.info("The message received is [" + sms.getMessage() + "]"); }