最近做了一个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);
}
}
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();
}
}
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);
}
}