上一节实现了简单的EchoServer,客户端说什么,服务器返回you say:什么,但它是怎么传输的呢,协议是什么格式的呢,业务逻辑和协议真的分得很开呢
它的编码解码在这里设置
acceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter( |
注:自己实现个ProtocolCodecFactory和ProtocolEncoder和ProtocolDecoder,然后修改上面的设置.
至于格式,简单
byte数组,变长 | \r\n(2个字节) |
接下来,我们要传输什么对象呢,java.lang.String对象? 不太好,还是封装一下吧
<span style="font-size:14px;">package com.skymr.mina.tcptest;
public class LineText {
private String text;
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String toString(){
return text;
}
}</span><span style="font-size: 16px;">
</span>
传输LineText对象,也没做什么事,只是将String包装下.
然后就要进行编码解码的代码编写了.
编码要实现ProtocolEncoder
package com.skymr.mina.tcptest;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoder;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
public class MyLineCodecEncoder implements ProtocolEncoder{
@Override
public void encode(IoSession session, Object message,
ProtocolEncoderOutput out) throws Exception {
LineText text = (LineText)message;
IoBuffer buffer = IoBuffer.allocate(1024, false);
buffer.put(text.getText().getBytes("UTF-8"));
buffer.flip();
out.write(buffer);
}
@Override
public void dispose(IoSession session) throws Exception {
}
}
编码的工作:
1)取得要编码的对象LineText
2)将LineText中的字符串对象写入到IoBuffer中
3)IoBuffer转换为读模式,写入到ProtocolEncoderOutput对象
再来看解码的实现:
package com.skymr.mina.tcptest;
import java.nio.ByteBuffer;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
public class MyLineCodecDecoder implements ProtocolDecoder{
private String key = "textKey1234";
@Override
public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)
throws Exception {
ByteBuffer buffer = null;
Object o = session.getAttribute(key);
if(o == null){
buffer = ByteBuffer.allocate(1024);
session.setAttribute(key, buffer);
}
else{
buffer = (ByteBuffer)o;
}
if(in.limit()==2){
byte b1 = in.get();
byte b2 = in.get();
if(b1 == 13 && b2 == 10){
String text = new String(readBuffer(buffer));
LineText lineText = new LineText();
lineText.setText(text);
out.write(lineText);
buffer.clear();
}
}
else if(in.limit()==1){
buffer.put(in.get());
}
}
public static byte[] readBuffer(ByteBuffer buffer){
buffer.flip();
int len = buffer.limit() - buffer.position();
byte[] ret = new byte[len];
System.arraycopy(buffer.array(), 0, ret, 0, len);
return ret;
}
@Override
public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
session.removeAttribute(key);
}
@Override
public void dispose(IoSession session) throws Exception {
session.removeAttribute(key);
}
}
每次调用decode方法时,只会收到一个字节的Buffer或者2个字节的\r\n, 没有想到会是这样的,这和Blocking IO有点像,每次传输的是1个字节,那怎么组装成一行呢,我们可以用一个缓冲区,当是一个字节传入时,放到缓冲区,当\r\n传入时,才将缓冲区写入到解码流中.有人会问,怎么保存这个缓冲区呢?作为属性变量吗,不可以,如果是只有一个客户端就没有问题,当接入了多个客户端时,数据就会混乱了,所有客户端的数据都放到了一个缓冲区,还不出错?可以用IoSession来保存,这和java Web中保存客户数据很像啊.
记得在客户端关闭时删除保存缓冲区的状态啊,否则....不知道会不会内存溢出,但保险起见吧
接下来实现ProtocolCodecFactory
package com.skymr.mina.tcptest;
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;
public class MyLineCodecFactory implements ProtocolCodecFactory{
private ProtocolEncoder encoder = new MyLineCodecEncoder();
private ProtocolDecoder decoder = new MyLineCodecDecoder();
public MyLineCodecFactory(){
}
public MyLineCodecFactory(Charset charset){
}
@Override
public ProtocolEncoder getEncoder(IoSession session) throws Exception {
return encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession session) throws Exception {
return decoder;
}
}
这里没有什么可说的吧
最后修改Handler
package com.skymr.mina.tcptest;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class EchoHandler extends IoHandlerAdapter{
@Override
public void sessionOpened(IoSession session) throws Exception {
System.out.println("sessionOpened");
LineText lt = new LineText();
lt.setText("please say something:\r\n");
session.write(lt);
super.sessionOpened(session);
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
if("%quit%".equals(message)){
System.out.println("收到退出消息");
session.write("You will exit soon.\r\n");
session.close(true);
return;
}
System.out.println("收到消息:"+message);
LineText lt = new LineText();
lt.setText("you say:"+message+"\r\n");
session.write(lt);
}
}
没啥牛拜的, 将String换成LineText发送而已