浅谈MINA的使用

最近做了一个POS终端通信的小项目,主要是给POS终端提供TCP接口。之前的工作中没有从事相关的开发,也没涉及到网络通信的相关框架,在网上找了一些相关资料,最终选用了apache下的mina框架。

mina框架是一个网络通信框架,也就是它是基于TCP/IP、UDP/IP的协议栈的通信框架。mina对java的socket进行了封装,我们在使用mina时不用把过多的精力放在网络通信的开发上,mina给我们提供了事件驱动、异步它的异步默认使用的是java noi作为异步支持。同时mina对通信的客户端和服务端都进行了封装,我们可以轻松的使用mina进行服务端可客户端的开发。

(1.) IoService:这个接口在一个线程上负责套接字的建立,拥有自己的Selector,监听是否有连接被建立。

(2.) IoProcessor:这个接口在另一个线程上,负责检查是否有数据在通道上读写,也就是说它也拥有自己的Selector,这是与我们使用JAVA NIO 编码时的一个不同之处,通常在JAVA NIO 编码中,我们都是使用一个Selector,也就是不区分IoService与IoProcessor 两个功能接口。另外,IoProcessor 负责调用注册在IoService 上的过滤器,并在过滤器链之后调用IoHandler。
(3.) IoFilter:这个接口定义一组拦截器,这些拦截器可以包括日志输出、黑名单过滤、数据的编码(write 方向)与解码(read 方向)等功能,其中数据的encode 与decode是最为重要的、也是你在使用Mina 时最主要关注的地方。
(4.) IoHandler:这个接口负责编写业务逻辑,也就是接收、发送数据的地方。

废话不多说,以下是我自己编写的服务端程序,贴出来供大家参考学习:

import java.net.InetSocketAddress;

import org.apache.log4j.Logger;
import org.apache.mina.core.session.IdleStatus;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;

import com.chinaums.umscard.socket.codec.MsgCodecFactory;
import com.chinaums.umscard.util.BaseConfig;

public class MinaServer {

	private static final Logger log = Logger.getLogger(MinaServer.class);
	
	public static void main(String[] args) {
		// 创建一个非阻塞的server端的Socket
		NioSocketAcceptor acceptor = new NioSocketAcceptor();
		try {
			// 设置过滤器(使用自己封装的编解码器)
			acceptor.getFilterChain().addLast("codec",new ProtocolCodecFilter(new MsgCodecFactory("UTF-8")));
			
			// 设置读取数据的缓冲区大小为1M
			acceptor.getSessionConfig().setReadBufferSize(1024*1024);
			// 读写通道10秒内无操作进入空闲状态
			acceptor.getSessionConfig().setIdleTime(IdleStatus.BOTH_IDLE, 10);
			// 绑定逻辑处理器
			acceptor.setHandler(new MinaServerHandler());
			// 绑定端口
			acceptor.bind(new InetSocketAddress(BaseConfig.PORT));
			
			log.info("服务端启动。。。。。端口号为:"+BaseConfig.PORT);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}

请注意,过滤器可以使用mina提供的过滤器,也可以自己根据需要编写过滤器。这里我使用的自己编写的过滤器
过滤器代码如下
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;

public class MsgCodecFactory implements ProtocolCodecFactory {
	
	//这里注册自己编写的解码工具
	private final MsgResponseEncoder encoder; // 编码
	private final MsgRequestDecoder decoder; // 解码

	public MsgCodecFactory(String charset) {
		encoder = new MsgResponseEncoder(charset);
		decoder = new MsgRequestDecoder(charset);
	}

	public ProtocolEncoder getEncoder(IoSession session) {
		return encoder;
	}

	public ProtocolDecoder getDecoder(IoSession session) {
		return decoder;
	}

}
解码代码如下
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.CumulativeProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;

import com.chinaums.umscard.util.StringUtils;

public class MsgRequestDecoder extends CumulativeProtocolDecoder {
	private static final Logger LOGGER = Logger.getLogger(MsgRequestDecoder.class);
	
	private final String charset;

	public MsgRequestDecoder(String charset) {
		this.charset = charset;
	}

	/**
	 *解码器,对传入的iobuffer 进行解码工作,注意顺序是先进先出原则。
	 */
	@Override
	protected boolean doDecode(IoSession session, IoBuffer in,
			ProtocolDecoderOutput out) throws Exception {
		// TODO Auto-generated method stub

		if (in.remaining() > 2) {

			in.mark();// 标记当前位置,以便reset
			
			byte[] lenBytes = new byte[3];
			in.get(lenBytes);
			int length = Integer.parseInt(StringUtils.hexStr2Str(StringUtils.bytes2HexString(lenBytes)));
//			int length = StringUtils.bytesToInt(lenBytes);
			
			if (length > in.remaining()) {// 如果消息内容不够,则重置,相当于不读取size
				System.out.println("package notenough  left=" + in.remaining()
						+ " length=" + length);
				in.reset();
				return false;// 接收新数据,以拼凑成完整数据
			} else {
				System.out.println("package =" + in.toString());
				byte[] bbIn = new byte[length];
				
				in.get(bbIn, 0, length);
				
				byte[] btotal = new byte[length];
				
				System.arraycopy(bbIn, 0, btotal, 0, bbIn.length);
				
				byte[] pwd = new byte[8];
				
				System.arraycopy(btotal, btotal.length-8, pwd, 0, 8);
				System.out.println(StringUtils.bytes2HexString(pwd));
				
				String result = StringUtils.bytes2HexString(btotal);
				out.write(result);
				
				if (in.remaining() > 0) {// 如果读取内容后还粘了包,就让父类再给一次,进行下一次解析
					// System.out.println("package left="+in.remaining()+" data="+in.toString());
				}
				return true;// 这里有两种情况1:没数据了,那么就结束当前调用,有数据就再次调用
			}
		}
		return false;// 处理成功,让父类进行接收下个包
	}

}
import java.nio.charset.Charset;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
public class MsgResponseEncoder extends ProtocolEncoderAdapter {
 
 private final String charset;
 public MsgResponseEncoder(String charset) {
  this.charset = charset;
 }
 
 public void encode(IoSession session, Object message,
   ProtocolEncoderOutput out) throws Exception {
  
  IoBuffer buf = IoBuffer.allocate(100).setAutoExpand(true);
  String strOut = message.toString();
//  buf.putInt(strOut.getBytes(Charset.forName("utf-8")).length);
  buf.putString(strOut,Charset.forName("GBK").newEncoder());
  buf.flip();
  out.write(buf);
 }
}
以下是业务处理类

import java.util.List;

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.chinaums.umscard.entity.AccountInfo;
import com.chinaums.umscard.entity.BasCardPos;
import com.chinaums.umscard.entity.CardMeracc;
import com.chinaums.umscard.entity.ConsumeParam;
import com.chinaums.umscard.entity.DataAccReturn;
import com.chinaums.umscard.entity.DataPosFlow;
import com.chinaums.umscard.entity.PayTranPay;
import com.chinaums.umscard.entity.RejectGoodsParam;
import com.chinaums.umscard.service.AccountInfoService;
import com.chinaums.umscard.service.CardMeraccService;
import com.chinaums.umscard.service.ConsumeService;
import com.chinaums.umscard.service.DataAccReturnService;
import com.chinaums.umscard.service.DataFlowService;
import com.chinaums.umscard.service.PosCheckService;
import com.chinaums.umscard.service.RejectGoodsService;
import com.chinaums.umscard.service.ReverseService;
import com.chinaums.umscard.service.impl.AccountInfoServiceImpl;
import com.chinaums.umscard.service.impl.CardMeraccServiceImpl;
import com.chinaums.umscard.service.impl.ConsumeServiceImpl;
import com.chinaums.umscard.service.impl.DataAccReturnServiceImpl;
import com.chinaums.umscard.service.impl.DataFlowServiceImpl;
import com.chinaums.umscard.service.impl.PosCheckServiceImpl;
import com.chinaums.umscard.service.impl.RejectGoodsServiceImpl;
import com.chinaums.umscard.service.impl.ReverseServiceImpl;
import com.chinaums.umscard.util.MySecurityUtil;
import com.chinaums.umscard.util.StringUtils;
import com.chinaums.umscard.util.XorUtil;

public class MinaServerHandler extends IoHandlerAdapter {
	
	public static final Logger log = Logger.getLogger(MinaServerHandler.class);
	
	@Override
	public void sessionCreated(IoSession session) throws Exception {
		log.info("服务端与客户端创建连接...");
	}
	
	@Override
	public void sessionOpened(IoSession session) throws Exception {
		log.info("服务端与客户端打开连接");
	}
	
	@Override
	public void sessionClosed(IoSession session) throws Exception {
		log.info("服务端与客户端关闭连接");
	}
	
	//这个方法用于接收监听接收到的报文数据,以进行相关的业务处理
	@Override
	public void messageReceived(IoSession session, Object message) throws Exception{
		
		String m = message.toString();
		
		String bcd = m.substring(m.length()-16);
		
//		System.out.println(bcd);
		
		byte[] bt = message.toString().getBytes();
		
		byte[] keypwdbt = new byte[8];
		
		System.arraycopy(bt, bt.length-8, keypwdbt, 0, 8);
		
		String result = MySecurityUtil.bcd2Str(keypwdbt);
		
		String msg = StringUtils.hexStr2Str(message.toString());
		
//		PosCheckService posService = new PosCheckServiceImpl();
		
		PosCheckService posCheckService = new PosCheckServiceImpl();
		
		DataFlowService dataFlowService = new DataFlowServiceImpl();
		
		MySecurityUtil ms = new MySecurityUtil();
		
//		PosCheckService posService = new PosCheckServiceImpl();
		
		if(msg != null){
			String msgArr = msg.replace("|", ",");
			String[] posArr = msgArr.split(",");
			
			if(msg.toString().startsWith("0001")){
				String checkmsg = "";//用于接收签到结果
				
				String posno = posArr[1];
				
				String key = posArr[2];
				
				String keypwd = posArr[3];//异或加密后的验证码
				
				StringBuffer sb = new StringBuffer();
				
				sb.append(posArr[0]);
				sb.append("|");
				
				//以下将报文进行异或加密处理(不取最后一个元素)
				for(int i=1;i<posArr.length-1;i++){
					sb.append(posArr[i]+",");
				}
				
				System.out.println(sb);
				//异或运算
				byte[] b = XorUtil.xorChar(sb.toString().toCharArray());
				
				//加密
				byte[] key1 = MySecurityUtil.hexStr2Str(key.substring(0, key.length()/2)).getBytes();
				byte[] key2 = MySecurityUtil.hexStr2Str(key.substring(key.length()/2)).getBytes();
				
				byte[] p = ms.TripleDesEncrypt(b, key1, key2);
				
				//转成字符串
				String s = MySecurityUtil.bcd2Str(p);
				
				BasCardPos basCardPos = posCheckService.checkPos(posno);
				
				if(basCardPos != null && basCardPos.getIkey().equals(posArr[2])){
					checkmsg = "00";//签到成功
				}else if(basCardPos != null && basCardPos.getIkey() != posArr[2]){
					checkmsg = "99";//密钥验证失败
				}else{
					checkmsg = "01";//签到失败
				}
				
				//插入日志
				DataPosFlow dataPosFlow = new DataPosFlow();
				dataPosFlow.setPosno(posno);
				dataPosFlow.setVreturn(checkmsg);
				
				dataFlowService.PosCheckDataFlow(dataPosFlow);
				
				session.write(checkmsg+"#"+s);
				log.info(checkmsg);
			}else if(msg.toString().startsWith("0002")){

				AccountInfoService accService = new AccountInfoServiceImpl();
				
				String selectmsg = "";//用于接收查询账户返回的状态码
				
				//取磁道内容
				String cardnoecp = posArr[1];
				//取终端号
				String posno = posArr[2];
				//获取密钥
				String key = posArr[3];
				//获取密码
				String pwd = posArr[4];
				//第一步检查终端的合法性
				BasCardPos bcp = posCheckService.checkPos(posno);
				
				StringBuffer sb = new StringBuffer();
				
				if(bcp != null && bcp.getIkey().equals(key)){
				
					List<AccountInfo> accList = accService.checkAccount(cardnoecp);
					
					DataPosFlow dataPosFlow = null;
					
					if(accList != null && accList.size() != 0){
						selectmsg = "00";//查询成功
						sb.append(selectmsg+"#");
						for(AccountInfo account : accList){
//							String hexAccountName = new String(account.getAccname().getBytes("UTF-8"), "GB18030");
							sb.append(account.getCardid()+","+account.getCardno()+","
							+account.getAccid()+","+account.getAccname()+","+account.getBalance()+"|");
							
							dataPosFlow = new DataPosFlow();
							
							dataPosFlow.setCardid(account.getCardid());
							dataPosFlow.setAccid(account.getAccid());
							dataPosFlow.setPosno(posno);
							dataPosFlow.setVreturn(selectmsg);
							
							dataFlowService.AccountInfoDataFlow(dataPosFlow, account);
						}
						session.write(sb);
					}else{
						selectmsg = "98";
						session.write(selectmsg+"#");
					}
				}else if(bcp != null && !key.equals(bcp.getIkey())){
					selectmsg = "99";
					session.write(selectmsg+"#");
				}
				log.info(selectmsg);
			}else if(msg.toString().startsWith("0003")){

				ConsumeService consumeService = new ConsumeServiceImpl();
				
				String consumemsg = "";
				
				//获取cardid
				String cardid = posArr[1];
				//获取accid
				String accid = posArr[2];
				
				String trantype = posArr[3];
				
				String amt = posArr[4];
				
				//获取终端号
				String posno = posArr[5];
				
				//获取密钥
				String key = posArr[6];
				
				String pwd = posArr[7];
				
				String possno = posArr[8];
				
//				String field1 = posArr[8];
				
				ConsumeParam param = new ConsumeParam();
				param.setCardid(Float.parseFloat(cardid));
				param.setAccid(3f);
				param.setAmt(Float.parseFloat(amt)/100);
				param.setPosno(posno);
				param.setPossno(possno);
				param.setTrainType(Float.parseFloat(trantype));
				
				BasCardPos bcp = posCheckService.checkPos(posno);
				
				//终端验证成功,验证卡是否可以在终端上使用
				if(bcp != null && key.equals(bcp.getIkey())){
					int count = consumeService.poscount(Integer.parseInt(cardid), Integer.parseInt(accid));
					
					//count不为0说明卡片可以在终端上使用
					if(count != 0){
						consumemsg = consumeService.consume(param);
					}else if(key != bcp.getIkey()){
						consumemsg = "99";//密钥验证失败
					}else if(count == 0){
						consumemsg = "98";//不存在卡
					}else{
						consumemsg = "97";//密码错误
					}
				}
				session.write(consumemsg+"#");
				log.info(consumemsg);
			}else if(msg.toString().startsWith("0004")){

				
				String rejectmsg = "";
				
				String cardid = posArr[1];
				
				String rejectTranType = posArr[2];
				
				String rejectPosno = posArr[3];
				
				String rejectkey = posArr[4];
				
				String rejectpwd = posArr[5];
				
				String rejectTranId = posArr[6];
				
				RejectGoodsParam param = new RejectGoodsParam();
				
				param.setCardid(Integer.parseInt(cardid));
				param.setKey(rejectkey);
				param.setPosno(rejectPosno);
				param.setPwd(rejectpwd);
				param.setTranid(rejectTranId);
				param.setTranType(Integer.parseInt(rejectTranType));//退货
				
				BasCardPos bcp = posCheckService.checkPos(rejectPosno);
				
				RejectGoodsService rgService = new RejectGoodsServiceImpl();
				
				DataAccReturnService dataAccReturnService = new DataAccReturnServiceImpl();
				
				if(bcp != null && rejectkey.equals(bcp.getIkey())){
					//根据传入的信息查询用户的卡信息和原单号,以便验证交易信息是否属实
					//验证成功,查询账户信息,返回账户信息
					rejectmsg = rgService.rejectGoods(param);
					
					//交易信息存在返回00向退货申请表中写入退货申请
					if (rejectmsg.equals("00")) {
						//查询交易记录
						PayTranPay pay = rgService.getPayTranPay(param);
						//验证成功将退货请求写入退货请求表
						DataAccReturn dataAccReturn = new DataAccReturn();
						dataAccReturn.setCashierno(rejectPosno);
						dataAccReturn.setTranid(rejectTranId);
						dataAccReturn.setPosno(rejectPosno);
						dataAccReturn.setCardid(Integer.parseInt(cardid));
						dataAccReturn.setAccid(Integer.parseInt(pay.getPaysubtype()));
						dataAccReturn.setAmt(pay.getAmt());
						dataAccReturn.setPosno(rejectPosno);
						dataAccReturnService.addDataAccReturn(dataAccReturn);
					}
					
				}else if(bcp != null && !rejectkey.equals(bcp.getIkey())){
					rejectmsg = "99";
				}else{
					rejectmsg = "97";
				}
				
				CardMeraccService cardMeraccService = new CardMeraccServiceImpl();
				
				CardMeracc cardm = cardMeraccService.getCardMeraccByCardId(Integer.parseInt(cardid));
				
				DataPosFlow dataPosFlow = new DataPosFlow();
				
				dataPosFlow.setCardid(Integer.parseInt(cardid));
				dataPosFlow.setPosno(rejectPosno);
				dataPosFlow.setVreturn(rejectmsg);
				
				//写入日志
				dataFlowService.RejectGoodsDataPosFlow(dataPosFlow, cardm);
				session.write(rejectmsg+"#");
			}else if(msg.toString().startsWith("0005")){
				
				String reveremsg = "";
				
				//pos终端号
				String posno = posArr[1];
				
				//密钥
				String key = posArr[2];
				
				//批次号(POS终端生成)
				String posSno = posArr[3];
				
				//预留字段
//				String field1 = posArr[4];
				
				ReverseService reverserService = new ReverseServiceImpl();
				
				reveremsg = reverserService.reverse(posSno, posno);
				
				session.write(reveremsg+"#");
			}
		}
		
		log.info("服务端收到的数据为:"+message);
	}
	
	@Override
	public void messageSent(IoSession session, Object message) throws Exception {
		log.info("服务端发送消息成功!");
	}
	
	@Override
	public void sessionIdle(IoSession session, IdleStatus status) throws Exception {
		log.info("服务端进入空闲状态...");
	}
	
	@Override
	public void exceptionCaught(IoSession session, Throwable cause) throws Exception {
		log.error("服务端发送异常...", cause);
	}
	
	
}
至此服务端代码编写完毕,下面给一段客户端的代码。一般我们测试时用TCPUDP测试工具进行测试,不必要编写客户端程序,为了让大家有个参考,这里我把代码贴出来,只是简单的客户端程序,代码如下
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.UnknownHostException;
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.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioSocketConnector;

public class MinaClient {
	
	private static final Logger logger = Logger.getLogger(MinaClient.class);
	
//	private static String ip = address.get
	
	public static void main(String[] args) throws UnknownHostException {
		InetAddress address = InetAddress.getLocalHost();
		
		String ip = address.getHostAddress().toString();
		
		// 创建一个非阻塞的客户端程序
		IoConnector connector = new NioSocketConnector();
		// 设置链接超时时间
		connector.setConnectTimeout(30000);
		
		// 添加过滤器
		connector.getFilterChain().addLast(
				"codec",
				new ProtocolCodecFilter(new TextLineCodecFactory(Charset
						.forName("UTF-8"), LineDelimiter.WINDOWS.getValue(),
						LineDelimiter.WINDOWS.getValue())));
		// 添加业务逻辑处理器类
		connector.setHandler(new MinaClientHandler());
		
		IoSession session = null;
		try {
			ConnectFuture future = connector.connect(new InetSocketAddress(
					ip, 2000));// 创建连接
			future.awaitUninterruptibly();// 等待连接创建完成
			session = future.getSession();// 获得session
			
//			Scanner s = new Scanner(System.in);
			
//			String msg = s.next();
			
			
			session.write("0001|10003,44444444444444444444444444444444,null");// 发送消息

			session.write("quit");//发送后与服务端断开连接
		} catch (Exception e) {
			logger.error("客户端链接异常...", e);
		}
		
		
//		session.getCloseFuture().awaitUninterruptibly();//等待连接断开
		connector.dispose();
	}

}
编写客户端监听业务处理Handler类
import org.apache.log4j.Logger;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;

public class MinaClientHandler extends IoHandlerAdapter {

	private static Logger logger = Logger.getLogger(MinaClientHandler.class);

	@Override
	public void messageReceived(IoSession session, Object message)
			throws Exception {
		String msg = message.toString();
		logger.info("客户端接收到的信息为:" + msg);
	}

	@Override
	public void exceptionCaught(IoSession session, Throwable cause)
			throws Exception {
		logger.error("客户端发生异常...", cause);
	}

}
代码全部编写完毕,有兴趣的童鞋们请关注,另转载请注明出处 http://blog.youkuaiyun.com/yuan16423276
以上代码或有不妥之处,欢迎大家多多指正,我将对其中的一些地方进行适当的优化。  

 


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值