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