├── .classpath
├── .gitignore
├── .project
├── .settings
└── org.eclipse.core.resources.prefs
├── LICENSE
├── README.md
└── zer0MQTTServer
├── .project
├── .settings
└── org.eclipse.jdt.core.prefs
├── lib
├── druid-1.0.9.jar
├── json.jar
├── log4j-1.2.17.jar
├── mapdb-1.0.8.jar
├── mysql-connector-java-5.0.4-bin.jar
├── netty-all-5.0.0.Alpha2.jar
├── pahoMqtt.jar
├── quartz-2.2.2.jar
├── quartz-jobs-2.2.2.jar
├── slf4j-api-1.7.7.jar
└── slf4j-log4j12-1.7.7.jar
├── resource
├── druid.properties
├── log4j.properties
└── mqtt.properties
└── src
├── com
└── syxy
│ ├── protocol
│ └── mqttImp
│ │ ├── MQTTDecoder.java
│ │ ├── MQTTEncoder.java
│ │ ├── MQTTMesageFactory.java
│ │ ├── MQTTProcess.java
│ │ ├── message
│ │ ├── ConnAckMessage.java
│ │ ├── ConnAckVariableHeader.java
│ │ ├── ConnectMessage.java
│ │ ├── ConnectPayload.java
│ │ ├── ConnectVariableHeader.java
│ │ ├── FixedHeader.java
│ │ ├── Message.java
│ │ ├── MessageType.java
│ │ ├── PackageIDManager.java
│ │ ├── PackageIdVariableHeader.java
│ │ ├── PublishMessage.java
│ │ ├── PublishVariableHeader.java
│ │ ├── QoS.java
│ │ ├── SubAckMessage.java
│ │ ├── SubAckPayload.java
│ │ ├── SubscribeMessage.java
│ │ ├── SubscribePayload.java
│ │ ├── TopicSubscribe.java
│ │ ├── UnSubscribeMessage.java
│ │ └── UnSubscribePayload.java
│ │ └── process
│ │ ├── ConnectionDescriptor.java
│ │ ├── Impl
│ │ ├── IdentityAuthenticator.java
│ │ └── dataHandler
│ │ │ ├── DBConnection.java
│ │ │ └── MapDBPersistentStore.java
│ │ ├── Interface
│ │ ├── IAuthenticator.java
│ │ ├── IMessagesStore.java
│ │ └── ISessionStore.java
│ │ ├── NettyAttrManager.java
│ │ ├── ProtocolProcess.java
│ │ ├── event
│ │ ├── PubRelEvent.java
│ │ ├── PublishEvent.java
│ │ └── job
│ │ │ ├── RePubRelJob.java
│ │ │ └── RePublishJob.java
│ │ └── subscribe
│ │ ├── SubscribeStore.java
│ │ ├── Subscription.java
│ │ ├── Token.java
│ │ └── TreeNode.java
│ ├── server
│ ├── StartServer.java
│ └── TcpServer.java
│ └── util
│ ├── BufferPool.java
│ ├── Constant.java
│ ├── MqttTool.java
│ ├── QuartzManager.java
│ ├── StringTool.java
│ ├── TimeUtils.java
│ └── coderTool.java
├── log4j.properties
└── test
└── MQTTClientTest.java
/.classpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #java
2 | *.class
3 |
4 | #Eclipse
5 | *.classpath
6 | *.settings
7 | *.project
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | zer0MQTTServer
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.settings/org.eclipse.core.resources.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | encoding//zer0MQTTServer/resource/druid.properties=UTF-8
3 | encoding//zer0MQTTServer/resource/mqtt.properties=UTF-8
4 | encoding/=UTF-8
5 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | GNU GENERAL PUBLIC LICENSE
2 | Version 2, June 1991
3 |
4 | Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
5 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
6 | Everyone is permitted to copy and distribute verbatim copies
7 | of this license document, but changing it is not allowed.
8 |
9 | Preamble
10 |
11 | The licenses for most software are designed to take away your
12 | freedom to share and change it. By contrast, the GNU General Public
13 | License is intended to guarantee your freedom to share and change free
14 | software--to make sure the software is free for all its users. This
15 | General Public License applies to most of the Free Software
16 | Foundation's software and to any other program whose authors commit to
17 | using it. (Some other Free Software Foundation software is covered by
18 | the GNU Lesser General Public License instead.) You can apply it to
19 | your programs, too.
20 |
21 | When we speak of free software, we are referring to freedom, not
22 | price. Our General Public Licenses are designed to make sure that you
23 | have the freedom to distribute copies of free software (and charge for
24 | this service if you wish), that you receive source code or can get it
25 | if you want it, that you can change the software or use pieces of it
26 | in new free programs; and that you know you can do these things.
27 |
28 | To protect your rights, we need to make restrictions that forbid
29 | anyone to deny you these rights or to ask you to surrender the rights.
30 | These restrictions translate to certain responsibilities for you if you
31 | distribute copies of the software, or if you modify it.
32 |
33 | For example, if you distribute copies of such a program, whether
34 | gratis or for a fee, you must give the recipients all the rights that
35 | you have. You must make sure that they, too, receive or can get the
36 | source code. And you must show them these terms so they know their
37 | rights.
38 |
39 | We protect your rights with two steps: (1) copyright the software, and
40 | (2) offer you this license which gives you legal permission to copy,
41 | distribute and/or modify the software.
42 |
43 | Also, for each author's protection and ours, we want to make certain
44 | that everyone understands that there is no warranty for this free
45 | software. If the software is modified by someone else and passed on, we
46 | want its recipients to know that what they have is not the original, so
47 | that any problems introduced by others will not reflect on the original
48 | authors' reputations.
49 |
50 | Finally, any free program is threatened constantly by software
51 | patents. We wish to avoid the danger that redistributors of a free
52 | program will individually obtain patent licenses, in effect making the
53 | program proprietary. To prevent this, we have made it clear that any
54 | patent must be licensed for everyone's free use or not licensed at all.
55 |
56 | The precise terms and conditions for copying, distribution and
57 | modification follow.
58 |
59 | GNU GENERAL PUBLIC LICENSE
60 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
61 |
62 | 0. This License applies to any program or other work which contains
63 | a notice placed by the copyright holder saying it may be distributed
64 | under the terms of this General Public License. The "Program", below,
65 | refers to any such program or work, and a "work based on the Program"
66 | means either the Program or any derivative work under copyright law:
67 | that is to say, a work containing the Program or a portion of it,
68 | either verbatim or with modifications and/or translated into another
69 | language. (Hereinafter, translation is included without limitation in
70 | the term "modification".) Each licensee is addressed as "you".
71 |
72 | Activities other than copying, distribution and modification are not
73 | covered by this License; they are outside its scope. The act of
74 | running the Program is not restricted, and the output from the Program
75 | is covered only if its contents constitute a work based on the
76 | Program (independent of having been made by running the Program).
77 | Whether that is true depends on what the Program does.
78 |
79 | 1. You may copy and distribute verbatim copies of the Program's
80 | source code as you receive it, in any medium, provided that you
81 | conspicuously and appropriately publish on each copy an appropriate
82 | copyright notice and disclaimer of warranty; keep intact all the
83 | notices that refer to this License and to the absence of any warranty;
84 | and give any other recipients of the Program a copy of this License
85 | along with the Program.
86 |
87 | You may charge a fee for the physical act of transferring a copy, and
88 | you may at your option offer warranty protection in exchange for a fee.
89 |
90 | 2. You may modify your copy or copies of the Program or any portion
91 | of it, thus forming a work based on the Program, and copy and
92 | distribute such modifications or work under the terms of Section 1
93 | above, provided that you also meet all of these conditions:
94 |
95 | a) You must cause the modified files to carry prominent notices
96 | stating that you changed the files and the date of any change.
97 |
98 | b) You must cause any work that you distribute or publish, that in
99 | whole or in part contains or is derived from the Program or any
100 | part thereof, to be licensed as a whole at no charge to all third
101 | parties under the terms of this License.
102 |
103 | c) If the modified program normally reads commands interactively
104 | when run, you must cause it, when started running for such
105 | interactive use in the most ordinary way, to print or display an
106 | announcement including an appropriate copyright notice and a
107 | notice that there is no warranty (or else, saying that you provide
108 | a warranty) and that users may redistribute the program under
109 | these conditions, and telling the user how to view a copy of this
110 | License. (Exception: if the Program itself is interactive but
111 | does not normally print such an announcement, your work based on
112 | the Program is not required to print an announcement.)
113 |
114 | These requirements apply to the modified work as a whole. If
115 | identifiable sections of that work are not derived from the Program,
116 | and can be reasonably considered independent and separate works in
117 | themselves, then this License, and its terms, do not apply to those
118 | sections when you distribute them as separate works. But when you
119 | distribute the same sections as part of a whole which is a work based
120 | on the Program, the distribution of the whole must be on the terms of
121 | this License, whose permissions for other licensees extend to the
122 | entire whole, and thus to each and every part regardless of who wrote it.
123 |
124 | Thus, it is not the intent of this section to claim rights or contest
125 | your rights to work written entirely by you; rather, the intent is to
126 | exercise the right to control the distribution of derivative or
127 | collective works based on the Program.
128 |
129 | In addition, mere aggregation of another work not based on the Program
130 | with the Program (or with a work based on the Program) on a volume of
131 | a storage or distribution medium does not bring the other work under
132 | the scope of this License.
133 |
134 | 3. You may copy and distribute the Program (or a work based on it,
135 | under Section 2) in object code or executable form under the terms of
136 | Sections 1 and 2 above provided that you also do one of the following:
137 |
138 | a) Accompany it with the complete corresponding machine-readable
139 | source code, which must be distributed under the terms of Sections
140 | 1 and 2 above on a medium customarily used for software interchange; or,
141 |
142 | b) Accompany it with a written offer, valid for at least three
143 | years, to give any third party, for a charge no more than your
144 | cost of physically performing source distribution, a complete
145 | machine-readable copy of the corresponding source code, to be
146 | distributed under the terms of Sections 1 and 2 above on a medium
147 | customarily used for software interchange; or,
148 |
149 | c) Accompany it with the information you received as to the offer
150 | to distribute corresponding source code. (This alternative is
151 | allowed only for noncommercial distribution and only if you
152 | received the program in object code or executable form with such
153 | an offer, in accord with Subsection b above.)
154 |
155 | The source code for a work means the preferred form of the work for
156 | making modifications to it. For an executable work, complete source
157 | code means all the source code for all modules it contains, plus any
158 | associated interface definition files, plus the scripts used to
159 | control compilation and installation of the executable. However, as a
160 | special exception, the source code distributed need not include
161 | anything that is normally distributed (in either source or binary
162 | form) with the major components (compiler, kernel, and so on) of the
163 | operating system on which the executable runs, unless that component
164 | itself accompanies the executable.
165 |
166 | If distribution of executable or object code is made by offering
167 | access to copy from a designated place, then offering equivalent
168 | access to copy the source code from the same place counts as
169 | distribution of the source code, even though third parties are not
170 | compelled to copy the source along with the object code.
171 |
172 | 4. You may not copy, modify, sublicense, or distribute the Program
173 | except as expressly provided under this License. Any attempt
174 | otherwise to copy, modify, sublicense or distribute the Program is
175 | void, and will automatically terminate your rights under this License.
176 | However, parties who have received copies, or rights, from you under
177 | this License will not have their licenses terminated so long as such
178 | parties remain in full compliance.
179 |
180 | 5. You are not required to accept this License, since you have not
181 | signed it. However, nothing else grants you permission to modify or
182 | distribute the Program or its derivative works. These actions are
183 | prohibited by law if you do not accept this License. Therefore, by
184 | modifying or distributing the Program (or any work based on the
185 | Program), you indicate your acceptance of this License to do so, and
186 | all its terms and conditions for copying, distributing or modifying
187 | the Program or works based on it.
188 |
189 | 6. Each time you redistribute the Program (or any work based on the
190 | Program), the recipient automatically receives a license from the
191 | original licensor to copy, distribute or modify the Program subject to
192 | these terms and conditions. You may not impose any further
193 | restrictions on the recipients' exercise of the rights granted herein.
194 | You are not responsible for enforcing compliance by third parties to
195 | this License.
196 |
197 | 7. If, as a consequence of a court judgment or allegation of patent
198 | infringement or for any other reason (not limited to patent issues),
199 | conditions are imposed on you (whether by court order, agreement or
200 | otherwise) that contradict the conditions of this License, they do not
201 | excuse you from the conditions of this License. If you cannot
202 | distribute so as to satisfy simultaneously your obligations under this
203 | License and any other pertinent obligations, then as a consequence you
204 | may not distribute the Program at all. For example, if a patent
205 | license would not permit royalty-free redistribution of the Program by
206 | all those who receive copies directly or indirectly through you, then
207 | the only way you could satisfy both it and this License would be to
208 | refrain entirely from distribution of the Program.
209 |
210 | If any portion of this section is held invalid or unenforceable under
211 | any particular circumstance, the balance of the section is intended to
212 | apply and the section as a whole is intended to apply in other
213 | circumstances.
214 |
215 | It is not the purpose of this section to induce you to infringe any
216 | patents or other property right claims or to contest validity of any
217 | such claims; this section has the sole purpose of protecting the
218 | integrity of the free software distribution system, which is
219 | implemented by public license practices. Many people have made
220 | generous contributions to the wide range of software distributed
221 | through that system in reliance on consistent application of that
222 | system; it is up to the author/donor to decide if he or she is willing
223 | to distribute software through any other system and a licensee cannot
224 | impose that choice.
225 |
226 | This section is intended to make thoroughly clear what is believed to
227 | be a consequence of the rest of this License.
228 |
229 | 8. If the distribution and/or use of the Program is restricted in
230 | certain countries either by patents or by copyrighted interfaces, the
231 | original copyright holder who places the Program under this License
232 | may add an explicit geographical distribution limitation excluding
233 | those countries, so that distribution is permitted only in or among
234 | countries not thus excluded. In such case, this License incorporates
235 | the limitation as if written in the body of this License.
236 |
237 | 9. The Free Software Foundation may publish revised and/or new versions
238 | of the General Public License from time to time. Such new versions will
239 | be similar in spirit to the present version, but may differ in detail to
240 | address new problems or concerns.
241 |
242 | Each version is given a distinguishing version number. If the Program
243 | specifies a version number of this License which applies to it and "any
244 | later version", you have the option of following the terms and conditions
245 | either of that version or of any later version published by the Free
246 | Software Foundation. If the Program does not specify a version number of
247 | this License, you may choose any version ever published by the Free Software
248 | Foundation.
249 |
250 | 10. If you wish to incorporate parts of the Program into other free
251 | programs whose distribution conditions are different, write to the author
252 | to ask for permission. For software which is copyrighted by the Free
253 | Software Foundation, write to the Free Software Foundation; we sometimes
254 | make exceptions for this. Our decision will be guided by the two goals
255 | of preserving the free status of all derivatives of our free software and
256 | of promoting the sharing and reuse of software generally.
257 |
258 | NO WARRANTY
259 |
260 | 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
261 | FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
262 | OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
263 | PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
264 | OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
265 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
266 | TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
267 | PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
268 | REPAIR OR CORRECTION.
269 |
270 | 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
271 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
272 | REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
273 | INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
274 | OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
275 | TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
276 | YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
277 | PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
278 | POSSIBILITY OF SUCH DAMAGES.
279 |
280 | END OF TERMS AND CONDITIONS
281 |
282 | How to Apply These Terms to Your New Programs
283 |
284 | If you develop a new program, and you want it to be of the greatest
285 | possible use to the public, the best way to achieve this is to make it
286 | free software which everyone can redistribute and change under these terms.
287 |
288 | To do so, attach the following notices to the program. It is safest
289 | to attach them to the start of each source file to most effectively
290 | convey the exclusion of warranty; and each file should have at least
291 | the "copyright" line and a pointer to where the full notice is found.
292 |
293 | {description}
294 | Copyright (C) {year} {fullname}
295 |
296 | This program is free software; you can redistribute it and/or modify
297 | it under the terms of the GNU General Public License as published by
298 | the Free Software Foundation; either version 2 of the License, or
299 | (at your option) any later version.
300 |
301 | This program is distributed in the hope that it will be useful,
302 | but WITHOUT ANY WARRANTY; without even the implied warranty of
303 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
304 | GNU General Public License for more details.
305 |
306 | You should have received a copy of the GNU General Public License along
307 | with this program; if not, write to the Free Software Foundation, Inc.,
308 | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
309 |
310 | Also add information on how to contact you by electronic and paper mail.
311 |
312 | If the program is interactive, make it output a short notice like this
313 | when it starts in an interactive mode:
314 |
315 | Gnomovision version 69, Copyright (C) year name of author
316 | Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
317 | This is free software, and you are welcome to redistribute it
318 | under certain conditions; type `show c' for details.
319 |
320 | The hypothetical commands `show w' and `show c' should show the appropriate
321 | parts of the General Public License. Of course, the commands you use may
322 | be called something other than `show w' and `show c'; they could even be
323 | mouse-clicks or menu items--whatever suits your program.
324 |
325 | You should also get your employer (if you work as a programmer) or your
326 | school, if any, to sign a "copyright disclaimer" for the program, if
327 | necessary. Here is a sample; alter the names:
328 |
329 | Yoyodyne, Inc., hereby disclaims all copyright interest in the program
330 | `Gnomovision' (which makes passes at compilers) written by James Hacker.
331 |
332 | {signature of Ty Coon}, 1 April 1989
333 | Ty Coon, President of Vice
334 |
335 | This General Public License does not permit incorporating your program into
336 | proprietary programs. If your program is a subroutine library, you may
337 | consider it more useful to permit linking proprietary applications with the
338 | library. If this is what you want to do, use the GNU Lesser General
339 | Public License instead of this License.
340 |
341 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## 目录
2 | * [说明](#1)
3 | * [功能](#2)
4 | * [如何使用](#3)
5 | * [参考帮助](#4)
6 |
7 | ## 更新日志
8 | 2015年12月8日 zer0MQTTServer第一版,实现了MQTT协议
9 |
10 | 2016年05月25日 zer0MQTTServer第二版,协议通信底层由 Java AIO 切换到 Netty5.0,使用netty的编码解码模块功能重写了全部的协议编码解码
11 |
12 | ## 说明
13 |
14 | MQTT 协议是 IBM 开发的即时通讯协议,相对于 IM 的实际上的准标准协议 XMPP 来说,MQTT 更小,更快,更轻量。MQTT 适合于任何计算能力有限,工作在低带宽、不可靠的网络中的设备,包括手机,传感器等等。
15 |
16 | 开发此客户端的目的,是因为实际项目中需要用到推送、即时通讯的内容,而第三方平台有时候不稳定,遇到问题难于调试。所以决定自己开发一套即时通讯系统。选用 MQTT 的原因正如上所说,它比XMPP更适合手机端使用。具体比较请参看:[Android推送方案分析(MQTT/XMPP/GCM)](http://m.oschina.net/blog/82059)。
17 |
18 | 项目断断续续写了快一年(2015年2月~12月),大部分在业余时间完成。代码中的每个函数都有明确的中文注释信息,对于 MQTT 实现的部分,更是细节到每个功能对应的文档的页数都进行了标明。此服务器既可以针对具体项目二次开发使用,也可以用于 MQTT 协议的学习。
19 |
20 | ## 功能
21 | 已实现:
22 | * 网络传输功能(使用 Netty5.0 实现)~~
23 | * 会话管理功能
24 | * 任务调度框架(使用Quartz框架为基础封装)
25 | * MQTT完整实现(推送,单聊,群聊)
26 |
27 |
28 | 未实现:
29 | * ~~安全层(消息加密、解密、防重放,防中间人等等)~~
30 | * ~~好友功能(添加好友,删除好友,好友列表等等)~~
31 | * ~~群组管理功能(添加群组,退出群组等等)~~
32 | * ~~语音~~
33 | * ~~视频~~
34 | * ~~总之就是可以拓展的应用层都没做~~
35 |
36 | ## 如何使用
37 | #### zer0MqttServer 的使用很简单:
38 | * 下载源码(源码中包括所有依赖包)
39 | * 检查 Java 版本是否为1.7或以上,不是则按照 Java 1.7及以上
40 | * 导入IDE
41 | * 引用依赖包
42 | * 运行包 com.syxy.server 下的 StartServer 文件,即可启动服务器。
43 |
44 | #### 测试方法也同样简单:
45 |
46 | 运行包 test 下的 MQTTClientTest 文件,即可开启测试客户端。
47 |
48 | 测试客户端包括的功能有:连接服务器、订阅主题、发送固定信息,客户端通过 pahoMqtt 第三方jar包编写,你可以自行修改代码进行更详细的测试
49 |
50 | #### 下面简述一下项目的目录结构:
51 |
52 | com.syxy.util 包中是一些公共类,包括缓冲池BufferPool、任务调度框架QuartzManager、字符串处理类StringTool、日期时间类TimeUtils等等
53 |
54 | com.syxy.server 是网络应用层,StartServer 用于启动服务器,并初始化协议相关的类。TcpServer用于处理配置文件中的系统常量,并启动服务器。
55 |
56 | com.syxy.protocol.mqttImp 定义了协议编码、解码、业务逻辑接口。并实现了具体的协议编码,解码,业务处理
57 |
58 | com.syxy.protocol.mqttImp.message 中包含了14种消息类型的实体类,并将每个消息类型划分成固定头部、可变头部、荷载三部分。
59 |
60 | com.syxy.protocol.mqttImp.process 中进行了协议的具体处理。最重要的是```ProtocolProcess.java```文件,其中完整实现了MQTT协议文件中的具体流程。
61 |
62 | resource 文件中包含了一些配置文件,其中 mqtt.properties 文件可以针对缓冲区大小、临时存储文件名、服务器端口等信息
63 |
64 | ## 参考帮助
65 |
66 | 1. [moquette开源项目](https://github.com/andsel/moquette)
67 |
68 | 2. [MQTT协议V3.1.1版本](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.pdf)
69 |
70 | 3. [MQTT协议V3.1版本](http://www.ibm.com/developerworks/webservices/library/ws-mqtt/ws-mqtt-pdf.pdf)
71 |
72 | 4. [Java AIO 基础](http://lxy2330.iteye.com/blog/1122849)
73 |
74 | 5. [聂永的博客](http://www.blogjava.net/yongboy/)
75 |
76 |
--------------------------------------------------------------------------------
/zer0MQTTServer/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | zer0MQTTServer
4 |
5 |
6 |
7 |
8 |
9 | org.eclipse.jdt.core.javabuilder
10 |
11 |
12 |
13 |
14 |
15 | org.eclipse.jdt.core.javanature
16 |
17 |
18 |
--------------------------------------------------------------------------------
/zer0MQTTServer/.settings/org.eclipse.jdt.core.prefs:
--------------------------------------------------------------------------------
1 | eclipse.preferences.version=1
2 | org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
3 | org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
4 | org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
5 | org.eclipse.jdt.core.compiler.compliance=1.7
6 | org.eclipse.jdt.core.compiler.debug.lineNumber=generate
7 | org.eclipse.jdt.core.compiler.debug.localVariable=generate
8 | org.eclipse.jdt.core.compiler.debug.sourceFile=generate
9 | org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
10 | org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
11 | org.eclipse.jdt.core.compiler.source=1.7
12 |
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/druid-1.0.9.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/druid-1.0.9.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/json.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/json.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/log4j-1.2.17.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/log4j-1.2.17.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/mapdb-1.0.8.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/mapdb-1.0.8.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/mysql-connector-java-5.0.4-bin.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/mysql-connector-java-5.0.4-bin.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/netty-all-5.0.0.Alpha2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/netty-all-5.0.0.Alpha2.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/pahoMqtt.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/pahoMqtt.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/quartz-2.2.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/quartz-2.2.2.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/quartz-jobs-2.2.2.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/quartz-jobs-2.2.2.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/slf4j-api-1.7.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/slf4j-api-1.7.7.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/lib/slf4j-log4j12-1.7.7.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zer0Black/zer0MQTTServer/210e8bfde2d1f337c170fef456707d964ec32f91/zer0MQTTServer/lib/slf4j-log4j12-1.7.7.jar
--------------------------------------------------------------------------------
/zer0MQTTServer/resource/druid.properties:
--------------------------------------------------------------------------------
1 | driverClassName = com.mysql.jdbc.Driver
2 | url = jdbc:mysql://10.0.3.12/zer0mqtt
3 | username = root
4 | password = root
5 |
6 | #初始化连接数量
7 | initialSize = 10
8 | #最大并发连接数
9 | maxActive = 200
10 | #最小空闲连接数
11 | minIdle = 5
12 | #配置获取连接等待超时的时间
13 | maxWait = 60000
14 | #超过时间限制是否回收
15 | removeAbandoned = true
16 | #超过时间限制多长
17 | removeAbandonedTimeout = 180
18 | #配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
19 | timeBetweenEvictionRunsMillis = 60000
20 | #配置一个连接在池中最小生存的时间,单位是毫秒
21 | minEvictableIdleTimeMillis = 300000
22 | #用来检测连接是否有效的sql,要求是一个查询语句
23 | validationQuery = SELECT 1 FROM DUAL
24 | #申请连接的时候检测
25 | testWhileIdle = true
26 | #申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
27 | testOnBorrow = false
28 | #归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
29 | testOnReturn = false
30 | #打开PSCache,并且指定每个连接上PSCache的大小
31 | poolPreparedStatements = false
32 | maxPoolPreparedStatementPerConnectionSize = 50
33 | #属性类型是字符串,通过别名的方式配置扩展插件,
34 | #常用的插件有:
35 | #日志用的filter:log4j
36 | #防御sql注入的filter:wall
37 | filter:wall
--------------------------------------------------------------------------------
/zer0MQTTServer/resource/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootLogger=debug,indicator
2 |
3 | log4j.appender.indicator=org.apache.log4j.ConsoleAppender
4 | log4j.appender.indicator.layout=org.apache.log4j.PatternLayout
5 | log4j.appender.indicator.layout.ConversionPattern=%d{HH:mm:ss} [%p] %l -%m%n
6 |
--------------------------------------------------------------------------------
/zer0MQTTServer/resource/mqtt.properties:
--------------------------------------------------------------------------------
1 | #mqtt服务器端口
2 | port=8088
3 | #socket发送缓存大小
4 | sockectSendBufferSize=64
5 | #socket接收缓存大小
6 | sockectReceiveBufferSize=5
7 | #缓冲区尺寸
8 | minBufferPoolSize=2000
9 | maxBufferPoolSize=2000
10 | #写缓冲区大小(Kb)
11 | writeBuffer=64
12 | #mqtt临时信息缓存文件
13 | path=moquette_store.mapdb
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/MQTTDecoder.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.handler.codec.DecoderException;
6 | import io.netty.handler.codec.ReplayingDecoder;
7 | import io.netty.util.CharsetUtil;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | import org.apache.log4j.Logger;
13 |
14 | import com.syxy.protocol.mqttImp.MQTTDecoder.DecoderState;
15 | import com.syxy.protocol.mqttImp.message.ConnAckMessage.ConnectionStatus;
16 | import com.syxy.protocol.mqttImp.message.ConnAckVariableHeader;
17 | import com.syxy.protocol.mqttImp.message.ConnectPayload;
18 | import com.syxy.protocol.mqttImp.message.ConnectVariableHeader;
19 | import com.syxy.protocol.mqttImp.message.FixedHeader;
20 | import com.syxy.protocol.mqttImp.message.Message;
21 | import com.syxy.protocol.mqttImp.message.MessageType;
22 | import com.syxy.protocol.mqttImp.message.PackageIdVariableHeader;
23 | import com.syxy.protocol.mqttImp.message.PublishVariableHeader;
24 | import com.syxy.protocol.mqttImp.message.QoS;
25 | import com.syxy.protocol.mqttImp.message.SubAckPayload;
26 | import com.syxy.protocol.mqttImp.message.SubscribePayload;
27 | import com.syxy.protocol.mqttImp.message.TopicSubscribe;
28 | import com.syxy.protocol.mqttImp.message.UnSubscribePayload;
29 |
30 | /**
31 | * MQTT协议解码
32 | * 修改(1)使用netty的ReplayingState来进行解码,该类使用状态机的方式防止代码的反复执行
33 | * @author zer0
34 | * @version 1.0
35 | * @date 2015-2-16
36 | * @date 2016-3-4(1)
37 | */
38 | public class MQTTDecoder extends ReplayingDecoder {
39 |
40 | private final static Logger Log = Logger.getLogger(MQTTDecoder.class);
41 |
42 | enum DecoderState{
43 | FIXED_HEADER,
44 | VARIABLE_HEADER,
45 | PAYLOAD,
46 | BAD_MESSAGE,
47 | }
48 |
49 | private FixedHeader fixedHeader;
50 | private Object variableHeader;
51 | private Object payload;
52 |
53 | public MQTTDecoder(){
54 | super(DecoderState.FIXED_HEADER);
55 | }
56 |
57 | @Override
58 | protected void decode(ChannelHandlerContext ctx, ByteBuf in,
59 | List out) throws Exception {
60 |
61 | int bytesRemainingInVariablePart = 0;
62 |
63 | switch (state()) {
64 | case FIXED_HEADER:
65 | fixedHeader = decodeFixedHeader(in);
66 | bytesRemainingInVariablePart = fixedHeader.getMessageLength();
67 | checkpoint(DecoderState.VARIABLE_HEADER);
68 |
69 | case VARIABLE_HEADER:
70 | final Result> variableHeaderResult = decodeVariableHeader(in, fixedHeader);
71 | variableHeader = variableHeaderResult.getValue();
72 | bytesRemainingInVariablePart -= variableHeaderResult.getUseNumOfBytes();
73 | checkpoint(DecoderState.PAYLOAD);
74 |
75 | case PAYLOAD:
76 | final Result> payloadResult = decodePayload(in, fixedHeader.getMessageType(),
77 | bytesRemainingInVariablePart, variableHeader);
78 | payload = payloadResult.getValue();
79 | bytesRemainingInVariablePart -= payloadResult.getUseNumOfBytes();
80 | if (bytesRemainingInVariablePart != 0) {
81 | throw new DecoderException("解码的字节数和剩余字节字段长度不匹配,最终剩余:" + bytesRemainingInVariablePart);
82 | }
83 | checkpoint(DecoderState.FIXED_HEADER);
84 | Message message = MQTTMesageFactory.newMessage(fixedHeader, variableHeader, payload);
85 | fixedHeader = null;
86 | variableHeader = null;
87 | payload = null;
88 | out.add(message);
89 | break;
90 | case BAD_MESSAGE:
91 | in.skipBytes(actualReadableBytes());
92 | break;
93 | default:
94 | throw new Error();
95 | }
96 | }
97 |
98 |
99 | /**
100 | * 解压缩固定头部,mqtt协议的所有消息类型,固定头部字段类型和长度都是一样的。
101 | * @param byteBuf
102 | * @return FixedHeader
103 | * @author zer0
104 | * @version 1.0
105 | * @date 2016-3-4
106 | */
107 | private FixedHeader decodeFixedHeader(ByteBuf byteBuf){
108 | //解码头部第一个字节
109 | byte headerData = byteBuf.readByte();
110 | MessageType type = MessageType.valueOf((headerData >> 4) & 0xF);
111 | Boolean dup = (headerData & 0x8) > 0;
112 | QoS qos = QoS.valueOf((headerData & 0x6) >> 1);
113 | Boolean retain = (headerData & 0x1) > 0;
114 |
115 | //解码头部第二个字节,余留长度
116 | int multiplier = 1;
117 | int remainLength = 0;
118 | byte digit = 0;
119 | int loop = 0;
120 | do {
121 | digit = byteBuf.readByte();
122 | remainLength += (digit & 0x7f) * multiplier;
123 | multiplier *= 128;
124 | loop++;
125 | }while ((digit & 0x80) != 0 && loop < 4);
126 |
127 | if (loop == 4 && (digit & 0x80) != 0) {
128 | throw new DecoderException("保留字段长度超过4个字节,与协议不符,消息类型:" + type);
129 | }
130 |
131 | FixedHeader fixedHeader = new FixedHeader(type, dup, qos, retain, remainLength);
132 |
133 | //返回时,针对所有协议进行头部校验
134 | return validateFixHeader(fixedHeader);
135 | }
136 |
137 | /**
138 | * 对所有消息类型进行头部校验,确保保留位的字段正确
139 | * @param fixedHeader
140 | * @return FixedHeader
141 | * @author zer0
142 | * @version 1.0
143 | * @date 2016-3-4
144 | */
145 | private FixedHeader validateFixHeader(FixedHeader fixedHeader){
146 | switch (fixedHeader.getMessageType()) {
147 | case PUBREL:
148 | case SUBSCRIBE:
149 | case UNSUBSCRIBE:
150 | if (fixedHeader.getQos() != QoS.AT_LEAST_ONCE) {
151 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Qos必须为1");
152 | }
153 |
154 | if (fixedHeader.isDup()) {
155 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Dup必须为0");
156 | }
157 |
158 | if (fixedHeader.isRetain()) {
159 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Retain必须为0");
160 | }
161 | break;
162 | case CONNECT:
163 | case CONNACK:
164 | case PUBACK:
165 | case PUBREC:
166 | case PUBCOMP:
167 | case SUBACK:
168 | case UNSUBACK:
169 | case PINGREQ:
170 | case PINGRESP:
171 | case DISCONNECT:
172 | if (fixedHeader.getQos() != QoS.AT_MOST_ONCE) {
173 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Qos必须为0");
174 | }
175 |
176 | if (fixedHeader.isDup()) {
177 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Dup必须为0");
178 | }
179 |
180 | if (fixedHeader.isRetain()) {
181 | throw new DecoderException(fixedHeader.getMessageType().name()+"的Retain必须为0");
182 | }
183 | break;
184 | default:
185 | return fixedHeader;
186 | }
187 | return fixedHeader;
188 | }
189 |
190 | /**
191 | * 解码可变头部,由于可变头部返回的值不确定,需要使用泛型来作为返回值
192 | * @param byteBuf
193 | * @return FixedHeader
194 | * @author zer0
195 | * @version 1.0
196 | * @date 2016-3-4
197 | */
198 | private Result> decodeVariableHeader(ByteBuf byteBuf, FixedHeader fixedHeader){
199 | switch (fixedHeader.getMessageType()) {
200 | case CONNECT:
201 | return decodeConnectVariableHeader(byteBuf);
202 | case CONNACK:
203 | return decodeConnAckVariableHeader(byteBuf);
204 | case PUBLISH:
205 | return decodePublishVariableHeader(byteBuf, fixedHeader);
206 | case SUBSCRIBE:
207 | case UNSUBSCRIBE:
208 | case SUBACK:
209 | case UNSUBACK:
210 | case PUBACK:
211 | case PUBREC:
212 | case PUBCOMP:
213 | case PUBREL:
214 | return decodePackageIdVariableHeader(byteBuf);
215 | case PINGREQ:
216 | case PINGRESP:
217 | case DISCONNECT:
218 | return new Result(null, 0);
219 | default:
220 | return new Result(null, 0);
221 | }
222 | }
223 |
224 | /**
225 | * 解码Connect可变头部
226 | * @param byteBuf
227 | * @return Result
228 | * @author zer0
229 | * @version 1.0
230 | * @date 2016-3-5
231 | */
232 | private Result decodeConnectVariableHeader(ByteBuf byteBuf){
233 | int useNumOfBytes = 0;//已解码字节
234 | //解码协议名
235 | Result protoResult = decodeUTF(byteBuf);
236 | String protoName = protoResult.getValue();
237 | useNumOfBytes += protoResult.getUseNumOfBytes();
238 | //解码协议级别
239 | byte protoLevel = byteBuf.readByte();
240 | useNumOfBytes += 1;//协议等级长度为1个字节
241 | //解码连接标志
242 | byte connectFlags = byteBuf.readByte();
243 | boolean isHasUserName = (connectFlags & 0x80) > 0;//0x80=10000000
244 | boolean isHasPassword = (connectFlags & 0x40) > 0;//0x40=01000000
245 | boolean willRetain = (connectFlags & 0x20) > 0;//0x20=00100000
246 | QoS willQos = QoS.valueOf(connectFlags >> 3 & 0x03);////0x03=00000011
247 | boolean hasWill = (connectFlags & 0x04) > 0;//0x04=00000100
248 | boolean cleanSession = (connectFlags & 0x02) > 0;//0x02=00000010
249 | boolean reservedIsZero = (connectFlags & 0x01) == 0;//0x00=0000001
250 | useNumOfBytes += 1;
251 | //解码心跳包时长
252 | int keepAlive = byteBuf.readUnsignedShort();
253 | useNumOfBytes += 2;
254 |
255 | ConnectVariableHeader connectVariableHeader = new ConnectVariableHeader(
256 | protoName, protoLevel, isHasUserName,
257 | isHasPassword, willRetain, willQos,
258 | hasWill, cleanSession, reservedIsZero, keepAlive);
259 | return new Result(connectVariableHeader, useNumOfBytes);
260 | }
261 |
262 | /**
263 | * 解码ConnAck可变头部
264 | * @param byteBuf
265 | * @return Result
266 | * @author zer0
267 | * @version 1.0
268 | * @date 2016-3-6
269 | */
270 | private Result decodeConnAckVariableHeader(ByteBuf byteBuf){
271 | int useNumOfBytes = 0;//已解码字节
272 |
273 | boolean sessionPresent = (byteBuf.readUnsignedByte() == 0)?false:true;
274 | useNumOfBytes += 1;
275 |
276 | byte returnCode = byteBuf.readByte();
277 | useNumOfBytes += 1;
278 |
279 | ConnAckVariableHeader connectVariableHeader = new ConnAckVariableHeader(ConnectionStatus.valueOf(returnCode), sessionPresent);
280 | return new Result(connectVariableHeader, useNumOfBytes);
281 | }
282 |
283 | /**
284 | * 解码Publish可变头部
285 | * @param byteBuf
286 | * @return Result
287 | * @author zer0
288 | * @version 1.0
289 | * @date 2016-3-6
290 | */
291 | private Result decodePublishVariableHeader(ByteBuf byteBuf, FixedHeader fixedHeader){
292 | int useNumOfBytes = 0;//已解码字节
293 | //解码协议名
294 | Result topicResult = decodeUTF(byteBuf);
295 | String topicName = topicResult.getValue();
296 | //publish消息中不能出现通配符,需要进行校验
297 | if (!valiadPublishTopicName(topicName)) {
298 | throw new DecoderException("无效的主题名:" + topicName + ",因为它包含了通配符");
299 | }
300 | useNumOfBytes += topicResult.getUseNumOfBytes();
301 | //解析包ID
302 | int packageID = -1;
303 | if (fixedHeader.getQos().value() > 0) {
304 | packageID = byteBuf.readUnsignedShort();
305 | //校验包ID
306 | if (packageID == 0) {
307 | throw new DecoderException("无效的包ID"+packageID);
308 | }
309 | useNumOfBytes += 2;
310 | }
311 | PublishVariableHeader publishVariableHeader = new PublishVariableHeader(topicName, packageID);
312 |
313 | return new Result(publishVariableHeader, useNumOfBytes);
314 | }
315 |
316 | private boolean valiadPublishTopicName(String topicName){
317 | final char[] TOPIC_WILADCARDS = {'#', '+'};
318 | for (char c : TOPIC_WILADCARDS) {
319 | if (topicName.indexOf(c) >= 0) {
320 | return false;
321 | }
322 | }
323 | return true;
324 | }
325 |
326 | /**
327 | * 解码只有包ID的可变头部
328 | * @param byteBuf
329 | * @return Result
330 | * @author zer0
331 | * @version 1.0
332 | * @date 2016-3-6
333 | */
334 | private Result decodePackageIdVariableHeader(ByteBuf byteBuf){
335 | int useNumOfBytes = 0;//已解码字节
336 |
337 | //解析包ID
338 | int packageID = byteBuf.readUnsignedShort();
339 | //校验包ID
340 | if (packageID == 0) {
341 | throw new DecoderException("无效的包ID"+packageID);
342 | }
343 | useNumOfBytes += 2;
344 |
345 | PackageIdVariableHeader packageIdVariableHeader = new PackageIdVariableHeader(packageID);
346 |
347 | return new Result(packageIdVariableHeader, useNumOfBytes);
348 | }
349 |
350 | /**
351 | * 解码荷载,有荷载的消息类型有connect,subscribe,suback,unsubscribe,publish
352 | * @param byteBuf
353 | * @return FixedHeader
354 | * @author zer0
355 | * @version 1.0
356 | * @date 2016-3-5
357 | */
358 | private Result> decodePayload(ByteBuf byteBuf,
359 | MessageType messageType,
360 | int bytesRemainVariablePart,
361 | Object variableHeader){
362 | switch (messageType) {
363 | case CONNECT:
364 | return decodeConnectPayload(byteBuf, (ConnectVariableHeader)variableHeader);
365 |
366 | case PUBLISH:
367 | return decodePublishPayload(byteBuf, bytesRemainVariablePart);
368 |
369 | case SUBSCRIBE:
370 | return decodeSubscribePayload(byteBuf, bytesRemainVariablePart);
371 |
372 | case SUBACK:
373 | return decodeSubAckPayload(byteBuf, bytesRemainVariablePart);
374 |
375 | case UNSUBSCRIBE:
376 | return decodeUnSubscribePayload(byteBuf, bytesRemainVariablePart);
377 |
378 | default:
379 | return new Result(null, 0);
380 | }
381 | }
382 |
383 | /**
384 | * 解码connect荷载
385 | * @param byteBuf
386 | * @return Result
387 | * @author zer0
388 | * @version 1.0
389 | * @date 2016-3-6
390 | */
391 | private Result decodeConnectPayload(ByteBuf byteBuf, ConnectVariableHeader variableHeader){
392 | int useNumOfBytes = 0;//已解码字节
393 |
394 | Result clientIDResult = decodeUTF(byteBuf);
395 | String clientID = clientIDResult.getValue();
396 | useNumOfBytes += clientIDResult.getUseNumOfBytes();
397 |
398 | Result willTopicResult = null;
399 | Result willMessageResult = null;
400 | if (variableHeader.isHasWill()) {
401 | willTopicResult = decodeUTF(byteBuf);
402 | useNumOfBytes += willTopicResult.getUseNumOfBytes();
403 | willMessageResult = decodeUTF(byteBuf);
404 | useNumOfBytes += willMessageResult.getUseNumOfBytes();
405 | }
406 |
407 | Result userNameResult = null;
408 | Result passwordResult = null;
409 | if (variableHeader.isHasUsername()) {
410 | userNameResult = decodeUTF(byteBuf);
411 | useNumOfBytes += userNameResult.getUseNumOfBytes();
412 | }
413 |
414 | if (variableHeader.isHasPassword()) {
415 | passwordResult = decodeUTF(byteBuf);
416 | useNumOfBytes += passwordResult.getUseNumOfBytes();
417 | }
418 |
419 | ConnectPayload connectPayload = new ConnectPayload(
420 | clientID,
421 | willTopicResult != null?willTopicResult.getValue():null,
422 | willMessageResult != null?willMessageResult.getValue():null,
423 | userNameResult != null?userNameResult.getValue():null,
424 | passwordResult != null?passwordResult.getValue():null);
425 |
426 | return new Result(connectPayload, useNumOfBytes);
427 | }
428 |
429 | /**
430 | * 解码publish荷载
431 | * @param byteBuf
432 | * @return Result
433 | * @author zer0
434 | * @version 1.0
435 | * @date 2016-3-6
436 | */
437 | private Result decodePublishPayload(ByteBuf byteBuf, int bytesRemainVariablePart){
438 | int useNumOfBytes = bytesRemainVariablePart;//已解码字节
439 |
440 | ByteBuf b = byteBuf.readSlice(bytesRemainVariablePart).retain();
441 | return new Result(b, useNumOfBytes);
442 | }
443 |
444 | /**
445 | * 解码subscribe荷载
446 | * @param byteBuf
447 | * @return Result
448 | * @author zer0
449 | * @version 1.0
450 | * @date 2016-3-6
451 | */
452 | private Result decodeSubscribePayload(ByteBuf byteBuf, int bytesRemainVariablePart){
453 | int useNumOfBytes = 0;//已解码字节
454 |
455 | final List subscribeTopics = new ArrayList();
456 | while (useNumOfBytes < bytesRemainVariablePart) {
457 | final Result topicNameResult = decodeUTF(byteBuf);
458 | useNumOfBytes += topicNameResult.getUseNumOfBytes();
459 | int qos = byteBuf.readUnsignedByte() & 0x03;
460 | useNumOfBytes++;
461 | subscribeTopics.add(new TopicSubscribe(topicNameResult.value, QoS.valueOf(qos)));
462 | }
463 | return new Result(new SubscribePayload(subscribeTopics), useNumOfBytes);
464 | }
465 |
466 | /**
467 | * 解码suback荷载
468 | * @param byteBuf
469 | * @return Result
470 | * @author zer0
471 | * @version 1.0
472 | * @date 2016-3-6
473 | */
474 | private Result decodeSubAckPayload(ByteBuf byteBuf, int bytesRemainVariablePart){
475 | int useNumOfBytes = 0;//已解码字节
476 |
477 | final List grantedQos = new ArrayList();
478 | while (useNumOfBytes < bytesRemainVariablePart) {
479 | int qos = byteBuf.readUnsignedByte() & 0x03;
480 | useNumOfBytes++;
481 | grantedQos.add(qos);
482 | }
483 | return new Result(new SubAckPayload(grantedQos), useNumOfBytes);
484 | }
485 |
486 | /**
487 | * 解码UnSubscribe荷载
488 | * @param byteBuf
489 | * @return Result
490 | * @author zer0
491 | * @version 1.0
492 | * @date 2016-3-6
493 | */
494 | private Result decodeUnSubscribePayload(ByteBuf byteBuf, int bytesRemainVariablePart){
495 | int useNumOfBytes = 0;//已解码字节
496 |
497 | final List unsubscribeTopics = new ArrayList();
498 | while (useNumOfBytes < bytesRemainVariablePart) {
499 | final Result topicNameResult = decodeUTF(byteBuf);
500 | useNumOfBytes += topicNameResult.getUseNumOfBytes();
501 | unsubscribeTopics.add(topicNameResult.value);
502 | }
503 | return new Result(
504 | new UnSubscribePayload(unsubscribeTopics),
505 | useNumOfBytes);
506 | }
507 |
508 | /**
509 | * 解码UTF字符串类型,首先读取两个字节的长度,再根据此长度读取后面的数据
510 | * @param byteBuf
511 | * @return Result
512 | * @author zer0
513 | * @version 1.0
514 | * @date 2016-3-6
515 | */
516 | private Result decodeUTF(ByteBuf byteBuf){
517 | final int MAX_LENGTH = 65535;
518 | final int MIN_LENGTH = 0;
519 |
520 | //读取两个字节的无符号short类型,即UTF的长度
521 | int utfLength = byteBuf.readUnsignedShort();
522 | if (utfLength < MIN_LENGTH || utfLength > MAX_LENGTH) {
523 | throw new DecoderException("该UTF字符串长度有误,长度"+utfLength);
524 | }
525 | //根据长度解码出String
526 | String utfStr = byteBuf.readBytes(utfLength).toString(CharsetUtil.UTF_8);
527 | //计算已解码掉的长度,为字符串长度加最前面两个长度字段字节的长度
528 | int useNumOfBytes = utfLength + 2;
529 | return new Result(utfStr, useNumOfBytes);
530 | }
531 |
532 |
533 | private static class Result{
534 | private final T value;
535 | private final int useNumOfBytes;//解码出的内容的长度
536 |
537 | Result(T value, int useNumOfBytes) {
538 | this.value = value;
539 | this.useNumOfBytes = useNumOfBytes;
540 | }
541 |
542 | private T getValue() {
543 | return value;
544 | }
545 |
546 | private int getUseNumOfBytes() {
547 | return useNumOfBytes;
548 | }
549 |
550 | }
551 |
552 | }
553 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/MQTTEncoder.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.ByteBufAllocator;
5 | import io.netty.buffer.Unpooled;
6 | import io.netty.channel.ChannelHandlerContext;
7 | import io.netty.handler.codec.CorruptedFrameException;
8 | import io.netty.handler.codec.MessageToByteEncoder;
9 | import io.netty.util.CharsetUtil;
10 |
11 | import java.util.List;
12 |
13 | import org.apache.log4j.Logger;
14 |
15 | import com.syxy.protocol.mqttImp.message.ConnAckMessage;
16 | import com.syxy.protocol.mqttImp.message.ConnectMessage;
17 | import com.syxy.protocol.mqttImp.message.ConnectPayload;
18 | import com.syxy.protocol.mqttImp.message.ConnectVariableHeader;
19 | import com.syxy.protocol.mqttImp.message.FixedHeader;
20 | import com.syxy.protocol.mqttImp.message.Message;
21 | import com.syxy.protocol.mqttImp.message.PackageIdVariableHeader;
22 | import com.syxy.protocol.mqttImp.message.PublishMessage;
23 | import com.syxy.protocol.mqttImp.message.PublishVariableHeader;
24 | import com.syxy.protocol.mqttImp.message.SubAckMessage;
25 | import com.syxy.protocol.mqttImp.message.SubAckPayload;
26 | import com.syxy.protocol.mqttImp.message.SubscribeMessage;
27 | import com.syxy.protocol.mqttImp.message.SubscribePayload;
28 | import com.syxy.protocol.mqttImp.message.TopicSubscribe;
29 | import com.syxy.protocol.mqttImp.message.UnSubscribeMessage;
30 | import com.syxy.protocol.mqttImp.message.UnSubscribePayload;
31 |
32 | /**
33 | * MQTT协议编码
34 | * 修改(1)
35 | *
36 | * @author zer0
37 | * @version 1.0
38 | * @date 2015-2-16
39 | * @date 2016-3-3(1)
40 | */
41 | public class MQTTEncoder extends MessageToByteEncoder {
42 |
43 | private final static Logger Log = Logger.getLogger(MQTTEncoder.class);
44 | public static final int MAX_LENGTH_LIMIT = 268435455;
45 | private final byte[] EMPTY = new byte[0];
46 | private final int UTF8_FIX_LENGTH = 2;//UTF编码的byte,最开始必须为2字节的长度字段
47 |
48 | @Override
49 | protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out)
50 | throws Exception {
51 | ByteBufAllocator byteBufAllocator = ctx.alloc();
52 | ByteBuf encodedByteBuf;
53 |
54 | switch (msg.getFixedHeader().getMessageType()) {
55 | case CONNECT:
56 | encodedByteBuf = encodeConnectMessage(byteBufAllocator, (ConnectMessage)msg);
57 | break;
58 | case CONNACK:
59 | encodedByteBuf = encodeConnAckMessage(byteBufAllocator, (ConnAckMessage)msg);
60 | break;
61 | case PUBLISH:
62 | encodedByteBuf = encodePublishMessage(byteBufAllocator, (PublishMessage)msg);
63 | break;
64 | case SUBSCRIBE:
65 | encodedByteBuf = encodeSubscribeMessage(byteBufAllocator, (SubscribeMessage)msg);
66 | break;
67 | case UNSUBSCRIBE:
68 | encodedByteBuf = encodeUnSubcribeMessage(byteBufAllocator, (UnSubscribeMessage)msg);
69 | break;
70 | case SUBACK:
71 | encodedByteBuf = encodeSubAckMessage(byteBufAllocator, (SubAckMessage)msg);
72 | break;
73 | case UNSUBACK:
74 | case PUBACK:
75 | case PUBREC:
76 | case PUBREL:
77 | case PUBCOMP:
78 | encodedByteBuf = encodeMessageByteFixedHeaderAndPackageId(byteBufAllocator, msg);
79 | break;
80 | case PINGREQ:
81 | case PINGRESP:
82 | case DISCONNECT:
83 | encodedByteBuf = encodeMessageByteFixedHeader(byteBufAllocator, msg);
84 | break;
85 | default:
86 | throw new IllegalArgumentException(
87 | "未知的MQTT协议类型:"+msg.getFixedHeader().getMessageType().value());
88 | }
89 | out.writeBytes(encodedByteBuf);
90 | }
91 |
92 | private ByteBuf encodeConnectMessage(ByteBufAllocator bufAllocator,
93 | ConnectMessage message){
94 | //把消息每个字段从POJO中取出,并计算其大小,写入byteBuf
95 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
96 | int variableHeaderSize = 10;//根据协议3.1.1,connect可变头固定大小为10
97 | int payloadSize = 0;//荷载大小
98 |
99 | FixedHeader fixedHeader = message.getFixedHeader();
100 | ConnectVariableHeader connectVariableHeader = message.getVariableHeader();
101 | ConnectPayload connectPayload = message.getPayload();
102 |
103 | //取出可变头部所有信息
104 | String mqttName = connectVariableHeader.getProtocolName();
105 | byte[] mqttNameBytes = encodeStringUTF8(mqttName);
106 | int mqttVersion = connectVariableHeader.getProtocolVersionNumber();
107 | int connectflags = connectVariableHeader.isCleanSession() ? 0x02 : 0;
108 | connectflags |= connectVariableHeader.isHasWill() ? 0x04 : 0;
109 | connectflags |= connectVariableHeader.getWillQoS() == null ? 0 : connectVariableHeader.getWillQoS().val << 3;
110 | connectflags |= connectVariableHeader.isWillRetain() ? 0x20 : 0;
111 | connectflags |= connectVariableHeader.isHasPassword() ? 0x40 : 0;
112 | connectflags |= connectVariableHeader.isHasUsername() ? 0x80 : 0;
113 | int keepAlive = connectVariableHeader.getKeepAlive();
114 |
115 | //取出荷载信息并计算荷载的大小
116 | String clientID = connectPayload.getClientId();
117 | byte[] clientIDBytes = encodeStringUTF8(clientID);
118 | payloadSize += clientIDBytes.length;
119 |
120 | String willTopic = connectPayload.getWillTopic();
121 | byte[] willTopicBytes = willTopic!=null?encodeStringUTF8(willTopic):EMPTY;
122 | String willMessage = connectPayload.getWillMessage();
123 | byte[] willMEssageBytes = willMessage!=null?encodeStringUTF8(willMessage):EMPTY;
124 | if (connectVariableHeader.isHasWill()) {
125 | payloadSize += UTF8_FIX_LENGTH;
126 | payloadSize += willTopicBytes.length;
127 | payloadSize += UTF8_FIX_LENGTH;
128 | payloadSize += willMEssageBytes.length;
129 | }
130 |
131 | String username = connectPayload.getUsername();
132 | byte[] usernameBytes = username!=null?encodeStringUTF8(username):EMPTY;
133 | if (connectVariableHeader.isHasUsername()) {
134 | payloadSize += UTF8_FIX_LENGTH;
135 | payloadSize += usernameBytes.length;
136 | }
137 |
138 | String password = connectPayload.getPassword();
139 | byte[] passwordBytes = password!=null?encodeStringUTF8(password):EMPTY;
140 | if (connectVariableHeader.isHasPassword()) {
141 | payloadSize += UTF8_FIX_LENGTH;
142 | payloadSize += passwordBytes.length;
143 | }
144 |
145 | //计算固定头部长度,长度为可变头部长度+荷载长度编码的长度
146 | fixHeaderSize += countVariableLengthInt(variableHeaderSize+payloadSize);
147 | //根据所有字段长度生成bytebuf
148 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize + payloadSize);
149 |
150 | //写入byteBuf
151 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
152 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize + payloadSize));//写固定头部第二个字节,剩余部分长度
153 |
154 | byteBuf.writeShort(mqttNameBytes.length);//写入协议名长度
155 | byteBuf.writeBytes(mqttNameBytes);//写入协议名
156 | byteBuf.writeByte(mqttVersion);//写入协议版本号
157 | byteBuf.writeByte(connectflags);//写入连接标志
158 | byteBuf.writeByte(keepAlive);//写入心跳包时长
159 |
160 | byteBuf.writeShort(clientIDBytes.length);//写入客户端ID长度
161 | byteBuf.writeBytes(clientIDBytes);//写入客户端ID
162 | if (connectVariableHeader.isHasWill()) {
163 | byteBuf.writeShort(willTopicBytes.length);//写入遗嘱主题长度
164 | byteBuf.writeBytes(willTopicBytes);//写入遗嘱主题
165 | byteBuf.writeShort(willMEssageBytes.length);//写入遗嘱正文长度
166 | byteBuf.writeBytes(willMEssageBytes);//写入遗嘱正文
167 | }
168 | if (connectVariableHeader.isHasUsername()) {
169 | byteBuf.writeShort(usernameBytes.length);
170 | byteBuf.writeBytes(usernameBytes);
171 | }
172 | if (connectVariableHeader.isHasPassword()) {
173 | byteBuf.writeShort(passwordBytes.length);
174 | byteBuf.writeBytes(passwordBytes);
175 | }
176 |
177 | return byteBuf;
178 | }
179 |
180 | private ByteBuf encodeConnAckMessage(ByteBufAllocator bufAllocator,
181 | ConnAckMessage msg){
182 | //由协议3.1.1 P28可知,ConnAck消息长度固定为4字节
183 | ByteBuf byteBuf = bufAllocator.buffer(4);
184 | byteBuf.writeBytes(encodeFixHeader(msg.getFixedHeader()));//写固定头部第一个字节
185 | byteBuf.writeByte(2);//写入可变头部长度,固定为2字节
186 | byteBuf.writeByte(msg.getVariableHeader().isSessionPresent()?0x01:0x00);//写入连接确认标志
187 | byteBuf.writeByte(msg.getVariableHeader().getStatus().value());//写入返回码
188 | return byteBuf;
189 | }
190 |
191 | private ByteBuf encodePublishMessage(ByteBufAllocator bufAllocator,
192 | PublishMessage message){
193 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
194 | int variableHeaderSize = 0;
195 | int payloadSize = 0;//荷载大小
196 |
197 | FixedHeader fixedHeader = message.getFixedHeader();
198 | PublishVariableHeader variableHeader = message.getVariableHeader();
199 | ByteBuf payload = message.getPayload().duplicate();
200 |
201 | String topicName = variableHeader.getTopic();
202 | byte[] topicNameBytes = encodeStringUTF8(topicName);
203 |
204 | variableHeaderSize += UTF8_FIX_LENGTH;
205 | variableHeaderSize += topicNameBytes.length;
206 | variableHeaderSize += fixedHeader.getQos().value()>0?2:0;//根据qos判断packageID的长度是否需要加上
207 | payloadSize = payload.readableBytes();
208 | fixHeaderSize += countVariableLengthInt(variableHeaderSize+payloadSize);
209 |
210 | //生成bytebuf
211 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize + payloadSize);
212 | //写入byteBuf
213 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
214 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize + payloadSize));//写固定头部第二个字节,剩余部分长度
215 | byteBuf.writeShort(topicNameBytes.length);
216 | byteBuf.writeBytes(topicNameBytes);
217 | if (fixedHeader.getQos().value()>0) {
218 | byteBuf.writeShort(variableHeader.getPackageID());
219 | }
220 | byteBuf.writeBytes(payload);//写入荷载
221 |
222 | return byteBuf;
223 | }
224 |
225 | private ByteBuf encodeSubscribeMessage(ByteBufAllocator bufAllocator,
226 | SubscribeMessage message){
227 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
228 | int variableHeaderSize = 2;//协议P37页,订阅类型的可变头部长度都为2
229 | int payloadSize = 0;//荷载大小
230 |
231 | FixedHeader fixedHeader = message.getFixedHeader();
232 | PackageIdVariableHeader variableHeader = message.getVariableHeader();
233 | SubscribePayload payload = message.getPayload();
234 |
235 | //遍历订阅消息组,计算荷载长度
236 | for (TopicSubscribe topic : payload.getTopicSubscribes()) {
237 | String topicName = topic.getTopicFilter();
238 | byte[] topicNameBytes = encodeStringUTF8(topicName);
239 | payloadSize += UTF8_FIX_LENGTH;
240 | payloadSize += topicNameBytes.length;
241 | payloadSize += 1;//添加qos的长度,qos长度只能为1
242 | }
243 |
244 | fixHeaderSize += countVariableLengthInt(variableHeaderSize+payloadSize);
245 |
246 | //生成bytebuf
247 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize + payloadSize);
248 | //写入byteBuf
249 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
250 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize + payloadSize));//写固定头部第二个字节,剩余部分长度
251 | byteBuf.writeShort(variableHeader.getPackageID());//写入可变头部中的包ID
252 | //写入荷载
253 | for (TopicSubscribe topic : payload.getTopicSubscribes()) {
254 | String topicName = topic.getTopicFilter();
255 | byte[] topicNameBytes = encodeStringUTF8(topicName);
256 | byteBuf.writeShort(topicNameBytes.length);
257 | byteBuf.writeBytes(topicNameBytes);
258 | byteBuf.writeByte(topic.getQos().value());
259 | }
260 |
261 | return byteBuf;
262 | }
263 |
264 | private ByteBuf encodeUnSubcribeMessage(ByteBufAllocator bufAllocator,
265 | UnSubscribeMessage message){
266 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
267 | int variableHeaderSize = 2;//协议P42页,取消订阅类型的可变头部长度固定为2
268 | int payloadSize = 0;//荷载大小
269 |
270 | FixedHeader fixedHeader = message.getFixedHeader();
271 | PackageIdVariableHeader variableHeader = message.getVariableHeader();
272 | UnSubscribePayload payload = message.getPayload();
273 |
274 | for (String topic : payload.getTopics()) {
275 | byte[] topicBytes = encodeStringUTF8(topic);
276 | payloadSize += UTF8_FIX_LENGTH;
277 | payloadSize += topicBytes.length;
278 | }
279 |
280 | fixHeaderSize += countVariableLengthInt(variableHeaderSize+payloadSize);
281 |
282 | //生成bytebuf
283 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize + payloadSize);
284 | //写入byteBuf
285 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
286 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize + payloadSize));//写固定头部第二个字节,剩余部分长度
287 | byteBuf.writeShort(variableHeader.getPackageID());//写入可变头部中的包ID
288 | //写入荷载
289 | for (String topic : payload.getTopics()) {
290 | byte[] topicBytes = encodeStringUTF8(topic);
291 | byteBuf.writeShort(topicBytes.length);
292 | byteBuf.writeBytes(topicBytes);
293 | }
294 |
295 | return byteBuf;
296 | }
297 |
298 | private ByteBuf encodeSubAckMessage(ByteBufAllocator bufAllocator,
299 | SubAckMessage message){
300 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
301 | int variableHeaderSize = 2;//协议P42页,取消订阅类型的可变头部长度固定为2
302 | int payloadSize = 0;//荷载大小
303 |
304 | FixedHeader fixedHeader = message.getFixedHeader();
305 | PackageIdVariableHeader variableHeader = message.getVariableHeader();
306 | SubAckPayload payload = message.getPayload();
307 |
308 | List grantedQosLevels = payload.getGrantedQosLevel();
309 | payloadSize += grantedQosLevels.size();
310 |
311 | fixHeaderSize += countVariableLengthInt(variableHeaderSize+payloadSize);
312 |
313 | //生成bytebuf
314 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize + payloadSize);
315 | //写入byteBuf
316 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
317 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize + payloadSize));//写固定头部第二个字节,剩余部分长度
318 | byteBuf.writeShort(variableHeader.getPackageID());//写入可变头部中的包ID
319 | for (Integer qos : grantedQosLevels) {
320 | byteBuf.writeByte(qos);
321 | }
322 |
323 | return byteBuf;
324 | }
325 |
326 | private ByteBuf encodeMessageByteFixedHeaderAndPackageId(ByteBufAllocator bufAllocator,
327 | Message message){
328 |
329 | int fixHeaderSize = 1;//固定头部有1字节+可变部分长度字节
330 | int variableHeaderSize = 2;//只包含包ID的可变头部,长度固定为2
331 |
332 | FixedHeader fixedHeader = message.getFixedHeader();
333 | PackageIdVariableHeader variableHeader = (PackageIdVariableHeader)message.getVariableHeader();
334 |
335 | fixHeaderSize += countVariableLengthInt(variableHeaderSize);
336 |
337 | //生成bytebuf
338 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize + variableHeaderSize);
339 | //写入byteBuf
340 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));//写固定头部第一个字节
341 | byteBuf.writeBytes(encodeRemainLength(variableHeaderSize));//写固定头部第二个字节,剩余部分长度
342 | byteBuf.writeShort(variableHeader.getPackageID());//写入可变头部中的包ID
343 |
344 | return byteBuf;
345 | }
346 |
347 | private ByteBuf encodeMessageByteFixedHeader(ByteBufAllocator bufAllocator,
348 | Message message){
349 |
350 | int fixHeaderSize = 2;//固定头部加上一个字节的剩余长度(剩余长度为0)
351 | FixedHeader fixedHeader = message.getFixedHeader();
352 |
353 | ByteBuf byteBuf = bufAllocator.buffer(fixHeaderSize);
354 | byteBuf.writeBytes(encodeFixHeader(fixedHeader));
355 | byteBuf.writeByte(0);//写入剩余长度,没有可变头部和荷载,所以剩余长度为0
356 |
357 | return null;
358 | }
359 |
360 | /**
361 | * 编码固定头部第一个字节
362 | * @param fixedHeader
363 | * @return byte[]
364 | * @author zer0
365 | * @version 1.0
366 | * @date 2016-3-4
367 | */
368 | private byte[] encodeFixHeader(FixedHeader fixedHeader){
369 | byte b = 0;
370 | b = (byte) (fixedHeader.getMessageType().value() << 4);
371 | b |= fixedHeader.isDup() ? 0x8 : 0x0;
372 | b |= fixedHeader.getQos().value() << 1;
373 | b |= fixedHeader.isRetain() ? 0x1 : 0;
374 |
375 | byte[] bArray = new byte[]{b};
376 | return bArray;
377 | }
378 |
379 | /**
380 | * 把消息长度信息编码成字节
381 | * @param length
382 | * @return ByteBuf
383 | * @author zer0
384 | * @version 1.0
385 | * @date 2016-3-4
386 | */
387 | private ByteBuf encodeRemainLength(int length){
388 | if (length > MAX_LENGTH_LIMIT || length < 0) {
389 | throw new CorruptedFrameException(
390 | "消息长度不能超过‘消息最大长度’:"+MAX_LENGTH_LIMIT+",当前长度:"+length);
391 | }
392 | //剩余长度字段最多可编码4字节
393 | ByteBuf encoded = Unpooled.buffer(4);
394 | byte digit;
395 | do{
396 | digit = (byte)(length%128);
397 | length = length/128;
398 | if (length > 0) {
399 | digit = (byte)(digit | 0x80);
400 | }
401 | encoded.writeByte(digit);
402 | }while(length>0);
403 | return encoded;
404 | }
405 |
406 | /**
407 | * 计算固定头部中长度编码占用的字节
408 | * 协议3.1.1 P16对长度有说明,长度/128即可得到需要使用的字节数,一直除到0
409 | * @param length
410 | * @return int
411 | * @author zer0
412 | * @version 1.0
413 | * @date 2016-3-4
414 | */
415 | private int countVariableLengthInt(int length){
416 | int count = 0;
417 | do{
418 | length /= 128;
419 | count++;
420 | }while(length > 0);
421 | return count;
422 | }
423 |
424 | /**
425 | * 将String类型编码为byte[]
426 | * @param length
427 | * @return int
428 | * @author zer0
429 | * @version 1.0
430 | * @date 2016-3-4
431 | */
432 | private byte[] encodeStringUTF8(String str){
433 | return str.getBytes(CharsetUtil.UTF_8);
434 | }
435 |
436 | }
437 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/MQTTMesageFactory.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp;
2 |
3 | import com.syxy.protocol.mqttImp.message.ConnAckMessage;
4 | import com.syxy.protocol.mqttImp.message.ConnAckVariableHeader;
5 | import com.syxy.protocol.mqttImp.message.ConnectMessage;
6 | import com.syxy.protocol.mqttImp.message.ConnectPayload;
7 | import com.syxy.protocol.mqttImp.message.ConnectVariableHeader;
8 | import com.syxy.protocol.mqttImp.message.FixedHeader;
9 | import com.syxy.protocol.mqttImp.message.Message;
10 | import com.syxy.protocol.mqttImp.message.PackageIdVariableHeader;
11 | import com.syxy.protocol.mqttImp.message.PublishMessage;
12 | import com.syxy.protocol.mqttImp.message.PublishVariableHeader;
13 | import com.syxy.protocol.mqttImp.message.SubAckMessage;
14 | import com.syxy.protocol.mqttImp.message.SubAckPayload;
15 | import com.syxy.protocol.mqttImp.message.SubscribeMessage;
16 | import com.syxy.protocol.mqttImp.message.SubscribePayload;
17 | import com.syxy.protocol.mqttImp.message.UnSubscribeMessage;
18 | import com.syxy.protocol.mqttImp.message.UnSubscribePayload;
19 |
20 | import io.netty.buffer.ByteBuf;
21 |
22 | public final class MQTTMesageFactory {
23 |
24 | public static Message newMessage(FixedHeader fixedHeader, Object variableHeader, Object payload) {
25 | switch (fixedHeader.getMessageType()) {
26 | case CONNECT :
27 | return new ConnectMessage(fixedHeader,
28 | (ConnectVariableHeader)variableHeader,
29 | (ConnectPayload)payload);
30 |
31 | case CONNACK:
32 | return new ConnAckMessage(fixedHeader, (ConnAckVariableHeader) variableHeader);
33 |
34 | case SUBSCRIBE:
35 | return new SubscribeMessage(
36 | fixedHeader,
37 | (PackageIdVariableHeader) variableHeader,
38 | (SubscribePayload) payload);
39 |
40 | case SUBACK:
41 | return new SubAckMessage(
42 | fixedHeader,
43 | (PackageIdVariableHeader) variableHeader,
44 | (SubAckPayload) payload);
45 |
46 | case UNSUBSCRIBE:
47 | return new UnSubscribeMessage(
48 | fixedHeader,
49 | (PackageIdVariableHeader) variableHeader,
50 | (UnSubscribePayload) payload);
51 |
52 | case PUBLISH:
53 | return new PublishMessage(
54 | fixedHeader,
55 | (PublishVariableHeader) variableHeader,
56 | (ByteBuf) payload);
57 |
58 | case PUBACK:
59 | case UNSUBACK:
60 | case PUBREC:
61 | case PUBREL:
62 | case PUBCOMP:
63 | return new Message(fixedHeader, variableHeader);
64 |
65 | case PINGREQ:
66 | case PINGRESP:
67 | case DISCONNECT:
68 | return new Message(fixedHeader);
69 |
70 | default:
71 | throw new IllegalArgumentException("unknown message type: " + fixedHeader.getMessageType());
72 | }
73 | }
74 |
75 | private MQTTMesageFactory() { }
76 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/MQTTProcess.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp;
2 |
3 | import io.netty.channel.ChannelHandlerAdapter;
4 | import io.netty.channel.ChannelHandlerContext;
5 | import io.netty.handler.timeout.IdleState;
6 | import io.netty.handler.timeout.IdleStateEvent;
7 | import io.netty.handler.timeout.IdleStateHandler;
8 |
9 | import com.syxy.protocol.mqttImp.message.ConnectMessage;
10 | import com.syxy.protocol.mqttImp.message.Message;
11 | import com.syxy.protocol.mqttImp.message.PackageIdVariableHeader;
12 | import com.syxy.protocol.mqttImp.message.PublishMessage;
13 | import com.syxy.protocol.mqttImp.message.SubscribeMessage;
14 | import com.syxy.protocol.mqttImp.message.UnSubscribeMessage;
15 | import com.syxy.protocol.mqttImp.process.ProtocolProcess;
16 |
17 | /**
18 | * MQTT协议业务处理
19 | *
20 | * @author zer0
21 | * @version 1.0
22 | * @date 2015-2-16
23 | */
24 | public class MQTTProcess extends ChannelHandlerAdapter {
25 |
26 | @Override
27 | public void channelRead(ChannelHandlerContext ctx, Object msg)
28 | throws Exception {
29 | ProtocolProcess process = ProtocolProcess.getInstance();
30 | Message message = (Message)msg;
31 |
32 | switch (message.getFixedHeader().getMessageType()) {
33 | case CONNECT:
34 | process.processConnect(ctx.channel(), (ConnectMessage)message);
35 | break;
36 | case CONNACK:
37 | break;
38 | case PUBLISH:
39 | process.processPublic(ctx.channel(), (PublishMessage)message);
40 | break;
41 | case PUBACK:
42 | process.processPubAck(ctx.channel(), (PackageIdVariableHeader)message.getVariableHeader());
43 | break;
44 | case PUBREL:
45 | process.processPubRel(ctx.channel(), (PackageIdVariableHeader)message.getVariableHeader());
46 | break;
47 | case PUBREC:
48 | process.processPubRec(ctx.channel(), (PackageIdVariableHeader)message.getVariableHeader());
49 | break;
50 | case PUBCOMP:
51 | process.processPubComp(ctx.channel(), (PackageIdVariableHeader)message.getVariableHeader());
52 | break;
53 | case SUBSCRIBE:
54 | process.processSubscribe(ctx.channel(), (SubscribeMessage)message);
55 | break;
56 | case SUBACK:
57 | break;
58 | case UNSUBSCRIBE:
59 | process.processUnSubscribe(ctx.channel(), (UnSubscribeMessage)message);
60 | break;
61 | case UNSUBACK:
62 | break;
63 | case PINGREQ:
64 | process.processPingReq(ctx.channel(), message);
65 | break;
66 | case DISCONNECT:
67 | process.processDisconnet(ctx.channel(), message);
68 | break;
69 |
70 | default:
71 | break;
72 | }
73 | }
74 |
75 | /**
76 | * 事件追踪,处理超时事件,一旦检测到读超时,就断开链接
77 | * @param ctx
78 | * @param evt
79 | * @author zer0
80 | * @version 1.0
81 | * @date 2016-3-7
82 | */
83 | @Override
84 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
85 | throws Exception {
86 | if (evt instanceof IdleStateEvent) {
87 | IdleStateEvent e = (IdleStateEvent)evt;
88 | if (e.state() == IdleState.READER_IDLE) {
89 | ctx.close();
90 | }else {
91 | //写超时不处理
92 | }
93 | }
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/ConnAckMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.nio.ByteBuffer;
7 |
8 | /**
9 | * MQTT协议ConnAck消息类型实现类,连接确认消息类型
10 | *
11 | * @author zer0
12 | * @version 1.0
13 | * @date 2015-3-2
14 | */
15 | public class ConnAckMessage extends Message {
16 |
17 | public enum ConnectionStatus {
18 | ACCEPTED(0x00),
19 |
20 | UNACCEPTABLE_PROTOCOL_VERSION(0x01),
21 |
22 | IDENTIFIER_REJECTED(0x02),
23 |
24 | SERVER_UNAVAILABLE(0x03),
25 |
26 | BAD_USERNAME_OR_PASSWORD(0x04),
27 |
28 | NOT_AUTHORIZED(0x05);
29 |
30 | private final int value;
31 |
32 | ConnectionStatus(int value) {
33 | this.value = value;
34 | }
35 |
36 | /**
37 | * 获取类型对应的值
38 | * @return int
39 | * @author zer0
40 | * @version 1.0
41 | * @date 2016-3-4
42 | */
43 | public int value() {
44 | return value;
45 | }
46 |
47 | //通过读取到的整型来获取对应的QoS类型
48 | public static ConnectionStatus valueOf(byte i) {
49 | for(ConnectionStatus q: ConnectionStatus.values()) {
50 | if (q.value == i)
51 | return q;
52 | }
53 | throw new IllegalArgumentException("连接响应值无效: " + i);
54 | }
55 | }
56 |
57 |
58 | public ConnAckMessage(FixedHeader fixedHeader, ConnAckVariableHeader variableHeader) {
59 | super(fixedHeader, variableHeader);
60 | }
61 |
62 | @Override
63 | public ConnAckVariableHeader getVariableHeader(){
64 | return (ConnAckVariableHeader)super.getVariableHeader();
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/ConnAckVariableHeader.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import com.syxy.protocol.mqttImp.message.ConnAckMessage.ConnectionStatus;
4 |
5 | /**
6 | * MQTT协议ConnAck消息类型的可变头部
7 | * @author zer0
8 | * @version 1.0
9 | * @date 2016-3-4
10 | */
11 | public class ConnAckVariableHeader{
12 | private ConnectionStatus status;//返回给客户端的状态码
13 | private Boolean sessionPresent;//sessionPresent是告知客户端服务器是否存储了session的位
14 |
15 | public ConnAckVariableHeader(ConnectionStatus status, Boolean sessionPresent) {
16 | this.status = status;
17 | this.sessionPresent = sessionPresent;
18 | }
19 | public ConnectionStatus getStatus() {
20 | return status;
21 | }
22 | public void setStatus(ConnectionStatus status) {
23 | this.status = status;
24 | }
25 | public Boolean isSessionPresent() {
26 | return sessionPresent;
27 | }
28 | public void setSessionPresent(Boolean sessionPresent) {
29 | this.sessionPresent = sessionPresent;
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/ConnectMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * MQTT协议Connect消息类型实现类,客户端请求服务器连接的消息类型
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2015-3-2
9 | */
10 | public class ConnectMessage extends Message {
11 |
12 | public ConnectMessage(FixedHeader fixedHeader, ConnectVariableHeader variableHeader,
13 | ConnectPayload payload) {
14 | super(fixedHeader, variableHeader, payload);
15 | }
16 |
17 | @Override
18 | public ConnectVariableHeader getVariableHeader() {
19 | return (ConnectVariableHeader)super.getVariableHeader();
20 | }
21 |
22 | @Override
23 | public ConnectPayload getPayload() {
24 | return (ConnectPayload)super.getPayload();
25 | }
26 |
27 | }
28 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/ConnectPayload.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | /**
6 | * MQTT协议Connect消息类型的荷载
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-4
11 | */
12 | public class ConnectPayload{
13 | private String clientId;//客户端ID
14 | private String willTopic;
15 | private String willMessage;
16 | private String username;//如果设置User Name标识,可以在此读取用户名称
17 | private String password;//如果设置Password标识,便可读取用户密码
18 |
19 | public ConnectPayload(String clientId, String willTopic,
20 | String willMessage, String username, String password) {
21 | super();
22 | this.clientId = clientId;
23 | this.willTopic = willTopic;
24 | this.willMessage = willMessage;
25 | this.username = username;
26 | this.password = password;
27 | }
28 |
29 | public String getClientId() {
30 | return clientId;
31 | }
32 | public void setClientId(String clientId) {
33 | this.clientId = clientId;
34 | }
35 | public String getWillTopic() {
36 | return willTopic;
37 | }
38 | public void setWillTopic(String willTopic) {
39 | this.willTopic = willTopic;
40 | }
41 | public String getWillMessage() {
42 | return willMessage;
43 | }
44 | public void setWillMessage(String willMessage) {
45 | this.willMessage = willMessage;
46 | }
47 | public String getUsername() {
48 | return username;
49 | }
50 | public void setUsername(String username) {
51 | this.username = username;
52 | }
53 | public String getPassword() {
54 | return password;
55 | }
56 | public void setPassword(String password) {
57 | this.password = password;
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/ConnectVariableHeader.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * MQTT协议Connect消息类型的可变头部
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2016-3-4
9 | */
10 | public class ConnectVariableHeader{
11 | private String protocolName = "MQTT";//协议规定的协议名
12 | private byte protocolVersionNumber = 4;//MQTT_v3.1.1协议的版本号
13 |
14 | //Connect Flags的六个参数
15 | private boolean hasUsername;//是否有用户名,与密码一起,要么都为0,要么都为1,否则无效
16 | private boolean hasPassword;//是否有密码,与用户名一起,要么都为0,要么都为1,否则无效
17 | private boolean willRetain;//设置Will Flag为1,Will Retain标志就是有效的,当客户端意
18 | //外断开服务器发布其Will Message之后,服务器是否应该继续保存
19 | private QoS willQoS;//设置Will Flag为1,Will QoS标志就是有效的
20 | private boolean hasWill;//是否设置遗嘱,设置以后,遗嘱生效。遗嘱就是客户端预先定义好,在自己
21 | //异常断开的情况下,所留下的最后遗愿
22 | private boolean cleanSession;//是否清理session
23 | private boolean reservedIsZero;//协议的保留位,此位必须校验且必须为0,不为0则断开连接
24 |
25 | private int keepAlive;//心跳包时长
26 |
27 | public ConnectVariableHeader(String protocolName,
28 | byte protocolVersionNumber, boolean hasUsername,
29 | boolean hasPassword, boolean willRetain, QoS willQoS,
30 | boolean hasWill, boolean cleanSession, boolean reservedIsZero,
31 | int keepAlive) {
32 | this.protocolName = protocolName;
33 | this.protocolVersionNumber = protocolVersionNumber;
34 | this.hasUsername = hasUsername;
35 | this.hasPassword = hasPassword;
36 | this.willRetain = willRetain;
37 | this.willQoS = willQoS;
38 | this.hasWill = hasWill;
39 | this.cleanSession = cleanSession;
40 | this.reservedIsZero = reservedIsZero;
41 | this.keepAlive = keepAlive;
42 | }
43 |
44 | public String getProtocolName() {
45 | return protocolName;
46 | }
47 |
48 | public byte getProtocolVersionNumber() {
49 | return protocolVersionNumber;
50 | }
51 |
52 | public boolean isHasUsername() {
53 | return hasUsername;
54 | }
55 |
56 | public boolean isHasPassword() {
57 | return hasPassword;
58 | }
59 |
60 | public boolean isWillRetain() {
61 | return willRetain;
62 | }
63 |
64 | public QoS getWillQoS() {
65 | return willQoS;
66 | }
67 |
68 | public boolean isHasWill() {
69 | return hasWill;
70 | }
71 |
72 | public boolean isCleanSession() {
73 | return cleanSession;
74 | }
75 |
76 | public boolean isReservedIsZero() {
77 | return reservedIsZero;
78 | }
79 |
80 | public int getKeepAlive() {
81 | return keepAlive;
82 | }
83 |
84 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/FixedHeader.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import io.netty.util.internal.StringUtil;
4 |
5 | /**
6 | * Mqtt协议的固定头部,有一个字节,由四个字段组成
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-3
11 | */
12 | public class FixedHeader {
13 | private final MessageType messageType;
14 | private boolean dup; //MQTT协议头第5bit,代表打开标志,表示是否第一次发送
15 | private QoS qos; //MQTT协议头前6,7bit,代表服务质量
16 | private boolean retain; //MQTT协议头第8bit,代表是否保持
17 | private int messageLength; //第二个字节
18 |
19 | public FixedHeader(MessageType messageType,
20 | boolean dup,
21 | QoS qos,
22 | boolean retain){
23 | this.messageType = messageType;
24 | this.dup = dup;
25 | this.qos = qos;
26 | this.retain = retain;
27 | }
28 |
29 | public FixedHeader(MessageType messageType,
30 | boolean dup,
31 | QoS qos,
32 | boolean retain,
33 | int messageLength){
34 | this.messageType = messageType;
35 | this.dup = dup;
36 | this.qos = qos;
37 | this.retain = retain;
38 | this.messageLength = messageLength;
39 | }
40 |
41 | public boolean isDup() {
42 | return dup;
43 | }
44 |
45 | public void setDup(boolean dup) {
46 | this.dup = dup;
47 | }
48 |
49 | public QoS getQos() {
50 | return qos;
51 | }
52 |
53 | public void setQos(QoS qos) {
54 | this.qos = qos;
55 | }
56 |
57 | public boolean isRetain() {
58 | return retain;
59 | }
60 |
61 | public void setRetain(boolean retain) {
62 | this.retain = retain;
63 | }
64 |
65 | public int getMessageLength() {
66 | return messageLength;
67 | }
68 |
69 | public void setMessageLength(int messageLength) {
70 | this.messageLength = messageLength;
71 | }
72 |
73 | public MessageType getMessageType() {
74 | return messageType;
75 | }
76 |
77 | @Override
78 | public String toString() {
79 | StringBuilder stringBuilder = new StringBuilder();
80 | stringBuilder.append(StringUtil.simpleClassName(this))
81 | .append('[')
82 | .append("messageType=").append(messageType)
83 | .append(", isDup=").append(dup)
84 | .append(", qosLevel=").append(qos)
85 | .append(", isRetain=").append(retain)
86 | .append(", messageLength=").append(messageLength)
87 | .append(']');
88 | return stringBuilder.toString();
89 | }
90 |
91 | public static FixedHeader getConnectFixedHeader(){
92 | return new FixedHeader(MessageType.CONNECT, false, QoS.AT_MOST_ONCE, false);
93 | }
94 |
95 | public static FixedHeader getConnAckFixedHeader(){
96 | return new FixedHeader(MessageType.CONNACK, false, QoS.AT_MOST_ONCE, false);
97 | }
98 |
99 | public static FixedHeader getPublishFixedHeader(
100 | boolean dup,
101 | QoS qos,
102 | boolean retain){
103 | return new FixedHeader(MessageType.PUBLISH, dup, qos, retain);
104 | }
105 |
106 | public static FixedHeader getPubAckFixedHeader(){
107 | return new FixedHeader(MessageType.PUBACK, false, QoS.AT_MOST_ONCE, false);
108 | }
109 |
110 | public static FixedHeader getPubRecFixedHeader(){
111 | return new FixedHeader(MessageType.PUBREC, false, QoS.AT_MOST_ONCE, false);
112 | }
113 |
114 | public static FixedHeader getPubRelFixedHeader(){
115 | return new FixedHeader(MessageType.PUBREL, false, QoS.AT_LEAST_ONCE, false);
116 | }
117 |
118 | public static FixedHeader getPubCompFixedHeader(){
119 | return new FixedHeader(MessageType.PUBCOMP, false, QoS.AT_MOST_ONCE, false);
120 | }
121 |
122 | public static FixedHeader getSubscribeFixedHeader(){
123 | return new FixedHeader(MessageType.SUBSCRIBE, false, QoS.AT_LEAST_ONCE, false);
124 | }
125 |
126 | public static FixedHeader getSubAckFixedHeader(){
127 | return new FixedHeader(MessageType.SUBACK, false, QoS.AT_MOST_ONCE, false);
128 | }
129 |
130 | public static FixedHeader getUnSubscribeFixedHeader(){
131 | return new FixedHeader(MessageType.UNSUBSCRIBE, false, QoS.AT_LEAST_ONCE, false);
132 | }
133 |
134 | public static FixedHeader getUnSubAckFixedHeader(){
135 | return new FixedHeader(MessageType.UNSUBACK, false, QoS.AT_MOST_ONCE, false);
136 | }
137 |
138 | public static FixedHeader getPingRespFixedHeader(){
139 | return new FixedHeader(MessageType.PINGRESP, false, QoS.AT_MOST_ONCE, false);
140 | }
141 |
142 | public static FixedHeader getDisconnectFixedHeader(){
143 | return new FixedHeader(MessageType.DISCONNECT, false, QoS.AT_MOST_ONCE, false);
144 | }
145 | }
146 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/Message.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import io.netty.util.internal.StringUtil;
4 |
5 | /**
6 | * 定义MQTT协议固定头部,并作为细分的message的基类
7 | * 修改(1)Message拆分为固定头部fixHeader,variableHeader,payload三部分
8 | *
9 | * @author zer0
10 | * @version 1.0
11 | * @date 2015-3-2
12 | * @date 2016-3-3(1)
13 | */
14 | public class Message {
15 |
16 | private final FixedHeader fixedHeader;
17 | private final Object variableHeader;
18 | private final Object payload;
19 |
20 | public Message(FixedHeader fixedHeader,
21 | Object variableHeader,
22 | Object payload){
23 | this.fixedHeader = fixedHeader;
24 | this.variableHeader = variableHeader;
25 | this.payload = payload;
26 | }
27 |
28 | /**
29 | * 初始化message,针对没有可变头部和荷载的协议类型
30 | * @author zer0
31 | * @version 1.0
32 | * @date 2016-3-3
33 | */
34 | public Message(FixedHeader fixedHeader){
35 | this(fixedHeader, null, null);
36 | }
37 |
38 | /**
39 | * 初始化message,针对没有荷载的协议类型
40 | * @author zer0
41 | * @version 1.0
42 | * @date 2016-3-3
43 | */
44 | public Message(FixedHeader fixedHeader, Object variableHeader){
45 | this(fixedHeader, variableHeader, null);
46 | }
47 |
48 | public FixedHeader getFixedHeader() {
49 | return fixedHeader;
50 | }
51 |
52 | public Object getVariableHeader() {
53 | return variableHeader;
54 | }
55 |
56 | public Object getPayload() {
57 | return payload;
58 | }
59 |
60 | @Override
61 | public String toString() {
62 | StringBuilder stringBuilder = new StringBuilder();
63 | stringBuilder.append(StringUtil.simpleClassName(this))
64 | .append("[")
65 | .append("fixedHeader=")
66 | .append(getFixedHeader() != null?getFixedHeader().toString():"")
67 | .append(",variableHeader=")
68 | .append(getVariableHeader() != null?getVariableHeader().toString():"")
69 | .append(",payload=")
70 | .append(getPayload() != null?getPayload().toString():"")
71 | .append("]");
72 | return stringBuilder.toString();
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/MessageType.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * Mqtt协议总共有十四种类型
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2016-3-3
9 | */
10 | public enum MessageType {
11 | CONNECT(1),
12 | CONNACK(2),
13 | PUBLISH(3),
14 | PUBACK(4),
15 | PUBREC(5),
16 | PUBREL(6),
17 | PUBCOMP(7),
18 | SUBSCRIBE(8),
19 | SUBACK(9),
20 | UNSUBSCRIBE(10),
21 | UNSUBACK(11),
22 | PINGREQ(12),
23 | PINGRESP(13),
24 | DISCONNECT(14);
25 |
26 | private final int value;
27 |
28 | MessageType(int value) {
29 | this.value = value;
30 | }
31 |
32 | /**
33 | * 获取类型对应的值
34 | * @return int
35 | * @author zer0
36 | * @version 1.0
37 | * @date 2016-3-3
38 | */
39 | public int value() {
40 | return value;
41 | }
42 |
43 | /**
44 | * 把值转变成对应的类型并返回
45 | * @return int
46 | * @author zer0
47 | * @version 1.0
48 | * @date 2016-3-3
49 | */
50 | public static MessageType valueOf(int type){
51 | for (MessageType m : values()) {
52 | if (m.value == type) {
53 | return m;
54 | }
55 | }
56 | throw new IllegalArgumentException("未知的MQTT协议类型:"+type);
57 | }
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/PackageIDManager.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.Hashtable;
4 |
5 | /**
6 | * 协议中的某些类型需要使用的包ID管理类
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-3
11 | */
12 | public class PackageIDManager {
13 |
14 | //包ID是两个字节,所以最大的是65535,最小是1
15 | private static final int MIN_MSG_ID = 1;
16 | private static final int MAX_MSG_ID = 65535;
17 | private static int nextMsgId = MIN_MSG_ID - 1;
18 | private static Hashtable inUseMsgIds = new Hashtable();
19 |
20 | private int packgeID;//包ID
21 |
22 |
23 | /**
24 | * 获取包ID
25 | * @return int
26 | * @author zer0
27 | * @version 1.0
28 | * @date 2015-3-5
29 | */
30 | public static synchronized int getNextMessageId(){
31 | int startingMessageId = nextMsgId;
32 | //循环两次是为了给异步出问题提供一个容错范围
33 | int loopCount = 0;
34 | do {
35 | nextMsgId++;
36 | if ( nextMsgId > MAX_MSG_ID ) {
37 | nextMsgId = MIN_MSG_ID;
38 | }
39 | if (nextMsgId == startingMessageId) {
40 | loopCount++;
41 | if (loopCount == 2) {
42 | throw new UnsupportedOperationException("获取不到可用的包ID");
43 | }
44 | }
45 | } while( inUseMsgIds.containsKey( new Integer(nextMsgId) ) );
46 | Integer id = new Integer(nextMsgId);
47 | inUseMsgIds.put(id, id);
48 | return nextMsgId;
49 | }
50 |
51 | /**
52 | * 释放不用的包ID
53 | * @param msgId
54 | * @author zer0
55 | * @version 1.0
56 | * @date 2015-3-3
57 | */
58 | public synchronized static void releaseMessageId(int msgId) {
59 | inUseMsgIds.remove(new Integer(msgId));
60 | }
61 |
62 | }
63 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/PackageIdVariableHeader.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * MQTT协议中,有部分消息类型的可变头部只含有包ID,把这部分抽取出来,单独成为一个可变头部
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2016-3-4
9 | */
10 | public class PackageIdVariableHeader {
11 |
12 | private int packageID;
13 |
14 | public PackageIdVariableHeader(int packageID) {
15 | if (packageID < 1 || packageID > 65535) {
16 | throw new IllegalArgumentException("消息ID:" + packageID + "必须在1~65535范围内");
17 | }
18 | this.packageID = packageID;
19 | }
20 |
21 | public int getPackageID() {
22 | return packageID;
23 | }
24 |
25 | public void setPackageID(int packageID) {
26 | this.packageID = packageID;
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/PublishMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | /**
6 | * MQTT协议Publish消息类型实现类,发布消息的消息类型
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2015-3-5
11 | */
12 | public class PublishMessage extends Message {
13 |
14 | public PublishMessage(FixedHeader fixedHeader, PublishVariableHeader variableHeader,
15 | ByteBuf payload) {
16 | super(fixedHeader, variableHeader, payload);
17 | }
18 |
19 | @Override
20 | public PublishVariableHeader getVariableHeader() {
21 | return (PublishVariableHeader)super.getVariableHeader();
22 | }
23 |
24 | @Override
25 | public ByteBuf getPayload() {
26 | return (ByteBuf)super.getPayload();
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/PublishVariableHeader.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * MQTT协议Publish消息类型的可变头部
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2016-3-4
9 | */
10 | public class PublishVariableHeader{
11 | private String topic;
12 | private int packageID;
13 |
14 | public PublishVariableHeader(String topic) {
15 | this.topic = topic;
16 | }
17 |
18 | public PublishVariableHeader(String topic, int packageID) {
19 | this.topic = topic;
20 | this.packageID = packageID;
21 | }
22 |
23 | public String getTopic() {
24 | return topic;
25 | }
26 | public void setTopic(String topic) {
27 | this.topic = topic;
28 | }
29 | public int getPackageID() {
30 | return packageID;
31 | }
32 | public void setPackageID(int packageID) {
33 | this.packageID = packageID;
34 | }
35 |
36 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/QoS.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | public enum QoS {
4 | AT_MOST_ONCE (0),
5 | AT_LEAST_ONCE (1),
6 | EXACTLY_ONCE (2),
7 | RESERVE(3);
8 |
9 | final public int val;
10 |
11 | QoS(int val) {
12 | this.val = val;
13 | }
14 |
15 | /**
16 | * 获取类型对应的值
17 | * @return int
18 | * @author zer0
19 | * @version 1.0
20 | * @date 2016-3-3
21 | */
22 | public int value() {
23 | return val;
24 | }
25 |
26 | //通过读取到的整型来获取对应的QoS类型
27 | public static QoS valueOf(int i) {
28 | for(QoS q: QoS.values()) {
29 | if (q.val == i)
30 | return q;
31 | }
32 | throw new IllegalArgumentException("Qos值无效: " + i);
33 | }
34 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/SubAckMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.List;
4 |
5 |
6 | /**
7 | * MQTT协议SubAck消息类型实现类,对Subscribe包的确认
8 | *
9 | * @author zer0
10 | * @version 1.0
11 | * @date 2015-3-5
12 | */
13 | public class SubAckMessage extends Message {
14 |
15 | public SubAckMessage(FixedHeader fixedHeader, PackageIdVariableHeader variableHeader,
16 | SubAckPayload payload) {
17 | super(fixedHeader, variableHeader, payload);
18 | }
19 |
20 | @Override
21 | public PackageIdVariableHeader getVariableHeader() {
22 | return (PackageIdVariableHeader)super.getVariableHeader();
23 | }
24 |
25 | @Override
26 | public SubAckPayload getPayload() {
27 | return (SubAckPayload)super.getPayload();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/SubAckPayload.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * MQTT协议SubAck消息类型的荷载
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-4
11 | */
12 | public class SubAckPayload{
13 | private List grantedQosLevel;
14 |
15 | public SubAckPayload(List grantedQosLevel) {
16 | this.grantedQosLevel = grantedQosLevel;
17 | }
18 |
19 | public List getGrantedQosLevel() {
20 | return grantedQosLevel;
21 | }
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/SubscribeMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.io.ByteArrayInputStream;
4 | import java.io.DataInputStream;
5 | import java.io.IOException;
6 | import java.io.InputStream;
7 | import java.nio.ByteBuffer;
8 | import java.util.ArrayList;
9 | import java.util.List;
10 |
11 | import com.syxy.util.BufferPool;
12 | import com.syxy.util.StringTool;
13 |
14 | /**
15 | * MQTT协议Subscribe消息类型实现类,用于订阅topic,订阅了消息的客户端,可以接受对应topic的信息
16 | *
17 | * @author zer0
18 | * @version 1.0
19 | * @date 2015-3-5
20 | */
21 | public class SubscribeMessage extends Message {
22 |
23 | public SubscribeMessage(FixedHeader fixedHeader, PackageIdVariableHeader variableHeader,
24 | SubscribePayload payload) {
25 | super(fixedHeader, variableHeader, payload);
26 | }
27 |
28 | @Override
29 | public PackageIdVariableHeader getVariableHeader() {
30 | return (PackageIdVariableHeader)super.getVariableHeader();
31 | }
32 |
33 | @Override
34 | public SubscribePayload getPayload() {
35 | return (SubscribePayload)super.getPayload();
36 | }
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/SubscribePayload.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * MQTT协议Subscribe消息类型的荷载
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-4
11 | */
12 | public class SubscribePayload{
13 | private List topicSubscribes;
14 |
15 | public SubscribePayload(List topicSubscribes) {
16 | super();
17 | this.topicSubscribes = topicSubscribes;
18 | }
19 |
20 | public List getTopicSubscribes() {
21 | return topicSubscribes;
22 | }
23 |
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/TopicSubscribe.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | /**
4 | * Subscribe荷载的封装,一个topic和一个qos是一组荷载
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2016-3-4
9 | */
10 | public class TopicSubscribe{
11 | private String topicFilter;
12 | private QoS qos;
13 |
14 | public TopicSubscribe(String topicFilter, QoS qos) {
15 | super();
16 | this.topicFilter = topicFilter;
17 | this.qos = qos;
18 | }
19 |
20 | public String getTopicFilter() {
21 | return topicFilter;
22 | }
23 | public void setTopicFilter(String topicFilter) {
24 | this.topicFilter = topicFilter;
25 | }
26 | public QoS getQos() {
27 | return qos;
28 | }
29 | public void setQos(QoS qos) {
30 | this.qos = qos;
31 | }
32 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/UnSubscribeMessage.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * MQTT协议UnSubscribe消息类型实现类,用于取消订阅topic
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2015-3-5
11 | */
12 | public class UnSubscribeMessage extends Message {
13 |
14 | public UnSubscribeMessage(FixedHeader fixedHeader, PackageIdVariableHeader variableHeader,
15 | UnSubscribePayload payload) {
16 | super(fixedHeader, variableHeader, payload);
17 | }
18 |
19 | @Override
20 | public PackageIdVariableHeader getVariableHeader() {
21 | return (PackageIdVariableHeader)super.getVariableHeader();
22 | }
23 |
24 | @Override
25 | public UnSubscribePayload getPayload() {
26 | return (UnSubscribePayload)super.getPayload();
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/message/UnSubscribePayload.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.message;
2 |
3 | import java.util.List;
4 |
5 | /**
6 | * MQTT协议Connect消息类型的荷载
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2016-3-4
11 | */
12 | public class UnSubscribePayload{
13 |
14 | private List topics;
15 |
16 | public UnSubscribePayload(List topics) {
17 | super();
18 | this.topics = topics;
19 | }
20 |
21 | public List getTopics() {
22 | return topics;
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/ConnectionDescriptor.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2012-2014 The original author or authors
3 | * ------------------------------------------------------
4 | * All rights reserved. This program and the accompanying materials
5 | * are made available under the terms of the Eclipse Public License v1.0
6 | * and Apache License v2.0 which accompanies this distribution.
7 | *
8 | * The Eclipse Public License is available at
9 | * http://www.eclipse.org/legal/epl-v10.html
10 | *
11 | * The Apache License v2.0 is available at
12 | * http://www.opensource.org/licenses/apache2.0.php
13 | *
14 | * You may elect to redistribute this code under either of these licenses.
15 | */
16 | package com.syxy.protocol.mqttImp.process;
17 |
18 | import io.netty.channel.Channel;
19 |
20 | /**
21 | * 此类是每个客户端的会话,客户端ID,cleanSession的保存
22 | *
23 | * @author zer0
24 | * @version 1.0
25 | * @date 2015-3-7
26 | */
27 | public class ConnectionDescriptor {
28 |
29 | private String clientID;
30 | private Channel client;
31 | private boolean cleanSession;
32 |
33 | public ConnectionDescriptor(String clientID, Channel session, boolean cleanSession) {
34 | this.clientID = clientID;
35 | this.client = session;
36 | this.cleanSession = cleanSession;
37 | }
38 |
39 | public String getClientID() {
40 | return clientID;
41 | }
42 |
43 | public void setClientID(String clientID) {
44 | this.clientID = clientID;
45 | }
46 |
47 | public Channel getClient() {
48 | return client;
49 | }
50 |
51 | public void setClient(Channel client) {
52 | this.client = client;
53 | }
54 |
55 | public boolean isCleanSession() {
56 | return cleanSession;
57 | }
58 |
59 | public void setCleanSession(boolean cleanSession) {
60 | this.cleanSession = cleanSession;
61 | }
62 |
63 | @Override
64 | public String toString() {
65 | return "ConnectionDescriptor{" + "m_clientID=" + clientID + ", m_cleanSession=" + cleanSession + '}';
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Impl/IdentityAuthenticator.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Impl;
2 |
3 | import java.sql.PreparedStatement;
4 | import java.sql.ResultSet;
5 |
6 | import com.alibaba.druid.pool.DruidPooledConnection;
7 | import com.syxy.protocol.mqttImp.process.Impl.dataHandler.DBConnection;
8 | import com.syxy.protocol.mqttImp.process.Interface.IAuthenticator;
9 |
10 | /**
11 | * 身份校验类,该类的校验仅允许数据库中有的用户通过验证
12 | *
13 | * @author zer0
14 | * @version 1.0
15 | * @date 2015-6-30
16 | */
17 | public class IdentityAuthenticator implements IAuthenticator {
18 |
19 | @Override
20 | public boolean checkValid(String username, String password) {
21 | //该处连接数据库,到数据库查询是否有该用户,有则通过验证
22 | int ret=0;
23 | DruidPooledConnection conn=null;
24 | PreparedStatement statement = null;
25 | ResultSet resultSet=null;
26 | try {
27 | conn=DBConnection.getInstance().openConnection();
28 | String sqlString="select * from zer0_user where username=? and password=?";
29 | statement = conn.prepareStatement(sqlString);
30 | statement.setString(1, username);
31 | statement.setString(2, password);
32 | resultSet=statement.executeQuery();
33 | while (resultSet.next()) {
34 | ret=1;
35 | break;
36 | }
37 | } catch (Exception e) {
38 | e.printStackTrace();
39 | }finally{
40 | DBConnection.getInstance().closeConnection(conn, statement, resultSet);
41 | }
42 | if (ret == 1) {
43 | return true;
44 | }else {
45 | return false;
46 | }
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Impl/dataHandler/DBConnection.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Impl.dataHandler;
2 |
3 | import java.io.FileInputStream;
4 | import java.sql.ResultSet;
5 | import java.sql.SQLException;
6 | import java.sql.Statement;
7 | import java.util.Properties;
8 |
9 | import com.alibaba.druid.pool.DruidDataSource;
10 | import com.alibaba.druid.pool.DruidDataSourceFactory;
11 | import com.alibaba.druid.pool.DruidPooledConnection;
12 |
13 | /**
14 | * 数据库连接类,处理了连接池和数据库打开关闭操作
15 | *
16 | * @author zer0
17 | * @version 1.0
18 | * @date 2015-6-30
19 | */
20 | public class DBConnection {
21 |
22 | private static DruidDataSource ds = null;
23 | private static DBConnection dbConnection = null;
24 | private static final String CONFIG_FILE = System.getProperty("user.dir") + "/zer0MQTTServer/resource/druid.properties";
25 |
26 | static {
27 | try{
28 | FileInputStream in = new FileInputStream(CONFIG_FILE);
29 | Properties props = new Properties();
30 | props.load(in);
31 | ds = (DruidDataSource) DruidDataSourceFactory.createDataSource(props);
32 | }catch(Exception ex){
33 | ex.printStackTrace();
34 | }
35 | }
36 |
37 | private DBConnection() {}
38 |
39 | public static synchronized DBConnection getInstance() {
40 | if (null == dbConnection) {
41 | dbConnection = new DBConnection();
42 | }
43 | return dbConnection;
44 | }
45 |
46 | public DruidPooledConnection openConnection() throws SQLException {
47 | return ds.getConnection();
48 | }
49 |
50 | /**
51 | * 关闭数据库连接
52 | * @param connection
53 | * @param statement
54 | * @param resultSet
55 | * @author zer0
56 | * @version 1.0
57 | * @date 2015-7-7
58 | */
59 | public void closeConnection(DruidPooledConnection connection,Statement statement,ResultSet resultSet) {
60 | if (resultSet!=null) {
61 | try {
62 | resultSet.close();
63 | } catch (SQLException e) {
64 | e.printStackTrace();
65 | }
66 | }
67 | if (statement!=null) {
68 | try {
69 | statement.close();
70 | } catch (SQLException e) {
71 | e.printStackTrace();
72 | }
73 | }
74 | if (connection!=null) {
75 | try {
76 | connection.close();
77 | } catch (SQLException e) {
78 | e.printStackTrace();
79 | }
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Impl/dataHandler/MapDBPersistentStore.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Impl.dataHandler;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | import java.io.File;
6 | import java.io.IOException;
7 | import java.util.ArrayList;
8 | import java.util.Collection;
9 | import java.util.Collections;
10 | import java.util.HashSet;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Set;
14 | import java.util.concurrent.ConcurrentMap;
15 |
16 | import org.apache.log4j.Logger;
17 | import org.mapdb.DB;
18 | import org.mapdb.DBMaker;
19 |
20 | import com.syxy.protocol.mqttImp.message.QoS;
21 | import com.syxy.protocol.mqttImp.process.Interface.IMessagesStore;
22 | import com.syxy.protocol.mqttImp.process.Interface.ISessionStore;
23 | import com.syxy.protocol.mqttImp.process.event.PubRelEvent;
24 | import com.syxy.protocol.mqttImp.process.event.PublishEvent;
25 | import com.syxy.protocol.mqttImp.process.subscribe.SubscribeStore;
26 | import com.syxy.protocol.mqttImp.process.subscribe.Subscription;
27 | import com.syxy.util.MqttTool;
28 |
29 | /**
30 | * 对数据进行保存,视情况决定是临时保存还是持久化保存
31 | *
32 | * @author zer0
33 | * @version 1.0
34 | * @date 2015-7-7
35 | */
36 | public class MapDBPersistentStore implements IMessagesStore, ISessionStore {
37 |
38 | private final static Logger Log = Logger.getLogger(MapDBPersistentStore.class);
39 |
40 | //为Session保存的的可能需要重发的消息
41 | private ConcurrentMap> persistentOfflineMessage;
42 | //为Qos1和Qos2临时保存的消息
43 | private ConcurrentMap persistentQosTempMessage;
44 | //为Qos2临时保存的PubRel消息
45 | private ConcurrentMap persistentPubRelTempMessage;
46 | //持久化存储session和与之对应的subscription Set
47 | private ConcurrentMap> persistentSubscriptionStore;
48 | //持久化的Retain
49 | private ConcurrentMap retainedStore;
50 | //保存publish包ID
51 | private ConcurrentMap publishPackgeIDStore;
52 | //保存pubRec包ID
53 | private ConcurrentMap pubRecPackgeIDStore;
54 |
55 | private DB m_db;
56 |
57 | @Override
58 | public void initStore() {
59 | String STORAGE_FILE_PATH = System.getProperty("user.dir") + File.separator + MqttTool.getProperty("path");
60 | Log.info("存储文件的初始化位置"+STORAGE_FILE_PATH);
61 | File tmpFile;
62 | try {
63 | tmpFile = new File(STORAGE_FILE_PATH);
64 | tmpFile.createNewFile();
65 | m_db = DBMaker.newFileDB(tmpFile).make();
66 | persistentOfflineMessage = m_db.getHashMap("offline");
67 | persistentQosTempMessage = m_db.getHashMap("publishTemp");
68 | persistentPubRelTempMessage = m_db.getHashMap("pubRelTemp");
69 | persistentSubscriptionStore = m_db.getHashMap("subscriptions");
70 | retainedStore = m_db.getHashMap("retained");
71 | publishPackgeIDStore = m_db.getHashMap("publishPID");
72 | pubRecPackgeIDStore = m_db.getHashMap("pubRecPID");
73 | } catch (IOException ex) {
74 | Log.error(null, ex);
75 | }
76 | }
77 |
78 | @Override
79 | public boolean searchSubscriptions(String clientID) {
80 | return persistentSubscriptionStore.containsKey(clientID);
81 | }
82 |
83 | @Override
84 | public void wipeSubscriptions(String clientID) {
85 | persistentSubscriptionStore.remove(clientID);
86 | m_db.commit();
87 | }
88 |
89 | @Override
90 | public void addNewSubscription(Subscription newSubscription, String clientID) {
91 | Log.info("添加新订阅,订阅:" + newSubscription + ",客户端ID:" + clientID );
92 | if (!persistentSubscriptionStore.containsKey(clientID)) {
93 | Log.info("客户端ID{" + clientID + "}不存在订阅集 , 为它创建订阅集");
94 | persistentSubscriptionStore.put(clientID, new HashSet());
95 | }
96 |
97 | Set subs = persistentSubscriptionStore.get(clientID);
98 | if (!subs.contains(newSubscription)) {
99 | Log.info("更新客户端" + clientID + "的订阅集");
100 | Subscription existingSubscription = null;
101 | //遍历订阅集里所有的订阅,查看是否有相同topic的订阅,有的话,移除之前的订阅,添加新的
102 | for (Subscription scanSub : subs) {
103 | if (newSubscription.getTopicFilter().equals(scanSub.getTopicFilter())) {
104 | existingSubscription = scanSub;
105 | break;
106 | }
107 | }
108 | if (existingSubscription != null) {
109 | subs.remove(existingSubscription);
110 | }
111 | subs.add(newSubscription);
112 | persistentSubscriptionStore.put(clientID, subs);
113 | Log.debug("客户端" + clientID + "的订阅集现在是这样的" + subs);
114 | }
115 | m_db.commit();
116 | }
117 |
118 | @Override
119 | public void removeSubscription(String topic, String clientID) {
120 | Log.info("删除客户端" + clientID + "的" + topic + "订阅");
121 | if (!persistentSubscriptionStore.containsKey(clientID)) {
122 | Log.debug("没客户端ID" + clientID + " , 无法删除");
123 | return;
124 | }
125 | Set subs = persistentSubscriptionStore.get(clientID);
126 | Subscription existingSubscription = null;
127 | for (Subscription subscription : subs) {
128 | String topicfilter = subscription.getTopicFilter();
129 | if (topicfilter.equals(topic)) {
130 | existingSubscription = subscription;
131 | }
132 | }
133 | if (existingSubscription != null) {
134 | subs.remove(existingSubscription);
135 | }
136 | m_db.commit();
137 | }
138 |
139 | @Override
140 | public List listMessagesInSession(String clientID) {
141 | List allEvents = new ArrayList();
142 | List storeEvents = persistentOfflineMessage.get(clientID);
143 | //如果该client无离线消息,则把storeEvents设置为空集合
144 | if (storeEvents == null) {
145 | storeEvents = Collections.emptyList();
146 | }
147 | for (PublishEvent event : storeEvents) {
148 | allEvents.add(event);
149 | }
150 | return allEvents;
151 | }
152 |
153 | @Override
154 | public void removeMessageInSessionForPublish(String clientID,
155 | Integer packgeID) {
156 | List events = persistentOfflineMessage.get(clientID);
157 | if (events == null) {
158 | return;
159 | }
160 | PublishEvent toRemoveEvt = null;
161 | for (PublishEvent evt : events) {
162 | if (evt.getPackgeID()== packgeID) {
163 | toRemoveEvt = evt;
164 | }
165 | }
166 | events.remove(toRemoveEvt);
167 | persistentOfflineMessage.put(clientID, events);
168 | m_db.commit();
169 | }
170 |
171 | @Override
172 | public void storeMessageToSessionForPublish(PublishEvent pubEvent) {
173 | List storedEvents;
174 | String clientID = pubEvent.getClientID();
175 | if (!persistentOfflineMessage.containsKey(clientID)) {
176 | storedEvents = new ArrayList();
177 | } else {
178 | storedEvents = persistentOfflineMessage.get(clientID);
179 | }
180 | storedEvents.add(pubEvent);
181 | persistentOfflineMessage.put(clientID, storedEvents);
182 | m_db.commit();
183 | }
184 |
185 | @Override
186 | public void storeQosPublishMessage(String publishKey, PublishEvent pubEvent) {
187 | persistentQosTempMessage.put(publishKey, pubEvent);
188 | m_db.commit();
189 | }
190 |
191 | @Override
192 | public void removeQosPublishMessage(String publishKey) {
193 | persistentQosTempMessage.remove(publishKey);
194 | m_db.commit();
195 | }
196 |
197 | @Override
198 | public PublishEvent searchQosPublishMessage(String publishKey) {
199 | return persistentQosTempMessage.get(publishKey);
200 | }
201 |
202 | @Override
203 | public void storePubRelMessage(String pubRelKey, PubRelEvent pubRelEvent) {
204 | persistentPubRelTempMessage.put(pubRelKey, pubRelEvent);
205 | m_db.commit();
206 | }
207 |
208 | @Override
209 | public void removePubRelMessage(String pubRelKey) {
210 | persistentPubRelTempMessage.remove(pubRelKey);
211 | m_db.commit();
212 | }
213 |
214 | @Override
215 | public PubRelEvent searchPubRelMessage(String pubRelKey) {
216 | return persistentPubRelTempMessage.get(pubRelKey);
217 | }
218 |
219 | @Override
220 | public void storeRetained(String topic, ByteBuf message, QoS qos) {
221 | //将ByteBuf转变为byte[]
222 | byte[] messageBytes = new byte[message.readableBytes()];
223 | message.getBytes(message.readerIndex(), messageBytes);
224 | if (messageBytes.length <= 0) {
225 | retainedStore.remove(topic);
226 | } else {
227 | StoredMessage storedMessage = new StoredMessage(messageBytes, qos, topic);
228 | retainedStore.put(topic, storedMessage);
229 | }
230 | m_db.commit();
231 | }
232 |
233 | @Override
234 | public void cleanRetained(String topic) {
235 | retainedStore.remove(topic);
236 | m_db.commit();
237 | }
238 |
239 | @Override
240 | public Collection searchRetained(String topic) {
241 | List results = new ArrayList();
242 | for (Map.Entry entry : retainedStore.entrySet()) {
243 | StoredMessage storedMsg = entry.getValue();
244 | if (SubscribeStore.matchTopics(entry.getKey(), topic)) {
245 | results.add(storedMsg);
246 | }
247 | }
248 | return results;
249 | }
250 |
251 | @Override
252 | public void storePublicPackgeID(String clientID, Integer packgeID) {
253 | publishPackgeIDStore.put(clientID, packgeID);
254 | m_db.commit();
255 | }
256 |
257 | @Override
258 | public void removePublicPackgeID(String clientID) {
259 | publishPackgeIDStore.remove(clientID);
260 | m_db.commit();
261 | }
262 |
263 | @Override
264 | public void storePubRecPackgeID(String clientID, Integer packgeID) {
265 | pubRecPackgeIDStore.put(clientID, packgeID);
266 | m_db.commit();
267 | }
268 |
269 | @Override
270 | public void removePubRecPackgeID(String clientID) {
271 | pubRecPackgeIDStore.remove(clientID);
272 | m_db.commit();
273 | }
274 |
275 | }
276 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Interface/IAuthenticator.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Interface;
2 |
3 | /**
4 | * 身份验证接口
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2015-3-8
9 | */
10 | public interface IAuthenticator {
11 |
12 | /**
13 | * 校验用户名和密码是否正确
14 | * @param username
15 | * @param password
16 | * @return boolean
17 | * @author zer0
18 | * @version 1.0
19 | * @date 2015-3-8
20 | */
21 | boolean checkValid(String username, String password);
22 |
23 | }
24 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Interface/IMessagesStore.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Interface;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | import java.io.Serializable;
6 | import java.nio.ByteBuffer;
7 | import java.util.Collection;
8 | import java.util.List;
9 |
10 | import com.syxy.protocol.mqttImp.message.QoS;
11 | import com.syxy.protocol.mqttImp.process.event.PubRelEvent;
12 | import com.syxy.protocol.mqttImp.process.event.PublishEvent;
13 |
14 | /**
15 | * 消息存储接口
16 | *
17 | * @author zer0
18 | * @version 1.0
19 | * @date 2015-05-07
20 | */
21 | public interface IMessagesStore {
22 |
23 | public static class StoredMessage implements Serializable {
24 | final QoS qos;
25 | final byte[] payload;
26 | final String topic;
27 |
28 | public StoredMessage(byte[] message, QoS qos, String topic) {
29 | this.qos = qos;
30 | this.payload = message;
31 | this.topic = topic;
32 | }
33 |
34 | public QoS getQos() {
35 | return qos;
36 | }
37 |
38 | public byte[] getPayload() {
39 | return payload;
40 | }
41 |
42 | public String getTopic() {
43 | return topic;
44 | }
45 |
46 | }
47 |
48 | /**
49 | * 初始化存储
50 | * @author zer0
51 | * @version 1.0
52 | * @date 2015-11-30
53 | */
54 | void initStore();
55 |
56 | /**
57 | * 返回某个clientID的离线消息列表
58 | * @param clientID
59 | * @author zer0
60 | * @version 1.0
61 | * @date 2015-05-18
62 | */
63 | List listMessagesInSession(String clientID);
64 |
65 |
66 | /**
67 | * 在重发以后,移除publish的离线消息事件
68 | * @param clientID
69 | * @param packgeID
70 | * @author zer0
71 | * @version 1.0
72 | * @date 2015-05-18
73 | */
74 | void removeMessageInSessionForPublish(String clientID, Integer packgeID);
75 |
76 | /**
77 | * 存储publish的离线消息事件,为CleanSession=0的情况做重发准备
78 | * @param pubEvent
79 | * @author zer0
80 | * @version 1.0
81 | * @date 2015-05-21
82 | */
83 | void storeMessageToSessionForPublish(PublishEvent pubEvent);
84 |
85 | /**
86 | * 存储Publish的包ID
87 | * @param clientID
88 | * @param packgeID
89 | * @author zer0
90 | * @version 1.0
91 | * @date 2015-05-21
92 | */
93 | void storePublicPackgeID(String clientID, Integer packgeID);
94 |
95 | /**
96 | * 移除Publish的包ID
97 | * @param clientID
98 | * @author zer0
99 | * @version 1.0
100 | * @date 2015-05-21
101 | */
102 | void removePublicPackgeID(String clientID);
103 |
104 | /**
105 | * 移除PubRec的包ID
106 | * @param clientID
107 | * @author zer0
108 | * @version 1.0
109 | * @date 2015-05-21
110 | */
111 | void removePubRecPackgeID(String clientID);
112 |
113 | /**
114 | * 存储PubRec的包ID
115 | * @param clientID
116 | * @param packgeID
117 | * @author zer0
118 | * @version 1.0
119 | * @date 2015-05-21
120 | */
121 | void storePubRecPackgeID(String clientID, Integer packgeID);
122 |
123 | /**
124 | * 当Qos>0的时候,临时存储Publish消息,用于重发
125 | * @param publishKey
126 | * @param pubEvent
127 | * @author zer0
128 | * @version 1.0
129 | * @date 2015-05-21
130 | */
131 | void storeQosPublishMessage(String publishKey, PublishEvent pubEvent);
132 |
133 | /**
134 | * 在收到对应的响应包后,删除Publish消息的临时存储
135 | * @param publishKey
136 | * @author zer0
137 | * @version 1.0
138 | * @date 2015-05-21
139 | */
140 | void removeQosPublishMessage(String publishKey);
141 |
142 | /**
143 | * 获取临时存储的Publish消息,在等待时间过后未收到对应的响应包,则重发该Publish消息
144 | * @param publishKey
145 | * @return PublishEvent
146 | * @author zer0
147 | * @version 1.0
148 | * @date 2015-11-28
149 | */
150 | PublishEvent searchQosPublishMessage(String publishKey);
151 |
152 | /**
153 | * 当Qos=2的时候,临时存储PubRel消息,在未收到PubComp包时用于重发
154 | * @param pubRelKey
155 | * @param pubRelEvent
156 | * @author zer0
157 | * @version 1.0
158 | * @date 2015-11-28
159 | */
160 | void storePubRelMessage(String pubRelKey, PubRelEvent pubRelEvent);
161 |
162 | /**
163 | * 在收到对应的响应包后,删除PubRel消息的临时存储
164 | * @param pubRelKey
165 | * @author zer0
166 | * @version 1.0
167 | * @date 2015-11-28
168 | */
169 | void removePubRelMessage(String pubRelKey);
170 |
171 | /**
172 | * 获取临时存储的PubRel消息,在等待时间过后未收到对应的响应包,则重发该PubRel消息
173 | * @param pubRelKey
174 | * @author zer0
175 | * @version 1.0
176 | * @date 2015-11-28
177 | */
178 | PubRelEvent searchPubRelMessage(String pubRelKey);
179 |
180 | /**
181 | * 持久化存储保留Retain为1的指定topic的最新信息,该信息会在新客户端订阅某主题的时候发送给此客户端
182 | * @param topic
183 | * @param message
184 | * @param qos
185 | * @author zer0
186 | * @version 1.0
187 | * @date 2015-05-26
188 | */
189 | void storeRetained(String topic, ByteBuf message, QoS qos);
190 |
191 | /**
192 | * 删除指定topic的Retain信息
193 | * @param topic
194 | * @author zer0
195 | * @version 1.0
196 | * @date 2015-05-26
197 | */
198 | void cleanRetained(String topic);
199 |
200 | /**
201 | * 从Retain中搜索对应topic中保存的信息
202 | * @param topic
203 | * @author zer0
204 | * @version 1.0
205 | * @date 2015-11-27
206 | */
207 | Collection searchRetained(String topic);
208 | }
209 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/Interface/ISessionStore.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.Interface;
2 |
3 | import com.syxy.protocol.mqttImp.process.subscribe.Subscription;
4 |
5 | /**
6 | * 会话存储类
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2015-05-07
11 | */
12 | public interface ISessionStore {
13 |
14 | /**
15 | * 查看是否已储存了该客户端ID,如果存储了则返回true
16 | * @param clientID
17 | * @return boolean
18 | * @author zer0
19 | * @version 1.0
20 | * @date 2015-05-07
21 | */
22 | boolean searchSubscriptions(String clientID);
23 |
24 | /**
25 | * 清理某个ID所有订阅信息
26 | * @param clientID
27 | * @author zer0
28 | * @version 1.0
29 | * @date 2015-05-19
30 | */
31 | void wipeSubscriptions(String clientID);
32 |
33 |
34 | /**
35 | * 添加某个订阅消息到存储
36 | * @param newSubscription
37 | * @param clientID
38 | * @author zer0
39 | * @version 1.0
40 | * @date 2015-05-25
41 | */
42 | void addNewSubscription(Subscription newSubscription, String clientID);
43 |
44 | /**
45 | * 从会话的持久化存储中移除某个订阅主题中的某个client
46 | * @param topic
47 | * @param clientID
48 | * @author zer0
49 | * @version 1.0
50 | * @date 2015-05-25
51 | */
52 | void removeSubscription(String topic, String clientID);
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/NettyAttrManager.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process;
2 |
3 | import io.netty.channel.Channel;
4 | import io.netty.util.AttributeKey;
5 |
6 | public class NettyAttrManager {
7 |
8 | public static final String CLIENT_ID = "ClientID";//客户端ID
9 | public static final String CLEAN_SESSION = "cleanSession";
10 | public static final String KEEP_ALIVE = "keepAlive";//心跳包时长
11 |
12 | private static final AttributeKey ATTR_KEY_KEEPALIVE = AttributeKey.valueOf(KEEP_ALIVE);
13 | private static final AttributeKey ATTR_KEY_CLEANSESSION = AttributeKey.valueOf(CLEAN_SESSION);
14 | private static final AttributeKey ATTR_KEY_CLIENTID = AttributeKey.valueOf(CLIENT_ID);
15 |
16 | public static String getAttrClientId(Channel channel) {
17 | return (String)channel.attr(NettyAttrManager.ATTR_KEY_CLIENTID).get();
18 | }
19 |
20 | public static void setAttrClientId(Channel channel, String clientID) {
21 | channel.attr(NettyAttrManager.ATTR_KEY_CLIENTID).set(clientID);
22 | }
23 |
24 | public static Boolean getAttrCleanSession(Channel channel) {
25 | return (Boolean)channel.attr(NettyAttrManager.ATTR_KEY_CLEANSESSION).get();
26 | }
27 |
28 | public static void setAttrCleanSession(Channel channel, Boolean cleansession) {
29 | channel.attr(NettyAttrManager.ATTR_KEY_CLEANSESSION).set(cleansession);
30 | }
31 |
32 | public static int getAttrKeepAlive(Channel channel) {
33 | return (int)channel.attr(NettyAttrManager.ATTR_KEY_KEEPALIVE).get();
34 | }
35 |
36 | public static void setAttrKeepAlive(Channel channel, int keepAlive) {
37 | channel.attr(NettyAttrManager.ATTR_KEY_KEEPALIVE).set(keepAlive);
38 | }
39 |
40 | }
41 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/ProtocolProcess.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process;
2 |
3 | import io.netty.buffer.ByteBuf;
4 | import io.netty.buffer.Unpooled;
5 | import io.netty.channel.Channel;
6 | import io.netty.handler.timeout.IdleStateHandler;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Collection;
10 | import java.util.HashMap;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.concurrent.ConcurrentHashMap;
14 | import java.util.concurrent.TimeUnit;
15 |
16 | import org.apache.log4j.Logger;
17 |
18 | import com.syxy.protocol.mqttImp.MQTTMesageFactory;
19 | import com.syxy.protocol.mqttImp.message.ConnAckMessage;
20 | import com.syxy.protocol.mqttImp.message.ConnAckMessage.ConnectionStatus;
21 | import com.syxy.protocol.mqttImp.message.ConnAckVariableHeader;
22 | import com.syxy.protocol.mqttImp.message.ConnectMessage;
23 | import com.syxy.protocol.mqttImp.message.FixedHeader;
24 | import com.syxy.protocol.mqttImp.message.Message;
25 | import com.syxy.protocol.mqttImp.message.PackageIDManager;
26 | import com.syxy.protocol.mqttImp.message.PackageIdVariableHeader;
27 | import com.syxy.protocol.mqttImp.message.PublishMessage;
28 | import com.syxy.protocol.mqttImp.message.PublishVariableHeader;
29 | import com.syxy.protocol.mqttImp.message.QoS;
30 | import com.syxy.protocol.mqttImp.message.SubAckMessage;
31 | import com.syxy.protocol.mqttImp.message.SubAckPayload;
32 | import com.syxy.protocol.mqttImp.message.SubscribeMessage;
33 | import com.syxy.protocol.mqttImp.message.TopicSubscribe;
34 | import com.syxy.protocol.mqttImp.message.UnSubscribeMessage;
35 | import com.syxy.protocol.mqttImp.process.Impl.IdentityAuthenticator;
36 | import com.syxy.protocol.mqttImp.process.Impl.dataHandler.MapDBPersistentStore;
37 | import com.syxy.protocol.mqttImp.process.Interface.IAuthenticator;
38 | import com.syxy.protocol.mqttImp.process.Interface.IMessagesStore;
39 | import com.syxy.protocol.mqttImp.process.Interface.ISessionStore;
40 | import com.syxy.protocol.mqttImp.process.event.PubRelEvent;
41 | import com.syxy.protocol.mqttImp.process.event.PublishEvent;
42 | import com.syxy.protocol.mqttImp.process.event.job.RePubRelJob;
43 | import com.syxy.protocol.mqttImp.process.event.job.RePublishJob;
44 | import com.syxy.protocol.mqttImp.process.subscribe.SubscribeStore;
45 | import com.syxy.protocol.mqttImp.process.subscribe.Subscription;
46 | import com.syxy.util.Constant;
47 | import com.syxy.util.QuartzManager;
48 | import com.syxy.util.StringTool;
49 |
50 | /**
51 | * 协议所有的业务处理都在此类,注释中所指协议为MQTT3.3.1协议英文版
52 | *
53 | * @author zer0
54 | * @version 1.0
55 | * @date 2015-2-16
56 | */
57 | public class ProtocolProcess {
58 |
59 | //遗嘱信息类
60 | static final class WillMessage {
61 | private final String topic;
62 | private final ByteBuf payload;
63 | private final boolean retained;
64 | private final QoS qos;
65 |
66 | public WillMessage(String topic, ByteBuf payload, boolean retained, QoS qos) {
67 | this.topic = topic;
68 | this.payload = payload;
69 | this.retained = retained;
70 | this.qos = qos;
71 | }
72 |
73 | public String getTopic() {
74 | return topic;
75 | }
76 |
77 | public ByteBuf getPayload() {
78 | return payload;
79 | }
80 |
81 | public boolean isRetained() {
82 | return retained;
83 | }
84 |
85 | public QoS getQos() {
86 | return qos;
87 | }
88 | }
89 |
90 | private final static Logger Log = Logger.getLogger(ProtocolProcess.class);
91 |
92 | private ConcurrentHashMap clients = new ConcurrentHashMap();// 客户端链接映射表
93 | //存储遗嘱信息,通过ID映射遗嘱信息
94 | private ConcurrentHashMap willStore = new ConcurrentHashMap<>();
95 |
96 | private IAuthenticator authenticator;
97 | private IMessagesStore messagesStore;
98 | private ISessionStore sessionStore;
99 | private SubscribeStore subscribeStore;
100 |
101 | public ProtocolProcess(){
102 | MapDBPersistentStore storge = new MapDBPersistentStore();
103 | this.authenticator = new IdentityAuthenticator();
104 | this.messagesStore = storge;
105 | this.messagesStore.initStore();//初始化存储
106 | this.sessionStore = storge;
107 | this.subscribeStore = new SubscribeStore();
108 | }
109 |
110 | //将此类单例
111 | private static ProtocolProcess INSTANCE;
112 | public static ProtocolProcess getInstance(){
113 | if (INSTANCE == null) {
114 | INSTANCE = new ProtocolProcess();
115 | }
116 | return INSTANCE;
117 | }
118 |
119 | /**
120 | * 处理协议的CONNECT消息类型
121 | * @param clientID
122 | * @param connectMessage
123 | * @author zer0
124 | * @version 1.0
125 | * @date 2015-3-7
126 | */
127 | public void processConnect(Channel client, ConnectMessage connectMessage){
128 | Log.info("处理Connect的数据");
129 | //首先查看保留位是否为0,不为0则断开连接,协议P24
130 | if (!connectMessage.getVariableHeader().isReservedIsZero()) {
131 | client.close();
132 | return;
133 | }
134 | //处理protocol name和protocol version, 如果返回码!=0,sessionPresent必为0,协议P24,P32
135 | if (!connectMessage.getVariableHeader().getProtocolName().equals("MQTT") ||
136 | connectMessage.getVariableHeader().getProtocolVersionNumber() != 4 ) {
137 |
138 | ConnAckMessage connAckMessage = (ConnAckMessage) MQTTMesageFactory.newMessage(
139 | FixedHeader.getConnAckFixedHeader(),
140 | new ConnAckVariableHeader(ConnectionStatus.UNACCEPTABLE_PROTOCOL_VERSION, false),
141 | null);
142 |
143 | client.writeAndFlush(connAckMessage);
144 | client.close();//版本或协议名不匹配,则断开该客户端连接
145 | return;
146 | }
147 |
148 | //处理Connect包的保留位不为0的情况,协议P24
149 | if (!connectMessage.getVariableHeader().isReservedIsZero()) {
150 | client.close();
151 | }
152 |
153 | //处理clientID为null或长度为0的情况,协议P29
154 | if (connectMessage.getPayload().getClientId() == null || connectMessage.getPayload().getClientId().length() == 0) {
155 | //clientID为null的时候,cleanSession只能为1,此时给client设置一个随机的,不存在的mac地址为ID,否则,断开连接
156 | if (connectMessage.getVariableHeader().isCleanSession()) {
157 | boolean isExist = true;
158 | String macClientID = StringTool.generalMacString();
159 | while (isExist) {
160 | ConnectionDescriptor connectionDescriptor = clients.get(macClientID);
161 | if (connectionDescriptor == null) {
162 | connectMessage.getPayload().setClientId(macClientID);
163 | isExist = false;
164 | } else {
165 | macClientID = StringTool.generalMacString();
166 | }
167 | }
168 | } else {
169 | Log.info("客户端ID为空,cleanSession为0,根据协议,不接收此客户端");
170 | ConnAckMessage connAckMessage = (ConnAckMessage) MQTTMesageFactory.newMessage(
171 | FixedHeader.getConnAckFixedHeader(),
172 | new ConnAckVariableHeader(ConnectionStatus.IDENTIFIER_REJECTED, false),
173 | null);
174 | client.writeAndFlush(connAckMessage);
175 | client.close();
176 | return;
177 | }
178 | }
179 |
180 | // //检查clientID的格式符合与否
181 | // if (!StringTool.isMacString(connectMessage.getPayload().getClientId())) {
182 | // Log.info("客户端ID为{"+connectMessage.getPayload().getClientId()+"},拒绝此客户端");
183 | // ConnAckMessage connAckMessage = (ConnAckMessage) MQTTMesageFactory.newMessage(
184 | // FixedHeader.getConnAckFixedHeader(),
185 | // new ConnAckVariableHeader(ConnectionStatus.IDENTIFIER_REJECTED, false),
186 | // null);
187 | // client.writeAndFlush(connAckMessage);
188 | // client.close();
189 | // return;
190 | // }
191 |
192 | //如果会话中已经存储了这个新连接的ID,就关闭之前的clientID
193 | if (clients.containsKey(connectMessage.getPayload().getClientId())) {
194 | Log.error("客户端ID{"+connectMessage.getPayload().getClientId()+"}已存在,强制关闭老连接");
195 | Channel oldChannel = clients.get(connectMessage.getPayload().getClientId()).getClient();
196 | boolean cleanSession = NettyAttrManager.getAttrCleanSession(oldChannel);
197 | if (cleanSession) {
198 | cleanSession(connectMessage.getPayload().getClientId());
199 | }
200 | oldChannel.close();
201 | }
202 |
203 | //若至此没问题,则将新客户端连接加入client的维护列表中
204 | ConnectionDescriptor connectionDescriptor =
205 | new ConnectionDescriptor(connectMessage.getPayload().getClientId(),
206 | client, connectMessage.getVariableHeader().isCleanSession());
207 | this.clients.put(connectMessage.getPayload().getClientId(), connectionDescriptor);
208 | //处理心跳包时间,把心跳包时长和一些其他属性都添加到会话中,方便以后使用
209 | int keepAlive = connectMessage.getVariableHeader().getKeepAlive();
210 | Log.debug("连接的心跳包时长是 {" + keepAlive + "} s");
211 | NettyAttrManager.setAttrClientId(client, connectMessage.getPayload().getClientId());
212 | NettyAttrManager.setAttrCleanSession(client, connectMessage.getVariableHeader().isCleanSession());
213 | //协议P29规定,在超过1.5个keepAlive的时间以上没收到心跳包PingReq,就断开连接(但这里要注意把单位是s转为ms)
214 | NettyAttrManager.setAttrKeepAlive(client, keepAlive);
215 | //添加心跳机制处理的Handler
216 | client.pipeline().addFirst("idleStateHandler", new IdleStateHandler(keepAlive, Integer.MAX_VALUE, Integer.MAX_VALUE, TimeUnit.SECONDS));
217 |
218 | //处理Will flag(遗嘱信息),协议P26
219 | if (connectMessage.getVariableHeader().isHasWill()) {
220 | QoS willQos = connectMessage.getVariableHeader().getWillQoS();
221 | ByteBuf willPayload = Unpooled.buffer().writeBytes(connectMessage.getPayload().getWillMessage().getBytes());//获取遗嘱信息的具体内容
222 | WillMessage will = new WillMessage(connectMessage.getPayload().getWillTopic(),
223 | willPayload, connectMessage.getVariableHeader().isWillRetain(),willQos);
224 | //把遗嘱信息与和其对应的的clientID存储在一起
225 | willStore.put(connectMessage.getPayload().getClientId(), will);
226 | }
227 |
228 | //处理身份验证(userNameFlag和passwordFlag)
229 | if (connectMessage.getVariableHeader().isHasUsername() &&
230 | connectMessage.getVariableHeader().isHasPassword()) {
231 | String userName = connectMessage.getPayload().getUsername();
232 | String pwd = connectMessage.getPayload().getPassword();
233 | //此处对用户名和密码做验证
234 | if (!authenticator.checkValid(userName, pwd)) {
235 | ConnAckMessage connAckMessage = (ConnAckMessage) MQTTMesageFactory.newMessage(
236 | FixedHeader.getConnAckFixedHeader(),
237 | new ConnAckVariableHeader(ConnectionStatus.BAD_USERNAME_OR_PASSWORD, false),
238 | null);
239 | client.writeAndFlush(connAckMessage);
240 | return;
241 | }
242 | }
243 |
244 | //处理cleanSession为1的情况
245 | if (connectMessage.getVariableHeader().isCleanSession()) {
246 | //移除所有之前的session并开启一个新的,并且原先保存的subscribe之类的都得从服务器删掉
247 | cleanSession(connectMessage.getPayload().getClientId());
248 | }
249 |
250 | //TODO 此处生成一个token(以后每次客户端每次请求服务器,都必须先验证此token正确与否),并把token保存到本地以及传回给客户端
251 | //鉴权获取不应该在这里做
252 |
253 | // String token = StringTool.generalRandomString(32);
254 | // sessionStore.addSession(connectMessage.getClientId(), token);
255 | // //把荷载封装成json字符串
256 | // JSONObject jsonObject = new JSONObject();
257 | // try {
258 | // jsonObject.put("token", token);
259 | // } catch (JSONException e) {
260 | // e.printStackTrace();
261 | // }
262 |
263 | //处理回写的CONNACK,并回写,协议P29
264 | ConnAckMessage okResp = null;
265 | //协议32,session present的处理
266 | if (!connectMessage.getVariableHeader().isCleanSession() &&
267 | sessionStore.searchSubscriptions(connectMessage.getPayload().getClientId())) {
268 | okResp = (ConnAckMessage) MQTTMesageFactory.newMessage(
269 | FixedHeader.getConnAckFixedHeader(),
270 | new ConnAckVariableHeader(ConnectionStatus.ACCEPTED, true),
271 | null);
272 | }else{
273 | okResp = (ConnAckMessage) MQTTMesageFactory.newMessage(
274 | FixedHeader.getConnAckFixedHeader(),
275 | new ConnAckVariableHeader(ConnectionStatus.ACCEPTED, false),
276 | null);
277 | }
278 |
279 | client.writeAndFlush(okResp);
280 | Log.info("CONNACK处理完毕并成功发送");
281 | Log.info("连接的客户端clientID="+connectMessage.getPayload().getClientId()+", " +
282 | "cleanSession为"+connectMessage.getVariableHeader().isCleanSession());
283 |
284 | //如果cleanSession=0,需要在重连的时候重发同一clientID存储在服务端的离线信息
285 | if (!connectMessage.getVariableHeader().isCleanSession()) {
286 | //force the republish of stored QoS1 and QoS2
287 | republishMessage(connectMessage.getPayload().getClientId());
288 | }
289 | }
290 |
291 | /**
292 | * 处理协议的publish消息类型,该方法先把public需要的事件提取出来
293 | * @param clientID
294 | * @param publishMessage
295 | * @author zer0
296 | * @version 1.0
297 | * @date 2015-5-18
298 | */
299 | public void processPublic(Channel client, PublishMessage publishMessage){
300 | Log.info("处理publish的数据");
301 | String clientID = NettyAttrManager.getAttrClientId(client);
302 | final String topic = publishMessage.getVariableHeader().getTopic();
303 | final QoS qos = publishMessage.getFixedHeader().getQos();
304 | final ByteBuf message = publishMessage.getPayload();
305 | final int packgeID = publishMessage.getVariableHeader().getPackageID();
306 | final boolean retain = publishMessage.getFixedHeader().isRetain();
307 |
308 | processPublic(clientID, topic, qos, retain, message, packgeID);
309 | }
310 |
311 | /**
312 | * 处理遗言消息的发送
313 | * @param clientID
314 | * @param willMessage
315 | * @author zer0
316 | * @version 1.0
317 | * @date 2015-5-26
318 | */
319 | public void processPublic(Channel client, WillMessage willMessage){
320 | Log.info("处理遗言的publish数据");
321 | String clientID = NettyAttrManager.getAttrClientId(client);
322 | final String topic = willMessage.getTopic();
323 | final QoS qos = willMessage.getQos();
324 | final ByteBuf message = willMessage.getPayload();
325 | final boolean retain = willMessage.isRetained();
326 |
327 | processPublic(clientID, topic, qos, retain, message, null);
328 | }
329 |
330 | /**
331 | * 根据协议进行具体的处理,处理不同的Qos等级下的public事件
332 | * @param clientID
333 | * @param topic
334 | * @param qos
335 | * @param recRetain
336 | * @param message
337 | * @param recPackgeID 此包ID只是客户端传过来的,用于发回pubAck用,发送给其他客户端的包ID,需要重新生成
338 | * @author zer0
339 | * @version 1.0
340 | * @date 2015-5-19
341 | */
342 | private void processPublic(String clientID, String topic, QoS qos, boolean recRetain, ByteBuf message, Integer recPackgeID){
343 | Log.info("接收public消息:{clientID="+clientID+",Qos="+qos+",topic="+topic+",packageID="+recPackgeID+"}");
344 | String publishKey = null;
345 | // int sendPackageID = PackageIDManager.getNextMessageId();
346 |
347 | //根据协议P34,Qos=3的时候,就关闭连接
348 | if (qos == QoS.RESERVE) {
349 | clients.get(clientID).getClient().close();
350 | }
351 |
352 | //根据协议P52,qos=0, Dup=0, 则把消息发送给所有注册的客户端即可
353 | if (qos == QoS.AT_MOST_ONCE) {
354 | boolean dup = false;
355 | boolean retain = false;
356 | sendPublishMessage(topic, qos, message, retain, dup);
357 | }
358 |
359 | //根据协议P53,publish的接受者需要发送该publish(Qos=1,Dup=0)消息给其他客户端,然后发送pubAck给该客户端。
360 | //发送该publish消息时候,按此流程: 存储消息→发送给所有人→等待pubAck到来→删除消息
361 | if (qos == QoS.AT_LEAST_ONCE) {
362 | boolean retain = false;
363 | boolean dup = false;
364 |
365 | sendPublishMessage(topic, qos, message, retain, dup);
366 | sendPubAck(clientID, recPackgeID);
367 | }
368 |
369 | //根据协议P54,P55
370 | //接收端:publish接收消息→存储包ID→发给其他客户端→发回pubRec→收到pubRel→抛弃第二步存储的包ID→发回pubcomp
371 | //发送端:存储消息→发送publish(Qos=2,Dup=0)→收到pubRec→抛弃第一步存储的消息→存储pubRec的包ID→发送pubRel→收到pubcomp→抛弃pubRec包ID的存储
372 | if (qos == QoS.EXACTLY_ONCE) {
373 | boolean dup = false;
374 | boolean retain = false;
375 | messagesStore.storePublicPackgeID(clientID, recPackgeID);
376 | sendPublishMessage(topic, qos, message, retain, dup);
377 | sendPubRec(clientID, recPackgeID);
378 | }
379 |
380 | //处理消息是否保留,注:publish报文中的主题名不能包含通配符(协议P35),所以retain中保存的主题名不会有通配符
381 | if (recRetain) {
382 | if (qos == QoS.AT_MOST_ONCE) {
383 | messagesStore.cleanRetained(topic);
384 | } else {
385 | messagesStore.storeRetained(topic, message, qos);
386 | }
387 | }
388 | }
389 |
390 | /**
391 | * 处理协议的pubAck消息类型
392 | * @param client
393 | * @param pubAckMessage
394 | * @author zer0
395 | * @version 1.0
396 | * @date 2015-5-21
397 | */
398 | public void processPubAck(Channel client, PackageIdVariableHeader pubAckVariableMessage){
399 | String clientID = NettyAttrManager.getAttrClientId(client);
400 | int pacakgeID = pubAckVariableMessage.getPackageID();
401 | String publishKey = String.format("%s%d", clientID, pacakgeID);
402 | //取消Publish重传任务
403 | QuartzManager.removeJob(publishKey, "publish", publishKey, "publish");
404 | //删除临时存储用于重发的Publish消息
405 | messagesStore.removeQosPublishMessage(publishKey);
406 | //最后把使用完的包ID释放掉
407 | PackageIDManager.releaseMessageId(pacakgeID);
408 | }
409 |
410 | /**
411 | * 处理协议的pubRec消息类型
412 | * @param client
413 | * @param pubRecMessage
414 | * @author zer0
415 | * @version 1.0
416 | * @date 2015-5-23
417 | */
418 | public void processPubRec(Channel client, PackageIdVariableHeader pubRecVariableMessage){
419 | String clientID = NettyAttrManager.getAttrClientId(client);
420 | int packageID = pubRecVariableMessage.getPackageID();
421 | String publishKey = String.format("%s%d", clientID, packageID);
422 |
423 | //取消Publish重传任务,同时删除对应的值
424 | QuartzManager.removeJob(publishKey, "publish", publishKey, "publish");
425 | messagesStore.removeQosPublishMessage(publishKey);
426 | //此处须额外处理,根据不同的事件,处理不同的包ID
427 | messagesStore.storePubRecPackgeID(clientID, packageID);
428 | //组装PubRel事件后,存储PubRel事件,并发回PubRel
429 | PubRelEvent pubRelEvent = new PubRelEvent(clientID, packageID);
430 | //此处的Key和Publish的key一致
431 | messagesStore.storePubRelMessage(publishKey, pubRelEvent);
432 | //发回PubRel
433 | sendPubRel(clientID, packageID);
434 | //开启PubRel重传事件
435 | Map jobParam = new HashMap();
436 | jobParam.put("ProtocolProcess", this);
437 | jobParam.put("pubRelKey", publishKey);
438 | QuartzManager.addJob(publishKey, "pubRel", publishKey, "pubRel", RePubRelJob.class, 10, 2, jobParam);
439 | }
440 |
441 | /**
442 | * 处理协议的pubRel消息类型
443 | * @param client
444 | * @param pubRelMessage
445 | * @author zer0
446 | * @version 1.0
447 | * @date 2015-5-23
448 | */
449 | public void processPubRel(Channel client, PackageIdVariableHeader pubRelVariableMessage){
450 | String clientID = NettyAttrManager.getAttrClientId(client);
451 | //删除的是接收端的包ID
452 | int pacakgeID = pubRelVariableMessage.getPackageID();
453 |
454 | messagesStore.removePublicPackgeID(clientID);
455 | sendPubComp(clientID, pacakgeID);
456 | }
457 |
458 | /**
459 | * 处理协议的pubComp消息类型
460 | * @param client
461 | * @param pubcompMessage
462 | * @author zer0
463 | * @version 1.0
464 | * @date 2015-5-23
465 | */
466 | public void processPubComp(Channel client, PackageIdVariableHeader pubcompVariableMessage){
467 | String clientID = NettyAttrManager.getAttrClientId(client);
468 | int packaageID = pubcompVariableMessage.getPackageID();
469 | String pubRelkey = String.format("%s%d", clientID, packaageID);
470 |
471 | //删除存储的PubRec包ID
472 | messagesStore.removePubRecPackgeID(clientID);
473 | //取消PubRel的重传任务,删除临时存储的PubRel事件
474 | QuartzManager.removeJob(pubRelkey, "pubRel", pubRelkey, "pubRel");
475 | messagesStore.removePubRelMessage(pubRelkey);
476 | //最后把使用完的包ID释放掉
477 | PackageIDManager.releaseMessageId(packaageID);
478 | }
479 |
480 | /**
481 | * 处理协议的subscribe消息类型
482 | * @param client
483 | * @param subscribeMessage
484 | * @author zer0
485 | * @version 1.0
486 | * @date 2015-5-24
487 | */
488 | public void processSubscribe(Channel client, SubscribeMessage subscribeMessage) {
489 | String clientID = NettyAttrManager.getAttrClientId(client);
490 | boolean cleanSession = NettyAttrManager.getAttrCleanSession(client);
491 | Log.info("处理subscribe数据包,客户端ID={"+clientID+"},cleanSession={"+cleanSession+"}");
492 | //一条subscribeMessage信息可能包含多个Topic和Qos
493 | List topicSubscribes = subscribeMessage.getPayload().getTopicSubscribes();
494 |
495 | List grantedQosLevel = new ArrayList();
496 | //依次处理订阅
497 | for (TopicSubscribe topicSubscribe : topicSubscribes) {
498 | String topicFilter = topicSubscribe.getTopicFilter();
499 | QoS qos = topicSubscribe.getQos();
500 | Subscription newSubscription = new Subscription(clientID, topicFilter, qos, cleanSession);
501 | //订阅新的订阅
502 | subscribeSingleTopic(newSubscription, topicFilter);
503 |
504 | //生成suback荷载
505 | grantedQosLevel.add(qos.value());
506 | }
507 |
508 | SubAckMessage subAckMessage = (SubAckMessage) MQTTMesageFactory.newMessage(
509 | FixedHeader.getSubAckFixedHeader(),
510 | new PackageIdVariableHeader(subscribeMessage.getVariableHeader().getPackageID()),
511 | new SubAckPayload(grantedQosLevel));
512 |
513 | Log.info("回写subAck消息给订阅者,包ID={"+subscribeMessage.getVariableHeader().getPackageID()+"}");
514 | client.writeAndFlush(subAckMessage);
515 | }
516 |
517 |
518 | /**
519 | * 处理协议的unSubscribe消息类型
520 | * @param client
521 | * @param unSubscribeMessage
522 | * @author zer0
523 | * @version 1.0
524 | * @date 2015-5-24
525 | */
526 | public void processUnSubscribe(Channel client, UnSubscribeMessage unSubscribeMessage){
527 | String clientID = NettyAttrManager.getAttrClientId(client);
528 | int packageID = unSubscribeMessage.getVariableHeader().getPackageID();
529 | Log.info("处理unSubscribe数据包,客户端ID={"+clientID+"}");
530 | List topicFilters = unSubscribeMessage.getPayload().getTopics();
531 | for (String topic : topicFilters) {
532 | //取消订阅树里的订阅
533 | subscribeStore.removeSubscription(topic, clientID);
534 | sessionStore.removeSubscription(topic, clientID);
535 | }
536 |
537 | Message unSubAckMessage = MQTTMesageFactory.newMessage(
538 | FixedHeader.getUnSubAckFixedHeader(),
539 | new PackageIdVariableHeader(packageID),
540 | null);
541 | Log.info("回写unSubAck信息给客户端,包ID为{"+packageID+"}");
542 | client.writeAndFlush(unSubAckMessage);
543 | }
544 |
545 | /**
546 | * 处理协议的pingReq消息类型
547 | * @param client
548 | * @param pingReqMessage
549 | * @author zer0
550 | * @version 1.0
551 | * @date 2015-5-24
552 | */
553 | public void processPingReq(Channel client, Message pingReqMessage){
554 | Log.info("收到心跳包");
555 | Message pingRespMessage = MQTTMesageFactory.newMessage(
556 | FixedHeader.getPingRespFixedHeader(),
557 | null,
558 | null);
559 | //重置心跳包计时器
560 | client.writeAndFlush(pingRespMessage);
561 | }
562 |
563 | /**
564 | * 处理协议的disconnect消息类型
565 | * @param client
566 | * @param disconnectMessage
567 | * @author zer0
568 | * @version 1.0
569 | * @date 2015-5-24
570 | */
571 | public void processDisconnet(Channel client, Message disconnectMessage){
572 | String clientID = NettyAttrManager.getAttrClientId(client);
573 | boolean cleanSession = NettyAttrManager.getAttrCleanSession(client);
574 | if (cleanSession) {
575 | cleanSession(clientID);
576 | }
577 |
578 | willStore.remove(clientID);
579 |
580 | this.clients.remove(clientID);
581 | client.close();
582 | }
583 |
584 | /**
585 | * 清除会话,除了要从订阅树中删掉会话信息,还要从会话存储中删除会话信息
586 | * @param client
587 | * @author zer0
588 | * @version 1.0
589 | * @date 2015-05-07
590 | */
591 | private void cleanSession(String clientID) {
592 | subscribeStore.removeForClient(clientID);
593 | //从会话存储中删除信息
594 | sessionStore.wipeSubscriptions(clientID);
595 | }
596 |
597 | /**
598 | * 在客户端重连以后,针对QoS1和Qos2的消息,重发存储的离线消息
599 | * @param clientID
600 | * @author zer0
601 | * @version 1.0
602 | * @date 2015-05-18
603 | */
604 | private void republishMessage(String clientID){
605 | //取出需要重发的消息列表
606 | //查看消息列表是否为空,为空则返回
607 | //不为空则依次发送消息并从会话中删除此消息
608 | List publishedEvents = messagesStore.listMessagesInSession(clientID);
609 | if (publishedEvents.isEmpty()) {
610 | Log.info("没有客户端{"+clientID+"}存储的离线消息");
611 | return;
612 | }
613 |
614 | Log.info("重发客户端{"+ clientID +"}存储的离线消息");
615 | for (PublishEvent pubEvent : publishedEvents) {
616 | boolean dup = true;
617 | sendPublishMessage(pubEvent.getClientID(),
618 | pubEvent.getTopic(),
619 | pubEvent.getQos(),
620 | Unpooled.buffer().writeBytes(pubEvent.getMessage()),
621 | pubEvent.isRetain(),
622 | pubEvent.getPackgeID(),
623 | dup);
624 | messagesStore.removeMessageInSessionForPublish(clientID, pubEvent.getPackgeID());
625 | }
626 | }
627 |
628 | /**
629 | * 在未收到对应包的情况下,重传Publish消息
630 | * @param publishKey
631 | * @author zer0
632 | * @version 1.0
633 | * @date 2015-11-28
634 | */
635 | public void reUnKnowPublishMessage(String publishKey){
636 | PublishEvent pubEvent = messagesStore.searchQosPublishMessage(publishKey);
637 | Log.info("重发PublishKey为{"+ publishKey +"}的Publish离线消息");
638 | boolean dup = true;
639 | PublishMessage publishMessage = (PublishMessage) MQTTMesageFactory.newMessage(
640 | FixedHeader.getPublishFixedHeader(dup, pubEvent.getQos(), pubEvent.isRetain()),
641 | new PublishVariableHeader(pubEvent.getTopic(), pubEvent.getPackgeID()),
642 | Unpooled.buffer().writeBytes(pubEvent.getMessage()));
643 | //从会话列表中取出会话,然后通过此会话发送publish消息
644 | this.clients.get(pubEvent.getClientID()).getClient().writeAndFlush(publishMessage);
645 | }
646 |
647 | /**
648 | * 在未收到对应包的情况下,重传PubRel消息
649 | * @param pubRelKey
650 | * @author zer0
651 | * @version 1.0
652 | * @date 2015-11-28
653 | */
654 | public void reUnKnowPubRelMessage(String pubRelKey){
655 | PubRelEvent pubEvent = messagesStore.searchPubRelMessage(pubRelKey);
656 | Log.info("重发PubRelKey为{"+ pubRelKey +"}的PubRel离线消息");
657 | sendPubRel(pubEvent.getClientID(), pubEvent.getPackgeID());
658 | // messagesStore.removeQosPublishMessage(pubRelKey);
659 | }
660 |
661 | /**
662 | * 取出所有匹配topic的客户端,然后发送public消息给客户端
663 | * @param topic
664 | * @param qos
665 | * @param message
666 | * @param retain
667 | * @param PackgeID
668 | * @author zer0
669 | * @version 1.0
670 | * @date 2015-05-19
671 | */
672 | private void sendPublishMessage(String topic, QoS originQos, ByteBuf message, boolean retain, boolean dup){
673 | for (final Subscription sub : subscribeStore.getClientListFromTopic(topic)) {
674 |
675 | String clientID = sub.getClientID();
676 | Integer sendPackageID = PackageIDManager.getNextMessageId();
677 | String publishKey = String.format("%s%d", clientID, sendPackageID);
678 | QoS qos = originQos;
679 |
680 | //协议P43提到, 假设请求的QoS级别被授权,客户端接收的PUBLISH消息的QoS级别小于或等于这个级别,PUBLISH 消息的级别取决于发布者的原始消息的QoS级别
681 | if (originQos.ordinal() > sub.getRequestedQos().ordinal()) {
682 | qos = sub.getRequestedQos();
683 | }
684 |
685 | PublishMessage publishMessage = (PublishMessage) MQTTMesageFactory.newMessage(
686 | FixedHeader.getPublishFixedHeader(dup, qos, retain),
687 | new PublishVariableHeader(topic, sendPackageID),
688 | message);
689 |
690 | if (this.clients == null) {
691 | throw new RuntimeException("内部错误,clients为null");
692 | } else {
693 | Log.debug("clients为{"+this.clients+"}");
694 | }
695 |
696 | if (this.clients.get(clientID) == null) {
697 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
698 | } else {
699 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
700 | }
701 |
702 | if (originQos == QoS.AT_MOST_ONCE) {
703 | publishMessage = (PublishMessage) MQTTMesageFactory.newMessage(
704 | FixedHeader.getPublishFixedHeader(dup, qos, retain),
705 | new PublishVariableHeader(topic),
706 | message);
707 | //从会话列表中取出会话,然后通过此会话发送publish消息
708 | this.clients.get(clientID).getClient().writeAndFlush(publishMessage);
709 | }else {
710 | publishKey = String.format("%s%d", clientID, sendPackageID);//针对每个重生成key,保证消息ID不会重复
711 | //将ByteBuf转变为byte[]
712 | byte[] messageBytes = new byte[message.readableBytes()];
713 | message.getBytes(message.readerIndex(), messageBytes);
714 | PublishEvent storePublishEvent = new PublishEvent(topic, qos, messageBytes, retain, clientID, sendPackageID);
715 |
716 | //从会话列表中取出会话,然后通过此会话发送publish消息
717 | this.clients.get(clientID).getClient().writeAndFlush(publishMessage);
718 | //存临时Publish消息,用于重发
719 | messagesStore.storeQosPublishMessage(publishKey, storePublishEvent);
720 | //开启Publish重传任务,在制定时间内未收到PubAck包则重传该条Publish信息
721 | Map jobParam = new HashMap();
722 | jobParam.put("ProtocolProcess", this);
723 | jobParam.put("publishKey", publishKey);
724 | QuartzManager.addJob(publishKey, "publish", publishKey, "publish", RePublishJob.class, 10, 2, jobParam);
725 | }
726 |
727 | Log.info("服务器发送消息给客户端{"+clientID+"},topic{"+topic+"},qos{"+qos+"}");
728 |
729 | if (!sub.isCleanSession()) {
730 | //将ByteBuf转变为byte[]
731 | byte[] messageBytes = new byte[message.readableBytes()];
732 | message.getBytes(message.readerIndex(), messageBytes);
733 | PublishEvent newPublishEvt = new PublishEvent(topic, qos, messageBytes,
734 | retain, sub.getClientID(),
735 | sendPackageID != null ? sendPackageID : 0);
736 | messagesStore.storeMessageToSessionForPublish(newPublishEvt);
737 | }
738 |
739 |
740 | }
741 | }
742 |
743 | /**
744 | * 发送publish消息给指定ID的客户端
745 | * @param clientID
746 | * @param topic
747 | * @param qos
748 | * @param message
749 | * @param retain
750 | * @param PackgeID
751 | * @param dup
752 | * @author zer0
753 | * @version 1.0
754 | * @date 2015-05-19
755 | */
756 | private void sendPublishMessage(String clientID, String topic, QoS qos, ByteBuf message, boolean retain, Integer packageID, boolean dup){
757 | Log.info("发送pulicMessage给指定客户端");
758 |
759 | String publishKey = String.format("%s%d", clientID, packageID);
760 |
761 | PublishMessage publishMessage = (PublishMessage) MQTTMesageFactory.newMessage(
762 | FixedHeader.getPublishFixedHeader(dup, qos, retain),
763 | new PublishVariableHeader(topic, packageID),
764 | message);
765 |
766 | if (this.clients == null) {
767 | throw new RuntimeException("内部错误,clients为null");
768 | } else {
769 | Log.debug("clients为{"+this.clients+"}");
770 | }
771 |
772 | if (this.clients.get(clientID) == null) {
773 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
774 | } else {
775 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
776 | }
777 |
778 | if (qos == QoS.AT_MOST_ONCE) {
779 | publishMessage = (PublishMessage) MQTTMesageFactory.newMessage(
780 | FixedHeader.getPublishFixedHeader(dup, qos, retain),
781 | new PublishVariableHeader(topic),
782 | message);
783 | //从会话列表中取出会话,然后通过此会话发送publish消息
784 | this.clients.get(clientID).getClient().writeAndFlush(publishMessage);
785 | }else {
786 | publishKey = String.format("%s%d", clientID, packageID);//针对每个重生成key,保证消息ID不会重复
787 | //将ByteBuf转变为byte[]
788 | byte[] messageBytes = new byte[message.readableBytes()];
789 | message.getBytes(message.readerIndex(), messageBytes);
790 | PublishEvent storePublishEvent = new PublishEvent(topic, qos, messageBytes, retain, clientID, packageID);
791 |
792 | //从会话列表中取出会话,然后通过此会话发送publish消息
793 | this.clients.get(clientID).getClient().writeAndFlush(publishMessage);
794 | //存临时Publish消息,用于重发
795 | messagesStore.storeQosPublishMessage(publishKey, storePublishEvent);
796 | //开启Publish重传任务,在制定时间内未收到PubAck包则重传该条Publish信息
797 | Map jobParam = new HashMap();
798 | jobParam.put("ProtocolProcess", this);
799 | jobParam.put("publishKey", publishKey);
800 | QuartzManager.addJob(publishKey, "publish", publishKey, "publish", RePublishJob.class, 10, 2, jobParam);
801 | }
802 | }
803 |
804 | /**
805 | * 发送保存的Retain消息
806 | * @param clientID
807 | * @param topic
808 | * @param qos
809 | * @param message
810 | * @param retain
811 | * @author zer0
812 | * @version 1.0
813 | * @date 2015-12-1
814 | */
815 | private void sendPublishMessage(String clientID, String topic, QoS qos, ByteBuf message, boolean retain){
816 | int packageID = PackageIDManager.getNextMessageId();
817 | sendPublishMessage(clientID, topic, qos, message, retain, packageID, false);
818 | }
819 |
820 | /**
821 | *回写PubAck消息给发来publish的客户端
822 | * @param clientID
823 | * @param packgeID
824 | * @author zer0
825 | * @version 1.0
826 | * @date 2015-5-21
827 | */
828 | private void sendPubAck(String clientID, Integer packageID) {
829 | Log.info("发送PubAck消息给客户端");
830 |
831 | Message pubAckMessage = MQTTMesageFactory.newMessage(
832 | FixedHeader.getPubAckFixedHeader(),
833 | new PackageIdVariableHeader(packageID),
834 | null);
835 |
836 | try {
837 | if (this.clients == null) {
838 | throw new RuntimeException("内部错误,clients为null");
839 | } else {
840 | Log.debug("clients为{"+this.clients+"}");
841 | }
842 |
843 | if (this.clients.get(clientID) == null) {
844 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
845 | } else {
846 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
847 | }
848 |
849 | this.clients.get(clientID).getClient().writeAndFlush(pubAckMessage);
850 | }catch(Throwable t) {
851 | Log.error(null, t);
852 | }
853 | }
854 |
855 | /**
856 | *回写PubRec消息给发来publish的客户端
857 | * @param clientID
858 | * @param packgeID
859 | * @author zer0
860 | * @version 1.0
861 | * @date 2015-5-21
862 | */
863 | private void sendPubRec(String clientID, Integer packageID) {
864 | Log.trace("发送PubRec消息给客户端");
865 |
866 | Message pubRecMessage = MQTTMesageFactory.newMessage(
867 | FixedHeader.getPubAckFixedHeader(),
868 | new PackageIdVariableHeader(packageID),
869 | null);
870 |
871 | try {
872 | if (this.clients == null) {
873 | throw new RuntimeException("内部错误,clients为null");
874 | } else {
875 | Log.debug("clients为{"+this.clients+"}");
876 | }
877 |
878 | if (this.clients.get(clientID) == null) {
879 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
880 | } else {
881 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
882 | }
883 |
884 | this.clients.get(clientID).getClient().writeAndFlush(pubRecMessage);
885 | }catch(Throwable t) {
886 | Log.error(null, t);
887 | }
888 | }
889 |
890 | /**
891 | *回写PubRel消息给发来publish的客户端
892 | * @param clientID
893 | * @param packgeID
894 | * @author zer0
895 | * @version 1.0
896 | * @date 2015-5-23
897 | */
898 | private void sendPubRel(String clientID, Integer packageID) {
899 | Log.trace("发送PubRel消息给客户端");
900 |
901 | Message pubRelMessage = MQTTMesageFactory.newMessage(
902 | FixedHeader.getPubAckFixedHeader(),
903 | new PackageIdVariableHeader(packageID),
904 | null);
905 |
906 | try {
907 | if (this.clients == null) {
908 | throw new RuntimeException("内部错误,clients为null");
909 | } else {
910 | Log.debug("clients为{"+this.clients+"}");
911 | }
912 |
913 | if (this.clients.get(clientID) == null) {
914 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
915 | } else {
916 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
917 | }
918 |
919 | this.clients.get(clientID).getClient().writeAndFlush(pubRelMessage);
920 | }catch(Throwable t) {
921 | Log.error(null, t);
922 | }
923 | }
924 |
925 | /**
926 | * 回写PubComp消息给发来publish的客户端
927 | * @param clientID
928 | * @param packgeID
929 | * @author zer0
930 | * @version 1.0
931 | * @date 2015-5-23
932 | */
933 | private void sendPubComp(String clientID, Integer packageID) {
934 | Log.trace("发送PubComp消息给客户端");
935 |
936 | Message pubcompMessage = MQTTMesageFactory.newMessage(
937 | FixedHeader.getPubAckFixedHeader(),
938 | new PackageIdVariableHeader(packageID),
939 | null);
940 |
941 | try {
942 | if (this.clients == null) {
943 | throw new RuntimeException("内部错误,clients为null");
944 | } else {
945 | Log.debug("clients为{"+this.clients+"}");
946 | }
947 |
948 | if (this.clients.get(clientID) == null) {
949 | throw new RuntimeException("不能从会话列表{"+this.clients+"}中找到clientID:{"+clientID+"}");
950 | } else {
951 | Log.debug("从会话列表{"+this.clients+"}查找到clientID:{"+clientID+"}");
952 | }
953 |
954 | this.clients.get(clientID).getClient().writeAndFlush(pubcompMessage);
955 | }catch(Throwable t) {
956 | Log.error(null, t);
957 | }
958 | }
959 |
960 | /**
961 | * 处理一个单一订阅,存储到会话和订阅数
962 | * @param newSubscription
963 | * @param topic
964 | * @author zer0
965 | * @version 1.0
966 | * @date 2015-5-25
967 | */
968 | private void subscribeSingleTopic(Subscription newSubscription, final String topic){
969 | Log.info("订阅topic{"+topic+"},Qos为{"+newSubscription.getRequestedQos()+"}");
970 | String clientID = newSubscription.getClientID();
971 | sessionStore.addNewSubscription(newSubscription, clientID);
972 | subscribeStore.addSubscrpition(newSubscription);
973 | //TODO 此处还需要将此订阅之前存储的信息发出去
974 | Collection messages = messagesStore.searchRetained(topic);
975 | for (IMessagesStore.StoredMessage storedMsg : messages) {
976 | Log.debug("send publish message for topic {" + topic + "}");
977 | sendPublishMessage(newSubscription.getClientID(), storedMsg.getTopic(), storedMsg.getQos(), Unpooled.buffer().writeBytes(storedMsg.getPayload()), true);
978 | }
979 | }
980 | }
981 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/event/PubRelEvent.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.event;
2 |
3 | /**
4 | * PubRel的事件类,只有Qos=2的时候才会有此事件
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2015-11-27
9 | */
10 | public class PubRelEvent {
11 | String clientID;
12 | int packgeID;
13 |
14 | public PubRelEvent(String clientID, Integer pkgID){
15 | this.clientID = clientID;
16 | this.packgeID = pkgID;
17 | }
18 |
19 | public String getClientID() {
20 | return clientID;
21 | }
22 |
23 | public void setClientID(String clientID) {
24 | this.clientID = clientID;
25 | }
26 |
27 | public int getPackgeID() {
28 | return packgeID;
29 | }
30 |
31 | public void setPackgeID(int packgeID) {
32 | this.packgeID = packgeID;
33 | }
34 |
35 |
36 |
37 | }
38 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/event/PublishEvent.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.event;
2 |
3 | import io.netty.buffer.ByteBuf;
4 |
5 | import java.io.Serializable;
6 |
7 | import com.syxy.protocol.mqttImp.message.QoS;
8 |
9 | /**
10 | * 发送消息的事件类,把协议的处理当做事件来进行就可以很好的进行封装
11 | *
12 | * @author zer0
13 | * @version 1.0
14 | * @date 2015-05-11
15 | */
16 | public class PublishEvent implements Serializable{
17 | String topic;
18 | QoS qos;
19 | byte[] message;
20 | boolean retain;
21 | String clientID;
22 | //针对Qos1和Qos2
23 | int packgeID;
24 |
25 | public PublishEvent(String topic, QoS qos, byte[] message, boolean retain, String clientID, Integer pkgID){
26 | this.topic = topic;
27 | this.qos = qos;
28 | this.message = message;
29 | this.retain = retain;
30 | this.clientID = clientID;
31 | if (qos != QoS.AT_MOST_ONCE) {
32 | this.packgeID = pkgID;
33 | }
34 | }
35 |
36 | public String getTopic() {
37 | return topic;
38 | }
39 |
40 | public void setTopic(String topic) {
41 | this.topic = topic;
42 | }
43 |
44 | public QoS getQos() {
45 | return qos;
46 | }
47 |
48 | public void setQos(QoS qos) {
49 | this.qos = qos;
50 | }
51 |
52 | public byte[] getMessage() {
53 | return message;
54 | }
55 |
56 | public void setMessage(byte[] message) {
57 | this.message = message;
58 | }
59 |
60 | public boolean isRetain() {
61 | return retain;
62 | }
63 |
64 | public void setRetain(boolean retain) {
65 | this.retain = retain;
66 | }
67 |
68 | public String getClientID() {
69 | return clientID;
70 | }
71 |
72 | public void setClientID(String clientID) {
73 | this.clientID = clientID;
74 | }
75 |
76 | public int getPackgeID() {
77 | return packgeID;
78 | }
79 |
80 | public void setPackgeID(int packgeID) {
81 | this.packgeID = packgeID;
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/event/job/RePubRelJob.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.event.job;
2 |
3 | import org.quartz.Job;
4 | import org.quartz.JobDataMap;
5 | import org.quartz.JobExecutionContext;
6 | import org.quartz.JobExecutionException;
7 |
8 | import com.syxy.protocol.mqttImp.process.ProtocolProcess;
9 |
10 | /**
11 | * Publish消息重发事件需要做的工作,即重发消息到对应的clientID
12 | *
13 | * @author zer0
14 | * @version 1.0
15 | * @date 2015-11-26
16 | */
17 | public class RePubRelJob implements Job{
18 |
19 | @Override
20 | public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
21 | //取出参数,参数为ProtocolProcess,调用此类的函数
22 | JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
23 | ProtocolProcess process = (ProtocolProcess) dataMap.get("ProtocolProcess");
24 | String pubRelKey = (String) dataMap.get("pubRelKey");
25 | process.reUnKnowPubRelMessage(pubRelKey);
26 | }
27 |
28 | }
29 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/event/job/RePublishJob.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.event.job;
2 |
3 | import org.apache.log4j.Logger;
4 | import org.quartz.Job;
5 | import org.quartz.JobDataMap;
6 | import org.quartz.JobExecutionContext;
7 | import org.quartz.JobExecutionException;
8 |
9 | import com.syxy.protocol.mqttImp.process.ProtocolProcess;
10 | import com.syxy.util.QuartzManager;
11 |
12 | /**
13 | * Publish消息重发事件需要做的工作,即重发消息到对应的clientID
14 | *
15 | * @author zer0
16 | * @version 1.0
17 | * @date 2015-11-26
18 | */
19 | public class RePublishJob implements Job{
20 |
21 | private final static Logger Log = Logger.getLogger(RePublishJob.class);
22 | int count = 0;
23 |
24 | @Override
25 | public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
26 | //取出参数,参数为ProtocolProcess,调用此类的函数
27 | JobDataMap dataMap = jobExecutionContext.getJobDetail().getJobDataMap();
28 | ProtocolProcess process = (ProtocolProcess) dataMap.get("ProtocolProcess");
29 | String publishKey = (String) dataMap.get("publishKey");
30 | process.reUnKnowPublishMessage(publishKey);
31 | }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/subscribe/SubscribeStore.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.subscribe;
2 |
3 | import java.text.ParseException;
4 | import java.util.ArrayList;
5 | import java.util.Collection;
6 | import java.util.Collections;
7 | import java.util.List;
8 | import java.util.Queue;
9 | import java.util.concurrent.LinkedBlockingDeque;
10 |
11 | /**
12 | * 订阅存储类,存储订阅的topic,即订阅树,订阅的通配符支持是此处的一个难点,
13 | * 根据协议P41,也可以选择不支持通配符主题过滤器,若不支持通配符过滤器,则必须
14 | * 拒绝含通配符过滤器的订阅请求
15 | * @author zer0
16 | * @version 1.0
17 | * @date 2015-4-11
18 | */
19 | public class SubscribeStore {
20 |
21 | private TreeNode root = new TreeNode(null);
22 |
23 | /**
24 | * 添加新的订阅到订阅树里
25 | * @param newSubscription
26 | * @author zer0
27 | * @version 1.0
28 | * @date 2015-04-19
29 | */
30 | public void addSubscrpition(Subscription newSubscription){
31 | List treeNodes = searchNodeList(newSubscription.topicFilter);
32 | for (TreeNode t : treeNodes) {
33 | t.addSubscription(newSubscription);
34 | }
35 | }
36 |
37 | /**
38 | * 根据topic搜索节点,若无此节点则创建,最后返回一个所搜索的所有节点的列表
39 | * @param topic
40 | * @return List
41 | * @author zer0
42 | * @version 1.0
43 | * @date 2015-04-25
44 | */
45 | public List searchNodeList(String topic){
46 | List tokens = new ArrayList();
47 | List childList = new ArrayList();
48 |
49 | try {
50 | tokens = parseTopic(topic);
51 | } catch (ParseException e) {
52 | e.printStackTrace();
53 | }
54 | TreeNode current = root;
55 | for (int i = 0; i < tokens.size(); i++) {
56 | Token token = tokens.get(i);
57 | TreeNode matchingChildren = current;
58 |
59 | //TODO 为实现简便,此处违反协议
60 | if (token == Token.SINGLE) {
61 | //判断‘+’是不是在队列最后一个
62 | if (i == tokens.size() - 1) {
63 | childList.addAll(matchingChildren.children);
64 | }
65 | return childList;
66 | }
67 |
68 | //遇到#,证明已搜索到底部,不再生成节点
69 | if (token == Token.MULTI) {
70 | if (i == tokens.size() - 1) {
71 | //判断‘#’是不是在队列最后一个
72 | childList.add(matchingChildren);
73 | childList.addAll(matchingChildren.getAllDescendant());
74 | }
75 | return childList;
76 | }
77 |
78 | if ((matchingChildren = current.childWithToken(token)) != null) {
79 | current = matchingChildren;
80 | } else {
81 | matchingChildren = new TreeNode(current);
82 | matchingChildren.setToken(token);
83 | current.addChild(matchingChildren);
84 | current = matchingChildren;
85 | }
86 |
87 | //如果该token是最后一个,则添加到返回列表里
88 | if (i == tokens.size() - 1) {
89 | childList.add(current);
90 | }
91 | }
92 | return childList;
93 | }
94 |
95 | /**
96 | * 解析topic,从topic获取到对应的客户端ID群
97 | * @param topic
98 | * @return List
99 | * @author zer0
100 | * @version 1.0
101 | * @date 2015-05-04
102 | */
103 | public List getClientListFromTopic(String topic) {
104 | List tokens;
105 | try {
106 | tokens = parseTopic(topic);
107 | } catch (ParseException e) {
108 | return Collections.emptyList();
109 | }
110 | Queue tokenQueue = new LinkedBlockingDeque(tokens);
111 | List matchingSubs = new ArrayList();
112 | root.getSubscription(tokenQueue, matchingSubs);
113 | return matchingSubs;
114 | }
115 |
116 | /**
117 | * 把某个clientID从订阅树里移走
118 | * @param clientID
119 | * @author zer0
120 | * @version 1.0
121 | * @date 2015-05-04
122 | */
123 | public void removeForClient(String clientID) {
124 | root.removeClientSubscription(clientID);
125 | }
126 |
127 | /**
128 | * 从订阅结构树中移除某个订阅主题中的某个client
129 | * @param topic
130 | * @param clientID
131 | * @author zer0
132 | * @version 1.0
133 | * @date 2015-06-29
134 | */
135 | public void removeSubscription(String topic, String clientID){
136 | List treeNodes = searchNodeList(topic);
137 | for (TreeNode t : treeNodes) {
138 | t.removeClientSubscription(clientID);
139 | //如果某个节点的订阅量为空,并且该节点无子节点,则删除它
140 | if (t.subscriptions.size() == 0 && t.children.size() == 0) {
141 | t.parent.children.remove(t);
142 | }
143 | }
144 | }
145 |
146 | /**
147 | * 比较两个Topic字段是否相等,需处理#,+等情况
148 | * @param retainTopic
149 | * @param subscriptionTopic
150 | * @return boolean
151 | * @author zer0
152 | * @version 1.0
153 | * @date 2015-12-1
154 | */
155 | public static boolean matchTopics(String retainTopic, String subscriptionTopic) {
156 | try {
157 | List retainTokens = SubscribeStore.parseTopic(retainTopic);
158 | List subscriptionTokens = SubscribeStore.parseTopic(subscriptionTopic);
159 | int i = 0;
160 | Token subToken = null;
161 | for (; i< subscriptionTokens.size(); i++) {
162 | subToken = subscriptionTokens.get(i);
163 | if (subToken != Token.MULTI && subToken != Token.SINGLE) {
164 | if (i >= retainTokens.size()) {
165 | return false;
166 | }
167 | Token msgToken = retainTokens.get(i);
168 | if (!msgToken.equals(subToken)) {
169 | return false;
170 | }
171 | } else {
172 | if (subToken == Token.MULTI) {
173 | return true;
174 | }
175 | if (subToken == Token.SINGLE) {
176 | //跳过
177 | }
178 | }
179 | }
180 | return i == retainTokens.size();
181 | } catch (ParseException ex) {
182 | ex.printStackTrace();
183 | throw new RuntimeException(ex);
184 | }
185 | }
186 |
187 | /**
188 | * 解析topic,根据协议得到对应的token列表
189 | * @param topic
190 | * @return List
191 | * @author zer0
192 | * @version 1.0
193 | * @date 2015-06-29
194 | */
195 | static List parseTopic(String topic) throws ParseException{
196 | List tokens = new ArrayList();
197 | String[] token = topic.split("/");
198 |
199 | if (token.length == 0) {
200 | tokens.add(Token.EMPTY);
201 | }
202 |
203 | for (int i = 0; i < token.length; i++) {
204 | String s = token[i];
205 | if (s.isEmpty()) {
206 | tokens.add(Token.EMPTY);
207 | }else if (s.equals("#")) {
208 | if (i != token.length - 1) {
209 | throw new ParseException("无效的的topic," + s + "#必须放在分隔符的最尾", i);
210 | }else {
211 | tokens.add(Token.MULTI);
212 | }
213 | }else if (s.contains("#")){
214 | throw new ParseException("无效的topic"+s, i);
215 | }else if (s.equals("+")) {
216 | //违反协议,不好实现,视情况看以后是否处理
217 | if (i != token.length - 1) {
218 | throw new ParseException("无效的的topic," + s + "此服务器暂不支持通配符‘+’作为中层分隔符", i);
219 | }else {
220 | tokens.add(Token.SINGLE);
221 | }
222 | }else if (s.contains("+")) {
223 | throw new ParseException("无效的topic"+s, i);
224 | }else{
225 | tokens.add(new Token(s));
226 | }
227 | }
228 | return tokens;
229 | }
230 | }
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/subscribe/Subscription.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.subscribe;
2 |
3 | import java.io.Serializable;
4 |
5 | import com.syxy.protocol.mqttImp.message.QoS;
6 |
7 | /**
8 | * 订阅的树节点,保存订阅的每个节点的信息
9 | *
10 | * @author zer0
11 | * @version 1.0
12 | * @date 2015-4-11
13 | */
14 | public class Subscription implements Serializable{
15 |
16 | private static final long serialVersionUID = 6159635655653102856L;
17 | QoS requestedQos; //max QoS acceptable
18 | String topicFilter;
19 | String clientID;
20 | boolean cleanSession;
21 | boolean active = true;
22 |
23 | public Subscription(String clientID, String topicFilter, QoS requestedQos, boolean cleanSession) {
24 | this.clientID = clientID;
25 | this.requestedQos = requestedQos;
26 | this.topicFilter = topicFilter;
27 | this.cleanSession = cleanSession;
28 | }
29 |
30 | public QoS getRequestedQos() {
31 | return requestedQos;
32 | }
33 |
34 | public void setRequestedQos(QoS requestedQos) {
35 | this.requestedQos = requestedQos;
36 | }
37 |
38 | public String getTopicFilter() {
39 | return topicFilter;
40 | }
41 |
42 | public void setTopicFilter(String topicFilter) {
43 | this.topicFilter = topicFilter;
44 | }
45 |
46 | public boolean isCleanSession() {
47 | return cleanSession;
48 | }
49 |
50 | public void setCleanSession(boolean cleanSession) {
51 | this.cleanSession = cleanSession;
52 | }
53 |
54 | public boolean isActive() {
55 | return active;
56 | }
57 |
58 | public void setActive(boolean active) {
59 | this.active = active;
60 | }
61 |
62 | public void setClientID(String clientID) {
63 | this.clientID = clientID;
64 | }
65 |
66 | public String getClientID() {
67 | return clientID;
68 | }
69 |
70 | @Override
71 | public String toString() {
72 | return String.format("[filter:%s, cliID: %s, qos: %s, active: %s]", this.topicFilter, this.clientID, this.requestedQos, this.active);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/subscribe/Token.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.subscribe;
2 |
3 | /**
4 | * 此类用于存储每个Topic解析出来的订阅(Topic:country/china/tianjin)
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2015-4-19
9 | */
10 | public class Token {
11 |
12 | static final Token MULTI = new Token("#");
13 | static final Token SINGLE = new Token("+");
14 | static final Token EMPTY = new Token("");
15 | String name;
16 |
17 | Token(String name){
18 | this.name = name;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/protocol/mqttImp/process/subscribe/TreeNode.java:
--------------------------------------------------------------------------------
1 | package com.syxy.protocol.mqttImp.process.subscribe;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.Queue;
6 | import java.util.concurrent.CopyOnWriteArrayList;
7 | import java.util.concurrent.LinkedBlockingDeque;
8 |
9 | /**
10 | * 订阅树的节点,包含父节点、代表节点的token、子节点列表和该节点包含的客户端ID,此部分参考moquette
11 | *
12 | * @author zer0
13 | * @version 1.0
14 | * @date 2015-4-26
15 | */
16 | public class TreeNode {
17 |
18 | TreeNode parent;
19 | Token token;
20 | List children = new CopyOnWriteArrayList();//子节点列表
21 | List subscriptions = new ArrayList();//客户ID列表,每个subscription代表一个clientID
22 |
23 | public TreeNode(TreeNode parent) {
24 | this.parent = parent;
25 | }
26 |
27 | /**
28 | * 添加新的clientID,clientID包含在subscription中,要注意避免重复添加和qos不一致但存在的情况
29 | * @param subscription
30 | * @author zer0
31 | * @version 1.0
32 | * @date 2015-4-28
33 | */
34 | void addSubscription(Subscription subscription){
35 | //避免同样的订阅添加进来
36 | if (subscriptions.contains(subscription)) {
37 | return;
38 | }
39 |
40 | //同一节点中topic是一样的,所以判断clientID是否重复,若topic和clientID一样,但qos不一样,就移除,重新添加
41 | for (Subscription s : subscriptions) {
42 | if (s.clientID.equals(subscription.clientID)) {
43 | return;
44 | }
45 | }
46 |
47 | subscriptions.add(subscription);
48 | }
49 |
50 | /**
51 | * 移除已订阅的主题
52 | * @param subscription
53 | * @author zer0
54 | * @version 1.0
55 | * @date 2015-4-28
56 | */
57 | void removeSubscription(Subscription subscription){
58 | //避免同样的订阅添加进来
59 | if (subscriptions.contains(subscription)) {
60 | return;
61 | }
62 |
63 | //同一节点中topic是一样的,所以判断clientID是否重复,若topic和clientID一样,但qos不一样,就移除,重新添加
64 | for (Subscription s : subscriptions) {
65 | if (s.clientID.equals(subscription.clientID)) {
66 | return;
67 | }
68 | }
69 |
70 | subscriptions.add(subscription);
71 | }
72 |
73 | /**
74 | * 添加子节点
75 | * @param child
76 | * @author zer0
77 | * @version 1.0
78 | * @date 2015-4-28
79 | */
80 | void addChild(TreeNode child){
81 | children.add(child);
82 | }
83 |
84 | /**
85 | * 查询该节点的子节点是否包含了某个token,包含了就返回节点,不包含则返回null
86 | * @param token
87 | * @return TreeNode
88 | * @author zer0
89 | * @version 1.0
90 | * @date 2015-4-28
91 | */
92 | TreeNode childWithToken(Token token) {
93 | for (TreeNode child : children) {
94 | if (child.getToken().equals(token)) {
95 | return child;
96 | }
97 | }
98 | return null;
99 | }
100 |
101 | /**
102 | * 返回此节点下的所有子孙节点
103 | * @return List
104 | * @author zer0
105 | * @version 1.0
106 | * @date 2015-4-29
107 | */
108 | List getAllDescendant() {
109 | List treeNodes = new ArrayList();
110 | if (this.children.size() > 0) {
111 | for (TreeNode t : children) {
112 | treeNodes.addAll(t.getAllDescendant());
113 | }
114 | }
115 | return treeNodes;
116 | }
117 |
118 | /**
119 | * 取出主题匹配的订阅
120 | * @param tokens
121 | * @param matchingSubs
122 | * @author zer0
123 | * @version 1.0
124 | * @date 2015-5-04
125 | */
126 | void getSubscription(Queue tokens, List matchingSubs){
127 | Token t = tokens.poll();
128 | //如果t为null,正面已经取到最后一个token,这时候就直接取出该节点的客户端列表
129 | if (t == null) {
130 | matchingSubs.addAll(subscriptions);
131 | return;
132 | }
133 |
134 | for (TreeNode n : children) {
135 | System.out.println(n.getToken().name);
136 | if (n.getToken().name.equals(t.name)) {
137 | n.getSubscription(new LinkedBlockingDeque(tokens), matchingSubs);
138 | }
139 | }
140 | }
141 |
142 | /**
143 | * 移除该节点以及其所有子节点中包含的此clientID
144 | * @param clientID
145 | * @author zer0
146 | * @version 1.0
147 | * @date 2015-5-04
148 | */
149 | void removeClientSubscription(String clientID){
150 | List subsToRemove = new ArrayList();
151 | for (Subscription s : subscriptions) {
152 | if (s.clientID.equals(clientID)) {
153 | subsToRemove.add(s);
154 | }
155 | }
156 |
157 | for (Subscription s : subsToRemove) {
158 | subscriptions.remove(s);
159 | }
160 |
161 | //遍历
162 | for (TreeNode child : children) {
163 | child.removeClientSubscription(clientID);
164 | }
165 | }
166 |
167 | public Token getToken() {
168 | return token;
169 | }
170 |
171 | public void setToken(Token token) {
172 | this.token = token;
173 | }
174 |
175 | }
176 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/server/StartServer.java:
--------------------------------------------------------------------------------
1 | package com.syxy.server;
2 |
3 | import io.netty.channel.ChannelFuture;
4 |
5 | /**
6 | * 启动服务器,主线程所在
7 | *
8 | * @author zer0
9 | * @version 1.0
10 | * @date 2015-2-14
11 | */
12 | public class StartServer {
13 |
14 | public static void main(String[] args){
15 | final TcpServer tcpServer = new TcpServer();
16 | ChannelFuture future = tcpServer.startServer();
17 |
18 | Runtime.getRuntime().addShutdownHook(new Thread(){
19 | @Override
20 | public void run() {
21 | tcpServer.destory();
22 | }
23 | });
24 | future.channel().closeFuture().syncUninterruptibly();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/server/TcpServer.java:
--------------------------------------------------------------------------------
1 | package com.syxy.server;
2 |
3 | import java.net.InetSocketAddress;
4 |
5 | import io.netty.bootstrap.ServerBootstrap;
6 | import io.netty.channel.Channel;
7 | import io.netty.channel.ChannelFuture;
8 | import io.netty.channel.ChannelInitializer;
9 | import io.netty.channel.ChannelOption;
10 | import io.netty.channel.ChannelPipeline;
11 | import io.netty.channel.EventLoopGroup;
12 | import io.netty.channel.nio.NioEventLoopGroup;
13 | import io.netty.channel.socket.SocketChannel;
14 | import io.netty.channel.socket.nio.NioServerSocketChannel;
15 | import io.netty.util.concurrent.Future;
16 |
17 | import org.apache.log4j.Logger;
18 |
19 | import com.syxy.protocol.mqttImp.MQTTDecoder;
20 | import com.syxy.protocol.mqttImp.MQTTEncoder;
21 | import com.syxy.protocol.mqttImp.MQTTProcess;
22 | import com.syxy.util.MqttTool;
23 |
24 | /**
25 | * 基于JAVA AIO的,面向TCP/IP的,非阻塞式Sockect服务器框架类
26 | *
27 | * @author zer0
28 | * @version 1.0
29 | * @date 2015-2-15
30 | */
31 | public class TcpServer {
32 |
33 | private final static Logger Log = Logger.getLogger(TcpServer.class);
34 |
35 | //系统常量配置
36 | private static final String PORT = "port";//端口号
37 |
38 | private volatile Integer port;//服务器端口
39 | private Channel channel;
40 | private EventLoopGroup bossGroup;
41 | private EventLoopGroup workerGroup;
42 |
43 | public TcpServer(){
44 | this.port = MqttTool.getPropertyToInt(PORT);// 从配置中获取端口号
45 | if(this.port == null){
46 | this.port = 8088;// 设置默认端口为8088
47 | }
48 | }
49 |
50 | /**
51 | * 启动服务器
52 | *
53 | * @author zer0
54 | * @version 1.0
55 | * @date 2015-2-19
56 | */
57 | public ChannelFuture startServer(){
58 | bossGroup = new NioEventLoopGroup();
59 | workerGroup = new NioEventLoopGroup();
60 | ServerBootstrap bootstrap = new ServerBootstrap();
61 | bootstrap.group(bossGroup, workerGroup)
62 | .channel(NioServerSocketChannel.class)
63 | .childHandler(new ChannelInitializer() {
64 |
65 | @Override
66 | protected void initChannel(SocketChannel ch)
67 | throws Exception {
68 | ChannelPipeline pipeline = ch.pipeline();
69 | pipeline.addLast("MQTTDecoder", new MQTTDecoder());
70 | pipeline.addLast("MQTTEncoder", new MQTTEncoder());
71 | pipeline.addLast("MQTTProcess", new MQTTProcess());
72 | //心跳处理在收到CONNECT消息协议的时候,根据协议内容动态添加
73 | }
74 | })
75 | .option(ChannelOption.SO_BACKLOG, 1024)
76 | .childOption(ChannelOption.SO_KEEPALIVE, true);
77 | try {
78 | ChannelFuture future = bootstrap.bind(new InetSocketAddress(port)).sync();
79 | channel = future.channel();
80 | Log.info("服务器已启动,端口:" + port);
81 | return future;
82 | } catch (InterruptedException e) {
83 | e.printStackTrace();
84 | }
85 | return null;
86 | }
87 |
88 | public void destory(){
89 | if (channel != null) {
90 | channel.close();
91 | }
92 | bossGroup.shutdownGracefully();
93 | workerGroup.shutdownGracefully();
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/util/BufferPool.java:
--------------------------------------------------------------------------------
1 | package com.syxy.util;
2 |
3 | import java.nio.ByteBuffer;
4 | import java.util.concurrent.ConcurrentLinkedQueue;
5 | import java.util.concurrent.atomic.AtomicInteger;
6 |
7 | import org.apache.log4j.Logger;
8 |
9 | /**
10 | * 缓冲池
11 | *
12 | * @author zer0
13 | * @version 1.0
14 | * @date 2015-3-5
15 | */
16 | public class BufferPool{
17 | private static Logger log = Logger.getLogger(BufferPool.class);// 日志记录器
18 |
19 | private static final String MAX_BUFFER_POOL_SIZE = "maxBufferPoolSize";// 直接缓冲区池上限大小
20 | private static final String MIN_BUFFER_POOL_SIZE = "minBufferPoolSize";// 直接缓冲区池下限大小
21 | private static final String WRITE_BUFFER = "writeBuffer";
22 |
23 | private static int maxBufferPoolSize = 1000;// 默认的直接缓冲区池上限大小1000
24 | private static int minBufferPoolSize = 1000;// 默认的直接缓冲区池下限大小1000
25 | private static int writeBufferSize = 64;// 响应缓冲区大小默认为64k
26 |
27 | private static BufferPool bufferPool = new BufferPool();// BufferPool的单实例
28 |
29 | private AtomicInteger usableCount = new AtomicInteger();// 可用缓冲区的数量
30 | private AtomicInteger createCount = new AtomicInteger();// 已创建了缓冲区的数量
31 | private ConcurrentLinkedQueue queue = new ConcurrentLinkedQueue();// 保存直接缓存的队列
32 |
33 | static{
34 | // 设置缓冲区池上限大小
35 | Integer maxSize = MqttTool.getPropertyToInt(MAX_BUFFER_POOL_SIZE);// 获取配置中的线程优先级
36 | if(maxSize != null){
37 | maxBufferPoolSize = maxSize;
38 | }
39 |
40 | // 设置缓冲区池下限大小
41 | Integer minSize = MqttTool.getPropertyToInt(MIN_BUFFER_POOL_SIZE);// 获取配置中的线程优先级
42 | if(minSize != null){
43 | minBufferPoolSize = minSize;
44 | }
45 |
46 | // 设置响应缓冲区大小
47 | Integer bufferSize = MqttTool.getPropertyToInt(WRITE_BUFFER);
48 | if(bufferSize != null){
49 | writeBufferSize = bufferSize;
50 | }
51 | }
52 |
53 | private BufferPool(){
54 | // 预先创建直接缓冲区
55 | for(int i = 0; i < minBufferPoolSize; ++i){
56 | ByteBuffer bb = ByteBuffer.allocate(writeBufferSize * 1024);
57 | this.queue.add(bb);
58 | }
59 |
60 | // 设置可用的缓冲区和已创建的缓冲区数量
61 | this.usableCount.set(minBufferPoolSize);
62 | this.createCount.set(minBufferPoolSize);
63 | }
64 |
65 | /**
66 | * 获取缓冲区
67 | *
68 | * @return ByteBuffer
69 | * @author zer0
70 | * @version 1.0
71 | * @date 2015-3-5
72 | */
73 | public ByteBuffer getBuffer(){
74 | ByteBuffer bytebuffer = this.queue.poll();
75 |
76 | if(bytebuffer == null){// 如果缓冲区不够则创建新的缓冲区
77 | bytebuffer = ByteBuffer.allocate(writeBufferSize * 1024);
78 | this.createCount.incrementAndGet();
79 | }else{
80 | this.usableCount.decrementAndGet();
81 | }
82 |
83 | return bytebuffer;
84 | }
85 |
86 | /**
87 | * 释放缓冲区
88 | *
89 | * @param bytebuffer
90 | * @author zer0
91 | * @version 1.0
92 | * @date 2015-3-5
93 | */
94 | public void releaseBuffer(ByteBuffer bytebuffer){
95 | if(this.createCount.intValue() > maxBufferPoolSize && (this.usableCount.intValue() > (this.createCount.intValue() / 2) ) ){
96 | bytebuffer = null;
97 | this.createCount.decrementAndGet();
98 | }else{
99 | this.queue.add(bytebuffer);
100 | this.usableCount.incrementAndGet();
101 | }
102 | }
103 |
104 | /**
105 | * 移除bytebuffer中的已读数据
106 | *
107 | * @param byteBuffer
108 | * @author zer0
109 | * @version 1.0
110 | * @date 2015-3-5
111 | */
112 | public static void removeReadedData(ByteBuffer byteBuffer){
113 | byteBuffer.compact();
114 | byteBuffer.flip();
115 | }
116 |
117 | public static BufferPool getInstance(){
118 | return bufferPool;
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/util/Constant.java:
--------------------------------------------------------------------------------
1 | package com.syxy.util;
2 |
3 | /**
4 | * 公共变量类
5 | *
6 | * @author zer0
7 | * @version 1.0
8 | * @date 2015-2-15
9 | */
10 | public class Constant {
11 |
12 |
13 | public static final String PING_ARRIVE = "pingIsArrive";//心跳包到达
14 | public static final String CONNECT_ARRIVE = "connectIsArrive";//连接数据包到达
15 | }
16 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/util/MqttTool.java:
--------------------------------------------------------------------------------
1 | package com.syxy.util;
2 |
3 | import java.io.FileInputStream;
4 | import java.io.IOException;
5 | import java.util.Properties;
6 |
7 | import org.apache.log4j.Logger;
8 |
9 | /**
10 | * Java Properties属性文件操作类
11 | *
12 | * @author zer0
13 | * @version 1.0
14 | * @date 2015-2-19
15 | */
16 | public class MqttTool {
17 |
18 | private final static Logger Log = Logger.getLogger(MqttTool.class);
19 |
20 | private static Properties props = new Properties();
21 | //配置文件路径
22 | private static final String CONFIG_FILE = System.getProperty("user.dir") + "/zer0MQTTServer/resource/mqtt.properties";
23 |
24 | static{
25 | loadProperties(CONFIG_FILE);
26 | }
27 |
28 | /**
29 | * 加载属性文件
30 | *
31 | * @param propertyFilePath
32 | * @author zer0
33 | * @version 1.0
34 | * @date 2015-2-19
35 | */
36 | private static void loadProperties(String propertyFilePath){
37 | try {
38 | FileInputStream in = new FileInputStream(propertyFilePath);
39 | props = new Properties();
40 | props.load(in);
41 | } catch (IOException e) {
42 | Log.error("属性文件读取错误");
43 | e.printStackTrace();
44 | }
45 | }
46 |
47 | /**
48 | * 从指定的键取得对应的值
49 | *
50 | * @param key
51 | * @return String
52 | * @author zer0
53 | * @version 1.0
54 | * @date 2015-2-19
55 | */
56 | public static String getProperty(String key){
57 | return props.getProperty(key);
58 | }
59 |
60 | /**
61 | * 从指定的键取得整数
62 | *
63 | * @param key
64 | * @return Integer
65 | * @author zer0
66 | * @version 1.0
67 | * @date 2015-2-19
68 | */
69 | public static Integer getPropertyToInt(String key){
70 | String str = props.getProperty(key);
71 | if(StringTool.isBlank(str.trim())){
72 | return null;
73 | }
74 | return Integer.valueOf(str.trim());
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/util/QuartzManager.java:
--------------------------------------------------------------------------------
1 | package com.syxy.util;
2 |
3 | import static org.quartz.DateBuilder.futureDate;
4 | import static org.quartz.JobBuilder.newJob;
5 |
6 | import java.util.Date;
7 | import java.util.Map;
8 |
9 | import org.apache.log4j.Logger;
10 | import org.quartz.JobDetail;
11 | import org.quartz.JobKey;
12 | import org.quartz.Scheduler;
13 | import org.quartz.SchedulerException;
14 | import org.quartz.SchedulerFactory;
15 | import org.quartz.SimpleScheduleBuilder;
16 | import org.quartz.Trigger;
17 | import org.quartz.TriggerBuilder;
18 | import org.quartz.DateBuilder.IntervalUnit;
19 | import org.quartz.TriggerKey;
20 | import org.quartz.impl.StdSchedulerFactory;
21 |
22 | /**
23 | * Quatrz调度框架的管理类,用于添加,删除,重置任务
24 | *
25 | * @author zer0
26 | * @version 1.0
27 | * @date 2015-11-30
28 | */
29 | public class QuartzManager {
30 |
31 | private final static Logger Log = Logger.getLogger(QuartzManager.class);
32 |
33 | private static SchedulerFactory sf = new StdSchedulerFactory();
34 |
35 | /**
36 | * 添加调度任务
37 | * @param jobName
38 | * @param jobGroupName
39 | * @param triggerName
40 | * @param triggerGroupName
41 | * @param jobClass
42 | * @param time
43 | * @param jobParam
44 | * @param count重复次数
45 | * @author zer0
46 | * @version 1.0
47 | * @date 2015-11-28
48 | */
49 | @SuppressWarnings("unchecked")
50 | public static void addJob(String jobName, String jobGroupName,
51 | String triggerName, String triggerGroupName, Class jobClass,
52 | int time, int count, Map jobParam) {
53 | try {
54 | Scheduler sched = sf.getScheduler();
55 | JobDetail job = newJob(jobClass).withIdentity(jobName, jobGroupName).build();// 任务名,任务组,任务执行类
56 | if (jobParam != null && jobParam.size() > 0) {
57 | //给job添加参数
58 | job.getJobDataMap().putAll(jobParam);
59 | }
60 | // 触发器
61 | Trigger trigger = TriggerBuilder.newTrigger().withIdentity(triggerName, triggerGroupName)
62 | .startAt(futureDate(time, IntervalUnit.SECOND))
63 | .withSchedule(SimpleScheduleBuilder
64 | .simpleSchedule()
65 | .withIntervalInSeconds(time)
66 | .withRepeatCount(count))
67 | .build();
68 | Date ft = sched.scheduleJob(job, trigger);
69 | Log.info(jobName + "启动于" + ft);
70 | // 启动
71 | if (!sched.isShutdown()) {
72 | sched.start();
73 | }
74 | } catch (Exception e) {
75 | throw new RuntimeException(e);
76 | }
77 | }
78 |
79 | /**
80 | * 删除调度任务
81 | * @param jobName
82 | * @param jobGroupName
83 | * @param triggerName
84 | * @param triggerGroupName
85 | * @author zer0
86 | * @version 1.0
87 | * @date 2015-11-28
88 | */
89 | public static void removeJob(String jobName,String jobGroupName,
90 | String triggerName,String triggerGroupName){
91 | try {
92 | Scheduler sched = sf.getScheduler();
93 | TriggerKey triggerKey = new TriggerKey(triggerName, triggerGroupName);
94 | JobKey jobKey = new JobKey(jobName, jobGroupName);
95 | sched.pauseTrigger(triggerKey);//停止触发器
96 | sched.unscheduleJob(triggerKey);//移除触发器
97 | sched.deleteJob(jobKey);//删除任务
98 | } catch (SchedulerException e) {
99 | e.printStackTrace();
100 | }
101 | }
102 |
103 | /**
104 | * 重置调度任务
105 | * @param jobName
106 | * @param jobGroupName
107 | * @param triggerName
108 | * @param triggerGroupName
109 | * @author zer0
110 | * @version 1.0
111 | * @date 2015-12-3
112 | */
113 | public static void resetJob(String jobName,String jobGroupName,
114 | String triggerName,String triggerGroupName, Class jobClass,
115 | int time, int count, Map jobParam){
116 | removeJob(jobName, jobGroupName, triggerName, triggerGroupName);
117 | addJob(jobName, jobGroupName, triggerName, triggerGroupName, jobClass, time, count, jobParam);
118 | }
119 |
120 |
121 | }
122 |
--------------------------------------------------------------------------------
/zer0MQTTServer/src/com/syxy/util/StringTool.java:
--------------------------------------------------------------------------------
1 | package com.syxy.util;
2 |
3 | import java.io.ByteArrayOutputStream;
4 | import java.io.DataOutputStream;
5 | import java.io.IOException;
6 | import java.nio.ByteBuffer;
7 | import java.util.Random;
8 | import java.util.regex.Matcher;
9 | import java.util.regex.Pattern;
10 |
11 | import sun.rmi.runtime.Log;
12 |
13 | /**
14 | * 字符串工具类
15 | *
16 | * @author zer0
17 | * @version 1.0
18 | * @date 2015-2-19
19 | */
20 | public class StringTool {
21 |
22 | /**
23 | * 判断字符串是否为空
24 | *
25 | * @param str
26 | * @return boolean
27 | * @author zer0
28 | * @version 1.0
29 | * @date 2015-2-19
30 | */
31 | public static boolean isBlank(String str){
32 | if(str==null){
33 | return true;
34 | }
35 | if(str.trim().length()<1){
36 | return true;
37 | }
38 | if(str.trim().equals("")){
39 | return true;
40 | }
41 | if(str.trim().toLowerCase().equals("null")){
42 | return true;
43 | }
44 | return false;
45 | }
46 |
47 | /**
48 | * 将字符串转换为byte数组
49 | *
50 | * @param string
51 | * @return byte[]
52 | * @author zer0
53 | * @version 1.0
54 | * @date 2015-3-3
55 | */
56 | public static byte[] stringToByte(String string) {
57 | if (string == null) {
58 | return new byte[0];
59 | }
60 | ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
61 | DataOutputStream dos = new DataOutputStream(byteOut);
62 | try {
63 | dos.writeUTF(string);
64 | } catch (IOException e) {
65 | return new byte[0];
66 | }
67 | return byteOut.toByteArray();
68 | }
69 |
70 | /**
71 | * 随机生成字符串
72 | *
73 | * @param length
74 | * @return String
75 | * @author zer0
76 | * @version 1.0
77 | * @date 2015-3-7
78 | */
79 | public static String generalRandomString(int length){
80 | String str="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
81 | Random random=new Random();
82 | StringBuffer sb=new StringBuffer();
83 | for(int i=0;i