SpringBoot集成netty在项目中的应用

最近做的这个项目,需要和服务端进行tcp通信。大概的需求是这样,服务端是物理硬件,由c++代码控制,后台和机器通过tcp进行通信。当前台输入数据给后台之后,后台作为客户端将前台的数据转换成byte数组给服务端发送数据,返回给后台,后台通过websocket将数据不停的发送到前台。下面记录一下后台作为客户端发送给机器的部分。


netty的客户端
package cn.zxw.netty.work;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.timeout.IdleStateHandler;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;

/**
 * @author zxw
 * @version 1.0
 * @description 连接的客户端
 * @data: 2020/3/6 21:33
 */
@Component
public class SayOnClient implements InitializingBean {

    private EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
    @Value("${netty.host}")
    private String host;
    @Value("${netty.port}")
    private int port;
    @Value("${netty.readTime}")
    private int readTime;
    @Value("${netty.writeTime}")
    private int writeTime;
    @Value("${netty.allTime}")
    private int allTime;

    public static Channel channel = null;

    @Override
    public void afterPropertiesSet() {
        createBootStrap(new Bootstrap(), eventLoopGroup);
    }

    void createBootStrap(Bootstrap bootstrap, EventLoopGroup eventLoopGroup) {
        try {
            if (bootstrap != null) {
                //定义handler  因为在内部类里面无法传入this对象定义到外面传递过去
                final SayOnHandler sayOnHandler = new SayOnHandler(this);
                bootstrap.group(eventLoopGroup)
                        .channel(NioSocketChannel.class)
                        .handler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
                                socketChannel.pipeline().addLast(new IdleStateHandler(readTime, writeTime, allTime))
                                        //添加自定义handler
                                        .addLast(sayOnHandler)
                                        //添加解码器
                                        .addLast("sayOnDecoder", new SayOnDecoder());
                            }
                        });
                ChannelFuture channelFuture = bootstrap.connect(host, port);
                channelFuture.addListener(new SayOnListener(this));
                //获得channel对象赋值给静态变量
                channel = channelFuture.channel();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}


自定义的handler
package cn.zxw.netty.work;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import org.springframework.stereotype.Component;

import java.nio.channels.SocketChannel;
import java.util.concurrent.TimeUnit;

/**
 * @author zxw
 * @version 1.0
 * @description 自定义handler  的区别就是SimpleChannelInboundHandler
 * 在接收到数据后会自动release掉数据占用的Bytebuffer资源(自动调用Bytebuffer.release())。
 * 而为何服务器端不能用呢,因为我们想让服务器把客户端请求的数据发送回去,
 * 而服务器端有可能在channelRead方法返回前还没有写完数据,因此不能让它自动release
 * @data: 2020/3/7 20:21
 */
@Component
@ChannelHandler.Sharable
public class SayOnHandler extends SimpleChannelInboundHandler<SocketChannel> {

    private SayOnClient sayOnClient;

    public SayOnHandler(SayOnClient sayOnClient) {
        this.sayOnClient = sayOnClient;
    }

    /**
     * 服务端断开连接会触发,断开后客户端会尝试重连操作
     *
     * @param ctx
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        try {
            final EventLoop eventLoop = ctx.channel().eventLoop();
            //获得eventLoop对象后进行任务调度,执行客户端再次连接方法
            eventLoop.schedule(() -> {
                sayOnClient.createBootStrap(new Bootstrap(), eventLoop);
            }, 10L, TimeUnit.SECONDS);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, SocketChannel socketChannel) throws Exception {
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
            switch (idleStateEvent.state()) {
                case WRITER_IDLE:
                    System.out.println("发送心跳----写数据");
                    ctx.channel().writeAndFlush("111");
                    break;
                case READER_IDLE:
                    System.out.println("读超时");
//                    ctx.channel().close();
                    break;
                default:
                    ctx.channel().close();
            }
        }
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("发生异常:");
        cause.printStackTrace();
        ctx.channel().close();
    }
}

自定义的listener
package cn.zxw.netty.work;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.EventLoop;

import java.util.concurrent.TimeUnit;

/**
 * @author zxw
 * @version 1.0
 * @description 监听器
 * @data: 2020/3/7 20:33
 */
public class SayOnListener implements ChannelFutureListener {

    private SayOnClient sayOnClient;

    public SayOnListener(SayOnClient sayOnClient){
        this.sayOnClient = sayOnClient;
    }

    @Override
    public void operationComplete(ChannelFuture channelFuture) throws Exception {
        if (!channelFuture.isSuccess()){
           final EventLoop eventLoop = channelFuture.channel().eventLoop();
           //如果连接失败,进行重连的任务调度
           eventLoop.schedule(()->{
               sayOnClient.createBootStrap(new Bootstrap(),eventLoop);
           },10L, TimeUnit.SECONDS);
            System.out.println("开始重连!!");
        } else {
            System.out.println("连接成功!!");
        }
    }
}

Java基本类型转byte数组
package cn.zxw.netty.work;

/**
 * @author zxw
 * @version 1.0
 * @description 数据类型转换工具类
 * @data: 2020/3/26 11:17
 */
public class DataConverterUtil {

    /**
     * char到字节数组的转换.
     */
    public static byte[] charToByte(char c) {
        byte[] b = new byte[2];
        b[0] = (byte) ((c & 0xFF00) >> 8);
        b[1] = (byte) (c & 0xFF);
        return b;
    }

    /**
     * 字节数组到char的转换
     * @param b 字节数组
     * @param index 从第几位开始
     */
    public static char byteToChar(byte[] b, int index) {
        return (char) (((b[index] & 0xFF) << 8) | (b[index + 1] & 0xFF));
    }

    /**
     * short到字节数组的转换.
     */
    public static byte[] shortToByte(short number) {
        int temp = number;
        byte[] b = new byte[2];
        for (int i = 0; i < b.length; i++) {
            // 将最低位保存在最低位
            b[i] = new Integer(temp & 0xff).byteValue();
            // 向右移8位
            temp = temp >> 8;
        }
        return b;
    }

    /**
     * 字节数组到short的转换.
     * @param b 字节数组
     * @param index 从数组的第几位开始取
     */
    public static short byteToShort(byte[] b, int index) {
        short s = 0;
        // 最低位
        short s0 = (short) (b[index] & 0xff);
        short s1 = (short) (b[index + 1] & 0xff);
        s1 <<= 8;
        s = (short) (s0 | s1);
        return s;
    }

    /**
     * int到字节数组的转换.
     */
    public static byte[] intToByte(int number) {
        int temp = number;
        byte[] b = new byte[4];
        for (int i = 0; i < b.length; i++) {
            // 将最低位保存在最低位
            b[i] = new Integer(temp & 0xff).byteValue();
            // 向右移8位
            temp = temp >> 8;
        }
        return b;
    }

    /**
     * 字节数组到int的转换.
     * @param b 字节数组
     * @param index 从数组的第几位开始取
     */
    public static int byteToInt(byte[] b, int index) {
        int s = 0;
        // 最低位
        int s0 = b[index] & 0xff;
        int s1 = b[index + 1] & 0xff;
        int s2 = b[index + 2] & 0xff;
        int s3 = b[index + 3] & 0xff;
        s3 <<= 24;
        s2 <<= 16;
        s1 <<= 8;
        s = s0 | s1 | s2 | s3;
        return s;
    }


    /**
     * 字节数组到int的转换.
     */
    public static int byteToInt(byte[] b) {
        int s;
        // 最低位
        int s0 = b[0] & 0xff;
        int s1 = b[1] & 0xff;
        int s2 = b[2] & 0xff;
        int s3 = b[3] & 0xff;
        s3 <<= 24;
        s2 <<= 16;
        s1 <<= 8;
        s = s0 | s1 | s2 | s3;
        return s;
    }

    /**
     * long类型转成byte数组
     */
    public static byte[] longToByte(long number) {
        long temp = number;
        byte[] b = new byte[8];
        for (int i = 0; i < b.length; i++) {
            // 将最低位保存在最低位 temp = temp
            b[i] = new Long(temp & 0xff).byteValue();
            // >> 8;// 向右移8位
            temp = temp >> 8;
        }
        return b;
    }

    /**
     * 字节数组到long的转换.
     */
    public static long inByteToLong(byte[] b, int index) {
        long s;
        // 最低位
        long s0 = b[index] & 0xff;
        long s1 = b[index + 1] & 0xff;
        long s2 = b[index + 2] & 0xff;
        long s3 = b[index + 3] & 0xff;
        long s4 = b[index + 4] & 0xff;
        long s5 = b[index + 5] & 0xff;
        long s6 = b[index + 6] & 0xff;
        long s7 = b[index + 7] & 0xff;

        // s0不变
        s1 <<= 8;
        s2 <<= 16;
        s3 <<= 24;
        s4 <<= 8 * 4;
        s5 <<= 8 * 5;
        s6 <<= 8 * 6;
        s7 <<= 8 * 7;
        s = s0 | s1 | s2 | s3 | s4 | s5 | s6 | s7;
        return s;
    }

    /**
     * double到字节数组的转换.
     */
    public static byte[] doubleToByte(double num) {
        byte[] b = new byte[8];
        long l = Double.doubleToLongBits(num);
        for (int i = 0; i < 8; i++) {
            b[i] = new Long(l).byteValue();
            l = l >> 8;
        }
        return b;
    }

    /**
     * 字节数组到double的转换
     * @param b 数组
     * @param index 第几位开始
     * @return 返回结果
     */
    public static double bytesToDouble(byte[] b, int index) {
        long m;
        m = b[0];
        m &= 0xff;
        m |= ((long) b[1] << 8);
        m &= 0xffff;
        m |= ((long) b[2] << 16);
        m &= 0xffffff;
        m |= ((long) b[3] << 24);
        m &= 0xffffffffL;
        m |= ((long) b[4] << 32);
        m &= 0xffffffffffL;
        m |= ((long) b[5] << 40);
        m &= 0xffffffffffffL;
        m |= ((long) b[6] << 48);
        m &= 0xffffffffffffffL;
        m |= ((long) b[7] << 56);
        return Double.longBitsToDouble(m);
    }

    /**
     * float到字节数组的转换.
     */
    public static byte[] floatToByte(float x) {
        // 先用 Float.floatToIntBits(f)转换成int
        byte[] b = new byte[4];
        int accum = Float.floatToIntBits(x);
        b = intToByte(accum);
        return b;
    }

    /**
     * 字节数组到float的转换.
     */
    public static float bytesToFloat(byte[] b) {
        // 4 bytes
        int accum = 0;
        for (int shiftBy = 0; shiftBy < 4; shiftBy++) {
            accum |= (b[shiftBy] & 0xff) << shiftBy * 8;
        }
        return Float.intBitsToFloat(accum);
    }


}

数据转换类(Java对象数据转byte数组)
package cn.zxw.netty.work;

import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.google.common.primitives.Bytes;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.jdbc.core.JdbcTemplate;
import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl;

import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.util.LinkedList;
import java.util.List;

/**
 * @author zxw
 * @version 1.0
 * @description 合并发送Java对象中数据转byte组
 * @data: 2020/3/23 21:41
 */
public class CodeUtil {

    /**
     * 游标和存放数据集合
     * 记录游标是为了假设需要将数组内部来进行增加的时候处理
     */
    private static int cursor = 0;
    private static List<Byte> list = new LinkedList<>();

    public static byte[] createMsg(Class targetClass, String targetJson) {
        JSONObject jsonObject = JSONUtil.parseObj(targetJson);
        //循环获得每个域对象
        Field[] fields = targetClass.getDeclaredFields();
        for (Field field : fields) {
            //暴力发射
            field.setAccessible(true);
            //根据业务不同来设定其它类型,比如我这会有list类型
            if (List.class.isAssignableFrom(field.getType())) {
                //先获得通用类型
                Type type = field.getGenericType();
                //获得实际类型参数
                Type listInnerType = ((ParameterizedTypeImpl) type).getActualTypeArguments()[0];
                //获得集合里的类型,可以进行判断是否是自己的所想想要再来赋值,如果类型比较多可以通过参数传递过来
                if (listInnerType.equals(User.class)) {
                    Field[] innerFields = User.class.getDeclaredFields();
                    for (Field innerField : innerFields) {
                        innerField.setAccessible(true);
                        Object innerType = jsonObject.get(field.getName());
                        //先转换成array获得第一个数据再转成jsonObject
                        JSONObject jsonInnerObj = (JSONObject) ((JSONArray) innerType).get(0);
                        //将域传入对象赋值
                        judgeDataType(innerField, jsonInnerObj);
                    }
                }
            } else {
                //如果没有集合直接是基本类型直接进行赋值
                judgeDataType(field, jsonObject);
            }
        }
        return Bytes.toArray(list);
    }

    /**
     * 判断数据类型进行赋值
     *
     * @param field      每个域
     * @param jsonObject json对象
     */
    private static void judgeDataType(Field field, JSONObject jsonObject) {
        if (field.getType().isArray()) {
            switch (field.getType().getComponentType().toString()) {
                case "byte":
                    JSONArray jsonArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonArray.size(); i++) {
                        Byte byteNum = jsonArray.getByte(i);
                        list.add(byteNum);
                        cursor += 1;
                    }
                    break;
                case "short":
                    JSONArray jsonShortArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonShortArray.size(); i++) {
                        byte[] shortData = DataConverterUtil.shortToByte(jsonShortArray.getShort(i));
                        putData(shortData, 2);
                    }
                    break;
                case "int":
                    JSONArray jsonIntArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonIntArray.size(); i++) {
                        byte[] intData = DataConverterUtil.intToByte(jsonIntArray.getInt(i));
                        putData(intData, 4);
                    }
                    break;
                case "float":
                    JSONArray jsonFloatArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonFloatArray.size(); i++) {
                        byte[] floatData = DataConverterUtil.floatToByte(jsonFloatArray.getFloat(i));
                        putData(floatData, 8);
                    }
                    break;
                case "double":
                    JSONArray jsonDouArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonDouArray.size(); i++) {
                        byte[] doubleData = DataConverterUtil.doubleToByte(jsonDouArray.getDouble(i));
                        putData(doubleData, 8);
                    }
                    break;
                case "long":
                    JSONArray jsonLongArray = jsonObject.getJSONArray(field.getName());
                    for (int i = 0; i < jsonLongArray.size(); i++) {
                        byte[] longData = DataConverterUtil.longToByte(jsonLongArray.getLong(i));
                        putData(longData, 8);
                    }
                    break;
                default:
                    throw new RuntimeException("无匹配数据");
            }
        } else {
            switch (field.getType().toString()) {
                case "byte":
                    Byte byteData = jsonObject.getByte(field.getName());
                    list.add(byteData);
                    cursor += 1;
                    break;
                case "short":
                    byte[] shortData = DataConverterUtil.shortToByte(jsonObject.getShort(field.getName()));
                    putData(shortData, 2);
                    break;
                case "int":
                    byte[] intData = DataConverterUtil.intToByte(jsonObject.getInt(field.getName()));
                    putData(intData, 4);
                    break;
                case "float":
                    byte[] floatData = DataConverterUtil.floatToByte(jsonObject.getFloat(field.getName()));
                    putData(floatData, 8);
                    break;
                case "double":
                    byte[] douData = DataConverterUtil.doubleToByte(jsonObject.getDouble(field.getName()));
                    putData(douData, 8);
                    break;
                case "long":
                    byte[] longData = DataConverterUtil.longToByte(jsonObject.getLong(field.getName()));
                    putData(longData, 8);
                    break;
                default:
                    throw new RuntimeException("无匹配数据");
            }
        }
    }

    /**
     * 添加数据
     *
     * @param bytes 转成byte数组的数据
     * @param num   加多少
     */
    private static void putData(byte[] bytes, int num) {
        for (byte every : bytes) {
            list.add(every);
            cursor += num;
        }
    }
}


One和User
package cn.zxw.netty.work;

import lombok.Data;

import java.awt.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @author zxw
 * @version 1.0
 * @description 测试编解码
 * @data: 2020/3/26 9:15
 */
@Data
public class One {
    private byte byteNum = 1;
    private short shortNum = 2;
    private int intNum = 3;
    private float floatNum = 4;
    private double doubleNum = 5;
    private long longNum = 6;
    private int[] arr = {1, 2, 3, 4};
    private List<User> userList;
}
/
/**
 * @author zxw
 * @version 1.0
 * @description bootstart
 * @data: 2020/3/23 22:20
 */
@Data
public class User {

    private int age = 8;
}


编写controller进行测试

/**
 * @author zxw
 * @version 1.0
 * @description bootstart
 * @data: 2020/2/28 11:10
 */
@RestController
@Api(tags = "TestController", description = "测试controller")
public class TestController {

    @ApiOperation("测试netty程序")
    @RequestMapping(value = "/nettyTest", method = RequestMethod.POST)
    public String nettyTest() {
        One one = new One();
        cn.zxw.netty.work.User user = new cn.zxw.netty.work.User();
        List<cn.zxw.netty.work.User> list = new LinkedList<>();
        list.add(user);
        one.setUserList(list);
        String s = JSONUtil.toJsonStr(one);
        byte[] msg = CodeUtil.createMsg(One.class, s);
        SayOnClient.channel.writeAndFlush(Unpooled.copiedBuffer(msg));
        return "success";
    }

使用tcp服务端工具起一个服务器进行测试,发送数据后如下

测试结果


此方法是自己第一次接触netty后在项目中的应用,如果有更好更规范的方式欢迎大家指点(因为客户端没有进行优雅关闭)

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值