自定义协议编解码,自定义协议是使用最广泛的,因为它非常的灵活!
制定协议
协议需求:向服务端发送请求(频道的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:会话被关闭;
状态转换图如下:
相关推荐
内容概要:本文详细介绍了基于SpringBoot和Vue开发的养老院管理系统的具体实现细节。该系统采用前后端不分离的架构,旨在快速迭代并满足中小项目的开发需求。文中涵盖了多个关键技术点,如数据库设计(组合唯一约束、触发器)、定时任务(@Scheduled、@Async)、前端数据绑定(Vue的条件渲染和动态class绑定)、权限控制(RBAC模型、自定义注解)以及报表导出(SXSSFWorkbook流式导出)。此外,还讨论了开发过程中遇到的一些常见问题及其解决方案,如CSRF防护、静态资源配置、表单提交冲突等。 适合人群:具备一定Java和前端开发经验的研发人员,尤其是对SpringBoot和Vue有一定了解的开发者。 使用场景及目标:适用于需要快速开发中小型管理系统的团队,帮助他们理解如何利用SpringBoot和Vue进行全栈开发,掌握前后端不分离架构的优势和注意事项。 其他说明:文章不仅提供了详细的代码示例和技术要点,还分享了许多实用的小技巧和避坑指南,有助于提高开发效率和系统稳定性。
家族企业如何应对人才流失问题?
员工关怀制度.doc
内容概要:本文详细探讨了对传统蚁群算法进行改进的方法,特别是在路径规划领域的应用。主要改进措施包括:采用排序搜索机制,即在每轮迭代后对所有路径按长度排序并只强化前20%的优质路径;调整信息素更新规则,如引入动态蒸发系数和分级强化策略;优化路径选择策略,增加排序权重因子;以及实现动态地图调整,使算法能够快速适应环境变化。实验结果显示,改进后的算法在收敛速度上有显著提升,在复杂地形中的表现更加稳健。 适合人群:从事路径规划研究的技术人员、算法工程师、科研工作者。 使用场景及目标:适用于需要高效路径规划的应用场景,如物流配送、机器人导航、自动驾驶等领域。目标是提高路径规划的效率和准确性,减少不必要的迂回路径,确保在动态环境中快速响应变化。 其他说明:改进后的蚁群算法不仅提高了收敛速度,还增强了对复杂环境的适应能力。建议在实际应用中结合可视化工具进行调参,以便更好地观察和优化蚂蚁的探索轨迹。此外,还需注意避免过度依赖排序机制而导致的过拟合问题。
内容概要:本文详细介绍了利用粒子群优化(PSO)算法解决配电网中分布式光伏系统的选址与定容问题的方法。首先阐述了问题背景,即在复杂的配电网环境中选择合适的光伏安装位置和确定合理的装机容量,以降低网损、减小电压偏差并提高光伏消纳效率。接着展示了具体的PSO算法实现流程,包括粒子初始化、适应度函数构建、粒子位置更新规则以及越界处理机制等关键技术细节。文中还讨论了目标函数的设计思路,将多个相互制约的目标如网损、电压偏差和光伏消纳通过加权方式整合为单一评价标准。此外,作者分享了一些实践经验,例如采用前推回代法进行快速潮流计算,针对特定应用场景调整权重系数,以及引入随机波动模型模拟光伏出力特性。最终实验结果显示,经过优化后的方案能够显著提升系统的整体性能。 适用人群:从事电力系统规划与设计的专业人士,尤其是那些需要处理分布式能源集成问题的研究人员和技术人员。 使用场景及目标:适用于希望深入了解如何运用智能优化算法解决实际工程难题的人士;旨在帮助读者掌握PSO算法的具体应用方法,从而更好地应对配电网中分布式光伏系统的选址定容挑战。 其他说明:文中提供了完整的Matlab源代码片段,便于读者理解和复现研究结果;同时也提到了一些潜在改进方向,鼓励进一步探索和创新。
内容概要:本文详细介绍了丰田Prius2004永磁同步电机的设计流程,涵盖从初始参数计算到最终温升仿真的各个环节。首先利用Excel进行基本参数计算,如铁芯叠厚、定子外径等,确保设计符合预期性能。接着使用Maxwell进行参数化仿真,通过Python脚本自动化调整磁钢尺寸和其他关键参数,优化电机性能并减少齿槽转矩。随后借助橡树岭实验室提供的实测数据验证仿真结果,确保模型准确性。最后采用MotorCAD进行温升仿真,优化冷却系统设计,确保电机运行安全可靠。文中还分享了许多实用技巧,如如何正确设置材料参数、避免常见的仿真错误等。 适合人群:从事电机设计的专业工程师和技术人员,尤其是对永磁同步电机设计感兴趣的读者。 使用场景及目标:适用于希望深入了解永磁同步电机设计全过程的技术人员,帮助他们在实际工作中提高设计效率和精度,解决常见问题,优化设计方案。 其他说明:文章提供了丰富的实战经验和具体的操作步骤,强调了理论与实践相结合的重要性。同时提醒读者注意一些容易忽视的细节,如材料参数的选择和仿真模型的准确性。
内容概要:本文详细介绍了基于DSP28335的单相逆变器的设计与实现,涵盖了多个关键技术模块。首先,ADC采样模块用于获取输入电压和电流的数据,确保后续控制的准确性。接着,PWM控制模块负责生成精确的脉宽调制信号,控制逆变器的工作状态。液晶显示模块则用于实时展示电压、电流等重要参数。单相锁相环电路实现了电网电压的频率和相位同步,确保逆变器输出的稳定性。最后,电路保护程序提供了过流保护等功能,保障系统的安全性。每个模块都有详细的代码示例和技术要点解析。 适合人群:具备一定嵌入式系统和电力电子基础知识的研发人员,尤其是对DSP28335感兴趣的工程师。 使用场景及目标:适用于单相逆变器项目的开发,帮助开发者理解和掌握各个模块的具体实现方法,提高系统的可靠性和性能。 其他说明:文中不仅提供了具体的代码实现,还分享了许多调试经验和常见问题的解决方案,有助于读者更好地理解和应用相关技术。
SecureCRT安装包
内容概要:本文详细介绍了如何利用C#、WPF和MVVM模式构建一个大屏看板3D可视化系统。主要内容涵盖WPF编程设计、自定义工业控件、数据库设计、MVVM架构应用以及典型的三层架构设计。文中不仅提供了具体的代码实例,还讨论了数据库连接配置、3D模型绑定、依赖属性注册等关键技术细节。此外,文章强调了项目开发过程中需要注意的问题,如3D坐标系换算、MVVM中命令传递、数据库连接字符串加密等。 适合人群:具备一定C#编程基础,对WPF和MVVM模式有一定了解的研发人员。 使用场景及目标:适用于希望深入了解WPF和MVVM模式在实际项目中应用的开发者,特别是那些从事工业控制系统、数据可视化平台开发的专业人士。通过学习本文,读者可以掌握如何构建高效、稳定的大屏看板3D可视化系统。 其他说明:本文提供的设计方案和技术实现方式,可以帮助开发者更好地理解和应用WPF和MVVM模式,同时也能为相关领域的项目开发提供有价值的参考。
基于ssm的系统设计,包含sql文件(Spring+SpringMVC+MyBatis)
内容概要:本文详细介绍了利用COMSOL进行非厄米超表面双参数传感器的设计与实现。首先,通过构建超表面单元并引入虚部折射率,实现了PT对称系统的增益-损耗交替分布。接着,通过频域扫描和参数化扫描,捕捉到了复频率空间中的能级劈裂现象,并找到了奇异点(Exceptional Point),从而显著提高了传感器对微小扰动的敏感度。此外,文章探讨了双参数检测的独特优势,如解耦温度和折射率变化的能力,并展示了其在病毒检测、工业流程监控等领域的潜在应用。 适合人群:从事光学传感器研究的专业人士,尤其是对非厄米系统和COMSOL仿真感兴趣的科研人员。 使用场景及目标:适用于需要高精度、多参数检测的应用场合,如生物医学检测、环境监测等。目标是提高传感器的灵敏度和分辨率,解决传统传感器中存在的参数交叉敏感问题。 其他说明:文中提供了详细的建模步骤和代码片段,帮助读者理解和重现实验结果。同时,强调了在建模过程中需要注意的关键技术和常见问题,如网格划分、参数设置等。
怎样健全员工福利体系.docx
离职证明范本.doc
6538b79724855900a9c930904a302920.part6
员工离职单.doc
内容概要:本文详细介绍了在COMSOL中进行超材料异常折射仿真的关键技术。首先解释了异常折射现象及其产生的原因,接着通过具体代码展示了如何利用相位梯度和结构色散精确计算折射角。文中还讨论了边界条件的设置、网格划分的优化以及参数化扫描的应用。此外,提供了多个实用脚本和技巧,帮助提高仿真的精度和效率。最后强调了验证结果的重要性和一些常见的注意事项。 适合人群:从事电磁仿真研究的专业人士,尤其是对超材料和异常折射感兴趣的科研人员和技术开发者。 使用场景及目标:适用于需要深入理解和解决超材料中异常折射问题的研究项目。主要目标是掌握COMSOL中异常折射仿真的完整流程,确保仿真结果的准确性并优化计算性能。 其他说明:文章不仅提供了详细的代码示例和技术细节,还分享了许多实践经验,有助于读者更好地应对实际仿真过程中可能出现的问题。
招聘工作数据分析表.xls
platform-tools-latest-windows.zip
个人资料临时存储QT资源
内容概要:本文详细介绍了微电网中三相交流下垂控制的工作原理和技术细节。首先,通过Matlab/Simulink搭建模型,展示了传统阻感型线路下垂特性的实现方法,特别是有功-频率和无功-电压下垂曲线的解析。文中强调了关键参数Kp和Kq的选择及其对系统稳定性的影响,并通过具体的仿真案例展示了不同参数设置下的动态响应。此外,文章讨论了波形分析中的注意事项,如谐波成分、滤波器设计以及虚拟阻抗的应用。最后,通过Python和C语言实现了下垂控制器的代码示例,进一步解释了实际工程中的实现细节。 适合人群:从事微电网研究和开发的技术人员,尤其是对下垂控制感兴趣的电气工程师和研究人员。 使用场景及目标:适用于希望深入了解微电网下垂控制原理及其实际应用的研究人员和技术人员。目标是帮助读者掌握下垂控制的核心概念和技术实现,提高在实际工程项目中的调试和优化能力。 其他说明:文章不仅提供了理论分析,还包括了大量的仿真代码和波形图,使读者能够更好地理解和验证所学内容。同时,文中提到的实际调试经验和常见错误也为初学者提供了宝贵的指导。