情景:
公司产品,之前的设备协议与现在的设备协议都是采用字符串协议,但是不同的是,一种采用(数据+换行符)而另一种采用(数据长度+真正数据的方式),对于前者,mina有自带的codec(编码/解码器),对于后者,我们需要自己来处理
代码如下:
@SuppressWarnings("deprecation")
public class MessageDecoder extends CumulativeProtocolDecoder{
//打印日志信息
// private final static Logger log = LoggerFactory
// .getLogger(MessageDecoder.class);
private static final int PACKHEAD_LENTH = 6;//6位的数据包头长度
@Override
protected boolean doDecode(IoSession session, IoBuffer in, ProtocolDecoderOutput out) throws Exception {
// int remaining = in.remaining();
// byte[] dst = new byte[remaining];
// in.get(dst );
// String str = new String(dst,"UTF-8");
// if(str.length()==6){//6位的数据头
//
// }
if(in.remaining() > PACKHEAD_LENTH){//说明缓冲区有数据
//1.标记当前position,以便后继的reset操作能恢复position位置
in.mark();
//2.解码数据为字符串
String string = in.getString(new UTF8Decoder());//读取字符串
//3.包长度判断
Integer pakageLength = resolvePakageHead(string);
if(pakageLength==null){
//直接丢弃
return false;//等待数据
}
//4.包头与包身长度相等
if(pakageLength==string.substring(PACKHEAD_LENTH).length()){
out.write(string); //发送给上层
return false;
}
//5.包头>包身长度 有数据未送达
if(pakageLength>string.substring(PACKHEAD_LENTH).length()){
in.reset();// 重置position到操作前
return false;//断包 等待新数据到来
}
//6.包头<包身 数据足够
if(pakageLength<string.substring(PACKHEAD_LENTH).length()){
in.reset();// 重置position到操作前
byte[] dst = new byte[pakageLength+PACKHEAD_LENTH];
in.get(dst );
String str = new String(dst,"UTF-8");
out.write(str); //发送给上层
doDecode(session,in,out);//粘包递归处理
return false;//等待数据
}
}
return false; //等待数据不提交给上层处理
//包头长度(int 的长度) 根据自定义协议的包头的长度
// int packHeadLenth = "000000".getBytes().length;
// if(in.remaining() > packHeadLenth){ //说明缓冲区中有数据
// in.mark();//标记当前position,以便后继的reset操作能恢复position位置
//
// //获取数据包长度
// int len = in.getInt();
// log.info("len = "+len);
//
// //上面的get会改变remaining()的值
//
// if(in.remaining() <len - packHeadLenth) {
// //内容不够, 重置position到操作前,进行下一轮接受新数据
// in.reset();
// return false;
// }else{
// //内容足够
// in.reset(); //重置回复position位置到操作前
// byte[] packArray = new byte[len];
// in.get(packArray, 0, len); //获取整条报文
//
// //根据自己需要解析接收到的东西 我的例子 把收到的报文转成String
// String str = new String(packArray);
// out.write(str); //发送出去 就算完成了
//
// if(in.remaining() > 0){//如果读取一个完整包内容后还粘了包,就让父类再调用一次,进行下一次解析
// return true;
// }
// }
// }
}
/**
* 解析数据包头
* @param string
* @return
*/
private Integer resolvePakageHead(String string) {
try {
return Integer.valueOf(string.substring(0,PACKHEAD_LENTH));//包长度
} catch (Exception e) {
// TODO: handle exception
}
return null;
}
}
/**
* 消息发送的编码器
* @author Administrator
*
*/
public class MessageEnCoder extends ProtocolEncoderAdapter{
//用于打印日志信息
// private final static Logger log = LoggerFactory
// .getLogger(MessageEnCoder.class);
//编码 将数据包转成字节数组
@Override
public void encode(IoSession session, Object message, ProtocolEncoderOutput out) throws Exception {
//message = EncodeAndDecodeMsg.object2Mgs(message);
String value = (message == null ? "" : (String)message);
IoBuffer buf = IoBuffer.allocate(value.length()).setAutoExpand(true);
buf.put(value.getBytes("UTF-8"));
buf.flip();
out.write(buf);
out.flush();
}
}
需要解决的问题
1.tcp连接数过多(该问题有以下问题导致)
2.清除不活跃的tcp
3.tcp的及时释放(加速四次挥手,项目可以快速重启,不然报端口占用)(setSoLinger配置解决)
所以以上问题就归结到,清除不活跃的tcp,或者说空闲的连接 和 快速关闭连接
解决方案:
我们这里的场景是(物联网方面),设备每隔1分钟,会发送心跳信息。
代码:
@Override
public void sessionCreated(IoSession session) throws Exception {
super.sessionCreated(session);
//及时关掉tcp 处于FIN_WAIT 状态的连接
SocketSessionConfig cfg = (SocketSessionConfig) session.getConfig();
// cfg.setReceiveBufferSize(2 * 1024 * 1024);
// cfg.setReadBufferSize(2 * 1024 * 1024);
cfg.setSendBufferSize(1024*1024*100);
cfg.setKeepAlive(true);
cfg.setSoLinger(0); //这个是根本解决问题的设置
//cfg.setOobInline(false);//关闭紧急数据接受,用于优先处理数据包
cfg.setIdleTime(IdleStatus.BOTH_IDLE, 2*60);//2个心跳包的空闲,用于处理不活跃的tcp连接(设备离线,tcp未关闭),超时时间到,触发下面的方法
}
@Override
public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
super.sessionIdle(session, status);
session.closeOnFlush();//关闭空闲连接
}
mina端对于不活跃的tcp连接,tcp状态容易从keepalive----->on 。导致数据存在于发送缓冲区,不能即时发送,
解决:
1.要么添加心跳
2.这里也是模拟方法1