自定义协议编解码,自定义协议是使用最广泛的,因为它非常的灵活!
制定协议
协议需求:向服务端发送请求(频道的ID和说明文字),返回响应结果(该频道下所有的节目信息)。
协议格式如下:
请求格式
Syntax | No. of Bits | Identifier |
_ description (){ |
|
|
Descriptor tag | 16 | 0x0001 |
descriptor length | 32 | 从下一字节开始至末尾的数据长度 |
ID | 16 | channel ID值 |
chanel_des_len | 8 | 频道说明文字 |
for(i=0;i< chanel_des_len;i++){ |
|
|
Byte_data | 8 |
|
} |
|
|
} |
| // end _description |
响应格式
Syntax | No. of Bits | Identifier |
_ description (){ |
|
|
Tag | 16 | 0x8001 |
Data_length | 32 | 从下一字节开始至末尾的数据长度 |
channel_addr | 32 | 频道名称的地址 |
channel _len | 8 | 频道名称的字符串长度 |
programme_count | 16 | 节目个数 |
for(i=0;i< programme_count;i++){ |
|
|
dayIndex | 8 | 属于哪一天(以当日为基准)(-1表示前一天;0表示当天;1表示下一天;2表示后两天) |
event_addr | 32 | 节目名称的地址 |
event _len | 8 | 节目名称的字符串长度 |
StartTime | 32 | 节目偏移开始时间 |
TotalTime | 16 | 节目总时长(以秒为单位) |
Status | 8 | 节目当前状态(已录制【0x01】//待录制【0x00】) |
url_addr | 32 | 节目播放地址的addr |
url _len | 8 | 节目播放地址的长度 |
} |
| // end for |
For(j=0;j<;j++){ |
|
|
Byte_data | 8 | 真实数据 |
} |
|
|
} |
| // end _description |
协议解释如下:
a.协议前两个字节(16Bits)是协议的唯一标识值;如上:请求部分的tag = 0x0001,响应部分的 tag = 0x8001
b.接着四个字节(32Bits)是传输消息的长度;
c.接下来是数据区。
1.分析请求部分:
Syntax | No. of Bits | Identifier |
_ description (){ |
|
|
Descriptor tag | 16 | 0x0001 |
descriptor length | 32 | 从下一字节开始至末尾的数据长度 |
ID | 16 | channel ID值 |
chanel_des_len | 8 | 频道说明文字 |
for(i=0;i< chanel_des_len;i++){ |
|
|
Byte_data | 8 |
|
} |
|
|
} |
| // end _description |
请求部分是客户端(机顶盒)向服务端发送的请求;协议I的请求只发送了两个个参数:channelID和channel_dec(频道描述信息)
各个参数分析:
a.descriptor tag:请求的唯一标识; -- 2个字节
b.descriptor length:数据区长度; -- 4个字节
c.ID:channelID; -- 2个字节
d.channel_dec_len:频道说明信息的字节长度 -- 1个字节
e.for循环:存放频道说明信息的真实数据(字节数组中)
2.协议格式总结
前面2个绿色部分称为报文头,固定6个字节;
中间2个蓝色部分称为基本数据区,用Java的8个基本数据类型描述;
最后的红色部分称为真实数据区,所有String类型的信息都放在这里;
基本数据区+真实数据区 =数据区
协议格式:报文头+数据区
图示如下:
总之,对于基本数据类型,直接存放在基本数据区,对于String类型,在基本数据区描述它的长度和在真实数据区的地址,然后存在在真实数据区;而Java对象,则是把对象属性分解为基本数据类型和String类型发送;因此,解码必须获得三个信息:
a.请求标识:根据请求的不同进行不同的解码
b.数据区总长度:定是否接受数据成功;
c.偏移地址:知道真实数据区位置,就可以解码String数据。
图示如下:
3.代码实现
a.首先定义消息的抽象类,定义获取3个解码信息的方法
package com.bijian.study.mina.message;
import java.nio.charset.Charset;
public abstract class AbstrMessage {
// 协议编号
public abstract short getTag();
// 数据区长度
public abstract int getLen(Charset charset);
// 真实数据偏移地址
public abstract int getDataOffset();
}
b.定义请求对象和响应对象;
package com.bijian.study.mina.message;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
/*
* 请求的Java对象
*/
public class ChannelInfoRequest extends AbstrMessage {
private Logger logger = Logger.getLogger(ChannelInfoRequest.class);
private String channel_desc;
private int channel_id;
@Override
public short getTag() {
return (short) 0x0001;
}
@Override
public int getLen(Charset charset) {
int len = 2 + 1;
try {
if (channel_desc != null && !"".equals(channel_desc)) {
len += channel_desc.getBytes(charset).length;
}
} catch (Exception e) {
logger.error("频道说明转换为字节码错误...", e);
}
return len;
}
@Override
public int getDataOffset() {
int len = 2 + 4 + 2 + 1;
return len;
}
public String getChannel_desc() {
return channel_desc;
}
public void setChannel_desc(String channel_desc) {
this.channel_desc = channel_desc;
}
public int getChannel_id() {
return channel_id;
}
public void setChannel_id(int channel_id) {
this.channel_id = channel_id;
}
}
响应的Java对象:
package com.bijian.study.mina.message;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
/*
* 响应的Java对象
*/
public class ChannelInfoResponse extends AbstrMessage {
private Logger logger = Logger.getLogger(ChannelInfoResponse.class);
private String ChannelName;
private EventDto[] events;
@Override
public short getTag() {
return (short) 0x8001;
}
@Override
public int getLen(Charset charset) {
int len = 4 + 1 + 2;
try {
if (events != null && events.length > 0) {
for (int i = 0; i < events.length; i++) {
EventDto edt = events[i];
len += 1 + 4 + 1 + 4 + 2 + 1 + 4 + 1 + edt.getLen(charset);
}
}
if (ChannelName != null && !"".equals(ChannelName)) {
len += ChannelName.getBytes(charset).length;
}
} catch (Exception e) {
logger.error("频道信息转换为字节码错误...", e);
}
return len;
}
@Override
public int getDataOffset() {
int len = 2 + 4 + 4 + 1 + 2;
if (events != null && events.length > 0) {
len += events.length * (1 + 4 + 1 + 4 + 2 + 1 + 4 + 1);
}
return len;
}
public String getChannelName() {
return ChannelName;
}
public void setChannelName(String channelName) {
ChannelName = channelName;
}
public EventDto[] getEvents() {
return events;
}
public void setEvents(EventDto[] events) {
this.events = events;
}
}
package com.bijian.study.mina.message;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
public class EventDto {
private Logger logger = Logger.getLogger(EventDto.class);
private String eventName;
private int beginTime;
private int totalTime;
private int dayIndex;
private int status;
private String url;
// 节目中字符数据的字节长度
public int getLen(Charset charset) {
int len = 0;
try {
if (eventName != null && !"".equals(eventName)) {
len += eventName.getBytes(charset).length;
}
if (url != null && !"".equals(url)) {
len += url.getBytes(charset).length;
}
} catch (Exception e) {
logger.error("节目信息转换为字节码错误...", e);
}
return len;
}
public String getEventName() {
return eventName;
}
public void setEventName(String eventName) {
this.eventName = eventName;
}
public int getBeginTime() {
return beginTime;
}
public void setBeginTime(int beginTime) {
this.beginTime = beginTime;
}
public int getTotalTime() {
return totalTime;
}
public void setTotalTime(int totalTime) {
this.totalTime = totalTime;
}
public int getDayIndex() {
return dayIndex;
}
public void setDayIndex(int dayIndex) {
this.dayIndex = dayIndex;
}
public int getStatus() {
return status;
}
public void setStatus(int status) {
this.status = status;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
c.解码器
package com.bijian.study.mina.codec;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import org.apache.log4j.Logger;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.filter.codec.demux.MessageDecoderResult;
import com.bijian.study.mina.message.AbstrMessage;
import com.bijian.study.mina.message.ChannelInfoRequest;
import com.bijian.study.mina.message.ChannelInfoResponse;
import com.bijian.study.mina.message.EventDto;
public class MyMessageDecoder implements MessageDecoder {
private Logger logger = Logger.getLogger(MyMessageDecoder.class);
private Charset charset;
public MyMessageDecoder(Charset charset) {
this.charset = charset;
}
// 检查给定的IoBuffer是否适合解码
public MessageDecoderResult decodable(IoSession session, IoBuffer in) {
// 报头长度==6
if (in.remaining() < 6) {
return MessageDecoderResult.NEED_DATA;
}
// tag正常
short tag = in.getShort();
// 注意先把16进制标识值转换为short类型的十进制数据,然后与tag比较
if (tag == (short) 0x0001 || tag == (short) 0x8001) {
logger.info("请求标识符:" + tag);
} else {
logger.error("未知的解码类型....");
return MessageDecoderResult.NOT_OK;
}
// 真实数据长度
int len = in.getInt();
if (in.remaining() < len) {
return MessageDecoderResult.NEED_DATA;
}
return MessageDecoderResult.OK;
}
public MessageDecoderResult decode(IoSession session, IoBuffer in,
ProtocolDecoderOutput out) throws Exception {
logger.info("解码:" + in.toString());
CharsetDecoder decoder = charset.newDecoder();
AbstrMessage message = null;
short tag = in.getShort(); // tag
int len = in.getInt(); // len
byte[] temp = new byte[len];
in.get(temp); // 数据区
// ===============解析数据做准备======================
IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);
buf.put(temp);
buf.flip(); // 为获取基本数据区长度做准备
IoBuffer databuf = IoBuffer.allocate(100).setAutoExpand(true);
databuf.putShort(tag);
databuf.putInt(len);
databuf.put(temp);
databuf.flip(); // 为获取真实数据区长度做准备
// ================开始解码=========================
// 注意先把16进制标识值转换为short类型的十进制数据,然后与tag比较
if (tag == (short) 0x0001) { // 服务端解码
ChannelInfoRequest req = new ChannelInfoRequest();
short channel_id = buf.getShort();
byte channel_desc_len = buf.get();
String channel_desc = null;
if (channel_desc_len > 0) {
channel_desc = buf.getString(channel_desc_len, decoder);
}
req.setChannel_id(channel_id);
req.setChannel_desc(channel_desc);
message = req;
} else if (tag == (short) 0x8001) { // 客户端解码
ChannelInfoResponse res = new ChannelInfoResponse();
int channel_addr = buf.getInt();
byte channel_len = buf.get();
if (databuf.position() == 0) {
databuf.position(channel_addr);
}
String channelName = null;
if (channel_len > 0) {
channelName = databuf.getString(channel_len, decoder);
}
res.setChannelName(channelName);
short event_num = buf.getShort();
EventDto[] events = new EventDto[event_num];
for (int i = 0; i < event_num; i++) {
EventDto edt = new EventDto();
byte dayIndex = buf.get();
buf.getInt();
byte eventName_len = buf.get();
String eventName = null;
if (eventName_len > 0) {
eventName = databuf.getString(eventName_len, decoder);
}
int beginTime = buf.getInt();
short totalTime = buf.getShort();
byte status = buf.get();
buf.getInt();
byte url_len = buf.get();
String url = null;
if (url_len > 0) {
url = databuf.getString(url_len, decoder);
}
edt.setDayIndex(dayIndex);
edt.setEventName(eventName);
edt.setBeginTime(beginTime);
edt.setTotalTime(totalTime);
edt.setStatus(status);
edt.setUrl(url);
events[i] = edt;
}
res.setEvents(events);
message = res;
} else {
logger.error("未找到解码器....");
}
out.write(message);
// ================解码成功=========================
return MessageDecoderResult.OK;
}
public void finishDecode(IoSession session, ProtocolDecoderOutput out)
throws Exception {
}
}
d.编码器
package com.bijian.study.mina.codec;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import org.apache.log4j.Logger;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import com.bijian.study.mina.message.AbstrMessage;
import com.bijian.study.mina.message.ChannelInfoRequest;
import com.bijian.study.mina.message.ChannelInfoResponse;
import com.bijian.study.mina.message.EventDto;
public class MyMessageEncoder implements MessageEncoder<AbstrMessage> {
private Logger logger = Logger.getLogger(MyMessageEncoder.class);
private Charset charset;
public MyMessageEncoder(Charset charset) {
this.charset = charset;
}
public void encode(IoSession session, AbstrMessage message,
ProtocolEncoderOutput out) throws Exception {
IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);
buf.putShort(message.getTag());
buf.putInt(message.getLen(charset));
// ===========编码数据区===============
if (message instanceof ChannelInfoRequest) {
ChannelInfoRequest req = (ChannelInfoRequest) message;
buf.putShort((short) req.getChannel_id());
buf.put((byte) req.getChannel_desc().getBytes(charset).length);
buf.putString(req.getChannel_desc(), charset.newEncoder());
} else if (message instanceof ChannelInfoResponse) {
ChannelInfoResponse res = (ChannelInfoResponse) message;
CharsetEncoder encoder = charset.newEncoder();
IoBuffer dataBuffer = IoBuffer.allocate(100).setAutoExpand(true); // 定义真实数据区
int offset = res.getDataOffset(); // 偏移地址
buf.putInt(offset); // 频道名称地址(偏移开始位置)
byte channelName_len = 0;
if (res.getChannelName() != null) {
channelName_len = (byte) res.getChannelName().getBytes(charset).length;
}
buf.put(channelName_len);
offset += channelName_len;
if (channelName_len > 0) {
dataBuffer.putString(res.getChannelName(), encoder);
}
EventDto[] events = res.getEvents();
if (events != null) {
buf.putShort((short) events.length);
for (int i = 0; i < events.length; i++) {
EventDto edt = events[i];
buf.put((byte) edt.getDayIndex());
buf.putInt(offset);
String eventName = edt.getEventName();
byte eventName_len = 0;
if (eventName != null) {
eventName_len = (byte) eventName.getBytes(charset).length;
}
offset += eventName_len;
buf.put(eventName_len);
if (eventName_len > 0) {
dataBuffer.putString(eventName, encoder);
}
buf.putInt(edt.getBeginTime());
buf.putShort((short) edt.getTotalTime());
buf.put((byte) edt.getStatus());
buf.putInt(offset);
String url = edt.getUrl();
byte url_len = 0;
if (url != null) {
url_len = (byte) url.getBytes(charset).length;
}
offset += url_len;
buf.put(url_len);
if (url_len > 0) {
dataBuffer.putString(url, encoder);
}
}
}
// 真实数据追加在基本数据后面
if (dataBuffer.position() > 0) {
buf.put(dataBuffer.flip());
}
}
// ==========编码成功=================
buf.flip();
logger.info("编码" + buf.toString());
out.write(buf);
}
}
e.编解码器工厂
package com.bijian.study.mina.codec;
import org.apache.mina.filter.codec.demux.DemuxingProtocolCodecFactory;
import org.apache.mina.filter.codec.demux.MessageDecoder;
import org.apache.mina.filter.codec.demux.MessageEncoder;
import com.bijian.study.mina.message.AbstrMessage;
public class MyMessageCodecFactory extends DemuxingProtocolCodecFactory {
private MessageDecoder decoder;
private MessageEncoder<AbstrMessage> encoder;
// 注册编解码器
public MyMessageCodecFactory(MessageDecoder decoder,
MessageEncoder<AbstrMessage> encoder) {
this.decoder = decoder;
this.encoder = encoder;
addMessageDecoder(this.decoder);
addMessageEncoder(AbstrMessage.class, this.encoder);
}
}
f.服务端和服务端处理类
package com.bijian.study.mina.server;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSessionConfig;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LogLevel;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import com.bijian.study.mina.codec.MyMessageCodecFactory;
import com.bijian.study.mina.codec.MyMessageDecoder;
import com.bijian.study.mina.codec.MyMessageEncoder;
import com.bijian.study.mina.handler.Demo2ServerHandler;
public class TestServer02 {
private static Logger logger = Logger.getLogger(TestServer02.class);
private static int PORT = 3005;
public static void main(String[] args) {
IoAcceptor acceptor = null;
try {
// 创建一个非阻塞的server端的Socket
acceptor = new NioSocketAcceptor();
// 设置过滤器(添加自带的编解码器)
acceptor.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new MyMessageCodecFactory(
new MyMessageDecoder(Charset.forName("utf-8")),
new MyMessageEncoder(Charset.forName("utf-8")))));
// 设置日志过滤器
LoggingFilter lf = new LoggingFilter();
lf.setMessageReceivedLogLevel(LogLevel.DEBUG);
acceptor.getFilterChain().addLast("logger", lf);
// 获得IoSessionConfig对象
IoSessionConfig cfg = acceptor.getSessionConfig();
// 读写通道10秒内无操作进入空闲状态
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 100);
// 绑定逻辑处理器
acceptor.setHandler(new Demo2ServerHandler());
// 绑定端口
acceptor.bind(new InetSocketAddress(PORT));
logger.info("服务端启动成功... 端口号为:" + PORT);
} catch (Exception e) {
logger.error("服务端启动异常....", e);
e.printStackTrace();
}
}
}
package com.bijian.study.mina.handler;
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.core.session.IoSession;
import com.bijian.study.mina.message.ChannelInfoRequest;
import com.bijian.study.mina.message.ChannelInfoResponse;
import com.bijian.study.mina.message.EventDto;
public class Demo2ServerHandler extends IoHandlerAdapter {
public static Logger logger = Logger.getLogger(Demo2ServerHandler.class);
@Override
public void sessionCreated(IoSession session) throws Exception {
logger.info("服务端与客户端创建连接...");
}
@Override
public void sessionOpened(IoSession session) throws Exception {
logger.info("服务端与客户端连接打开...");
}
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
if (message instanceof ChannelInfoRequest) {
ChannelInfoRequest req = (ChannelInfoRequest) message;
int channel_id = req.getChannel_id();
String channel_desc = req.getChannel_desc();
logger.info("服务端接收到的数据为:channel_id=" + channel_id
+ " channel_desc=" + channel_desc);
// ================具体操作,比如查询数据库等,这里略....=============
ChannelInfoResponse res = new ChannelInfoResponse();
res.setChannelName("CCTV1高清频道");
EventDto[] events = new EventDto[2];
for (int i = 0; i < events.length; i++) {
EventDto edt = new EventDto();
edt.setBeginTime(10);
edt.setDayIndex(1);
edt.setEventName("风云第一的" + i);
edt.setStatus(1);
edt.setTotalTime(100 + i);
edt.setUrl("www.baidu.com");
events[i] = edt;
}
res.setEvents(events);
session.write(res);
} else {
logger.info("未知请求!");
}
}
@Override
public void messageSent(IoSession session, Object message) throws Exception {
session.close();
logger.info("服务端发送信息成功...");
}
@Override
public void sessionClosed(IoSession session) throws Exception {
}
@Override
public void sessionIdle(IoSession session, IdleStatus status)
throws Exception {
logger.info("服务端进入空闲状态...");
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
logger.error("服务端发送异常...", cause);
}
}
g.客户端和客户端处理类
package com.bijian.study.mina.client;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.log4j.Logger;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoConnector;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import com.bijian.study.mina.codec.MyMessageCodecFactory;
import com.bijian.study.mina.codec.MyMessageDecoder;
import com.bijian.study.mina.codec.MyMessageEncoder;
import com.bijian.study.mina.handler.Demo2ClientHandler;
import com.bijian.study.mina.message.ChannelInfoRequest;
public class TestClient02 {
private static Logger logger = Logger.getLogger(TestClient02.class);
private static String HOST = "127.0.0.1";
private static int PORT = 3005;
public static void main(String[] args) {
// 创建一个非阻塞的客户端程序
IoConnector connector = new NioSocketConnector();
// 设置链接超时时间
connector.setConnectTimeout(30000);
// 添加过滤器
connector.getFilterChain().addLast(
"codec",
new ProtocolCodecFilter(new MyMessageCodecFactory(
new MyMessageDecoder(Charset.forName("utf-8")),
new MyMessageEncoder(Charset.forName("utf-8")))));
// 添加业务逻辑处理器类
connector.setHandler(new Demo2ClientHandler());
IoSession session = null;
try {
ConnectFuture future = connector.connect(new InetSocketAddress(
HOST, PORT));// 创建连接
future.awaitUninterruptibly();// 等待连接创建完成
session = future.getSession();// 获得session
ChannelInfoRequest req = new ChannelInfoRequest(); // 发送请求
req.setChannel_id(12345);
req.setChannel_desc("mina在做测试哦哦....哇呀呀!!!");
session.write(req);// 发送消息
} catch (Exception e) {
logger.error("客户端链接异常...", e);
}
session.getCloseFuture().awaitUninterruptibly();// 等待连接断开
connector.dispose();
}
}
package com.bijian.study.mina.handler;
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import com.bijian.study.mina.message.ChannelInfoResponse;
import com.bijian.study.mina.message.EventDto;
public class Demo2ClientHandler extends IoHandlerAdapter {
private static Logger logger = Logger.getLogger(Demo2ClientHandler.class);
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
if (message instanceof ChannelInfoResponse) {
ChannelInfoResponse res = (ChannelInfoResponse) message;
String channelName = res.getChannelName();
EventDto[] events = res.getEvents();
logger.info("客户端接收到的消息为:channelName=" + channelName);
if(events!=null && events.length>0){
for (int i = 0; i < events.length; i++) {
EventDto edt = events[i];
logger.info("客户端接收到的消息为:BeginTime=" + edt.getBeginTime());
logger.info("客户端接收到的消息为:DayIndex=" + edt.getDayIndex());
logger.info("客户端接收到的消息为:EventName=" + edt.getEventName());
logger.info("客户端接收到的消息为:Status=" + edt.getStatus());
logger.info("客户端接收到的消息为:TotalTime=" + edt.getTotalTime());
logger.info("客户端接收到的消息为:url=" + edt.getUrl());
}
}
}else{
logger.info("未知类型!");
}
}
@Override
public void exceptionCaught(IoSession session, Throwable cause)
throws Exception {
logger.error("客户端发生异常...", cause);
}
}
h.运行测试
服务端输出信息:
2016-02-15 23:05:26,539 INFO TestServer02 - 服务端启动成功... 端口号为:3005 2016-02-15 23:05:36,531 INFO LoggingFilter - CREATED 2016-02-15 23:05:36,547 INFO Demo2ServerHandler - 服务端与客户端创建连接... 2016-02-15 23:05:36,547 INFO LoggingFilter - OPENED 2016-02-15 23:05:36,548 INFO Demo2ServerHandler - 服务端与客户端连接打开... 2016-02-15 23:05:36,550 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-02-15 23:05:36,552 INFO MyMessageDecoder - 请求标识符:1 2016-02-15 23:05:36,553 INFO MyMessageDecoder - 解码:HeapBuffer[pos=0 lim=53 cap=2048: 00 01 00 00 00 2F 30 39 2C 6D 69 6E 61 E5 9C A8...] 2016-02-15 23:05:36,554 DEBUG LoggingFilter - RECEIVED: com.bijian.study.mina.message.ChannelInfoRequest@21ba1b 2016-02-15 23:05:36,554 INFO Demo2ServerHandler - 服务端接收到的数据为:channel_id=12345 channel_desc=mina在做测试哦哦....哇呀呀!!! 2016-02-15 23:05:36,558 INFO MyMessageEncoder - 编码HeapBuffer[pos=0 lim=124 cap=128: 80 01 00 00 00 76 00 00 00 31 11 00 02 01 00 00...] 2016-02-15 23:05:36,561 INFO LoggingFilter - SENT: com.bijian.study.mina.message.ChannelInfoResponse@487cc7 2016-02-15 23:05:36,561 INFO Demo2ServerHandler - 服务端发送信息成功... 2016-02-15 23:05:36,562 INFO LoggingFilter - CLOSED
客户端输出信息:
2016-02-15 23:05:36,538 INFO MyMessageEncoder - 编码HeapBuffer[pos=0 lim=53 cap=100: 00 01 00 00 00 2F 30 39 2C 6D 69 6E 61 E5 9C A8...] 2016-02-15 23:05:36,563 DEBUG ProtocolCodecFilter - Processing a MESSAGE_RECEIVED for session 1 2016-02-15 23:05:36,566 INFO MyMessageDecoder - 请求标识符:-32767 2016-02-15 23:05:36,567 INFO MyMessageDecoder - 解码:HeapBuffer[pos=0 lim=124 cap=2048: 80 01 00 00 00 76 00 00 00 31 11 00 02 01 00 00...] 2016-02-15 23:05:36,568 INFO Demo2ClientHandler - 客户端接收到的消息为:channelName=CCTV1高清频道 2016-02-15 23:05:36,568 INFO Demo2ClientHandler - 客户端接收到的消息为:BeginTime=10 2016-02-15 23:05:36,569 INFO Demo2ClientHandler - 客户端接收到的消息为:DayIndex=1 2016-02-15 23:05:36,569 INFO Demo2ClientHandler - 客户端接收到的消息为:EventName=风云第一的0 2016-02-15 23:05:36,569 INFO Demo2ClientHandler - 客户端接收到的消息为:Status=1 2016-02-15 23:05:36,569 INFO Demo2ClientHandler - 客户端接收到的消息为:TotalTime=100 2016-02-15 23:05:36,570 INFO Demo2ClientHandler - 客户端接收到的消息为:url=www.baidu.com 2016-02-15 23:05:36,570 INFO Demo2ClientHandler - 客户端接收到的消息为:BeginTime=10 2016-02-15 23:05:36,570 INFO Demo2ClientHandler - 客户端接收到的消息为:DayIndex=1 2016-02-15 23:05:36,570 INFO Demo2ClientHandler - 客户端接收到的消息为:EventName=风云第一的1 2016-02-15 23:05:36,570 INFO Demo2ClientHandler - 客户端接收到的消息为:Status=1 2016-02-15 23:05:36,571 INFO Demo2ClientHandler - 客户端接收到的消息为:TotalTime=101 2016-02-15 23:05:36,572 INFO Demo2ClientHandler - 客户端接收到的消息为:url=www.baidu.com
4.总结
IoFilter是转码和解码用滴,它是Mina最值得研究的地方,建议阅读它的源码!
在实际的应用开发中,自定义协议是必用的,因为很多客户端和服务端是不同语言实现的。
三.IoHandler接口
IoHandler是Mina实现其业务逻辑的顶级接口;它相当简单,你就理解它是根据事件触发的简单应用程序即可。
在IoHandler中定义了7个方法,根据I/O事件来触发对应的方法:
import java.io.IOException;
public interface IoHandler {
void sessionCreated(IoSession session) throws Exception;
void sessionOpened(IoSession session) throws Exception;
void sessionClosed(IoSession session) throws Exception;
void sessionIdle(IoSession session, IdleStatus status) throws Exception;
void exceptionCaught(IoSession session, Throwable cause) throws Exception;
void messageReceived(IoSession session, Object message) throws Exception;
void messageSent(IoSession session, Object message) throws Exception;
}
sessionCreated:当一个新的连接建立时,由I/O processor thread调用;
sessionOpened:当连接打开是调用;
messageReceived:当接收了一个消息时调用;
messageSent:当一个消息被(IoSession#write)发送出去后调用;
sessionIdle:当连接进入空闲状态时调用;
sessionClosed:当连接关闭时调用;
exceptionCaught:当实现IoHandler的类抛出异常时调用;
一般情况下,我们最关心的只有messageReceived方法,接收消息并处理,然后调用IoSession的write方法发送出消息!(注意:这里接收到的消息都是Java对象,在IoFilter中所有二进制数据都被解码啦!)
一般情况下很少有人实现IoHandler接口,而是继承它的一个实现类IoHandlerAdapter,这样不用覆盖它的7个方法,只需要根据具体需求覆盖其中的几个方法就可以!
a.Iohandler的7个方法其实是根据session的4个状态值间变化来调用的:
b.Connected:会话被创建并使用;
c.Idle:会话在一段时间(可配置)内没有任何请求到达,进入空闲状态;
d.Closing:会话将被关闭(剩余message将被强制flush);
e.Closed:会话被关闭;
状态转换图如下: