spring boot+netty+rabbitmq

521 阅读3分钟

pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-amqp</artifactId>
</dependency>

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.6.Final</version>
</dependency>

application.properties:

#对于rabbitMQ的支持
spring.rabbitmq.host=127.0.0.1
spring.rabbitmq.port=5672
spring.rabbitmq.username=guest
spring.rabbitmq.password=guest

定义rabbitmq信息:

import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.DirectExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DirectRabbitConfig {

    //队列 起名:DeviceQueue
    @Bean
    public Queue DeviceQueue() {
        // durable:是否持久化,默认是false,持久化队列:会被存储在磁盘上,当消息代理重启时仍然存在,暂存队列:当前连接有效
        // exclusive:默认也是false,只能被当前创建的连接使用,而且当连接关闭后队列即被删除。此参考优先级高于durable
        // autoDelete:是否自动删除,当没有生产者或者消费者使用此队列,该队列会自动删除。
        // return new Queue("DeviceQueue",true,true,false);

        //一般设置一下队列的持久化就好,其余两个就是默认false
        return new Queue("DeviceQueue",true);
    }

    //Direct交换机 起名:DeviceExchange
    @Bean
    DirectExchange DeviceExchange() {
        //  return new DirectExchange("DeviceExchange",true,true);
        return new DirectExchange("DeviceExchange",true,false);
    }

    //绑定  将队列和交换机绑定, 并设置用于匹配键:DeviceRouting
    @Bean
    Binding bindingDirect() {
        return BindingBuilder.bind(DeviceQueue()).to(DeviceExchange()).with("DeviceRouting");
    }
}

netty的启动类:

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 *  Netty服务端编写
 *
 * @author Administrator
 *
 */
@Component
@Order(3)
public class HttpServer implements CommandLineRunner {

    @Override
    public void run(String[] args) throws Exception{
        //构造两个线程组
        //用于接受服务端发起的请求
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        //用于处理bossGroup接收到的请求
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            //服务端启动辅助类
            ServerBootstrap bootstrap = new ServerBootstrap();
            //装配线程池;指定服务端监听通道;设置业务职责链
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new NettyServerInitializer());
            //绑定端口
            ChannelFuture future = bootstrap.bind(2012).sync();
            //等待服务端口关闭
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            // 优雅退出,释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

Netty服务端初始化器:

import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.socket.SocketChannel;

/** 
* 初始化配置器,channel注册成功后,会执行里面相应的初始化方法 
*/
public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel sc) {
        //channel获取相应的管道
        ChannelPipeline pipeline = sc.pipeline();
        //当请求到服务端,我们需要对写出到客户端的数据做编码处理
        pipeline.addLast("httpCode",new HttpServerCodec());
        //添加自定义的ChannelHandler
        pipeline.addLast("httpServerHandler",new NettyServerHandler());
    }

自定义NettyHandler:

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.AttributeKey;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.util.Arrays;
import java.util.Locale;
import java.util.concurrent.ConcurrentHashMap;

/*
 * Netty中注入 Spring Autowired
 * */
@Component
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    //链接池
    private final ConcurrentHashMap<String, String> channelMap = new ConcurrentHashMap<>();

    private static RabbitTemplate rabbitTemplate;

    @Autowired
    public void setRabbitTemplate(RabbitTemplate rabbitTemplate) {
        NettyServerHandler.rabbitTemplate = rabbitTemplate;
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端建立连接,通道开启!");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端断开连接,通道关闭!");
        channelMap.remove(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf requestByteBuf = (ByteBuf) msg;
        int length = requestByteBuf.readableBytes();
        byte[] bytes = new byte[length];
        requestByteBuf.readBytes(bytes);
        //解析请求包结果  result
        String result = HexDump.toHexString(bytes).toLowerCase(Locale.ROOT);
        System.out.println("result:"+result);
        //获取result中的设备id
        byte[] data = Arrays.copyOfRange(bytes, 3, 5);
        String deviceId = HexDump.toHexString(data).toLowerCase(Locale.ROOT);
        //保存channel通道
        online(deviceId,ctx.channel());
        //写入rabbitmq
        rabbitTemplate.convertAndSend("DeviceExchange", "DeviceRouting", deviceId);
        //定义回复包
        byte[] replyByte = HexDump.parseHexStr2Byte("自定义");
        ByteBuf replyByteBuf = Unpooled.wrappedBuffer(replyByte);
        ctx.writeAndFlush(replyByteBuf);
    }

    /**
     * 上线一个用户
     *
     * @param deviceId
     * @param channel
     */
    private void online (String deviceId, Channel channel){
        this.channelMap.put(channel.id().toString(), deviceId);
        AttributeKey<String> key = AttributeKey.valueOf("device");
        channel.attr(key).set(deviceId);
    }

    /**
     * 根据deviceId获取该设备的通道
     *
     * @param channelId
     * @return
     */
    public String getChannelByDeviceId (String channelId){
        return this.channelMap.get(channelId);
    }

}

字节转换工具类:

public class HexDump {
    private final static char[] HEX_DIGITS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};//十六进制的组成元素

    /**
     * 字节数组以String形式输出 以空格分隔,如:FE 00 12 0F 0E
     *
     * @param array 字节数组
     * @return
     */
    public static String dumpHexString(byte[] array) {
        return dumpHexString(array, 0, array.length);
    }

    /**
     * 字节数组以String形式输出 字节间以空格分隔
     *
     * @param array  字节数组
     * @param offset 起始位置
     * @param length 长度
     * @return
     */
    public static String dumpHexString(byte[] array, int offset, int length) {
        StringBuilder result = new StringBuilder();

        byte[] line = new byte[16];
        int lineIndex = 0;

        result.append("\n0x");
        result.append(toHexString(offset));

        for (int i = offset; i < offset + length; i++) {
            if (lineIndex == 16) {
                result.append(" ");

                for (int j = 0; j < 16; j++) {
                    if (line[j] > ' ' && line[j] < '~') {
                        result.append(new String(line, j, 1));
                    } else {
                        result.append(".");
                    }
                }

                result.append("\n0x");
                result.append(toHexString(i));
                lineIndex = 0;
            }

            byte b = array[i];
            result.append(" ");
            result.append(HEX_DIGITS[(b >>> 4) & 0x0F]);
            result.append(HEX_DIGITS[b & 0x0F]);

            line[lineIndex++] = b;
        }

        if (lineIndex != 16) {
            int count = (16 - lineIndex) * 3;
            count++;
            for (int i = 0; i < count; i++) {
                result.append(" ");
            }

            for (int i = 0; i < lineIndex; i++) {
                if (line[i] > ' ' && line[i] < '~') {
                    result.append(new String(line, i, 1));
                } else {
                    result.append(".");
                }
            }
        }

        return result.toString();
    }

    /**
     * 字节转16进制String
     *
     * @param b 字节
     * @return
     */
    public static String toHexString(byte b) {
        return toHexString(toByteArray(b));
    }

    /**
     * 字节数组转16进制String,无分隔,如:FE00120F0E
     *
     * @param array 字节数组
     * @return
     */
    public static String toHexString(byte[] array) {
        return toHexString(array, 0, array.length);
    }

    /**
     * 字节数组转16进制String,无分隔,如:FE00120F0E
     *
     * @param array  字节数组
     * @param offset 起始
     * @param length 长度
     * @return
     */
    public static String toHexString(byte[] array, int offset, int length) {
        char[] buf = new char[length * 2];

        int bufIndex = 0;
        for (int i = offset; i < offset + length; i++) {
            byte b = array[i];
            buf[bufIndex++] = HEX_DIGITS[(b >>> 4) & 0x0F];
            buf[bufIndex++] = HEX_DIGITS[b & 0x0F];
        }

        return new String(buf);
    }

    /**
     * int转16进制String
     *
     * @param i
     * @return
     */
    public static String toHexString(int i) {
        return toHexString(toByteArray(i));
    }

    /**
     * 字节转数组
     *
     * @param b
     * @return
     */
    public static byte[] toByteArray(byte b) {
        byte[] array = new byte[1];
        array[0] = b;
        return array;
    }

    /**
     * int转字节数组
     *
     * @param i
     * @return
     */
    public static byte[] toByteArray(int i) {
        byte[] array = new byte[4];

        array[3] = (byte) (i & 0xFF);
        array[2] = (byte) ((i >> 8) & 0xFF);
        array[1] = (byte) ((i >> 16) & 0xFF);
        array[0] = (byte) ((i >> 24) & 0xFF);

        return array;
    }

    /**
     * 十六进制转int
     *
     * @param c
     * @return
     */
    private static int toByte(char c) {
        if (c >= '0' && c <= '9') return (c - '0');
        if (c >= 'A' && c <= 'F') return (c - 'A' + 10);
        if (c >= 'a' && c <= 'f') return (c - 'a' + 10);

        throw new RuntimeException("Invalid hex char '" + c + "'");
    }

    /**
     * 十六进制字符串转字节数组
     *
     * @param hexString 如:FE00120F0E
     * @return
     */
    public static byte[] hexStringToByteArray(String hexString) {
        int length = hexString.length();
        byte[] buffer = new byte[length / 2];

        for (int i = 0; i < length; i += 2) {
            buffer[i / 2] = (byte) ((toByte(hexString.charAt(i)) << 4) | toByte(hexString.charAt(i + 1)));
        }

        return buffer;
    }

    /**
     * 将16进制字符串转换为二进制数组
     *
     * @param hexStr
     * @return
     */
    public static byte[] parseHexStr2Byte(String hexStr) {
        if (hexStr.length() < 1)
            return null;
        byte[] result = new byte[hexStr.length() / 2];
        for (int i = 0; i < hexStr.length() / 2; i++) {
            int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
            int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2),
                    16);
            result[i] = (byte) (high * 16 + low);
        }
        return result;
    }

    //大小端转换
    public static int byteToIntLittle(byte[] buffer) {

        int int1=buffer[0]&0xff;
        int int2=(buffer[1]&0xff)<<8;
        return int1|int2;
    }
}