mina 二进制协议客户端开发

来源:互联网 发布:ubuntu怎么安装php 编辑:程序博客网 时间:2024/05/23 11:54

由Java开发数据编码的客户端场景并不多见,通常的作法一般直接创建socket套接字来完成。但是考虑到需要定义N个数组以及数组之间的转换来完成编包,所以干脆采用mina框架来实现客户端的开发。

开发前通常要考虑日志的跟踪,以及后续的容灾策略。此作法在接触大数据后变的尤为重要,此处暂不考虑容灾。

1、日志采用的是slf4j的2个jar包,因为整个应用都需要日志的来跟踪,因此日志类应该作为基类来定义,所以在mina主程序中自定义的核心处理类如:handle、filter等必须采用接口实现或者直接定义匿名类。

ApplicationLogging :

package com.jp.system;import org.apache.commons.lang.exception.ExceptionUtils;import org.slf4j.Logger;import org.slf4j.LoggerFactory;/** * 日志基类 * @author zh.h * */public abstract class ApplicationLogging {protected final Logger log;public ApplicationLogging() {super();log = LoggerFactory.getLogger(this.getClass());}protected final void debug(Object o) {log.debug(String.valueOf(o));}protected final void debug(String msg) {log.debug(msg);}protected final void debug(String msg, Object... objects) {log.debug(msg, objects);}protected final void debug(Throwable ex) {log.debug(ex.getMessage(), ex);Throwable re = ExceptionUtils.getRootCause(ex);if (re != null && ex != re) {log.debug("root cause", re);}}protected final void error(String msg) {log.error(msg);}protected final void error(String msg, Object... objects) {log.error(msg, objects);}protected final void error(Throwable ex) {log.error(ex.getMessage(), ex);Throwable re = ExceptionUtils.getRootCause(ex);if (re != null && ex != re) {log.error("root cause", re);}}protected final void info(String msg) {log.info(msg);}protected final void info(String msg, Object... objects) {log.info(msg, objects);}protected final void info(Throwable ex) {log.info(ex.getMessage(), ex);Throwable re = ExceptionUtils.getRootCause(ex);if (re != null && ex != re) {log.info("root cause", re);}}protected final void trace(Object o) {log.trace(String.valueOf(o));}protected final void trace(String msg) {log.trace(msg);}protected final void trace(String msg, Object... objects) {log.trace(msg, objects);}protected final void trace(Throwable ex) {log.trace(ex.getMessage(), ex);Throwable re = ExceptionUtils.getRootCause(ex);if (re != null && ex != re) {log.trace("root cause", re);}}protected final void warn(String msg) {log.warn(msg);}protected final void warn(String msg, Object... objects) {log.warn(msg, objects);}protected final void warn(Throwable ex) {log.warn(ex.getMessage(), ex);Throwable re = ExceptionUtils.getRootCause(ex);if (re != null && ex != re) {log.warn("root cause", re);}}}

2、主程序入口

socket与socketServer连接时需要考虑两点:

 a) connect 重连 ->代码已实现

 b) session 重连 :需要在connector中声明监听器,并实现或者重写IoServiceListener中的sessionDestroyed方法

package com.jp.main.client;import java.net.InetSocketAddress;import java.text.SimpleDateFormat;import java.util.Date;import java.util.Queue;import java.util.concurrent.LinkedBlockingQueue;import org.apache.mina.core.future.ConnectFuture;import org.apache.mina.core.service.IoConnector;import org.apache.mina.core.service.IoHandlerAdapter;import org.apache.mina.core.session.IoSession;import org.apache.mina.filter.codec.ProtocolCodecFilter;import org.apache.mina.filter.logging.LoggingFilter;import org.apache.mina.transport.socket.nio.NioSocketConnector;import com.jp.main.entity.CarTakeVo;import com.jp.main.listener.IoListener;import com.jp.main.protocolFiter.ProcessCodecFactory;import com.jp.main.service.ConvertHelper;import com.jp.system.ApplicationLogging;import com.jp.system.ConvergeTransmit;/** * TCP短连接 * @author zh.h * */public class TcpShortClient extends ApplicationLogging{public static Queue<CarTakeVo> queue=new LinkedBlockingQueue<CarTakeVo>();private IoConnector connector = null;private ConnectFuture future = null;private IoSession session = null;private ConvertHelper helper;public TcpShortClient(){}public TcpShortClient(ConvertHelper helper){this.helper = helper;}public void config(){log.info(">>>>>>>>>>>>>>>>>>>>>>>TcpShortClient contextInitialized config<<<<<<<<<<<<<<<<<<<<<<<<<<<<<");connector = new NioSocketConnector();connector.setConnectTimeoutMillis(30000);    connector.getSessionConfig().setReadBufferSize(2048);connector.getFilterChain().addLast("logger", new LoggingFilter());connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(new ProcessCodecFactory(helper)));//connector.setHandler(new MinaShortClientHandler(log,helper));connector.setHandler(new IoHandlerAdapter(){@Overridepublic void exceptionCaught(IoSession session, Throwable cause)throws Exception {super.exceptionCaught(session, cause);log.warn("TcpShortClient session Caught Exception! : "+cause+"->"+cause.getMessage());}@Overridepublic void sessionClosed(IoSession session) throws Exception {InetSocketAddress remoteAddress = (InetSocketAddress)session.getRemoteAddress();    log.info("sucessfully sessionClosed from :="+remoteAddress.getAddress().getHostAddress()+" and port:="+remoteAddress.getPort());    log.info("current_sessinId :="+String.valueOf(session.getId()));}@Overridepublic void sessionOpened(IoSession session) throws Exception {super.sessionOpened(session);InetSocketAddress remoteAddress = (InetSocketAddress)session.getRemoteAddress();    log.info("sucessfully getSession from :="+remoteAddress.getAddress().getHostAddress()+" and port:="+remoteAddress.getPort());    log.info("current_sessinId :="+String.valueOf(session.getId()));}});connector.addListener(new IoListener(){@Overridepublic void sessionDestroyed(IoSession session) throws Exception {if(null==session){log.error("session been closed by server with violation! ");}}});for (;;) {  try {  future = connector.connect(new InetSocketAddress(ConvergeTransmit.IP,ConvergeTransmit.PORT));  future.awaitUninterruptibly(); // 等待连接创建成功    session = future.getSession(); // 获取会话     log.info("connect server ==> [" + ConvergeTransmit.IP + ":" + ConvergeTransmit.PORT + "] sucessfully ! " + ",time:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));  break;  } catch (Exception e) {  log.error("connect server ==> [" + ConvergeTransmit.IP + ":" + ConvergeTransmit.PORT + "] failed ! " + ", Client will reconnect after 10s ! ,time:" + new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())+"\n" + e.getMessage(), e);  try {Thread.sleep(10000);// 连接失败后,重连间隔5s  } catch (InterruptedException e1) {e1.printStackTrace();}}  }  }public void start(){if(null != session && session.isConnected()){}else{this.config();}while(!queue.isEmpty()){long start = System.currentTimeMillis();session.write(queue.poll());ConvergeTransmit.count ++;log.debug("datas in queue convert.......count=:"+ConvergeTransmit.count+",castTime:="+(System.currentTimeMillis()-start)+" ms");}session.getCloseFuture().awaitUninterruptibly();//等待服务端关闭sessionconnector.dispose();}}

3、协议编码

 采用mina一个主要原因就是它的IoBuffer的强大,动态化增加长度,同时这也是IoBuffer需要吐槽的地方:

  a) 当需要定义两个IoBuffer时,即一个IoBuffer buffA put一个IoBuffer  buffB,由于IoBuffer设置为会自动增长,但是在调用array()是并不会将无效元素清除。因此buffA的数据包的长度将大于实际数据包的长度。

 因此通常的做法是buffB.array()后调用Arrays.copyOfRange来截取有效数据。

package com.jp.main.service.impl;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.IOException;import java.nio.charset.Charset;import java.text.SimpleDateFormat;import java.util.Arrays;import javax.imageio.stream.FileImageInputStream;import org.apache.mina.core.buffer.IoBuffer;import org.apache.mina.core.session.IoSession;import com.jp.main.entity.CarTakeVo;import com.jp.main.service.ConvertHelper;import com.jp.system.ApplicationLogging;public class ConvertFixPackage extends ApplicationLogging implements ConvertHelper{private final byte START = (byte)0xAA;private final byte END = (byte)0x55;private final byte ZERO = (byte)0; //填充0private final byte VERSION= (byte)1;private final byte TYPE = (byte)2;private final String SIGNATURE = "xxx";private final short PROTOCOLTYPE = 1101;private final byte[] xxbh =new byte[15];private final byte[] kkbh =new byte[18];private final byte[] sbbh =new byte[18];private final byte[] jgsj =new byte[24];private final byte[] cdbh = new byte[10];private final byte[] hphm = new byte[15];private final byte[] hpys = new byte[1];private final byte[] cwhphm = new byte[15];private final byte[] cwhpys = new byte[1];private final byte[] hpyz = new byte[1];private final byte[] xszt = new byte[4];private final byte[] clpp = new byte[4];private final byte[] clwx = new byte[4];private final byte[] csys = new byte[5];private final byte[] cb = new byte[2];private final byte[] cllx = new byte[4];private final byte[] hpzl = new byte[2];private final byte[] ssdq = new byte[10];private final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");private final Charset charset=Charset.forName("UTF-8");public byte[] pack(IoSession session,Object obj){return pack(session, obj, true);}private byte[] pack(IoSession session, Object obj, boolean b) {return null;}public IoBuffer pack(IoBuffer buff, Object message ) {return pack(buff, message, true);}private IoBuffer pack(IoBuffer buff, Object message, boolean b) {CarTakeVo vo = null;if(message instanceof CarTakeVo){vo = (CarTakeVo) message; this.convert(buff,vo);}return buff;}private void convert(IoBuffer buff, CarTakeVo vo) {log.debug("convert starting "+vo.toString());try{int index = 0;buff.put(START);buff.put(START);buff.put(SIGNATURE.getBytes(charset));buff.put(VERSION);buff.put(TYPE);buff.putShort(PROTOCOLTYPE);IoBuffer ioBuff = IoBuffer.allocate(1024);ioBuff.setAutoExpand(true);if(null !=vo.getXxbh() && !"".equals(vo.getXxbh())){//信息编号if(vo.getXxbh().getBytes().length>16){fillArray(ioBuff, ZERO, xxbh);}else{fillArray(ioBuff,ZERO, vo.getXxbh().getBytes(charset), 0, xxbh, LenDifference(xxbh,vo.getXxbh()));}}else{fillArray(ioBuff, ZERO, xxbh);}index += 15;if(null !=vo.getKkbh() && !"".equals(vo.getKkbh())){//卡口编号if(vo.getKkbh().getBytes().length>19){fillArray(ioBuff, ZERO, kkbh);}else{fillArray(ioBuff,ZERO, vo.getKkbh().getBytes(charset), 0, kkbh, LenDifference(kkbh,vo.getKkbh()));}}else{fillArray(ioBuff, ZERO, kkbh);}index += 18;if(null !=vo.getSbbh() && !"".equals(vo.getSbbh())){//设备名称if(vo.getSbbh().getBytes().length>19){fillArray(ioBuff, ZERO, sbbh);}else{fillArray(ioBuff,ZERO, vo.getSbbh().getBytes(charset), 0, sbbh, LenDifference(sbbh,vo.getSbbh()));}}else{fillArray(ioBuff, ZERO, sbbh);}index += 18;if(null !=vo.getJgsj() && !"".equals(vo.getJgsj())){//经过时间if(sdf.format(vo.getJgsj()).getBytes().length>25){fillArray(ioBuff, ZERO, jgsj);}else{fillArray(ioBuff,ZERO, sdf.format(vo.getJgsj()).getBytes(charset), 0, jgsj, LenDifference(jgsj,sdf.format(vo.getJgsj())));}}else{ioBuff.put("0000-00-00 00:00:00.000".getBytes(charset));}index += 24;if(null !=vo.getCdbh() && !"".equals(vo.getCdbh())){//车道编号if(vo.getCdbh().getBytes().length>11){fillArray(ioBuff, ZERO, cdbh);}else{fillArray(ioBuff,ZERO, vo.getCdbh().getBytes(charset), 0, cdbh, LenDifference(cdbh,vo.getCdbh()));}}else{fillArray(ioBuff, ZERO, cdbh);}index += 10;if(null !=vo.getHphm() && !"".equals(vo.getHphm())){//号牌号码if(vo.getHphm().getBytes().length>11){fillArray(ioBuff, ZERO, hphm);}else{fillArray(ioBuff,ZERO, vo.getHphm().getBytes(charset), 0, hphm, LenDifference(hphm,vo.getHphm()));}}else{fillArray(ioBuff, ZERO, hphm);}index += 15;if(null !=vo.getHpys() && !"".equals(vo.getHpys())){//号牌颜色if(vo.getHpys().getBytes().length>2){fillArray(ioBuff, ZERO, hpys);}else{fillArray(ioBuff,ZERO, vo.getHpys().getBytes(charset), 0, hpys, LenDifference(hpys,vo.getHpys()));}}else{ioBuff.put("4".getBytes(charset));}index += 1;if(null !=vo.getCwhphm() && !"".equals(vo.getCwhphm())){//车尾号牌号码if(vo.getCwhphm().getBytes().length>16){fillArray(ioBuff, ZERO, cwhphm);}else{fillArray(ioBuff,ZERO, vo.getCwhphm().getBytes(charset), 0, cwhphm, LenDifference(cwhphm,vo.getCwhphm()));}}else{fillArray(ioBuff, ZERO, cwhphm);}index += 15;if(null !=vo.getCwhpys() && !"".equals(vo.getCwhpys())){//车尾号牌颜色if(vo.getCwhpys().getBytes().length>2){fillArray(ioBuff, ZERO, cwhpys);}else{fillArray(ioBuff,ZERO, vo.getCwhpys().getBytes(charset), 0, cwhpys, LenDifference(cwhpys,vo.getCwhpys()));}}else{ioBuff = ioBuff.put("4".getBytes(charset));}index += 1;if(null !=vo.getHpyz() && !"".equals(vo.getHpyz())){//号牌一致if(vo.getHpyz().getBytes().length>2){fillArray(ioBuff, ZERO, hpyz);}else{fillArray(ioBuff,ZERO, vo.getHpyz().getBytes(charset), 0, hpyz, LenDifference(hpyz,vo.getHpyz()));}}else{ioBuff = ioBuff.put("0".getBytes(charset));}index += 1;if(null !=vo.getClsd() && !"".equals(vo.getClsd())){//车辆速度ioBuff = ioBuff.putFloat(vo.getClsd());}else{ioBuff = ioBuff.putFloat(0.0f);}index += 4;if(null !=vo.getClxs() && !"".equals(vo.getClxs())){//车辆限速ioBuff = ioBuff.putFloat(vo.getClxs());}else{ioBuff = ioBuff.putFloat(0.0f);}index += 4;if(null !=vo.getCscd() && !"".equals(vo.getCscd())){//车身长度ioBuff = ioBuff.putInt(vo.getCscd());}else{ioBuff = ioBuff.putInt(0);}index += 4;if(null !=vo.getXszt() && !"".equals(vo.getXszt())){//行驶状态if(vo.getXszt().getBytes().length>5){fillArray(ioBuff, ZERO, xszt);}else{fillArray(ioBuff,ZERO, vo.getXszt().getBytes(charset), 0, xszt, LenDifference(xszt,vo.getXszt()));}}else{fillArray(ioBuff, ZERO, xszt);}index += 4;if(null !=vo.getClpp() && !"".equals(vo.getClpp())){//车辆品牌if(vo.getClpp().getBytes().length>5){fillArray(ioBuff, ZERO, clpp);}else{fillArray(ioBuff,ZERO, vo.getClpp().getBytes(charset), 0, clpp, LenDifference(clpp,vo.getClpp()));}}else{fillArray(ioBuff, ZERO, clpp);}index += 4;if(null !=vo.getClwx() && !"".equals(vo.getClwx())){//车辆外型if(vo.getClwx().getBytes().length>5){fillArray(ioBuff, ZERO, clwx);}else{fillArray(ioBuff,ZERO, vo.getClwx().getBytes(charset), 0, clwx, LenDifference(clwx,vo.getClwx()));}}else{fillArray(ioBuff, ZERO, clwx);}index += 4;if(null !=vo.getCsys() && !"".equals(vo.getCsys())){//车身颜色if(vo.getCsys().getBytes().length>6){fillArray(ioBuff, ZERO, csys);}else{fillArray(ioBuff,ZERO, vo.getCsys().getBytes(charset), 0, csys, LenDifference(csys,vo.getCsys()));}}else{fillArray(ioBuff, ZERO, csys);}index += 5;if(null !=vo.getCb() && !"".equals(vo.getCb())){//车标if(vo.getCb().getBytes().length>3){fillArray(ioBuff, ZERO, cb);}else{fillArray(ioBuff,ZERO, vo.getCb().getBytes(charset), 0, cb, LenDifference(cb,vo.getCb()));}}else{fillArray(ioBuff, ZERO, cb);}index += 2;if(null !=vo.getCllx() && !"".equals(vo.getCllx())){//车辆类型if(vo.getCllx().getBytes().length>5){fillArray(ioBuff, ZERO, cllx);}else{fillArray(ioBuff,ZERO, vo.getCllx().getBytes(charset), 0, cllx, LenDifference(cllx,vo.getCllx()));}}else{fillArray(ioBuff, ZERO, cllx);}index += 4;if(null !=vo.getHpzl() && !"".equals(vo.getHpzl())){//号牌种类if(vo.getHpzl().getBytes().length>3){fillArray(ioBuff, ZERO, hpzl);}else{fillArray(ioBuff,ZERO, vo.getHpzl().getBytes(charset), 0, hpzl, LenDifference(hpzl,vo.getHpzl()));}}else{fillArray(ioBuff, ZERO, hpzl);}index += 2;if(null !=vo.getSsdq() && !"".equals(vo.getSsdq())){//所属地区if(vo.getSsdq().getBytes().length>11){fillArray(ioBuff, ZERO, ssdq);}else{fillArray(ioBuff,ZERO, vo.getSsdq().getBytes(charset), 0, ssdq, LenDifference(ssdq,vo.getSsdq()));}}else{fillArray(ioBuff, ZERO, ssdq);}index += 10;if(null !=vo.getTpsl() && !"".equals(vo.getTpsl())){//图片数量ioBuff = ioBuff.putInt(vo.getTpsl());}else{ioBuff = ioBuff.putInt(0);}index += 4;if(null !=vo.getTx1() && !"".equals(vo.getTx1())){//图片URIioBuff.putInt(getPicLength(vo.getTx1()));index += 4;System.out.println("xxbh_index:"+index+" IoBuffer.position:"+ioBuff.position());byte[] picByte=getPic2byteArray(vo.getTx1());ioBuff.put(picByte);index += picByte.length;}else{ioBuff.putInt(0);}byte[] data = Arrays.copyOfRange(ioBuff.array(), 0,ioBuff.position());//index +=ioBuff.position();System.out.println(index+":数据包总长度");System.out.println(ioBuff.position()+":ioBuff.position()");System.out.println(data.length+":Arrays.copyOfRange");System.out.println("---------------------------------------------------------");//System.out.println("data:="+Arrays.toString(data));//System.out.println("ioBuff.array:="+Arrays.toString(ioBuff.array()));buff.putInt(index);System.out.println(buff.position()+":buff未加数据包长度");buff.put(data);System.out.println(buff.position()+":未加结尾长度");buff.put(END);buff.put(END);System.out.println(buff.position()+":总包实际长度");System.out.println(buff.array().length+":总包长度");}catch(Exception e){log.error("ConvertFixPackage Convert error ! "+e.getMessage());}}public int LenDifference(byte[] arr,String str){return arr.length-str.getBytes(charset).length;}public void fillArray(IoBuffer buff,byte b ,byte[] a , int x , byte[] arr , int y){Arrays.fill(arr, b);System.arraycopy(a, x, arr, y, a.length);buff.put(arr);}public void fillArray(IoBuffer buff , byte b ,byte[] arr){Arrays.fill(arr, b);buff.put(arr);}public int getPicLength(String picPath){File file =new File(picPath);if (file.exists()){return (int) file.length();}else{return 0;}}public byte[] getPic2byteArray(String picPath){File file =new File(picPath);FileImageInputStream in=null;ByteArrayOutputStream out=null;byte[] data = null;try {in = new FileImageInputStream(file);out = new ByteArrayOutputStream();byte[] buf = new byte[1024];int numBytesRead = 0;    while ((numBytesRead = in.read(buf)) != -1) {      out.write(buf, 0, numBytesRead);    }    data = out.toByteArray();} catch (Exception e) {log.error(picPath +" is not exists!");}finally{try {out.close();in.close();} catch (IOException e1) {log.error(" I/O can not been closed! "+e1);}}return data;}}


4、既然是协议编包的短连接客户端,即发送完数据后将session等关闭即可,但是还是需要定义解包规则。mina框架的好处在于能考虑到各种因素,比如当数据发送成功后Server有数据返回如何处理。并且在mina的session调用中会默认调用IoHandle的messageRecive方法,所以即便没有消息返回也需要定义解码规则。

package com.jp.main.utils;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;import com.jp.system.ApplicationLogging;public class ProcessCodecDecoder extends ApplicationLogging implements ProtocolDecoder{public ProcessCodecDecoder(){}public void decode(IoSession session, IoBuffer in, ProtocolDecoderOutput out)throws Exception {//CharsetDecoder cd = Charset.forName("UTF-8").newDecoder();  log.debug("#############message --> decode############");IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true); long start = System.currentTimeMillis();while(in.hasRemaining()){  buf.put(in.get());  }  buf.flip();  out.write(buf);  log.debug(">>>>>>>>>>>>> decode costTime-----------:"+(System.currentTimeMillis()-start)+" ms");}public void dispose(IoSession arg0) throws Exception {// TODO Auto-generated method stub}public void finishDecode(IoSession arg0, ProtocolDecoderOutput arg1)throws Exception {// TODO Auto-generated method stub}}



0 0