理论知识(个人见解)
要使用MINA搭建一个最基本的自定义协议服务器, 核心在于编码和解码, 也就是Filter.
在网络通讯中, 收发的都是字节流(byte[])
接收数据流时, 在这些数据变得可被处理(理解)之前, 要经过Filter的处理, 即解码
反之亦反, 发送数据时, 也要经过Filter的处理, 最终变成字节流, 即编码
在MINA中, Filter是以链的方式存储, 在收发数据时都有机会去编码/解码数据
一个Filter具备了处理一种数据类型的能力, 举个栗子
假设有三个Filter, 分别对应 1.打包/解包 2.压缩/解压缩 3.序列化/反序列化
在接收数据时, 先解包, 再解压缩, 最后反序列化, 交给Handler进行业务逻辑处理
在发送数据时, 先序列化, 再压缩, 再打包, 交给MINA把数据送出去
那么如何辨别哪种数据要交给哪个Filter处理?
本人理解的是: MINA会顺序调用这些Filter, 直到对象类型是被期望的
1.接收数据时, 数据经过层层Filter, 最终到达Handler.
在接收数据时可以没有Filter去解码数据, 最终还会调用Handler, 但这肯定会引起异常
2.发送数据时, 数据经过层层Filter, 最终变成MINA期望的IoBuffer类型, 发送出去.
除非Handler发送的就是IoBuffer类型, 不会调用任何Filter编码
否则在经过Filter的处理后必须转换成IoBuffer类型, 若不是, MINA报异常
3. IoBuffer可以理解为ByteArray的再封装
说明示例
实现一个服务器
能够和telnet客户端交互, 规定交互的内容必须是数字, 服务器应答此数字乘以2是多少
那么就有以下几个Filter
1.IoBuffer <--> byte[]
2. byte[] <--> String
3. String <--> Long
对于业务层的Handler来说, 它收到的和发送的都是数值型(Long), 转换过程交由Filter处理, 是透明的
为了避免一些问题, 1和2用MINA自带的TextLineCodec来处理
先看看逻辑处理层
class MyIoHandler extends IoHandlerAdapter {
// 对于Handler来说
// 收到的和发送出去的都是Long类型
// 中间的数据转换一概不关心, 全部交由Filter处理
@Override
public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
cause.printStackTrace();
}
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
// 有数据到来时调用
Long input = (Long)message;
session.write(input * 2);
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
// 数据发送出去后调用此方法, 这里不作处理
}
@Override
public void sessionOpened(IoSession session) throws Exception {
// 客户端连接时调用
session.write("0"); // 想一下为什么可以发送字符串
}
@Override
public void sessionClosed(IoSession session) throws Exception {
// 客户端断开时调用
}
}
很简单, 看看注释即可, 功能就是把收到的数字乘以2再返回给客户端
TextLineCodec可以在官方源码里找到, 具有缓冲/判断一条信息完整等功能
下面来看自定义Filter的实现
class MyFilter extends IoFilterAdapter {
// Long <--> String
@Override
public void filterWrite(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
Object message = writeRequest.getMessage();
if (message instanceof Long) {
Long num = (Long)message;
nextFilter.filterWrite(session, new DefaultWriteRequest(num.toString(), writeRequest.getFuture(), writeRequest.getDestination()));
return;
}
nextFilter.filterWrite(session, writeRequest);
}
@Override
public void messageReceived(NextFilter nextFilter, IoSession session, Object message) throws Exception {
if (message instanceof String) {
String str = (String)message;
nextFilter.messageReceived(session, Long.parseLong(str));
return;
}
nextFilter.messageReceived(session, message);
}
@Override
public void messageSent(NextFilter nextFilter, IoSession session, WriteRequest writeRequest) throws Exception {
Object message = writeRequest.getMessage();
if (message instanceof String) {
String str = (String)message;
nextFilter.messageSent(session, new DefaultWriteRequest(Long.parseLong(str), writeRequest.getFuture(), writeRequest.getDestination()));
return;
}
nextFilter.messageSent(session, writeRequest);
}
}
也不难, 从String到Long的相互转换
最后附上main函数
public class Server {
public static void main(String argv[]) {
new Server().Start();
}
public Server() {
}
public void Start() {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast("buffer2string", new ProtocolCodecFilter(new TextLineCodecFactory()));
acceptor.getFilterChain().addLast("string2long", new MyFilter());
acceptor.setHandler(new MyIoHandler());
try {
acceptor.bind(new InetSocketAddress(8989));
} catch (IOException e) {
e.printStackTrace();
}
}
}
理解和经验还需要自己体会
接收数据时: 字节流-->TextLineCodec-->MyFilter-->MyIoHandler
发送数据时: MyIoHandler-->MyFilter-->TextLineCodec-->字节流
细心的朋友会发现代码中发送了一个字符串"0", 也是可以的
这是因为即使不符合MyFilter处理的类型, 也会传递给下一个Filter, 而TextLineCodec可以处理