本文仅说明netty服务端接收自定义通讯协议的报文后,如何根据协议内容进行解析。
采用socket TCP 进行通信,服务端应采用多线程处理方式。
typedef struct _smart_head
{
unsigned char headFlag[3]; //头部标识 headFlag[0]=0x2E headFlag[1]=0x81 headFlag[2]=0xD0
unsigned char Command[2]; //命令 Command=0x5204
unsigned char packetID; //序列号,递增
short retcode; //返回值
unsigned short DataLen; //本次数据包的长度,不含包头和包尾
}smart_head;
typedef struct _smart_Vichleinfo
{
unsigned char DeviceNo[12]; //设备号
unsigned char CarNum[16]; //车牌号
unsigned char DriverID[16]; //当前司机编号
unsigned char X[4]; //经度
unsigned char Y[4]; //纬度
unsigned char speed; //当前的速度 km/小时
unsigned char DrverAuth:2, //当前司机人脸识别状态 0-未认证,1-已认证,2-认证失败
gpsinvalid:1, //GPS 模块状态 1-异常
overZoneWarn:1, //越界报警
overSpeedWarn:1, //超速报警
overWeightWarn:1, //超重报警
TryeWarn:1, //胎温\胎压\轴温报警
DriverStatusWarn:1; //ADAS和DSM报警
unsigned short rpm; //发动机转速
unsigned char direc[10]; //卫星定位角度
unsigned short ContinueDrvTime; //司机连续开车的时间分钟
unsigned short DataLen; //后面的数据长度
unsigned char data[]; //数据在随后定义
} smart_Vichleinfo;
typedef struct _smart_Datainfo
{
Unsigned long DataType:16, //数据类型 data中包含的数据类型 1-温度数据,2-胎温
//胎压数据 3- smart_Weightinfo 4- ADAS疲劳驾驶 5-照片数据
DataLen:16; //后面data的长度
Unsigned char data[]; //后面的5种数据中的一种
} smart_Datainfo;
1)温度数据
typedef struct _smart_Tempinfo
{
int BoxTemp; //车厢的温度 冷链车专用 精确到0.01摄氏度
int humidity; //车厢的湿度 -冷链车专用精确到0.01%
unsigned long ObjectWarn:4, //违规放置物品报价
resv:12;
} smart_Tempinfo;
2)胎温胎压数据
typedef struct _smart_Carinfo
{
unsigned char TryeWarType[32]; //轮胎报警类型 0-无效 1-温度 2-压力 3 漏气
4-轮胎传感器异常报警
unsigned char TryeID[32]; //轮胎编号 0-无效
unsigned short TryeValue[32]; //轮胎温度或者压力的值
unsigned char AxiesID[16]; //车轴编号
unsigned short AxiesValue[16]; //车轴温度
} smart_Carinfo
3)重量数据
typedef struct _smart_Weightinfo
{
unsigned long weight; //当前的起吊的重量,精确到0.1公斤
} smart_Weightinfo
4)ADAS和DSM
typedef struct _smart_Weightinfo
{
unsigned long WarnType; //报警类型,见数据定义
} smart_Weightinfo
5)照片视频数据
typedef struct _smart_photoinfo
{
unsigned char FileName[20]; //照片或者视频文件名称
unsigned char url[256]; //照片或者视频的下载链接
} smart_ photoinfo;
typedef struct _smart_tail
{
unsigned short CRC; //校验和,包含包头和数据
}smart_tail;
package com.jxhtyw.modules.socket.server;
import org.apache.log4j.Logger;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class MyServer {
private final int port;
private static final Logger logger = Logger.getLogger(MyServerHandler.class);
public MyServer(int port) {
this.port = port;
}
public void start() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap sb = new ServerBootstrap();
sb.option(ChannelOption.SO_BACKLOG, 1024);
sb.group(group, bossGroup) // 绑定线程池
.channel(NioServerSocketChannel.class) // 指定使用的channel
.localAddress(this.port)// 绑定监听端口
.childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作
@Override
protected void initChannel(SocketChannel ch) throws Exception {
logger.info("-------------有客户端连接--------------");
logger.info("IP:" + ch.localAddress().getHostName());
logger.info("Port:" + ch.localAddress().getPort());
ch.pipeline().addLast(new MyDecoder()); //数据解析
ch.pipeline().addLast(new MyServerHandler()); //业务处理
}
});
ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
logger.info("socket服务端启动成功,监听端口: " + cf.channel().localAddress());
System.out.println("socket服务端启动成功,监听端口: " + cf.channel().localAddress());
cf.channel().closeFuture().sync(); // 关闭服务器通道
} finally {
group.shutdownGracefully().sync(); // 释放线程池资源
bossGroup.shutdownGracefully().sync();
}
}
public static void main(String[] args) throws Exception {
new MyServer(8888).start(); // 启动
}
}
package com.jxhtyw.modules.socket.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.ByteToMessageDecoder;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.log4j.Logger;
/**
* 车载终端解码器
* @author liul 2020-05-13
*/
public class MyDecoder extends ByteToMessageDecoder {
private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final Logger logger = Logger.getLogger(MyDecoder.class);
private static String clientIpAllowed = "117.74.136.117"; //该编码器只试用于指定客户端传入的数据,因为协议是根据客户端数据类型定义的
@Override
protected void decode(ChannelHandlerContext ctx, ByteBuf buf, List<Object> out) throws Exception {
System.out.println("...............客户端连接服务器...............");
String client = ctx.channel().remoteAddress().toString();
System.out.println("客户端IP: " + client);
System.out.println("连接时间:"+ sdf.format(new Date()));
//判断IP合法性
if (StringUtils.isNotBlank(client) && client.contains(clientIpAllowed)) {
System.out.println("...............客户端IP合法,开始数据解析 ...............");
//简单的解释下,ByteBuf拿到的就是数据包,tcp头已被netty处理,直接根据协议对ByteBuf内容进行解析即可
//解析时需根据协议定义的数据类型进行解析,如:
//unsigned char headFlag[3]; 定义了headFlag这个字段是长度为3的无符号char类型数组,每个char占一个字节,所以应该读取3位:ByteBuf headFlag = buf.readBytes(3);根据协议可知,headFlag存储的是 headFlag[0]=0x2E headFlag[1]=0x81 headFlag[2]=0xD016进制数据,所以需转化为16进制显示: dataMap.put("headFlag", ByteBufUtil.hexDump(headFlag))。
//unsigned char packetID; 定义了packetID是一个无符号的char类型字符,直接使用buf.readUnsignedByte()进行解析。
//short retcode; 定义了retcode是一个short类型数值,使用buf.readShortLE()解析,注意:字符类型大于1byte的,需要注意大小端,使用LE结尾的方法进行解析,如readShortLE(),如果使用LE结尾方法解析的值不正确,可以再使用不带LE的方法进行解析,如:readShort()。
// 注意 ByteBuf 的read 操作是会改变索引 如果读取和规定格式不一样 是会读串数据的.
//开始解析
//定义一个map接收数据
LinkedHashMap<String, Object> dataMap = new LinkedHashMap<String, Object>();
//定义个StringBuffer用于字符串拼接
StringBuffer sb = new StringBuffer();
//数据接收时间
dataMap.put("dataTime",sdf.format(new Date()));
//按照规定的数据协议读取数据
//------------------包头-------------------
//头部标识
ByteBuf headFlag = buf.readBytes(3);
dataMap.put("headFlag", ByteBufUtil.hexDump(headFlag));
//命令
ByteBuf command = buf.readBytes(2);
dataMap.put("command", ByteBufUtil.hexDump(command));
//序列号
dataMap.put("packetID", buf.readUnsignedByte());
//返回值
dataMap.put("retcode", buf.readShortLE());
//本次数据包的长度
int DataLen = buf.readUnsignedShortLE();
dataMap.put("DataLen", DataLen);
//--------------数据包---------------
if (DataLen > 0) {
//设备号
ByteBuf DeviceNo = buf.readBytes(12);
dataMap.put("DeviceNo", convertByteBufToString(DeviceNo));
//车牌号
ByteBuf CarNum = buf.readBytes(16);
dataMap.put("CarNum", convertByteBufToString(CarNum));
//当前司机编号
ByteBuf DriverID = buf.readBytes(16);
dataMap.put("DriverID", convertByteBufToString(DriverID));
//经度
sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
if (i==0) {
sb.append(String.valueOf(buf.readUnsignedByte()) + ".");
} else {
sb.append(String.valueOf(buf.readUnsignedByte()));
}
}
dataMap.put("x", sb.toString());
//纬度
sb = new StringBuffer();
for (int i = 0; i < 4; i++) {
if (i==0) {
sb.append(String.valueOf(buf.readUnsignedByte()) + ".");
} else {
sb.append(String.valueOf(buf.readUnsignedByte()));
}
}
dataMap.put("y", sb.toString());
//当前的速度
dataMap.put("speed", buf.readUnsignedByte());
//当前司机人脸识别状态
dataMap.put("DrverAuth", buf.readUnsignedByte());
//GPS模块状态
dataMap.put("gpsinvalid", buf.readUnsignedByte());
//越界报警
dataMap.put("overZoneWarn", buf.readUnsignedByte());
//超速报警
dataMap.put("overSpeedWarn", buf.readUnsignedByte());
//超重报警
dataMap.put("overWeightWarn", buf.readUnsignedByte());
//胎温\胎压\轴温报警
dataMap.put("TryeWarn", buf.readUnsignedByte());
//ADAS和DSM报警
dataMap.put("DriverStatusWarn", buf.readUnsignedByte());
//发动机转速
dataMap.put("rpm", buf.readUnsignedShortLE());
//卫星定位角度
ByteBuf direc = buf.readBytes(10);
dataMap.put("direc", convertByteBufToString(direc));
//司机连续开车的时间分钟
dataMap.put("ContinueDrvTime", buf.readUnsignedShortLE());
//后面的数据长度
int DataLen2 = buf.readUnsignedShortLE();
dataMap.put("DataLen2", DataLen2);
if(DataLen2>0){
//数据类型
long DataType = buf.readUnsignedIntLE();
dataMap.put("DataType", DataType);
//后面的数据长度
long DataLen3 = buf.readUnsignedIntLE();
dataMap.put("DataLen3", DataLen3);
if (DataLen3>0) {
//1.温度数据
if (DataType == 1) {
//车厢的温度 冷链车专用 精确到0.01摄氏
dataMap.put("BoxTemp", buf.readIntLE());
//车厢的湿度 -冷链车专用精确到0.01%
dataMap.put("humidity", buf.readIntLE());
//违规放置物品报价
dataMap.put("ObjectWarn", buf.readUnsignedIntLE());
//
dataMap.put("resv", buf.readUnsignedIntLE());
}
//2.胎温胎压数据
else if (DataType == 2) {
//轮胎报警类型
ByteBuf TryeWarType = buf.readBytes(32);
dataMap.put("TryeWarType", convertByteBufToString(TryeWarType));
//轮胎编号
ByteBuf TryeID = buf.readBytes(32);
dataMap.put("TryeID", convertByteBufToString(TryeID));
//轮胎温度或者压力的值
sb = new StringBuffer();
for (int i = 0; i < 32; i++) {
sb.append(buf.readUnsignedShortLE());
}
dataMap.put("TryeValue", sb.toString());
//车轴编号
ByteBuf AxiesID = buf.readBytes(16);
dataMap.put("AxiesID", convertByteBufToString(AxiesID));
//车轴温度
sb = new StringBuffer();
for (int i = 0; i < 32; i++) {
sb.append(buf.readUnsignedShortLE());
}
dataMap.put("AxiesValue", sb.toString());
}
//3.重量数据
else if (DataType == 3) {
//当前的起吊的重量,精确到0.1
dataMap.put("weight", buf.readUnsignedIntLE());
}
//4.ADAS和DSM
else if (DataType == 4) {
//报警类型
dataMap.put("WarnType", buf.readUnsignedIntLE());
}
//5.照片视频数据
else if (DataType == 5) {
//照片或者视频文件名称
ByteBuf FileName = buf.readBytes(20);
dataMap.put("FileName", convertByteBufToString(FileName));
//照片或者视频的下载链接
ByteBuf url = buf.readBytes(256);
dataMap.put("url", convertByteBufToString(url));
}
}
}
//--------------包尾---------------
//校验和,包含包头和数据
dataMap.put("CRC", buf.readUnsignedShort());
}
//存到map中 ,传递到下一个业务处理的handler
out.add(dataMap);
} else {
System.out.println("...............服务端暂不接收该IP数据 ...............");
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("...............数据解析异常 ...............");
cause.printStackTrace();
ctx.close();
}
/**
* ByteBuf转String
* @param buf
* @return
* @throws UnsupportedEncodingException
*/
public String convertByteBufToString(ByteBuf buf) throws UnsupportedEncodingException {
String str;
if(buf.hasArray()) { // 处理堆缓冲区
str = new String(buf.array(), buf.arrayOffset() + buf.readerIndex(), buf.readableBytes());
} else { // 处理直接缓冲区以及复合缓冲区
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
str = new String(bytes, 0, buf.readableBytes());
}
return str;
}
}
package com.jxhtyw.modules.socket.server;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
import java.io.UnsupportedEncodingException;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.log4j.Logger;
public class MyServerHandler extends ChannelInboundHandlerAdapter {
private static final Logger logger = Logger.getLogger(MyServerHandler.class);
/**
* channelAction
* channel 通道 action 活跃的
* 当客户端主动链接服务端的链接后,这个通道就是活跃的了。也就是客户端与服务端建立了通信通道并且可以传输数据
*/
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务端:" + ctx.channel().localAddress().toString() + " 通道开启!");
}
/**
* channelInactive
* channel 通道 Inactive 不活跃的
* 当客户端主动断开服务端的链接后,这个通道就是不活跃的。也就是说客户端与服务端的关闭了通信通道并且不可以传输数据
*/
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("服务端:" + ctx.channel().localAddress().toString() + " 通道关闭!");
// 关闭流
}
/**
*
* @author Taowd
* TODO 此处用来处理收到的数据中含有中文的时 出现乱码的问题
* @param buf
* @return
*/
private String getMessage(ByteBuf buf) {
byte[] con = new byte[buf.readableBytes()];
buf.readBytes(con);
try {
return new String(con, "GBK");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
return null;
}
}
/**
* 功能:读取客户端发送过来的信息
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
LinkedHashMap<String, Object> map = (LinkedHashMap<String, Object>) msg;
for(Map.Entry<String, Object> entry : map.entrySet()){
String mapKey = entry.getKey();
String mapValue = entry.getValue().toString();
System.out.println(mapKey+":"+mapValue);
}
// String jsonString = JSON.toJSONString(map);
// System.out.println("数据解析内容:" + jsonString);
//业务逻辑处理
//业务逻辑处理
//必须释放,如果继承simplechannelinboundhandler会自动释放,但是报文处理写在channelRead0
ReferenceCountUtil.release(msg);
}
/**
* 功能:读取完毕客户端发送过来的数据之后的操作
*/
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
System.out.println("...............数据接收-完毕...............");
// 第一种方法:写一个空的buf,并刷新写出区域。完成后关闭sock channel连接。
ctx.writeAndFlush(Unpooled.EMPTY_BUFFER).addListener(ChannelFutureListener.CLOSE);
// String returnInfo = "服务端已接收数据!";
// ctx.writeAndFlush(Unpooled.copiedBuffer(returnInfo, CharsetUtil.UTF_8)).addListener(ChannelFutureListener.CLOSE);
}
/**
* 功能:服务端发生异常的操作
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("...............业务处理异常...............");
cause.printStackTrace();
ctx.close();
}
}