# Java基于netty,根据自定义协议,解析TCP报文(报文为C++结构体)

本文仅说明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;

netty服务端接收数据并解析

netty服务端MyServer.java

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(); // 启动
    }
}

解码器MyDecoder.java

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;
    }
    
}

业务处理器MyServerHandler.java

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();
    }
    
}

你可能感兴趣的:(技术分享,问题解决)