标签:void add handler 切割 二进制协议 一个 tar contex active
一、前言
前面博客大部分介绍了基于EMQ中间件,通信协议使用的是MQTT,而传输的数据为纯文本数据,采用JSON格式。这种方式,大部分一看就知道是熟悉Web开发、软件开发的人喜欢用的方式。由于我也是做web软件开发的,也是比较喜欢这种方式。阿里的物联网平台,也是推荐这种方式。但是,但是做惯硬件开发,嵌入式开发就比较喜欢用裸TCP-Socket连接。采用的是二进制协议。基于此大部分应用场合为了兼容旧设备,就需要单独开发一个TCP服务器的网关。这里使用以前学过的,也是比较流行的Netty框架。
话不多说,下面就开始了。
二、协议
|
定义 |
描述 |
|
|
启动符‘@@’ (2字节) |
数据包的第1、2字节,为固定值 64,64。 |
|
|
控制单元
|
业务流水号 (2字节) |
数据包的第3、4字节。发送/确认模式下,业务流水号由发送端在发送新的数据包时按顺序加一,确认方按发送包的业务流水号返回;请求/应答模式下,业务流水号由请求端在发送新的请求命令时按顺序加一,应答方按请求包的业务流水号返回。低字节传输在前。业务流水号是一个2字节的正整数,由通信双方第一次建立网络连接时确定,初始值为0。业务流水号由业务发起方(业务发起方指发送/确认模式下的发送端或者请求/应答模式下的请求端)独立管理。业务发起方负责业务流水号的分配和回收,保证在业务存续期间业务流水号的唯一性。 |
|
协议版本号 (2字节) |
协议版本号包含主版本号(第5字节)和用户版本号(第6字节)。主版本号为固定值1,用户版本号由用户自行定义。 |
|
|
时间标签 (6字节) |
数据包的第7~12字节,为数据包发出的时间,具体定义表2。 |
|
|
源地址 (6字节) |
数据包的第13~18字节,为数据包的源地址(监控中心或用户信息传输装置地址)。低字节传输在前。 |
|
|
目的地址 (6字节) |
数据包的第19~24字节,为数据包的目的地址(监控中心或用户信息传输装置地址)。低字节传输在前。 |
|
|
应用数据单元长度 (2字节) |
数据包的第25、26字节,为应用数据单元的长度,长度不应大于1024;低字节传输在前。 |
|
|
命令字节 (1字节) |
数据包的第27字节,为控制单元的命令字节,具体定义见表3。 |
|
|
应用数据单元 (最大1024字节) |
应用数据单元,基本格式见表3,对于确认/否认等命令包,此单元可为空。 |
|
|
校验和 (1字节) |
控制单元中各字节数据(第3~第27字节)及应用数据单元的算术校验和,舍去8位以上的进位位后所形成的1字节二进制数。 |
|
|
结束符‘##’ (2字节) |
为固定值 35,35。 |
|
上面这个是本次需要处理的二进制数据格式。
三、代码部分
3.0 Pom.xml
1 <?xml version="1.0" encoding="UTF-8"?> 2 <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 3 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4 <modelVersion>4.0.0</modelVersion> 5 <parent> 6 <groupId>org.springframework.boot</groupId> 7 <artifactId>spring-boot-starter-parent</artifactId> 8 <version>2.1.1.RELEASE</version> 9 <relativePath/> <!-- lookup parent from repository --> 10 </parent> 11 <groupId>com.wunaozai.iot.nettyplatform</groupId> 12 <artifactId>NettyPlatform</artifactId> 13 <version>0.0.1-SNAPSHOT</version> 14 <name>IoTNettyPlatForm</name> 15 <description>基于自定义协议,使用Netty,物联网通信平台</description> 16 17 <properties> 18 <java.version>1.8</java.version> 19 </properties> 20 21 <dependencies> 22 <dependency> 23 <groupId>org.springframework.boot</groupId> 24 <artifactId>spring-boot-starter</artifactId> 25 </dependency> 26 27 <!-- https://mvnrepository.com/artifact/io.netty/netty-all --> 28 <dependency> 29 <groupId>io.netty</groupId> 30 <artifactId>netty-all</artifactId> 31 </dependency> 32 <dependency> 33 <groupId>org.springframework.boot</groupId> 34 <artifactId>spring-boot-configuration-processor</artifactId> 35 <optional>true</optional> 36 </dependency> 37 38 <!-- web项目必要的依赖 --> 39 <dependency> 40 <groupId>org.springframework.boot</groupId> 41 <artifactId>spring-boot-starter-web</artifactId> 42 </dependency> 43 44 <!-- 热启动devtools --> 45 <dependency> 46 <groupId>org.springframework.boot</groupId> 47 <artifactId>spring-boot-devtools</artifactId> 48 <optional>true</optional> 49 <scope>true</scope> 50 </dependency> 51 52 <dependency> 53 <groupId>org.springframework.boot</groupId> 54 <artifactId>spring-boot-starter-test</artifactId> 55 <scope>test</scope> 56 </dependency> 57 </dependencies> 58 59 <build> 60 <plugins> 61 <plugin> 62 <groupId>org.springframework.boot</groupId> 63 <artifactId>spring-boot-maven-plugin</artifactId> 64 <configuration> 65 <fork>true</fork> 66 </configuration> 67 </plugin> 68 </plugins> 69 </build> 70 71 </project>
3.1 SmartIotProtocol.java
这个主要对通信协议模型进行简单封装
1 package com.wunaozai.iot.nettyplatform.code;
2
3 /**
4 * 自定义协议
5 * @author Administrator
6 * @see https://www.cnblogs.com/sidesky/p/6913109.html
7 */
8 public class SmartIotProtocol {
9
10 /**
11 * 协议最短长度 30 字节
12 */
13 public static int MIN_LEN = 30;
14
15 /**
16 * 数据包启动符号 @@
17 */
18 public static short START = 25700;
19
20 /**
21 * 业务流水号
22 */
23 private short flowid;
24 /**
25 * 主版本
26 */
27 private byte version_major;
28 /**
29 * 次版本
30 */
31 private byte version_minor;
32 /**
33 * 秒
34 */
35 private byte second;
36 /**
37 * 分钟
38 */
39 private byte minute;
40 /**
41 * 小时
42 */
43 private byte hour;
44 /**
45 * 日
46 */
47 private byte day;
48 /**
49 * 月
50 */
51 private byte month;
52 /**
53 * 年
54 */
55 private byte year;
56 /**
57 * 数据包的源地址
58 */
59 private byte[] src;
60 /**
61 * 数据包的目的地址
62 */
63 private byte[] dest;
64 /**
65 * 应用数据单元长度 长度不应大于1024;低字节传输在前
66 */
67 private short data_len;
68 /**
69 * 命令字节 为控制单元的命令字节
70 */
71 private byte cmd;
72 /**
73 * 应用数据单元 对于确认/否认等命令包,此单元可为空
74 */
75 private byte[] data;
76 /**
77 * 校验和 控制单元中各字节数据(第3~第27字节)及应用数据单元的算术校验和,舍去8位以上的进位位后所形成的1字节二进制数
78 */
79 private byte checksum;
80 /**
81 * 协议结束符号 ##
82 */
83 public static short END = 13621;
84
85 /**
86 * 打印调试信息
87 */
88 public void printDebugInfo(){
89 System.out.println("---------完整数据包开始------------");
90 System.out.println("|开始标志: " + printHexShort(START));
91 System.out.println("|业务流水: " + printHexShort(flowid) + "\tFlowID:" + flowid);
92 System.out.println("|协议版本: " + printHexByte(version_major) + printHexByte(version_minor));
93 System.out.println("|时间标签: " + "20" + year + "-" + month + "-" + day + " " + hour + ":" + minute + ":" + second);
94 System.out.println("|源地址 : " + printHexBytes(src));
95 System.out.println("|目的地址: " + printHexBytes(dest));
96 System.out.println("|数据长度: " + data_len);
97 System.out.println("|命令字节: " + printHexByte(cmd));
98 System.out.println("|应用数据: " + printHexBytes(data));
99 System.out.println("|校验字节: " + printHexByte(checksum));
100 System.out.println("|结束标志: " + printHexShort(END));
101 System.out.println("---------------------------------");
102 }
103 private String printHexByte(byte b){
104 return String.format("%02X", b);
105 }
106 private String printHexBytes(byte[] bytes){
107 String str = "";
108 for(int i=0; i<bytes.length; i++){
109 str += String.format("%02X", bytes[i]);
110 }
111 return str;
112 }
113 private String printHexShort(int s){
114 byte[] bytes = hexShort(s);
115 return printHexBytes(bytes);
116 }
117 private byte[] hexShort(int s){
118 byte[] bytes = new byte[2];
119 bytes[0] = (byte)((s << 24) >> 24);
120 bytes[1] = (byte)((s << 16) >> 24);
121 return bytes;
122 }
123 private byte[] hexInt(int n){
124 byte[] bytes = new byte[4];
125 bytes[3] = (byte) ((n ) >> 24);
126 bytes[2] = (byte) ((n << 8) >> 24);
127 bytes[1] = (byte) ((n << 16) >> 24);
128 bytes[0] = (byte) ((n << 24) >> 24);
129 return bytes;
130 }
131
132 public short getFlowid() {
133 return flowid;
134 }
135 public void setFlowid(short flowid) {
136 this.flowid = flowid;
137 }
138 public byte getVersion_major() {
139 return version_major;
140 }
141 public void setVersion_major(byte version_major) {
142 this.version_major = version_major;
143 }
144 public byte getVersion_minor() {
145 return version_minor;
146 }
147 public void setVersion_minor(byte version_minor) {
148 this.version_minor = version_minor;
149 }
150 public byte getSecond() {
151 return second;
152 }
153 public void setSecond(byte second) {
154 this.second = second;
155 }
156 public byte getMinute() {
157 return minute;
158 }
159 public void setMinute(byte minute) {
160 this.minute = minute;
161 }
162 public byte getHour() {
163 return hour;
164 }
165 public void setHour(byte hour) {
166 this.hour = hour;
167 }
168 public byte getDay() {
169 return day;
170 }
171 public void setDay(byte day) {
172 this.day = day;
173 }
174 public byte getMonth() {
175 return month;
176 }
177 public void setMonth(byte month) {
178 this.month = month;
179 }
180 public byte getYear() {
181 return year;
182 }
183 public void setYear(byte year) {
184 this.year = year;
185 }
186 public byte[] getSrc() {
187 return src;
188 }
189 public void setSrc(byte[] src) {
190 this.src = src;
191 }
192 public byte[] getDest() {
193 return dest;
194 }
195 public void setDest(byte[] dest) {
196 this.dest = dest;
197 }
198 public short getData_len() {
199 return data_len;
200 }
201 public void setData_len(short data_len) {
202 this.data_len = data_len;
203 }
204 public byte getCmd() {
205 return cmd;
206 }
207 public void setCmd(byte cmd) {
208 this.cmd = cmd;
209 }
210 public byte[] getData() {
211 return data;
212 }
213 public void setData(byte[] data) {
214 this.data = data;
215 }
216 public byte getChecksum() {
217 return checksum;
218 }
219 public void setChecksum(byte checksum) {
220 this.checksum = checksum;
221 }
222
223 }
3.2 SmartIotDecoder.java
解码器,这个是本次的重点,这个解码器最主要是解决TCP粘包拆包问题,如果有不清楚的,要重点理解一下。
1 package com.wunaozai.iot.nettyplatform.code;
2
3 import java.util.List;
4
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import io.netty.buffer.ByteBuf;
9 import io.netty.channel.ChannelHandlerContext;
10 import io.netty.handler.codec.ByteToMessageDecoder;
11
12 /**
13 * 自定义协议解析
14 * @author Administrator
15 *
16 */
17 public class SmartIotDecoder extends ByteToMessageDecoder {
18
19
20 private static final Logger log = LoggerFactory.getLogger(SmartIotDecoder.class);
21
22 @Override
23 protected void decode(ChannelHandlerContext ctx, ByteBuf buffer, List<Object> out) throws Exception {
24 log.debug("启动解码器...");
25 log.debug("目前数据缓存大小: " + buffer.readableBytes());
26 // 刻度长度必须大于基本最小长度
27 if(buffer.readableBytes() >= SmartIotProtocol.MIN_LEN){
28 log.debug("符合最小长度,进行解析");
29 //防止socket字节流攻击、客户端传来的数据过大,这里需要对数据进行过滤掉
30 if(buffer.readableBytes() >= 4096){
31 buffer.skipBytes(buffer.readableBytes());
32 return ;
33 }
34
35 //记录包头开始位置
36 int beginReader = 0;
37 while(true){
38 beginReader = buffer.readerIndex(); //记录包头开始位置
39 buffer.markReaderIndex(); //标记包头开始index
40 //读取协议开始标志
41 if(buffer.readShort() == SmartIotProtocol.START){
42 break; //如果是开始标记,那么就结束查找
43 }
44
45 //如果找不到包头,这里要一个一个字节跳过
46 buffer.resetReaderIndex();
47 buffer.readByte();
48
49 //当跳过后,如果数据包又不符合长度的,结束本次协议解析
50 if(buffer.readableBytes() < SmartIotProtocol.MIN_LEN){
51 return ;
52 }
53 }
54
55 short flowid = buffer.readShort();
56 byte version_major = buffer.readByte();
57 byte version_minor = buffer.readByte();
58 byte second = buffer.readByte();
59 byte minute = buffer.readByte();
60 byte hour = buffer.readByte();
61 byte day = buffer.readByte();
62 byte month = buffer.readByte();
63 byte year = buffer.readByte();
64 byte[] src = new byte[6];
65 src[0] = buffer.readByte();
66 src[1] = buffer.readByte();
67 src[2] = buffer.readByte();
68 src[3] = buffer.readByte();
69 src[4] = buffer.readByte();
70 src[5] = buffer.readByte();
71 byte[] dest = new byte[6];
72 dest[0] = buffer.readByte();
73 dest[1] = buffer.readByte();
74 dest[2] = buffer.readByte();
75 dest[3] = buffer.readByte();
76 dest[4] = buffer.readByte();
77 dest[5] = buffer.readByte();
78 short data_len = buffer.readShort();
79 if(buffer.readableBytes() < data_len + 4){
80 //还原读指针
81 buffer.readerIndex(beginReader);
82 return ;
83 }
84 byte cmd = buffer.readByte();
85 byte[] data = null;
86 if(data_len > 0){
87 //读取应用数据单元
88 data = new byte[data_len];
89 buffer.readBytes(data);
90 }
91
92 byte checksum = buffer.readByte();
93 short end = buffer.readShort();
94
95 if(end == SmartIotProtocol.END){
96 log.debug("完成解析,并输出.");
97 SmartIotProtocol iot = new SmartIotProtocol();
98 iot.setFlowid(flowid);
99 iot.setVersion_major(version_major);
100 iot.setVersion_minor(version_minor);
101 iot.setSecond(second);
102 iot.setMinute(minute);
103 iot.setHour(hour);
104 iot.setDay(day);
105 iot.setMonth(month);
106 iot.setYear(year);
107 iot.setSrc(src);
108 iot.setDest(dest);
109 iot.setData_len(data_len);
110 iot.setCmd(cmd);
111 if(data_len > 0){
112 iot.setData(data);
113 }else{
114 iot.setData(null);
115 }
116 iot.setChecksum(checksum);
117 out.add(iot);
118 }
119 }
120 }
121
122 }
3.3 SmartIotEncoder.java
相对于解码,这个编码器,就相对简单了,按照协议,一个byte一本byte进行发送即可。
1 package com.wunaozai.iot.nettyplatform.code;
2
3 import io.netty.buffer.ByteBuf;
4 import io.netty.channel.ChannelHandlerContext;
5 import io.netty.handler.codec.MessageToByteEncoder;
6
7 /**
8 * 自定义协议数据解析
9 * @author Administrator
10 *
11 */
12 public class SmartIotEncoder extends MessageToByteEncoder<SmartIotProtocol> {
13
14 @Override
15 protected void encode(ChannelHandlerContext ctx, SmartIotProtocol msg, ByteBuf out) throws Exception {
16 //写入消息SmartIot具体内容
17 out.writeShort(SmartIotProtocol.START);
18 out.writeShort(msg.getFlowid());
19 out.writeByte(msg.getVersion_major());
20 out.writeByte(msg.getVersion_minor());
21 out.writeByte(msg.getSecond());
22 out.writeByte(msg.getMinute());
23 out.writeByte(msg.getHour());
24 out.writeByte(msg.getDay());
25 out.writeByte(msg.getMonth());
26 out.writeByte(msg.getYear());
27 out.writeBytes(msg.getSrc());
28 out.writeBytes(msg.getDest());
29 out.writeShort(msg.getData_len());
30 out.writeByte(msg.getCmd());
31 out.writeBytes(msg.getData());
32 out.writeByte(msg.getChecksum());
33 out.writeShort(SmartIotProtocol.END);
34 }
35
36 }
3.4 SmartIotHandler.java
这个是工程里面的主要业务操作类,用户Handler处理所有业务操作,这里也可以理解为是一个入口、网关。所有命令都从这里进行分发到子模块。
1 package com.wunaozai.iot.nettyplatform.code;
2
3 import java.net.InetSocketAddress;
4
5 import org.slf4j.Logger;
6 import org.slf4j.LoggerFactory;
7
8 import io.netty.channel.ChannelHandlerContext;
9 import io.netty.channel.SimpleChannelInboundHandler;
10
11 /**
12 * 服务Handler 处理
13 * @author Administrator
14 *
15 */
16 public class SmartIotHandler extends SimpleChannelInboundHandler<SmartIotProtocol> {
17
18
19 private static final Logger log = LoggerFactory.getLogger(SmartIotHandler.class);
20
21 @Override
22 protected void channelRead0(ChannelHandlerContext ctx, SmartIotProtocol iot)
23 throws Exception {
24 log.info("收到设备数据包: " + iot.getFlowid());
25 iot.printDebugInfo();
26 ctx.write("ok");
27 }
28
29 @Override
30 public void channelActive(ChannelHandlerContext ctx) throws Exception {
31 InetSocketAddress socket = (InetSocketAddress) ctx.channel().remoteAddress();
32 String ip = socket.getAddress().getHostAddress();
33 log.info("收到客户端IP: " + ip);
34 }
35
36 @Override
37 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
38 ctx.close();
39 }
40 }
3.5 NettyServerInitializer.java
这个就是初始化本次Netty框架中,使用的编解码器,还有对应的处理类。
1 package com.wunaozai.iot.nettyplatform.config;
2
3 import com.wunaozai.iot.nettyplatform.code.SmartIotDecoder;
4 import com.wunaozai.iot.nettyplatform.code.SmartIotEncoder;
5 import com.wunaozai.iot.nettyplatform.code.SmartIotHandler;
6
7 import io.netty.channel.ChannelInitializer;
8 import io.netty.channel.ChannelPipeline;
9 import io.netty.channel.socket.SocketChannel;
10
11 /**
12 * 服务器初始化
13 * @author Administrator
14 *
15 */
16 public class NettyServerInitializer extends ChannelInitializer<SocketChannel> {
17
18 @Override
19 protected void initChannel(SocketChannel ch) throws Exception {
20 // ChannelPipeline pipeline = ch.pipeline();
21 // //自定义切割符
22 // //ByteBuf delimiter = Unpooled.copiedBuffer(new byte[] {16});
23 // ByteBuf delimiter = Unpooled.copiedBuffer("$_".getBytes());
24 //
25 // pipeline.addLast(new DelimiterBasedFrameDecoder(8192, delimiter));
26 // pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
27 // pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
28 // pipeline.addLast(new NettyServerHandler());
29
30 ChannelPipeline pipeline = ch.pipeline();
31 //添加自定义编解码器
32 pipeline.addLast(new SmartIotEncoder());
33 pipeline.addLast(new SmartIotDecoder());
34 //处理网络IO
35 pipeline.addLast(new SmartIotHandler());
36 }
37
38 }
3.6 NettyServer.java
Netty功能的入口类,所有Netty框架初始化步骤都在这里进行简单处理。
1 package com.wunaozai.iot.nettyplatform.config;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.stereotype.Component;
6
7 import io.netty.bootstrap.ServerBootstrap;
8 import io.netty.channel.ChannelFuture;
9 import io.netty.channel.ChannelOption;
10 import io.netty.channel.EventLoopGroup;
11 import io.netty.channel.nio.NioEventLoopGroup;
12 import io.netty.channel.socket.nio.NioServerSocketChannel;
13 import io.netty.handler.logging.LogLevel;
14 import io.netty.handler.logging.LoggingHandler;
15
16 /**
17 * Netty 服务器
18 * @author Administrator
19 *
20 */
21 @Component
22 public class NettyServer {
23
24 private static final Logger log = LoggerFactory.getLogger(NettyServer.class);
25
26 private int port = 7777;
27
28 public void run(){
29 EventLoopGroup bossGroup = new NioEventLoopGroup();
30 EventLoopGroup workerGroup = new NioEventLoopGroup();
31 try {
32 ServerBootstrap serverBootstrap = new ServerBootstrap();
33 serverBootstrap.group(bossGroup, workerGroup);
34 serverBootstrap.channel(NioServerSocketChannel.class);
35 serverBootstrap.option(ChannelOption.SO_BACKLOG, 1024);
36 serverBootstrap.handler(new LoggingHandler(LogLevel.INFO));
37 serverBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
38 serverBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
39 serverBootstrap.childHandler(new NettyServerInitializer());
40 // 绑定端口,开始接收进来的连接
41 ChannelFuture channelFuture = serverBootstrap.bind(port).sync();
42 log.info("netty服务启动: [port:" + port + "]");
43 // 等待服务器socket关闭
44 channelFuture.channel().closeFuture().sync();
45 } catch (Exception e) {
46 log.error("Netty 服务启动失败: " + e.getMessage());
47 }finally {
48 bossGroup.shutdownGracefully();
49 workerGroup.shutdownGracefully();
50 }
51 }
52 }
3.7 IotNettyPlatFormApplication.java
这个是Spring Boot项目的入口函数。在这里调用Netty的入口函数。
1 package com.wunaozai.iot.nettyplatform;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.springframework.boot.SpringApplication;
6 import org.springframework.boot.autoconfigure.SpringBootApplication;
7 import org.springframework.context.annotation.ComponentScan;
8 import org.springframework.web.servlet.config.annotation.EnableWebMvc;
9
10 import com.wunaozai.iot.nettyplatform.config.NettyServer;
11
12 @SpringBootApplication
13 public class IoTNettyPlatFormApplication {
14
15 private static final Logger log = LoggerFactory.getLogger(IoTNettyPlatFormApplication.class);
16
17
18 public static void main(String[] args) {
19 SpringApplication.run(IoTNettyPlatFormApplication.class, args);
20 run();
21 }
22
23 private static NettyServer nettyServer = new NettyServer();
24
25 private static void run(){
26 Thread thread = new Thread(new Runnable() {
27 @Override
28 public void run() {
29 nettyServer.run();
30 }
31 });
32 thread.start();
33 }
34
35 }
我这里通过在@SpringBootApplication 这里调用NettyServer。同时还有其他方式:
1) 通过实现ApplicationListener
1 import org.slf4j.Logger;
2 import org.slf4j.LoggerFactory;
3 import org.springframework.context.ApplicationListener;
4 import org.springframework.context.event.ContextRefreshedEvent;
5 import org.springframework.stereotype.Component;
6
7 /**
8 * 项目初始化
9 * @author wunaozai
10 * @date 2018-05-24
11 */
12 @Component
13 public class OnStartListener implements ApplicationListener<ContextRefreshedEvent> {
14
15 private static final Logger log = LoggerFactory.getLogger(OnStartListener.class);
16
17 @Override
18 public void onApplicationEvent(ContextRefreshedEvent arg0) {
19 log.info("Run on Start Listener.");
20 }
21
22 }
2) 通过实现CommandLineRunner
1 import org.slf4j.Logger;
2 import org.slf4j.LoggerFactory;
3 import org.springframework.boot.CommandLineRunner;
4 import org.springframework.core.annotation.Order;
5 import org.springframework.stereotype.Component;
6
7 /**
8 * 项目启动时初始化资源<br>
9 * 如 一些初始化操作,提前加载加密证书,初始化线程池等
10 * @author wunaozai
11 * @date 2018-05-24
12 */
13 @Component
14 @Order(value = 1) //执行顺序
15 public class Runner implements CommandLineRunner {
16
17 private static final Logger log = LoggerFactory.getLogger(Runner.class);
18
19 @Override
20 public void run(String... args) throws Exception {
21 log.info("The Runner start to Initialize.");
22 }
23
24 }
物联网架构成长之路(35)-利用Netty解析物联网自定义协议
标签:void add handler 切割 二进制协议 一个 tar contex active
原文地址:https://www.cnblogs.com/1124li/p/12308079.html