├── .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