前一篇文章我以经实现了基于java原生的socket来实现TCP服务器,并可以解析数据,可以说是一个比较简单的结构。后来我通过研究Netty发出,Netty是一个很好的框架,比较稳定。
1,还是那个拓扑结构
2.后台流程图
3.代码
1)MainPrl.java
main函数实现了设备动态加载,可以通过配置文件很灵活的设置设备的增减。
package qx.drc.main;
import java.io.IOException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import qx.drc.ser.DataRecvServer;
import qx.drc.utils.CommTool;
import qx.drc.utils.MyPath;
public class MainPrl {
private static final Logger logger = LoggerFactory.getLogger(MainPrl.class);
private static DataReceiveConfig cfg=DataReceiveConfig.getInstance();
public static void main(String[] args) {
// TODO 自动生成的方法存根
List list=cfg.listDrcPros;
for (DrcProInfo drcProInfo : list) {
try {
logger.info("初始化程序:"+drcProInfo.getParseDataName());
CommTool.printInfo("初始化程序:"+drcProInfo.getParseDataName());
CommTool.printInfo( drcProInfo.getJarPath());
URL url=new URL("file:"+ drcProInfo.getJarPath());
URLClassLoader myClassLoader=new URLClassLoader(new URL[]{url},
Thread.currentThread().getContextClassLoader());
Class myClass=null;
ParseData parseData=null;
try {
myClass = myClassLoader.loadClass(drcProInfo.getJarClassFullName());
parseData=(ParseData)myClass.newInstance();
parseData.setDrcProInfo(drcProInfo);
} catch (ClassNotFoundException e) {
e.printStackTrace();
continue;
}
catch (InstantiationException e) {
e.printStackTrace();
continue;
} catch (IllegalAccessException e) {
e.printStackTrace();
continue;
}
DataRecvServer server = new DataRecvServer(drcProInfo.getPort(),
drcProInfo.getParseDataName(),parseData);
try {
server.start();
} catch (Exception e) {
// TODO 自动生成的 catch 块
e.printStackTrace();
}
myClassLoader.close();
} catch (IOException e) {
logger.error(e.getMessage());
e.printStackTrace();
}
}
}
}
2) DataRecvServer.java
DataRecvServer类主要实现了新建一个服务器,并初始化服务器设置
import java.io.IOException;
import javax.xml.stream.events.StartDocument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import qx.drc.main.ParseData;
public class DataRecvServer {
private static final Logger logger = LoggerFactory.getLogger(DataRecvServer.class);
private int port; //接收数据端口
private String proName; // 接收数据名称
private ParseData parseData; //解析数据对象
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
public DataRecvServer(int port,String proName,ParseData parseData){
this.port=port;
this.proName=proName;
this.parseData=parseData;
}
public void start() throws Exception{
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
//创建ServerBootstrap实例
ServerBootstrap serverBootstrap=new ServerBootstrap();
//初始化ServerBootstrap的线程组
serverBootstrap.group(bossGroup,workerGroup);
//设置将要被实例化的ServerChannel类
serverBootstrap.channel(NioServerSocketChannel.class);
//serverBootstrap.handler(new LoggingHandler(LogLevel.ERROR));
//在ServerChannelInitializer中初始化ChannelPipeline责任链,并添加到serverBootstrap中
serverBootstrap.childHandler(new ServerChannelInitializer(this.parseData));
//标识当服务器请求处理线程全满时,用于临时存放已完成三次握手的请求的队列的最大长度
serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
// 是否启用心跳保活机机制
serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定端口后,开启监听
/*ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
if(channelFuture.isSuccess()){
System.out.println("TCP服务启动 成功---------------");
}*/
ChannelFuture f = serverBootstrap.bind(port);
f.addListener(future -> {
if (future.isSuccess()) {
System.out.printf("%s 开启端口 %s 成功\n",proName, port);
} else {
System.out.printf("%s 开启端口 %s 失败\n", proName,port);
}
});
}
public void setPort(int port) {
this.port = port;
}
public void setProName(String proName) {
this.proName = proName;
}
public void setParseData(ParseData parseData) {
this.parseData = parseData;
}
}
4)ServerChannelInitializer.java
ServerChannelInitializer类主要实现了,初化服务器通道的,即加载编码器,解码器及数据解析
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.bytes.ByteArrayEncoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import qx.drc.main.ParseData;
public class ServerChannelInitializer extends ChannelInitializer {
private static final Logger logger = LoggerFactory.getLogger(ServerChannelInitializer.class);
static final EventExecutorGroup group = new DefaultEventExecutorGroup(2);
private ParseData parseData;
public ServerChannelInitializer(ParseData parseData) throws InterruptedException {
this.parseData = parseData;
}
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
//IdleStateHandler心跳机制,如果超时触发Handle中userEventTrigger()方法
pipeline.addLast("idleStateHandler",new IdleStateHandler(15, 0, 0, TimeUnit.MINUTES));
pipeline.addLast(new ByteArrayEncoder());
pipeline.addLast(new DataDecoder(this.parseData));
pipeline.addLast(new DataServerHandler(this.parseData));
}
}
5)DataDecoder.java
DataDecoder是一个公共解码类,解码方式传递接口的方式结出。
import java.util.List;
import org.apache.log4j.lf5.PassingLogRecordFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import qx.drc.main.ParseData;
import qx.drc.utils.ByteUtils;
import qx.drc.utils.CommTool;
/**
* 解码器
* @author 70910
*
*/
public class DataDecoder extends ByteToMessageDecoder{
protected final Logger log = LoggerFactory.getLogger(getClass());
private ParseData parseData;
public DataDecoder(ParseData parseData) {
this.parseData = parseData;
}
@Override
protected void decode(ChannelHandlerContext arg0, ByteBuf in, List
6)DataServerHandler.java
公共解析类,通过传递的接口类,实现数据解析与存储
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import qx.drc.main.ParseData;
import qx.drc.utils.ByteUtils;
import qx.drc.utils.CommTool;
public class DataServerHandler extends ChannelInboundHandlerAdapter{
protected final Logger log = LoggerFactory.getLogger(getClass());
private ParseData parseData;
public DataServerHandler(ParseData parseData) {
this.parseData = parseData;
}
/**
* 当我们通道进行激活的时候 触发的监听方法
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.err.println(parseData.getDrcProInfo().getParseDataName() + "--------通道激活------------");
}
/**
* 当我们的通道里有数据进行读取的时候 触发的监听方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx /*NETTY服务上下文*/, Object msg /*实际的传输数据*/) throws Exception {
if(msg instanceof byte[]){
byte[] bytes = (byte[]) msg;
CommTool.printInfo(parseData.getDrcProInfo().getParseDataName()+",接收到数据 " + bytes.length + " bytes");
//解析 ,需要判定是否是心跳包
if(!parseData.parse(bytes)){
log.info(parseData.getDrcProInfo().getParseDataName()+",接收数据解析失败");
CommTool.printInfo(parseData.getDrcProInfo().getParseDataName()+",数据解析失败");
return;
}
//存储
if(!parseData.save()){
log.info(parseData.getDrcProInfo().getParseDataName()+",数据存储失败");
CommTool.printInfo(parseData.getDrcProInfo().getParseDataName()+",数据存储失败");
return;
}
if(!parseData.save2Txt()){
log.info(parseData.getDrcProInfo().getParseDataName()+",数据存储到TXT失败");
CommTool.printInfo(parseData.getDrcProInfo().getParseDataName()+",数据存储到TXT失败");
return;
}
CommTool.printInfo(parseData.getDrcProInfo().getParseDataName()+",数据解析存储成功");
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.err.println(parseData.getDrcProInfo().getParseDataName() + "--------数据读取完毕----------");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
System.err.println(parseData.getDrcProInfo().getParseDataName() + "--------数据读异常----------: ");
cause.printStackTrace();
ctx.close();
}
}
7) ParseData.java
解码,解析接口类,通过继承可以实现具体的解码和解析功能
import java.util.Date;
import java.util.List;
import io.netty.buffer.ByteBuf;
/**
* 解析接口
* @author yangze
*
*/
public interface ParseData {
//解析
boolean parse(byte[] bytes);
//解码
boolean decoder(ByteBuf in,List
8)实现解码与解析类,关注decoder与parse两个方法即可
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import io.netty.buffer.ByteBuf;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import qx.drc.main.DeviceInfo;
import qx.drc.main.DrcProInfo;
import qx.drc.main.ParamInfo;
import qx.drc.main.ParseData;
import qx.drc.utils.ByteUtils;
import qx.drc.utils.CRC16_Modbus;
import qx.drc.utils.CommTool;
import qx.drc.utils.DateUtils;
import qx.drc.utils.MyPath;
/**
* 解析负氧离子数据
* @author yangze
* 2019-08-21
*
*/
public class AnionSensorDataParse implements ParseData {
private static Logger logger = Logger.getLogger(AnionSensorDataParse.class);
public DrcProInfo drcProInfo;
private int deviAddr; //设备地址
private int funcCode; //功能码
private int dataLength; //数据长度
private int anionData;
private String typeOfPk;
private byte[] srcData;
private DeviceInfo deviceInfo = new DeviceInfo();
private Map name2ParamInfoMap= new HashMap();
private static Connection conn = null;
private Date lastDataTime = new Date();
private boolean isHeartPk = false;
public AnionSensorDataParse(){
}
// 初始化数据库
private boolean iniDB() {
if (conn != null){
try {
if(conn.isClosed()){
conn = DriverManager.getConnection(drcProInfo.getDbConnUrl());
}
return true;
} catch (SQLException ex) {
logger.error(drcProInfo.getParseDataName()+","+ex.getMessage());
CommTool.printInfo(drcProInfo.getParseDataName()+",数据库链接失败");
ex.printStackTrace();
}
}
try {
Class.forName("com.mysql.jdbc.Driver").newInstance();
} catch (Exception ex) {
logger.error(drcProInfo.getParseDataName()+","+ex.getMessage());
CommTool.printInfo(drcProInfo.getParseDataName()+",数据库驱动载入失败");
return false;
}
try {
conn = DriverManager.getConnection(drcProInfo.getDbConnUrl());
return true;
} catch (SQLException ex) {
logger.error(drcProInfo.getParseDataName()+","+ex.getMessage());
CommTool.printInfo(drcProInfo.getParseDataName()+","+ex.toString());
return false;
}
}
/**
* 获取设备信息
* @return
*/
private boolean getDeviceInfo() {
if(iniDB()){
try{
Statement stmt = conn.createStatement(); //创建Statement对象
String sql = "select devi_id,devi_code,devi_name from device_info where devi_code='"+ drcProInfo.getDeviceCode()+"'" ;
ResultSet rs = stmt.executeQuery(sql);//创建数据对象
ResultSetMetaData rsmd = rs.getMetaData() ;
int columnCount = rsmd.getColumnCount();
if(columnCount <= 0) return false;
while (rs.next()){
deviceInfo.setDeviId(rs.getString(1));
deviceInfo.setDeviCode(rs.getString(2));
deviceInfo.setDeviName(rs.getString(3));
}
rs.close();
stmt.close();
return true;
}catch(Exception e){
logger.error(drcProInfo.getParseDataName()+",error:"+e.getMessage());
CommTool.printInfo(drcProInfo.getParseDataName()+",获取设备信息错误");
return false;
}
}
else
return false;
}
/**
* 获取设备监测指标信息
* @return
*/
private boolean getDeviceParamsInfo() {
if(iniDB()){
try{
Statement stmt = conn.createStatement(); //创建Statement对象
String sql = "SELECT p.para_id,p.para_name,p.para_unit FROM devi_para_rel d " +
"LEFT JOIN param_info p ON p.para_id = d.para_id " +
"where d.devi_id='" + deviceInfo.getDeviId() + "'";
ResultSet rs = stmt.executeQuery(sql);//创建数据对象
ResultSetMetaData rsmd = rs.getMetaData() ;
int columnCount = rsmd.getColumnCount();
if(columnCount <= 0) return false;
while (rs.next()){
ParamInfo paramInfo = new ParamInfo();
paramInfo.setParaId(rs.getString(1));
paramInfo.setParaName(rs.getString(2));
paramInfo.setParaUnit(rs.getString(3));
name2ParamInfoMap.put(paramInfo.getParaName(), paramInfo);
}
rs.close();
stmt.close();
return true;
}catch(Exception e){
logger.error(drcProInfo.getParseDataName()+",error:"+e.getMessage());
CommTool.printInfo(drcProInfo.getParseDataName()+",获取设备信息错误");
return false;
}
}
else
return false;
}
@Override
public boolean decoder(ByteBuf in,List