Repository: Cicizz/jmqtt Branch: master Commit: 032ac7eababf Files: 139 Total size: 414.7 KB Directory structure: gitextract_nylx23xb/ ├── .dockerignore ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── README_CN.md ├── jmqtt-acceptance/ │ ├── README.md │ └── pom.xml ├── jmqtt-admin/ │ └── pom.xml ├── jmqtt-broker/ │ ├── pom.xml │ └── src/ │ ├── main/ │ │ ├── java/ │ │ │ └── org/ │ │ │ └── jmqtt/ │ │ │ └── broker/ │ │ │ ├── BrokerController.java │ │ │ └── BrokerStartup.java │ │ └── resources/ │ │ ├── bin/ │ │ │ ├── jmqttshutdown │ │ │ └── runbroker.sh │ │ └── conf/ │ │ ├── client.cer │ │ ├── jmqtt.properties │ │ ├── jmqtt.sql │ │ ├── log4j2.xml │ │ └── server.pfx │ └── test/ │ └── java/ │ └── org/ │ └── jmqtt/ │ └── broker/ │ └── test/ │ └── Runtime.java ├── jmqtt-bus/ │ ├── README.md │ ├── pom.xml │ └── src/ │ ├── main/ │ │ └── java/ │ │ └── org/ │ │ └── jmqtt/ │ │ └── bus/ │ │ ├── Authenticator.java │ │ ├── BusController.java │ │ ├── ClusterEventManager.java │ │ ├── DeviceMessageManager.java │ │ ├── DeviceSessionManager.java │ │ ├── DeviceSubscriptionManager.java │ │ ├── enums/ │ │ │ ├── ClusterEventCodeEnum.java │ │ │ ├── DeviceOnlineStateEnum.java │ │ │ ├── MessageAckEnum.java │ │ │ ├── MessageFlowEnum.java │ │ │ ├── MessageSourceEnum.java │ │ │ └── TransportProtocolEnum.java │ │ ├── event/ │ │ │ ├── EventCenter.java │ │ │ └── GatewayListener.java │ │ ├── impl/ │ │ │ ├── ClusterEventManagerImpl.java │ │ │ ├── DefaultAuthenticator.java │ │ │ ├── DeviceMessageManagerImpl.java │ │ │ ├── DeviceSessionManagerImpl.java │ │ │ └── DeviceSubscriptionManagerImpl.java │ │ ├── model/ │ │ │ ├── ClusterEvent.java │ │ │ ├── DeviceInboxMessage.java │ │ │ ├── DeviceMessage.java │ │ │ ├── DeviceSession.java │ │ │ └── DeviceSubscription.java │ │ ├── store/ │ │ │ ├── AbstractDBStore.java │ │ │ ├── DBCallback.java │ │ │ ├── DBUtils.java │ │ │ ├── daoobject/ │ │ │ │ ├── DeviceInboxMessageDO.java │ │ │ │ ├── EventDO.java │ │ │ │ ├── MessageDO.java │ │ │ │ ├── RetainMessageDO.java │ │ │ │ ├── SessionDO.java │ │ │ │ └── SubscriptionDO.java │ │ │ └── mapper/ │ │ │ ├── ClientInboxMessageMapper.java │ │ │ ├── EventMapper.java │ │ │ ├── MessageMapper.java │ │ │ ├── RetainMessageMapper.java │ │ │ ├── SessionMapper.java │ │ │ └── SubscriptionMapper.java │ │ └── subscription/ │ │ ├── CNode.java │ │ ├── CTrie.java │ │ ├── CTrieSubscriptionMatcher.java │ │ ├── DefaultSubscriptionTreeMatcher.java │ │ ├── DumpTreeVisitor.java │ │ ├── INode.java │ │ ├── SubscriptionCounterVisitor.java │ │ ├── SubscriptionMatcher.java │ │ ├── TNode.java │ │ ├── Token.java │ │ ├── Topic.java │ │ └── model/ │ │ └── Subscription.java │ └── test/ │ └── java/ │ └── org/ │ └── jmqtt/ │ ├── AppTest.java │ └── bus/ │ └── subscription/ │ ├── CTrieSubscriptionMatcherMatchingTest.java │ ├── CTrieTest.java │ └── TopicTest.java ├── jmqtt-doc/ │ ├── README.md │ ├── docs/ │ │ ├── README.md │ │ └── TEST_REPORT.md │ └── pom.xml ├── jmqtt-example/ │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── jmqtt/ │ ├── java/ │ │ ├── Consumer.java │ │ └── Producer.java │ └── websocket/ │ ├── paho-mqtt-min.js │ ├── paho-mqtt.js │ └── webSocket.html ├── jmqtt-manager ├── jmqtt-mqtt/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── jmqtt/ │ └── mqtt/ │ ├── ConnectManager.java │ ├── MQTTConnection.java │ ├── MQTTConnectionFactory.java │ ├── MQTTServer.java │ ├── codec/ │ │ ├── ByteBuf2WebSocketEncoder.java │ │ └── WebSocket2ByteBufDecoder.java │ ├── event/ │ │ └── MqttEventListener.java │ ├── model/ │ │ └── MqttTopic.java │ ├── netty/ │ │ ├── MqttNettyUtils.java │ │ ├── MqttRemotingServer.java │ │ ├── NettyMqttHandler.java │ │ └── NettySslHandler.java │ ├── protocol/ │ │ ├── RequestProcessor.java │ │ └── impl/ │ │ ├── ConnectProcessor.java │ │ ├── DisconnectProcessor.java │ │ ├── PingProcessor.java │ │ ├── PubAckProcessor.java │ │ ├── PubCompProcessor.java │ │ ├── PubRecProcessor.java │ │ ├── PubRelProcessor.java │ │ ├── PublishProcessor.java │ │ ├── SubscribeProcessor.java │ │ └── UnSubscribeProcessor.java │ ├── retain/ │ │ ├── RetainMessageHandler.java │ │ └── impl/ │ │ └── RetainMessageHandlerImpl.java │ ├── session/ │ │ └── MqttSession.java │ └── utils/ │ ├── MqttMessageUtil.java │ └── MqttMsgHeader.java ├── jmqtt-support/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── jmqtt/ │ └── support/ │ ├── config/ │ │ ├── BrokerConfig.java │ │ └── NettyConfig.java │ ├── exception/ │ │ ├── RemotingConnectException.java │ │ ├── RemotingException.java │ │ ├── RemotingSendRequestException.java │ │ ├── RemotingTimeoutException.java │ │ └── RemotingTooMuchRequestException.java │ ├── helper/ │ │ ├── MixAll.java │ │ ├── Pair.java │ │ ├── RejectHandler.java │ │ └── ThreadFactoryImpl.java │ ├── log/ │ │ ├── JmqttLogger.java │ │ └── LogUtil.java │ └── remoting/ │ ├── RemotingHelper.java │ └── RemotingService.java ├── jmqtt-tcp/ │ ├── README.md │ ├── pom.xml │ └── src/ │ └── main/ │ └── java/ │ └── org/ │ └── jmqtt/ │ └── App.java └── pom.xml ================================================ FILE CONTENTS ================================================ ================================================ FILE: .dockerignore ================================================ rocksdb.db .git .env *.jpg *.png *.md **/target ================================================ FILE: .gitignore ================================================ storage/ run/ test_out/ tmp/ doc/ .directory .loadpath .metadata *.pydevproject *.bak *.swp *.tmp *~.nib local.properties */dependency-reduced-pom.xml # Maven # ######### target/ # Eclipse # ########### .project .classpath .settings/ .externalToolBuilders/ *.launch .recommenders/ # Intellij # ############ .idea/ *.iml *.ipr *.iws # Compiled source # ################### *.class *.com *.dll *.exe *.o *.so ======= # Compiled class file *.class # Log file *.log # BlueJ files *.ctxt # Mobile Tools for Java (J2ME) .mtj.tmp/ # Package Files # *.jar *.war *.nar *.ear *.zip *.tar.gz *.rar # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml hs_err_pid* rocksdb.db/ ================================================ FILE: Dockerfile ================================================ # Builder FROM maven:3-jdk-8-slim AS builder RUN mkdir /opt/jmqtt WORKDIR /opt/jmqtt ADD . ./ RUN mvn -Ppackage-all -DskipTests clean install -U # App FROM openjdk:8-jre-slim RUN mkdir /opt/jmqtt VOLUME /var/jmqtt-data WORKDIR /opt/jmqtt COPY --from=builder /opt/jmqtt ./ COPY --from=builder /opt/jmqtt/jmqtt-distribution/conf /var/jmqtt-data/conf/ EXPOSE 1883 EXPOSE 1884 CMD ["./jmqtt-distribution/target/jmqtt/bin/jmqttstart", "-h", "/var/jmqtt-data", "-c", "/var/jmqtt-data/conf/jmqtt.properties"] ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. ================================================ FILE: README.md ================================================ **English** | [中文](README_CN.md) # Jmqtt ![Jmqtt logo](jmqtt.png) ## Features * Full support of mqtt3.1.1 protocol * Support data persistence and clustering based on MySQL * Support friendly secondary development, plug-in development: cluster / storage / device connection, publish subscribe authentication * Support tcp, websocket, SSL, WSS ## Official documents [Official documents](https://arrogant95.github.io/jmqtt-docs/) ## Quick start 1. Download [release](https://github.com/Cicizz/jmqtt/releases) (Version above 3. X) Or `clone` this project 2. Execute in the broker directory:`mvn -Ppackage-all -DskipTests clean install -U` 3. Configuration file for configuration response:`/jmqtt-broker/resources/conf` 4. Execute the start command:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -H is followed by the configuration file directory, which needs to contain jmqtt.properties And log4j2. XML ## Online trial Server address: 81.69.46.38 TCP port: 1883 Websocket port: 8883 SSL port: 1884 WSS port: 8884 ## QQ technology exchange group ![jmqtt技术交流群](jmqtt_qq2.png) ## test report jmqtt-doc/docs/TEST_REPORT.md ================================================ FILE: README_CN.md ================================================ [English](README.md) | **中文** ## Jmqtt ![Jmqtt logo](jmqtt.png) ## 功能特性 * 完整支持mqtt3.1.1协议 * 支持基于mysql的数据持久化和集群 * 支持友好的二次开发,插件化开发:集群/存储/设备连接,发布订阅认证 * 支持tcp, websocket,ssl,wss ## 官方文档 [官方文档](https://arrogant95.github.io/jmqtt-docs/) ## 快速开始 1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目 2. 在broker模块下执行:`mvn -Ppackage-all -DskipTests clean install -U` 3. 配置配置文件并初始化db的sql:`/jmqtt-broker/resources/conf`目录下 4. 执行启动命令:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录,里面需要包含jmqtt.properties和log4j2.xml等配置文件 ## 技术交流群 欢迎小伙伴们给个star并使用。 ![jmqtt技术交流群](jmqtt_qq2.png) ## 测试报告 https://www.yuque.com/tristan-ku8np/zze/xghq80 ================================================ FILE: jmqtt-acceptance/README.md ================================================ 这里是存放iot mq所有的功能测试,性能测试脚本及测试结果 ## 功能测试计划(基于集群) ### 1.连接功能项 1. 连接 2. 连接 with cleasession = false(mqtt) 3. 连接 with will 消息(mqtt) 4. 使用ws连接 5. 连接使用ssl 6. 重新发起连接 ### 2.发布消息 1. 发布qos0消息(mqtt) 2. 发布qos1消息(mqtt) 3. 发布qos2消息(mqtt) 4. 发布retain消息(mqtt) ### 3. 订阅 1. 订阅普通topic 2. 订阅多级topic 3. 订阅收到retain消息(mqtt) 4. 发布订阅拉通测试发送消息 ### 4. 关闭连接 ## 性能测试计划 ## 1. 同时在线连接数压测 1. 连接数分级:100连接数/单机,500连接数/单机,1000连接数/单机,5000连接数/单机,1W连接数/单机 2. 同时请求连接:10/s,50/s,100/s,200/s,500/s -- 单机 3. 集群测试(2+服务器):1W连接数/集群;5W连接数/集群;10W连接数/集群 4. 集群测试(2+服务器):1000/s,2000/s 5. 集群测试(2+服务器):同时在线10W设备,1000设备发送消息 ## 2.发送消息TPS 消息字节大小:100byte[] 1. 单机:500/s,1000/s,5000/s,10000/s 2. 集群(2+台服务器):10000/s,15000/s,20000/s ## 3.订阅 1. 单设备订阅topic:10topic/单设备,50topic/单设备,100topic/单设备 2. 单机订阅树:10topic/单设备,50topic/单设备,100topic/单设备:分别连接1000设备,5000设备,10000设备 ## 4.消费消息 1. 集群测试:同时在线10W设备,1000设备消费消息 ================================================ FILE: jmqtt-acceptance/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 UTF-8 1.8 1.8 jmqtt-acceptance org.eclipse.paho org.eclipse.paho.client.mqttv3 1.2.5 commons-cli commons-cli ${commons-cli.version} org.apache.maven.plugins maven-assembly-plugin jar-with-dependencies make-assembly package single ================================================ FILE: jmqtt-admin/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 3.0.0 jmqtt-admin org.jmqtt jmqtt-broker io.netty netty-all ================================================ FILE: jmqtt-broker/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-broker 3.0.0 jmqtt-broker UTF-8 1.8 1.8 2.6.13 2.13 org.jmqtt jmqtt-mqtt org.assertj assertj-core org.junit.jupiter junit-jupiter-api test org.springframework.boot spring-boot-maven-plugin true org.jmqtt.broker.BrokerStartup ZIP repackage ================================================ FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerController.java ================================================ package org.jmqtt.broker; import org.jmqtt.bus.BusController; import org.jmqtt.mqtt.MQTTServer; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.config.NettyConfig; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; /** * 具体控制类:负责加载配置文件,初始化环境,启动服务等 */ public class BrokerController { private static final Logger log = JmqttLogger.brokerLog; private BrokerConfig brokerConfig; private NettyConfig nettyConfig; /** bus */ private BusController busController; /** mqtt gateway */ private MQTTServer mqttServer; /** coap gateway todo */ public BrokerController(BrokerConfig brokerConfig, NettyConfig nettyConfig){ this.brokerConfig = brokerConfig; this.nettyConfig = nettyConfig; this.busController = new BusController(brokerConfig); this.mqttServer = new MQTTServer(busController,brokerConfig,nettyConfig); } public void start(){ this.busController.start(); this.mqttServer.start(); LogUtil.info(log,"Jmqtt start. version:" + brokerConfig.getVersion()); } public void shutdown(){ this.mqttServer.shutdown(); this.busController.shutdown(); } } ================================================ FILE: jmqtt-broker/src/main/java/org/jmqtt/broker/BrokerStartup.java ================================================ package org.jmqtt.broker; import org.apache.commons.cli.*; import org.apache.commons.lang3.StringUtils; import org.apache.logging.log4j.Level; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.core.LoggerContext; import org.apache.logging.log4j.core.config.Configuration; import org.apache.logging.log4j.core.config.LoggerConfig; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.config.NettyConfig; import org.jmqtt.support.helper.MixAll; import java.io.*; import java.util.Map; import java.util.Objects; import java.util.Properties; /** * 技术问题,二次开发问题:请加 qq群:578185385 */ public class BrokerStartup { public static void main(String[] args) { try { start(args); } catch (Exception e) { System.out.println("Jmqtt start failure,cause = " + e); e.printStackTrace(); System.exit(-1); } } public static BrokerController start(String[] args) throws Exception { Options options = buildOptions(); CommandLineParser parser = new DefaultParser(); CommandLine commandLine = parser.parse(options,args); String jmqttHome = null; String logLevel = null; BrokerConfig brokerConfig = new BrokerConfig(); NettyConfig nettyConfig = new NettyConfig(); if(commandLine != null){ jmqttHome = commandLine.getOptionValue("h"); logLevel = commandLine.getOptionValue("l"); } if(StringUtils.isEmpty(jmqttHome)){ jmqttHome = brokerConfig.getJmqttHome(); } if(StringUtils.isEmpty(jmqttHome)){ throw new Exception("please set JMQTT_HOME."); } String jmqttConfigPath = jmqttHome + File.separator + "jmqtt.properties"; initConfig(jmqttConfigPath,brokerConfig,nettyConfig); // 日志配置加载 try { LoggerContext context = (org.apache.logging.log4j.core.LoggerContext) LogManager.getContext(false); File file = new File(jmqttHome + File.separator + "log4j2.xml"); context.setConfigLocation(file.toURI()); Configuration configuration = context.getConfiguration(); Map loggerConfigMap = configuration.getLoggers(); Level newLevel = logLevel == null? null : Level.getLevel(logLevel); if (newLevel == null) { newLevel = Level.INFO; } for (LoggerConfig value : loggerConfigMap.values()) { value.setLevel(newLevel); } context.updateLoggers(configuration); } catch (Exception ex) { System.err.print("Log4j2 load error,ex:" + ex); } // 启动服务,线程等 BrokerController brokerController = new BrokerController(brokerConfig,nettyConfig); brokerController.start(); Runtime.getRuntime().addShutdownHook(new Thread(new Runnable() { @Override public void run() { brokerController.shutdown(); } })); return brokerController; } private static Options buildOptions(){ Options options = new Options(); Option opt = new Option("h",true,"jmqttHome,eg: /wls/xxx"); opt.setRequired(false); options.addOption(opt); opt = new Option("c",true,"jmqtt.properties path,eg: /wls/xxx/xxx.properties"); opt.setRequired(false); options.addOption(opt); opt = new Option("l",true,"DEBUG"); opt.setRequired(false); options.addOption(opt); return options; } /** * convert properties to java config class * @param jmqttConfigPath * @param brokerConfig * @param nettyConfig */ private static void initConfig(String jmqttConfigPath, BrokerConfig brokerConfig, NettyConfig nettyConfig){ Properties properties = new Properties(); BufferedReader bufferedReader = null; try { bufferedReader = new BufferedReader(new FileReader(jmqttConfigPath)); properties.load(bufferedReader); MixAll.properties2POJO(properties,brokerConfig); MixAll.properties2POJO(properties,nettyConfig); } catch (FileNotFoundException e) { System.out.println("jmqtt.properties cannot find,cause + " + e + ",path:"+jmqttConfigPath); } catch (IOException e) { System.out.println("Handle jmqttConfig IO exception,cause = " + e); } finally { try { if(Objects.nonNull(bufferedReader)){ bufferedReader.close(); } } catch (IOException e) { System.out.println("Handle jmqttConfig IO exception,cause = " + e); } } } } ================================================ FILE: jmqtt-broker/src/main/resources/bin/jmqttshutdown ================================================ #!/bin/sh pid=`ps ax | grep -i 'org.jmqtt.broker.BrokerStartup' |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No jmqttBroker running." exit -1; fi echo "The jmqttBroker(${pid}) is running..." kill ${pid} echo "Send shutdown request to jmqttBroker(${pid}) OK" ================================================ FILE: jmqtt-broker/src/main/resources/bin/runbroker.sh ================================================ #!/bin/sh JAVA_OPT="${JAVA_OPT} -server -Xms1280m -Xmx1280m -Xmn640m" JAVA_OPT="${JAVA_OPT} -XX:+UseG1GC -XX:G1HeapRegionSize=16m -XX:G1ReservePercent=25 -XX:InitiatingHeapOccupancyPercent=30 -XX:SoftRefLRUPolicyMSPerMB=0 -XX:SurvivorRatio=8" JAVA_OPT="${JAVA_OPT} -verbose:gc -Xloggc:/dev/shm/mq_gc_%p.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:+PrintAdaptiveSizePolicy" JAVA_OPT="${JAVA_OPT} -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=5 -XX:GCLogFileSize=30m" JAVA_OPT="${JAVA_OPT} -XX:-OmitStackTraceInFastThrow" JAVA_OPT="${JAVA_OPT} -XX:+AlwaysPreTouch" JAVA_OPT="${JAVA_OPT} -XX:MaxDirectMemorySize=15g" JAVA_OPT="${JAVA_OPT} -XX:-UseLargePages -XX:-UseBiasedLocking" JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.ssl=false" JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.authenticate=false" JAVA_OPT="${JAVA_OPT} -Dcom.sun.management.jmxremote.port=1099" #JAVA_OPT="${JAVA_OPT} -Xdebug -Xrunjdwp:transport=dt_socket,address=9555,server=y,suspend=n" nohup java ${JAVA_OPT} -jar $@ >/dev/null 2>&1 & echo 'jmqtt start.' ================================================ FILE: jmqtt-broker/src/main/resources/conf/client.cer ================================================ -----BEGIN CERTIFICATE----- MIICWDCCAcGgAwIBAgIJANmxJ6Gxp3rbMA0GCSqGSIb3DQEBCwUAMEUxCzAJBgNV BAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEwHwYDVQQKDBhJbnRlcm5ldCBX aWRnaXRzIFB0eSBMdGQwHhcNMTkwNjMwMDk1ODE4WhcNMjkwNjI3MDk1ODE4WjBF MQswCQYDVQQGEwJBVTETMBEGA1UECAwKU29tZS1TdGF0ZTEhMB8GA1UECgwYSW50 ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB gQCgz0+Bb1uY3TZhYx/bdBegTqLLsgmLlwHqdUuWbPEg9UdhLWWAeXpjXr+w1AjH wQ0aQtDPc6bK6Bu8H11fw4RfrhEAx7TVnka/88gEUx3qivFtIt5fBDKN5dEE1PfM kXqQV2szrJNRWsQbE8rbT26hNzc16Sdj0InLlJ9Y2pa0VQIDAQABo1AwTjAdBgNV HQ4EFgQUD4R4hfIOYFFyR3gIi7PGOag7tcwwHwYDVR0jBBgwFoAUD4R4hfIOYFFy R3gIi7PGOag7tcwwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQsFAAOBgQBHdaLA RMg67YI0rEFc24MciGE+xBMSQEGegjIfo/p5frmU0kPBrdcv3XndblMPWDoGXnsE j1KZHu5Mw6dg8kid4ytvqTgvVjcjwNZ8f6Uj2mhxPJa15K5FF0D3xgCes+WiA+wF VDmoGEemCmGTIOjN4Nwb+V4/KKhOkoWhQBSLsg== -----END CERTIFICATE----- ================================================ FILE: jmqtt-broker/src/main/resources/conf/jmqtt.properties ================================================ # 是否开启匿名访问 anonymousEnable=true # max mqtt message size maxMsgSize=524288 # 是否开启tcp服务 startTcp=true tcpPort=1883 # 是否开启tcp ssl 服务 startSslTcp=true sslTcpPort=1884 #集群模式:1.基于发布订阅,集群主动push消息给Jmqtt; 2.基于poll,jmqtt主动从集群拉消息 clusterMode = 2 # 采用2.poll方式时,一次从集群中最多拉的消息数目和间隔拉取的时间(ms) maxPollEventNum = 10 pollWaitInterval = 10 # 是否开启websocket服务,ws协议 startWebsocket=true websocketPort=8883 # 是否开启websocket服务,wss协议 startSslWebsocket=true sslWebsocketPort=8884 # db config if jmqtt run with db driver=com.mysql.cj.jdbc.Driver url=jdbc:mysql://127.0.0.0:3306/jmqtt?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false&useSSL=false username=root password=CallmeZ2013 ================================================ FILE: jmqtt-broker/src/main/resources/conf/jmqtt.sql ================================================ DROP INDEX `uk_client_id` ON `jmqtt_session`; DROP INDEX `uk_client_id_topic` ON `jmqtt_subscription`; DROP INDEX `idx_client_id` ON `jmqtt_message`; DROP INDEX `idx_client_id_ack` ON `jmqtt_client_inbox`; DROP INDEX `uk_topic` ON `jmqtt_retain_message`; DROP TABLE `jmqtt_session`; DROP TABLE `jmqtt_subscription`; DROP TABLE `jmqtt_message`; DROP TABLE `jmqtt_client_inbox`; DROP TABLE `jmqtt_retain_message`; DROP TABLE `jmqtt_cluster_event`; CREATE TABLE `jmqtt_session` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `client_id` varchar(64) NOT NULL COMMENT '客户端id', `online` varchar(12) NOT NULL COMMENT '状态:ONLINE,OFFLINE两种', `transport_protocol` varchar(20) NOT NULL COMMENT '传输协议:MQTT,TCP,COAP等', `client_ip` varchar(32) NOT NULL COMMENT '客户端ip', `server_ip` varchar(32) NOT NULL COMMENT '连接的服务端ip', `last_offline_time` timestamp NULL COMMENT '上一次离线时间', `online_time` timestamp NOT NULL COMMENT '最近连接在线时间', `properties` text NULL COMMENT '扩展信息', PRIMARY KEY (`id`) , UNIQUE INDEX `uk_client_id` (`client_id` ASC) ) COMMENT = '客户端会话状态'; CREATE TABLE `jmqtt_subscription` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `client_id` varchar(64) NOT NULL COMMENT '客户端id', `topic` varchar(64) NOT NULL COMMENT '订阅的topic', `subscribe_time` timestamp NOT NULL COMMENT '订阅时间', `properties` text NULL COMMENT '订阅的额外属性', PRIMARY KEY (`id`) , UNIQUE INDEX `uk_client_id_topic` (`client_id` ASC, `topic` ASC) ) COMMENT = '客户端订阅关系'; CREATE TABLE `jmqtt_message` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `source` varchar(32) NOT NULL COMMENT '消息来源:DEVICE/PLATFORM', `content` mediumblob NOT NULL COMMENT '消息体内容:字节', `topic` varchar(64) NULL COMMENT '发送的目标topic', `from_client_id` varchar(64) NULL COMMENT '消息来源若是设备,从属设备id', `stored_time` timestamp NOT NULL COMMENT '消息落库时间', `properties` text NULL COMMENT '消息额外属性', PRIMARY KEY (`id`) , INDEX `idx_client_id` (`source` ASC) ) COMMENT = '入栈消息表'; CREATE TABLE `jmqtt_client_inbox` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键id', `client_id` varchar(64) NOT NULL COMMENT '客户端id', `message_id` bigint(20) NOT NULL COMMENT '消息id', `ack` tinyint(2) NOT NULL COMMENT '客户端是否收到消息:0未收到,1到达', `stored_time` timestamp NOT NULL COMMENT '收件箱时间', `ack_time` timestamp NULL, PRIMARY KEY (`id`) , INDEX `idx_client_id_ack` (`client_id` ASC, `ack` ASC) ) COMMENT = '设备消息收件箱表'; CREATE TABLE `jmqtt_retain_message` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键', `topic` varchar(64) NOT NULL COMMENT '所属topic', `content` mediumblob NOT NULL COMMENT '消息体内容', `from_client_id` varchar(64) NOT NULL COMMENT 'retain消息来源设备id', `stored_time` timestamp NOT NULL COMMENT '存储事件', `properties` text NOT NULL COMMENT '额外属性', PRIMARY KEY (`id`) , UNIQUE INDEX `uk_topic` (`topic` ASC) ) COMMENT = 'mqtt协议的retain 消息表'; CREATE TABLE `jmqtt_cluster_event` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键:也是集群节点批量拉消息的offset', `content` longtext NOT NULL COMMENT '消息体', `gmt_create` timestamp(6) NOT NULL COMMENT '创建时间', `node_ip` varchar(32) NOT NULL COMMENT 'jmqtt集群节点ip', `event_code` varchar(64) NOT NULL COMMENT '事件码:参考代码', PRIMARY KEY (`id`) ) COMMENT = 'jmqtt 集群事件转发表:由发送端将消息发送到该表中,其他节点批量拉取该表中的事件进行处理'; ================================================ FILE: jmqtt-broker/src/main/resources/conf/log4j2.xml ================================================ ${pattern} ================================================ FILE: jmqtt-broker/src/test/java/org/jmqtt/broker/test/Runtime.java ================================================ package org.jmqtt.broker.test; import com.sun.management.OperatingSystemMXBean; import org.junit.jupiter.api.Test; import java.lang.management.ManagementFactory; public class Runtime { @Test public void subscribe() { // 虚拟机级内存情况查询 long vmFree = 0; long vmUse = 0; long vmTotal = 0; long vmMax = 0; int byteToMb = 1024 * 1024; java.lang.Runtime rt = java.lang.Runtime.getRuntime(); vmTotal = rt.totalMemory() / byteToMb; vmFree = rt.freeMemory() / byteToMb; vmMax = rt.maxMemory() / byteToMb; vmUse = vmTotal - vmFree; System.out.println("JVM内存已用的空间为:" + vmUse + " MB"); System.out.println("JVM内存的空闲空间为:" + vmFree + " MB"); System.out.println("JVM总内存空间为:" + vmTotal + " MB"); System.out.println("JVM总内存空间为:" + vmMax + " MB"); System.out.println("======================================"); // 操作系统级内存情况查询 OperatingSystemMXBean osmxb = (OperatingSystemMXBean) ManagementFactory.getOperatingSystemMXBean(); String os = System.getProperty("os.name"); long physicalFree = osmxb.getFreePhysicalMemorySize() / byteToMb; long physicalTotal = osmxb.getTotalPhysicalMemorySize() / byteToMb; long physicalUse = physicalTotal - physicalFree; System.out.println("操作系统的版本:" + os); System.out.println("操作系统物理内存已用的空间为:" + physicalFree + " MB"); System.out.println("操作系统物理内存的空闲空间为:" + physicalUse + " MB"); System.out.println("操作系统总物理内存:" + physicalTotal + " MB"); // 获得线程总数 ThreadGroup parentThread; int totalThread = 0; for (parentThread = Thread.currentThread().getThreadGroup(); parentThread .getParent() != null; parentThread = parentThread.getParent()) { totalThread = parentThread.activeCount(); } System.out.println("获得线程总数:" + totalThread); ThreadGroup currentGroup = Thread.currentThread().getThreadGroup(); int noThreads = currentGroup.activeCount(); Thread[] lstThreads = new Thread[noThreads]; currentGroup.enumerate(lstThreads); for (int i = 0; i < noThreads; i++) System.out.println("线程号:" + i + " = " + lstThreads[i].getName()); } } ================================================ FILE: jmqtt-bus/README.md ================================================ iot 消息bus ================================================ FILE: jmqtt-bus/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-bus jmqtt-bus org.assertj assertj-core com.google.guava guava org.mybatis mybatis com.alibaba druid mysql mysql-connector-java org.apache.logging.log4j log4j-api org.apache.logging.log4j log4j-core org.jmqtt jmqtt-support org.junit.jupiter junit-jupiter-api test ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/Authenticator.java ================================================ package org.jmqtt.bus; public interface Authenticator { boolean login(String clientId,String userName,byte[] password); boolean onBlackList(String clientId,String remoteIpAddress); boolean clientIdVerify(String clientId); boolean subscribeVerify(String clientId,String topic); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/BusController.java ================================================ package org.jmqtt.bus; import org.jmqtt.bus.impl.*; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.subscription.CTrieSubscriptionMatcher; import org.jmqtt.bus.subscription.SubscriptionMatcher; import org.jmqtt.support.config.BrokerConfig; public class BusController { private BrokerConfig brokerConfig; private Authenticator authenticator; private DeviceSessionManager deviceSessionManager; private DeviceMessageManager deviceMessageManager; private DeviceSubscriptionManager deviceSubscriptionManager; private SubscriptionMatcher subscriptionMatcher; private ClusterEventManager clusterEventManager; public BusController(BrokerConfig brokerConfig){ this.brokerConfig = brokerConfig; this.authenticator = new DefaultAuthenticator(); this.deviceMessageManager = new DeviceMessageManagerImpl(); this.deviceSessionManager = new DeviceSessionManagerImpl(); //this.subscriptionMatcher = new DefaultSubscriptionTreeMatcher(); this.subscriptionMatcher = new CTrieSubscriptionMatcher(); this.deviceSubscriptionManager = new DeviceSubscriptionManagerImpl(subscriptionMatcher); this.clusterEventManager = new ClusterEventManagerImpl(subscriptionMatcher); } public void start(){ DBUtils.getInstance().start(brokerConfig); this.clusterEventManager.start(); } public void shutdown(){ this.clusterEventManager.shutdown(); DBUtils.getInstance().shutdown(); } public Authenticator getAuthenticator() { return authenticator; } public DeviceSessionManager getDeviceSessionManager() { return deviceSessionManager; } public DeviceMessageManager getDeviceMessageManager() { return deviceMessageManager; } public DeviceSubscriptionManager getDeviceSubscriptionManager() { return deviceSubscriptionManager; } public ClusterEventManager getClusterEventManager() { return clusterEventManager; } public SubscriptionMatcher getSubscriptionMatcher() { return subscriptionMatcher; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/ClusterEventManager.java ================================================ package org.jmqtt.bus; import org.jmqtt.bus.event.GatewayListener; import org.jmqtt.bus.model.ClusterEvent; /** * cluster event send and listen */ public interface ClusterEventManager { void sendEvent(ClusterEvent clusterEvent); void registerEventListener(GatewayListener listener); void start(); void shutdown(); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceMessageManager.java ================================================ package org.jmqtt.bus; import org.jmqtt.bus.enums.MessageAckEnum; import org.jmqtt.bus.model.DeviceMessage; import java.util.List; public interface DeviceMessageManager { void clearUnAckMessage(String clientId); /** * send message to bus * @param deviceMessage */ void dispatcher(DeviceMessage deviceMessage); Long storeMessage(DeviceMessage deviceMessage); List queryUnAckMessages(String clientId,int limit); List queryByIds(List ids); Long addClientInBoxMsg(String clientId,Long messageId, MessageAckEnum ackEnum); boolean ackMessage(String clientId,Long messageId); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSessionManager.java ================================================ package org.jmqtt.bus; import org.jmqtt.bus.model.DeviceSession; public interface DeviceSessionManager { DeviceSession getSession(String clientId); void storeSession(DeviceSession deviceSession); void offline(String clientId); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/DeviceSubscriptionManager.java ================================================ package org.jmqtt.bus; import org.jmqtt.bus.model.DeviceSubscription; import java.util.Set; public interface DeviceSubscriptionManager { /** * 1. Add subscription to the sub tree * 2. Persistence subscription * @param deviceSubscription * @return true:sub success false: sub fail */ boolean subscribe(DeviceSubscription deviceSubscription); /** * 1. Remove subscription from the sub tree * 2. Delete subscription from db */ boolean unSubscribe(String clientId,String topic); boolean isMatch(String pubTopic,String subTopic); boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription); boolean onlyUnUnSubscribeFromTree(String clientId,String topic); Set getAllSubscription(String clientId); void deleteAllSubscription(String clientId); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/ClusterEventCodeEnum.java ================================================ package org.jmqtt.bus.enums; /** * cluster event code */ public enum ClusterEventCodeEnum { MQTT_CLEAR_SESSION("MQTT_CLEAR_SESSION"), DISPATCHER_CLIENT_MESSAGE("DISPATCHER_CLIENT_MESSAGE"), ; private String code; ClusterEventCodeEnum(String code) { this.code = code; } public String getCode() { return code; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/DeviceOnlineStateEnum.java ================================================ package org.jmqtt.bus.enums; public enum DeviceOnlineStateEnum { ONLINE("ONLINE"), OFFLINE("OFFLINE"), ; private String code; DeviceOnlineStateEnum(String code) { this.code = code; } public String getCode() { return code; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageAckEnum.java ================================================ package org.jmqtt.bus.enums; public enum MessageAckEnum { UN_ACK(0), ACK(1), ; private int code; MessageAckEnum(int code) { this.code = code; } public int getCode() { return code; } public static MessageAckEnum getByCode(int code) { for (MessageAckEnum value : MessageAckEnum.values()) { if (value.code == code) { return value; } } return null; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageFlowEnum.java ================================================ package org.jmqtt.bus.enums; public enum MessageFlowEnum { INBOUND("INBOUND"), OUTBOUND("OUTBOUND"), ; private String code; MessageFlowEnum(String code) { this.code = code; } public String getCode() { return code; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/MessageSourceEnum.java ================================================ package org.jmqtt.bus.enums; public enum MessageSourceEnum { DEVICE("DEVICE"), PLATFORM("PLATFORM"), ; private String code; MessageSourceEnum(String code) { this.code = code; } public String getCode() { return code; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/enums/TransportProtocolEnum.java ================================================ package org.jmqtt.bus.enums; public enum TransportProtocolEnum { MQTT("MQTT"), TCP("TCP"), COAP("COAP"), ; private String code; TransportProtocolEnum(String code) { this.code = code; } public String getCode() { return code; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/EventCenter.java ================================================ package org.jmqtt.bus.event; import com.google.common.eventbus.EventBus; import org.jmqtt.bus.model.ClusterEvent; public class EventCenter { private final EventBus eventBus; public EventCenter(){ this.eventBus = new EventBus(); } /** * register gateway listener * @param listener */ public void register(GatewayListener listener){ this.eventBus.register(listener); } /** * send event to bus,and gateway will consume this event * @param clusterEvent */ public void sendEvent(ClusterEvent clusterEvent){ this.eventBus.post(clusterEvent); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/event/GatewayListener.java ================================================ package org.jmqtt.bus.event; import org.jmqtt.bus.model.ClusterEvent; /** * gateway listener * iot gateway 实现该接口,接收bus的事件进行客户端触达和处理 */ public interface GatewayListener { void consume(ClusterEvent clusterEvent); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/ClusterEventManagerImpl.java ================================================ package org.jmqtt.bus.impl; import com.alibaba.fastjson.JSONObject; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.ClusterEventManager; import org.jmqtt.bus.enums.ClusterEventCodeEnum; import org.jmqtt.bus.event.EventCenter; import org.jmqtt.bus.event.GatewayListener; import org.jmqtt.bus.model.ClusterEvent; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.store.DBCallback; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.store.daoobject.EventDO; import org.jmqtt.bus.subscription.SubscriptionMatcher; import org.jmqtt.bus.subscription.model.Subscription; import org.jmqtt.support.helper.MixAll; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; /** * send current node's event to other nodes 集群事件处理,两种方式,根据实际情况,选一个实现即可: * 1. 发布订阅: a. 发送消息 A -> mq系统转发 b. 第二阶段:消息订阅分发给本节点的设备 jmqtt B,C,D —> jmqtt B的连接设备 * 2. jmqtt服务主动拉取: a. A —> 消息存储服务: b. 第二阶段:主动拉取 jmqtt B,C,D broker 定时批量拉取 —> 从消息存储服务拉取 —> 拉取后推送给 B,C,D上连接的设备 */ public class ClusterEventManagerImpl implements ClusterEventManager { private static final Logger log = JmqttLogger.busLog; private static final AtomicLong offset = new AtomicLong(); private AtomicBoolean pollStoped = new AtomicBoolean(false); private int maxPollNum = 100; private int pollWaitInterval = 10; // ms private final EventCenter eventCenter; private SubscriptionMatcher subscriptionMatcher; public ClusterEventManagerImpl(SubscriptionMatcher subscriptionMatcher){ this.eventCenter = new EventCenter(); this.subscriptionMatcher = subscriptionMatcher; } @Override public void start() { Long maxId = DBUtils.operate(new DBCallback() { @Override public Long operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).getMaxOffset(); } }); if (maxId == null) { offset.set(0); } else { offset.set(maxId); } new Thread(() -> { while (!pollStoped.get()) { try { List eventList = pollEvent(maxPollNum); if (!MixAll.isEmpty(eventList)) { for (ClusterEvent event : eventList) { // Send event to iot gateway consumeEvent(event); } } if (MixAll.isEmpty(eventList) || eventList.size() < 5) { Thread.sleep(pollWaitInterval); } } catch (Exception e) { LogUtil.warn(log, "Poll event from cluster error.", e); } } }).start(); LogUtil.info(log,"Cluster event server start."); } @Override public void shutdown() { this.pollStoped.compareAndSet(false,true); } /** * 集群消息往这里投递 */ private void consumeEvent(ClusterEvent event){ // 若是消息,进行订阅树的匹配 if (event.getClusterEventCode() == ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE){ DeviceMessage deviceMessage = JSONObject.parseObject(event.getContent(),DeviceMessage.class); if (deviceMessage == null) { LogUtil.error(log,"[BUS EVENT] event content is empty."); return; } Set subscriptionSet = this.subscriptionMatcher.match(deviceMessage.getTopic()); deviceMessage.setContent(null); if (subscriptionSet != null) { subscriptionSet.forEach(item -> { ClusterEvent stayEvent = event.clone(); stayEvent.setSubscription(item); eventCenter.sendEvent(stayEvent); }); } return; } eventCenter.sendEvent(event); } private List pollEvent(int maxPollNum) { // offset: min -> max long currentOffset = offset.get(); List eventDOList = DBUtils.operate(new DBCallback() { @Override public List operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).consumeEvent(currentOffset,maxPollNum); } }); if (eventDOList == null || eventDOList.size() == 0) { return Collections.emptyList(); } List events = new ArrayList<>(); for (EventDO eventDO : eventDOList) { ClusterEvent event = new ClusterEvent(); event.setNodeIp(eventDO.getNodeIp()); event.setGmtCreate(eventDO.getGmtCreate()); event.setContent(eventDO.getContent()); event.setClusterEventCode(ClusterEventCodeEnum.valueOf(eventDO.getEventCode())); events.add(event); } // reset offset EventDO eventDO = eventDOList.get(eventDOList.size()-1); if (!offset.compareAndSet(currentOffset,eventDO.getId())) { LogUtil.warn(log,"[RDBClusterEventHandler] pollEvent offset is wrong,expectOffset:{},currentOffset:{},maxOffset:{}", offset.get(),currentOffset,eventDO.getId()); offset.set(eventDO.getId()); } return events; } @Override public void sendEvent(ClusterEvent clusterEvent) { EventDO eventDO = convert(clusterEvent); DBUtils.operate(new DBCallback() { @Override public Object operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.eventMapperClass).sendEvent(eventDO); } }); } public void registerEventListener(GatewayListener listener){ this.eventCenter.register(listener); } private EventDO convert(ClusterEvent clusterEvent) { EventDO eventDO = new EventDO(); eventDO.setContent(clusterEvent.getContent()); eventDO.setNodeIp(clusterEvent.getNodeIp()); eventDO.setEventCode(clusterEvent.getClusterEventCode().getCode()); eventDO.setGmtCreate(clusterEvent.getGmtCreate()); return eventDO; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DefaultAuthenticator.java ================================================ package org.jmqtt.bus.impl; import org.jmqtt.bus.Authenticator; public class DefaultAuthenticator implements Authenticator { @Override public boolean login(String clientId, String userName, byte[] password) { return true; } @Override public boolean onBlackList(String clientId, String remoteIpAddress) { return false; } @Override public boolean clientIdVerify(String clientId) { return true; } @Override public boolean subscribeVerify(String clientId, String topic) { return true; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceMessageManagerImpl.java ================================================ package org.jmqtt.bus.impl; import com.alibaba.fastjson.JSONObject; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.DeviceMessageManager; import org.jmqtt.bus.enums.ClusterEventCodeEnum; import org.jmqtt.bus.enums.MessageAckEnum; import org.jmqtt.bus.enums.MessageSourceEnum; import org.jmqtt.bus.model.DeviceInboxMessage; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.store.DBCallback; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO; import org.jmqtt.bus.store.daoobject.EventDO; import org.jmqtt.bus.store.daoobject.MessageDO; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingHelper; import org.slf4j.Logger; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; public class DeviceMessageManagerImpl implements DeviceMessageManager { private static final Logger log = JmqttLogger.busLog; @Override public void clearUnAckMessage(String clientId) { DBUtils.operate(new DBCallback() { @Override public Integer operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.clientInboxMessageMapperClass).truncateUnAck(clientId); } }); } @Override public void dispatcher(DeviceMessage deviceMessage) { // store message and send event SqlSession sqlSession = null; try { sqlSession = DBUtils.getSqlSessionWithTrans(); MessageDO messageDO = convert(deviceMessage); DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO); Long id = messageDO.getId(); if (id == null || id <= 0) { LogUtil.error(log,"[BUS] store message failure."); return; } deviceMessage.setId(id); EventDO eventDO = convert2Event(deviceMessage); DBUtils.getMapper(sqlSession,DBUtils.eventMapperClass).sendEvent(eventDO); Long eventId = eventDO.getId(); if (eventId == null || eventId <= 0) { LogUtil.error(log,"[BUS] send event message failure."); sqlSession.rollback(); return; } sqlSession.commit(); } catch (Exception ex) { LogUtil.error(log,"[BUS] dispatcher message store caught exception,{}",ex); if (sqlSession != null) { sqlSession.rollback(); } } finally { if (sqlSession != null) { sqlSession.close(); } } } @Override public Long storeMessage(DeviceMessage deviceMessage) { MessageDO messageDO = convert(deviceMessage); DBUtils.operate(new DBCallback() { @Override public Long operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.messageMapperClass).storeMessage(messageDO); } }); return messageDO.getId(); } @Override public List queryUnAckMessages(String clientId, int pageSize) { List inboxMessageDOS = DBUtils.operate(new DBCallback() { @Override public List operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).getUnAckMessages(clientId,pageSize); } }); if (inboxMessageDOS == null) { return null; } List ids = new ArrayList<>(); inboxMessageDOS.forEach(itemDO -> { ids.add(itemDO.getMessageId()); }); return queryByIds(ids); } @Override public List queryByIds(List ids) { List messages = DBUtils.operate(new DBCallback() { @Override public List operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.messageMapperClass).queryMessageByIds(ids); } }); List deviceMessageList = new ArrayList<>(messages.size()); messages.forEach(item -> { deviceMessageList.add(convert(item)); }); return deviceMessageList; } @Override public Long addClientInBoxMsg(String clientId, Long messageId, MessageAckEnum ackEnum) { DeviceInboxMessageDO deviceInboxMessageDO = new DeviceInboxMessageDO(); deviceInboxMessageDO.setAck(ackEnum.getCode()); deviceInboxMessageDO.setClientId(clientId); deviceInboxMessageDO.setMessageId(messageId); deviceInboxMessageDO.setStoredTime(new Date()); DBUtils.operate(new DBCallback() { @Override public Long operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).addInboxMessage(deviceInboxMessageDO); } }); return deviceInboxMessageDO.getId(); } @Override public boolean ackMessage(String clientId, Long messageId) { Integer effect = DBUtils.operate(new DBCallback() { @Override public Integer operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.clientInboxMessageMapperClass).ack(clientId,messageId); } }); return effect > 0; } private EventDO convert2Event(DeviceMessage deviceMessage) { EventDO eventDO = new EventDO(); eventDO.setNodeIp(RemotingHelper.getLocalAddr()); eventDO.setContent(JSONObject.toJSONString(deviceMessage)); eventDO.setEventCode(ClusterEventCodeEnum.DISPATCHER_CLIENT_MESSAGE.getCode()); eventDO.setGmtCreate(new Date()); return eventDO; } private MessageDO convert(DeviceMessage deviceMessage) { MessageDO messageDO = new MessageDO(); messageDO.setContent(deviceMessage.getContent()); messageDO.setFromClientId(deviceMessage.getFromClientId()); messageDO.setSource(deviceMessage.getSource().getCode()); messageDO.setStoredTime(deviceMessage.getStoredTime()); messageDO.setTopic(deviceMessage.getTopic()); if (deviceMessage.getProperties() != null) { messageDO.setProperties(JSONObject.toJSONString(deviceMessage.getProperties())); } return messageDO; } private DeviceMessage convert(MessageDO messageDO) { DeviceMessage deviceMessage = new DeviceMessage(); deviceMessage.setTopic(messageDO.getTopic()); deviceMessage.setStoredTime(messageDO.getStoredTime()); deviceMessage.setSource(MessageSourceEnum.valueOf(messageDO.getSource())); deviceMessage.setContent(messageDO.getContent()); deviceMessage.setId(messageDO.getId()); deviceMessage.setFromClientId(messageDO.getFromClientId()); String properties = messageDO.getProperties(); if (properties != null) { deviceMessage.setProperties(JSONObject.parseObject(properties, Map.class)); } return deviceMessage; } private DeviceInboxMessage convert(DeviceInboxMessageDO deviceInboxMessageDO) { DeviceInboxMessage deviceInboxMessage = new DeviceInboxMessage(); deviceInboxMessage.setAck(deviceInboxMessageDO.getAck()); deviceInboxMessage.setAckTime(deviceInboxMessageDO.getAckTime()); deviceInboxMessage.setClientId(deviceInboxMessageDO.getClientId()); deviceInboxMessage.setId(deviceInboxMessageDO.getId()); deviceInboxMessage.setMessageId(deviceInboxMessageDO.getMessageId()); deviceInboxMessage.setStoredTime(deviceInboxMessageDO.getStoredTime()); return deviceInboxMessage; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSessionManagerImpl.java ================================================ package org.jmqtt.bus.impl; import com.alibaba.fastjson.JSONObject; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.DeviceSessionManager; import org.jmqtt.bus.enums.DeviceOnlineStateEnum; import org.jmqtt.bus.enums.TransportProtocolEnum; import org.jmqtt.bus.model.DeviceSession; import org.jmqtt.bus.store.DBCallback; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.store.daoobject.SessionDO; import java.util.Date; import java.util.Map; public class DeviceSessionManagerImpl implements DeviceSessionManager { @Override public DeviceSession getSession(String clientId) { // optimize: add cache SessionDO sessionDO = (SessionDO) DBUtils.operate(new DBCallback() { @Override public Object operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).getSession(clientId); } }); if (sessionDO == null) { return null; } return convert(sessionDO); } @Override public void storeSession(DeviceSession deviceSession) { SessionDO sessionDO = convert(deviceSession); DBUtils.operate(new DBCallback() { @Override public Long operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).storeSession(sessionDO); } }); } @Override public void offline(String clientId) { SessionDO sessionDO = new SessionDO(); sessionDO.setClientId(clientId); sessionDO.setOnline(DeviceOnlineStateEnum.OFFLINE.getCode()); sessionDO.setLastOfflineTime(new Date()); DBUtils.operate(new DBCallback() { @Override public Object operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession, DBUtils.sessionMapperClass).offline(sessionDO); } }); } private DeviceSession convert(SessionDO sessionDO) { DeviceSession deviceSession = new DeviceSession(); deviceSession.setClientId(sessionDO.getClientId()); deviceSession.setClientIp(sessionDO.getClientIp()); deviceSession.setLastOfflineTime(sessionDO.getLastOfflineTime()); deviceSession.setOnline(DeviceOnlineStateEnum.valueOf(sessionDO.getOnline())); deviceSession.setOnlineTime(sessionDO.getOnlineTime()); String properties = sessionDO.getProperties(); if (properties != null) { deviceSession.setProperties(JSONObject.parseObject(properties, Map.class)); } deviceSession.setServerIp(deviceSession.getServerIp()); deviceSession.setTransportProtocol(TransportProtocolEnum.valueOf(sessionDO.getTransportProtocol())); return deviceSession; } private SessionDO convert(DeviceSession deviceSession) { SessionDO sessionDO = new SessionDO(); sessionDO.setClientId(deviceSession.getClientId()); sessionDO.setClientIp(deviceSession.getClientIp()); sessionDO.setLastOfflineTime(deviceSession.getLastOfflineTime()); sessionDO.setOnline(deviceSession.getOnline().getCode()); sessionDO.setOnlineTime(deviceSession.getOnlineTime()); sessionDO.setProperties(deviceSession.getProperties() == null ? null : JSONObject.toJSONString(deviceSession.getProperties())); sessionDO.setServerIp(deviceSession.getServerIp()); sessionDO.setTransportProtocol(deviceSession.getTransportProtocol().getCode()); return sessionDO; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/impl/DeviceSubscriptionManagerImpl.java ================================================ package org.jmqtt.bus.impl; import com.alibaba.fastjson.JSONObject; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.DeviceSubscriptionManager; import org.jmqtt.bus.model.DeviceSubscription; import org.jmqtt.bus.store.DBCallback; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.store.daoobject.SubscriptionDO; import org.jmqtt.bus.subscription.SubscriptionMatcher; import org.jmqtt.bus.subscription.Topic; import org.jmqtt.bus.subscription.model.Subscription; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; public class DeviceSubscriptionManagerImpl implements DeviceSubscriptionManager { private static final Logger log = JmqttLogger.busLog; private SubscriptionMatcher subscriptionMatcher; public DeviceSubscriptionManagerImpl(SubscriptionMatcher subscriptionMatcher){ this.subscriptionMatcher = subscriptionMatcher; } @Override public boolean subscribe(DeviceSubscription deviceSubscription) { SqlSession sqlSession = null; try { sqlSession = DBUtils.getSqlSessionWithTrans(); SubscriptionDO subscriptionDO = convert(deviceSubscription); DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).storeSubscription(subscriptionDO); if (subscriptionDO.getId() == null || subscriptionDO.getId() <= 0) { LogUtil.error(log,"[SUBSCRIBE] store subscription fail."); return false; } Subscription subscription = convert2Sub(deviceSubscription); boolean addSubTree = subscriptionMatcher.subscribe(subscription); if (!addSubTree) { LogUtil.error(log,"[SUBSCRIBE] sub tree fail."); sqlSession.rollback(); return false; } sqlSession.commit(); return true; } catch (Exception ex) { LogUtil.error(log,"[SUBSCRIBE] subscribe failure.ex:{}",ex); if (sqlSession != null) { sqlSession.rollback(); } } finally { if (sqlSession != null) { sqlSession.close(); } } return false; } @Override public boolean unSubscribe(String clientId,String topic) { SqlSession sqlSession = null; try { sqlSession = DBUtils.getSqlSessionWithTrans(); Integer effectNum = DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).delSubscription(clientId,topic); if (effectNum == null || effectNum <= 0) { LogUtil.error(log,"[UNSUBSCRIBE] del subscription fail."); return false; } boolean subSub = subscriptionMatcher.unSubscribe(topic,clientId); if (!subSub) { LogUtil.error(log,"[SUBSUBSCRIBE] unsub from tree fail."); sqlSession.rollback(); return false; } sqlSession.commit(); return true; } catch (Exception ex) { LogUtil.error(log,"[SUBSCRIBE] subscribe failure.ex:{}",ex); if (sqlSession != null) { sqlSession.rollback(); } } finally { if (sqlSession != null) { sqlSession.close(); } } return false; } @Override public boolean isMatch(String pubTopic, String subTopic) { Topic topic = new Topic(pubTopic); return topic.match(new Topic(subTopic)); } @Override public boolean onlySubscribe2Tree(DeviceSubscription deviceSubscription) { Subscription subscription = convert2Sub(deviceSubscription); return subscriptionMatcher.subscribe(subscription); } @Override public boolean onlyUnUnSubscribeFromTree(String clientId, String topic) { return subscriptionMatcher.unSubscribe(topic,clientId); } @Override public Set getAllSubscription(String clientId) { List subscriptionDOS = DBUtils.operate(new DBCallback() { @Override public List operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).getAllSubscription(clientId); } }); if (subscriptionDOS == null) { return null; } Set subscriptions = new HashSet<>(subscriptionDOS.size()); subscriptionDOS.forEach(item -> { subscriptions.add(convert(item)); }); return subscriptions; } @Override public void deleteAllSubscription(String clientId) { DBUtils.operate(new DBCallback() { @Override public Integer operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.subscriptionMapperClass).clearSubscription(clientId); } }); } private Subscription convert2Sub(DeviceSubscription deviceSubscription){ return new Subscription(deviceSubscription.getClientId(),deviceSubscription.getTopic(),deviceSubscription.getProperties()); } private DeviceSubscription convert(SubscriptionDO subscriptionDO){ DeviceSubscription deviceSubscription = new DeviceSubscription(); deviceSubscription.setClientId(subscriptionDO.getClientId()); deviceSubscription.setTopic(subscriptionDO.getTopic()); deviceSubscription.setSubscribeTime(subscriptionDO.getSubscribeTime()); if (subscriptionDO.getProperties() != null) { deviceSubscription.setProperties(JSONObject.parseObject(subscriptionDO.getProperties(), Map.class)); } return deviceSubscription; } private SubscriptionDO convert(DeviceSubscription deviceSubscription){ SubscriptionDO subscriptionDO = new SubscriptionDO(); subscriptionDO.setClientId(deviceSubscription.getClientId()); subscriptionDO.setTopic(deviceSubscription.getTopic()); subscriptionDO.setProperties(deviceSubscription.getProperties() != null? JSONObject.toJSONString(deviceSubscription.getProperties()): null); subscriptionDO.setSubscribeTime(deviceSubscription.getSubscribeTime()); return subscriptionDO; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/ClusterEvent.java ================================================ package org.jmqtt.bus.model; import org.jmqtt.bus.enums.ClusterEventCodeEnum; import org.jmqtt.bus.subscription.model.Subscription; import java.util.Date; public class ClusterEvent { private Long id; private String content; private Date gmtCreate; private String nodeIp; private ClusterEventCodeEnum clusterEventCode; /** * 待分发消息时的订阅关系 */ private Subscription subscription; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public String getNodeIp() { return nodeIp; } public void setNodeIp(String nodeIp) { this.nodeIp = nodeIp; } public ClusterEventCodeEnum getClusterEventCode() { return clusterEventCode; } public void setClusterEventCode(ClusterEventCodeEnum clusterEventCode) { this.clusterEventCode = clusterEventCode; } public Subscription getSubscription() { return subscription; } public void setSubscription(Subscription subscription) { this.subscription = subscription; } public ClusterEvent clone() { ClusterEvent event = new ClusterEvent(); event.setClusterEventCode(this.getClusterEventCode()); event.setContent(this.getContent()); event.setGmtCreate(this.getGmtCreate()); event.setNodeIp(this.getNodeIp()); event.setId(this.getId()); event.setSubscription(this.getSubscription()); return event; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceInboxMessage.java ================================================ package org.jmqtt.bus.model; import java.util.Date; public class DeviceInboxMessage { private Long id; private String clientId; private Long messageId; private Integer ack; private Date storedTime; private Date ackTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public Long getMessageId() { return messageId; } public void setMessageId(Long messageId) { this.messageId = messageId; } public Integer getAck() { return ack; } public void setAck(Integer ack) { this.ack = ack; } public Date getStoredTime() { return storedTime; } public void setStoredTime(Date storedTime) { this.storedTime = storedTime; } public Date getAckTime() { return ackTime; } public void setAckTime(Date ackTime) { this.ackTime = ackTime; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceMessage.java ================================================ package org.jmqtt.bus.model; import org.jmqtt.bus.enums.MessageSourceEnum; import java.util.Date; import java.util.Map; public class DeviceMessage { private Long id; private byte[] content; private MessageSourceEnum source; private String fromClientId; private Date storedTime; private String topic; private Map properties; public T getProperty(String key){ if (properties == null || properties.containsKey(key)) { return (T) properties.get(key); } return null; } public Long getId() { return id; } public void setId(Long id) { this.id = id; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public MessageSourceEnum getSource() { return source; } public void setSource(MessageSourceEnum source) { this.source = source; } public Date getStoredTime() { return storedTime; } public void setStoredTime(Date storedTime) { this.storedTime = storedTime; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } public String getFromClientId() { return fromClientId; } public void setFromClientId(String fromClientId) { this.fromClientId = fromClientId; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSession.java ================================================ package org.jmqtt.bus.model; import org.jmqtt.bus.enums.DeviceOnlineStateEnum; import org.jmqtt.bus.enums.TransportProtocolEnum; import java.util.Date; import java.util.Map; /** * session */ public class DeviceSession { private String clientId; private TransportProtocolEnum transportProtocol; private String clientIp; private String serverIp; private DeviceOnlineStateEnum online; private Date onlineTime; private Date lastOfflineTime; private Map properties; public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public TransportProtocolEnum getTransportProtocol() { return transportProtocol; } public void setTransportProtocol(TransportProtocolEnum transportProtocol) { this.transportProtocol = transportProtocol; } public String getClientIp() { return clientIp; } public void setClientIp(String clientIp) { this.clientIp = clientIp; } public String getServerIp() { return serverIp; } public void setServerIp(String serverIp) { this.serverIp = serverIp; } public DeviceOnlineStateEnum getOnline() { return online; } public void setOnline(DeviceOnlineStateEnum online) { this.online = online; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } public Date getOnlineTime() { return onlineTime; } public void setOnlineTime(Date onlineTime) { this.onlineTime = onlineTime; } public Date getLastOfflineTime() { return lastOfflineTime; } public void setLastOfflineTime(Date lastOfflineTime) { this.lastOfflineTime = lastOfflineTime; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/model/DeviceSubscription.java ================================================ package org.jmqtt.bus.model; import java.util.Date; import java.util.Map; public class DeviceSubscription { private String clientId; private String topic; private Date subscribeTime; private Map properties; public T getProperty(String key){ if (properties == null) { return null; } return (T) properties.get(key); } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Date getSubscribeTime() { return subscribeTime; } public void setSubscribeTime(Date subscribeTime) { this.subscribeTime = subscribeTime; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/AbstractDBStore.java ================================================ package org.jmqtt.bus.store; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.store.mapper.*; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.log.JmqttLogger; import org.slf4j.Logger; public abstract class AbstractDBStore { protected final static Logger log = JmqttLogger.storeLog; protected void start(BrokerConfig brokerConfig) { DBUtils.getInstance().start(brokerConfig); } protected void shutdown() { DBUtils.getInstance().shutdown(); } protected T getMapper(SqlSession sqlSession,Class clazz) { return sqlSession.getMapper(clazz); } protected Object operate(DBCallback dbCallback){ return DBUtils.getInstance().operate(dbCallback); } public SqlSession getSqlSessionWithTrans() { return DBUtils.getInstance().getSqlSessionWithTrans(); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBCallback.java ================================================ package org.jmqtt.bus.store; import org.apache.ibatis.session.SqlSession; public interface DBCallback { T operate(SqlSession sqlSession); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/DBUtils.java ================================================ package org.jmqtt.bus.store; import com.alibaba.druid.pool.DruidDataSource; import org.apache.ibatis.datasource.DataSourceFactory; import org.apache.ibatis.mapping.Environment; import org.apache.ibatis.session.Configuration; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import org.apache.ibatis.transaction.TransactionFactory; import org.apache.ibatis.transaction.jdbc.JdbcTransactionFactory; import org.jmqtt.bus.store.mapper.*; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import javax.sql.DataSource; import java.sql.SQLException; import java.util.Properties; import java.util.concurrent.atomic.AtomicBoolean; /** * db 工具类 */ public class DBUtils { private static final Logger log = JmqttLogger.storeLog; public static final Class sessionMapperClass = SessionMapper.class; public static final Class subscriptionMapperClass = SubscriptionMapper.class; public static final Class eventMapperClass = EventMapper.class; public static final Class clientInboxMessageMapperClass = ClientInboxMessageMapper.class; public static final Class messageMapperClass = MessageMapper.class; public static final Class retainMessageMapperClass = RetainMessageMapper.class; private static final DBUtils dbUtils = new DBUtils(); private DBUtils(){} private SqlSessionFactory sqlSessionFactory; private AtomicBoolean start = new AtomicBoolean(false); public static DBUtils getInstance(){ return dbUtils; } public void start(BrokerConfig brokerConfig){ if (this.start.compareAndSet(false,true)) { LogUtil.info(log,"DB store start..."); DataSource dataSource = new DataSourceFactory() { @Override public void setProperties(Properties properties) { } @Override public DataSource getDataSource() { DruidDataSource dds = new DruidDataSource(); dds.setDriverClassName(brokerConfig.getDriver()); dds.setUrl(brokerConfig.getUrl()); dds.setUsername(brokerConfig.getUsername()); dds.setPassword(brokerConfig.getPassword()); // 其他配置可自行补充 dds.setKeepAlive(true); dds.setMinEvictableIdleTimeMillis(180000); dds.setMaxWait(10*1000); dds.setInitialSize(5); dds.setMinIdle(5); try { dds.init(); } catch (SQLException e) { e.printStackTrace(); System.exit(-1); } return dds; } }.getDataSource(); TransactionFactory transactionFactory = new JdbcTransactionFactory(); Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); // 初始化所有mapper configuration.addMapper(SessionMapper.class); configuration.addMapper(SubscriptionMapper.class); configuration.addMapper(MessageMapper.class); configuration.addMapper(EventMapper.class); configuration.addMapper(ClientInboxMessageMapper.class); configuration.addMapper(RetainMessageMapper.class); configuration.setMapUnderscoreToCamelCase(true); this.sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration); LogUtil.info(log,"DB store start success..."); } } public void shutdown(){} public static final T operate(DBCallback dbCallback) { try (SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(true)){ return dbCallback.operate(sqlSession); } } public static final T getMapper(SqlSession sqlSession,Class clazz) { return sqlSession.getMapper(clazz); } /** * 获取关闭事物的session,需要手动提交事物 */ public static final SqlSession getSqlSessionWithTrans() { SqlSession sqlSession = DBUtils.getInstance().sqlSessionFactory.openSession(false); return sqlSession; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/DeviceInboxMessageDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class DeviceInboxMessageDO implements Serializable { private static final long serialVersionUID = 143413131231231L; private Long id; private String clientId; private Long messageId; private Integer ack; private Date storedTime; private Date ackTime; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public Long getMessageId() { return messageId; } public void setMessageId(Long messageId) { this.messageId = messageId; } public Integer getAck() { return ack; } public void setAck(Integer ack) { this.ack = ack; } public Date getStoredTime() { return storedTime; } public void setStoredTime(Date storedTime) { this.storedTime = storedTime; } public Date getAckTime() { return ackTime; } public void setAckTime(Date ackTime) { this.ackTime = ackTime; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/EventDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class EventDO implements Serializable { private static final long serialVersionUID = 12213213131231231L; private Long id; private String content; private Date gmtCreate; private String nodeIp; private String eventCode; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getContent() { return content; } public void setContent(String content) { this.content = content; } public Date getGmtCreate() { return gmtCreate; } public void setGmtCreate(Date gmtCreate) { this.gmtCreate = gmtCreate; } public String getNodeIp() { return nodeIp; } public void setNodeIp(String nodeIp) { this.nodeIp = nodeIp; } public String getEventCode() { return eventCode; } public void setEventCode(String eventCode) { this.eventCode = eventCode; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/MessageDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class MessageDO implements Serializable { private static final long serialVersionUID = 12313131231231L; private Long id; private String source; private byte[] content; private String topic; private String fromClientId; private Date storedTime; private String properties; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getSource() { return source; } public void setSource(String source) { this.source = source; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public String getFromClientId() { return fromClientId; } public void setFromClientId(String fromClientId) { this.fromClientId = fromClientId; } public Date getStoredTime() { return storedTime; } public void setStoredTime(Date storedTime) { this.storedTime = storedTime; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/RetainMessageDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class RetainMessageDO implements Serializable { private static final long serialVersionUID = 12213131231231L; private Long id; private String topic; private byte[] content; private String fromClientId; private Date storedTime; private String properties; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public byte[] getContent() { return content; } public void setContent(byte[] content) { this.content = content; } public String getFromClientId() { return fromClientId; } public void setFromClientId(String fromClientId) { this.fromClientId = fromClientId; } public Date getStoredTime() { return storedTime; } public void setStoredTime(Date storedTime) { this.storedTime = storedTime; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SessionDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class SessionDO implements Serializable { private static final long serialVersionUID = 12213131231231L; private Long id; private String clientId; private String online; private String transportProtocol; private String clientIp; private String serverIp; private Date lastOfflineTime; private Date onlineTime; private String properties; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getOnline() { return online; } public void setOnline(String online) { this.online = online; } public String getTransportProtocol() { return transportProtocol; } public void setTransportProtocol(String transportProtocol) { this.transportProtocol = transportProtocol; } public String getClientIp() { return clientIp; } public void setClientIp(String clientIp) { this.clientIp = clientIp; } public String getServerIp() { return serverIp; } public void setServerIp(String serverIp) { this.serverIp = serverIp; } public Date getLastOfflineTime() { return lastOfflineTime; } public void setLastOfflineTime(Date lastOfflineTime) { this.lastOfflineTime = lastOfflineTime; } public Date getOnlineTime() { return onlineTime; } public void setOnlineTime(Date onlineTime) { this.onlineTime = onlineTime; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/daoobject/SubscriptionDO.java ================================================ package org.jmqtt.bus.store.daoobject; import java.io.Serializable; import java.util.Date; public class SubscriptionDO implements Serializable { private static final long serialVersionUID = 12213131231231L; private Long id; private String clientId; private String topic; private Date subscribeTime; private String properties; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Date getSubscribeTime() { return subscribeTime; } public void setSubscribeTime(Date subscribeTime) { this.subscribeTime = subscribeTime; } public String getProperties() { return properties; } public void setProperties(String properties) { this.properties = properties; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/ClientInboxMessageMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.*; import org.jmqtt.bus.store.daoobject.DeviceInboxMessageDO; import java.util.List; public interface ClientInboxMessageMapper { @Insert("INSERT INTO jmqtt_client_inbox(client_id,message_id,ack,stored_time) VALUES (#{clientId},#{messageId},#{ack},#{storedTime})") @Options(useGeneratedKeys=true,keyProperty="id") Long addInboxMessage(DeviceInboxMessageDO deviceInboxMessageDO); @Update("UPDATE jmqtt_client_inbox set ack = 1,ack_time = now()" + "WHERE client_id = #{clientId} and message_id = #{messageId}") Integer ack(@Param("clientId") String clientId,@Param("messageId") Long messageId); @Update("DELETE FROM jmqtt_client_inbox WHERE client_id = #{clientId} AND ack = 0") Integer truncateUnAck(@Param("clientId") String clientId); @Select("SELECT * FROM jmqtt_client_inbox WHERE client_id = #{clientId} and ack = 0 ORDER BY stored_time ASC LIMIT #{limit} ") List getUnAckMessages(@Param("clientId") String clientId, @Param("limit") int limit); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/EventMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.jmqtt.bus.store.daoobject.EventDO; import java.util.List; public interface EventMapper { @Insert("insert into jmqtt_cluster_event (content,gmt_create,node_ip,event_code) values " + "(#{content},#{gmtCreate},#{nodeIp},#{eventCode})") @Options(useGeneratedKeys=true,keyProperty="id") Long sendEvent(EventDO eventDO); @Select("select id,content,gmt_create,node_ip,event_code from jmqtt_cluster_event " + "where id > #{offset} order by id asc limit #{maxNum}") List consumeEvent(@Param("offset") long offset, @Param("maxNum") int maxNum); @Select("SELECT max(id) FROM jmqtt_cluster_event") Long getMaxOffset(); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/MessageMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.jmqtt.bus.store.daoobject.MessageDO; import java.util.List; public interface MessageMapper { @Insert("INSERT INTO jmqtt_message(id,source,content,topic,from_client_id,stored_time,properties) VALUES(#{id},#{source},#{content}" + ",#{topic},#{fromClientId},#{storedTime},#{properties})") @Options(useGeneratedKeys=true,keyProperty="id") Long storeMessage(MessageDO messageDO); @Select("SELECT id,source,content,topic,from_client_id,stored_time,properties FROM jmqtt_message WHERE id = #{id}") MessageDO getMessage(@Param("id") long id); @Select("") List queryMessageByIds(@Param("ids")List ids); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/RetainMessageMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.Delete; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Select; import org.jmqtt.bus.store.daoobject.RetainMessageDO; import java.util.List; public interface RetainMessageMapper { @Insert("INSERT INTO jmqtt_retain_message(topic,content,from_client_id,stored_time,properties) VALUES" + "(#{topic},#{content},#{fromClientId},#{storedTime},#{properties})" + " on DUPLICATE key update content = #{content},from_client_id = #{fromClientId},stored_time=#{storedTime},properties = #{properties}") @Options(useGeneratedKeys=true,keyProperty="id") Long storeRetainMessage(RetainMessageDO retainMessageDO); @Select("SELECT topic,content,from_client_id,stored_time,properties FROM jmqtt_retain_message") List getAllRetainMessage(); @Delete("DELETE FROM jmqtt_retain_message WHERE topic = #{topic}") Integer delRetainMessage(String topic); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SessionMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Options; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import org.jmqtt.bus.store.daoobject.SessionDO; public interface SessionMapper { @Select("select client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties from jmqtt_session where client_id = #{clientId}") SessionDO getSession(String clientId); @Insert("insert into jmqtt_session(client_id,online,transport_protocol,client_ip,server_ip,last_offline_time,online_time,properties) values " + "(#{clientId},#{online},#{transportProtocol},#{clientIp},#{serverIp},#{lastOfflineTime},#{onlineTime},#{properties}) " + "on DUPLICATE key update online = #{online},last_offline_time = #{lastOfflineTime},online_time = #{onlineTime}" + ",client_ip=#{clientIp},server_ip=#{serverIp},transport_protocol=#{transportProtocol}") @Options(useGeneratedKeys=true,keyProperty="id") Long storeSession(SessionDO sessionDO); @Update("update jmqtt_session set last_offline_time = #{lastOfflineTime},online = #{online} where " + "client_id = #{clientId}") Long offline(SessionDO sessionDO); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/store/mapper/SubscriptionMapper.java ================================================ package org.jmqtt.bus.store.mapper; import org.apache.ibatis.annotations.*; import org.jmqtt.bus.store.daoobject.SubscriptionDO; import java.util.List; public interface SubscriptionMapper { @Insert("INSERT INTO jmqtt_subscription (client_id,topic,properties,subscribe_time) " + "VALUES (#{clientId},#{topic},#{properties},#{subscribeTime}) on duplicate key update properties = #{properties},subscribe_time=#{subscribeTime}") @Options(useGeneratedKeys=true,keyProperty="id") Long storeSubscription(SubscriptionDO subscriptionDO); @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId}") Integer clearSubscription(String clientId); @Delete("DELETE FROM jmqtt_subscription WHERE client_id = #{clientId} AND topic = #{topic}") Integer delSubscription(@Param("clientId") String clientId, @Param("topic") String topic); @Select("SELECT id,client_id,topic,properties,subscribe_time FROM jmqtt_subscription WHERE client_id = #{clientId}") List getAllSubscription(String clientId); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CNode.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import java.util.*; public class CNode { private Token token; private List children; Set subscriptions; CNode() { this.children = new ArrayList<>(); this.subscriptions = new HashSet<>(); } //Copy constructor private CNode(Token token, List children, Set subscriptions) { this.token = token; // keep reference, root comparison in directory logic relies on it for now. this.subscriptions = new HashSet<>(subscriptions); this.children = new ArrayList<>(children); } public Token getToken() { return token; } public void setToken(Token token) { this.token = token; } boolean anyChildrenMatch(Token token) { for (INode iNode : children) { final CNode child = iNode.mainNode(); if (child.equalsToken(token)) { return true; } } return false; } List allChildren() { return this.children; } INode childOf(Token token) { for (INode iNode : children) { final CNode child = iNode.mainNode(); if (child.equalsToken(token)) { return iNode; } } throw new IllegalArgumentException("Asked for a token that doesn't exists in any child [" + token + "]"); } private boolean equalsToken(Token token) { return token != null && this.token != null && this.token.equals(token); } @Override public int hashCode() { return Objects.hash(token); } CNode copy() { return new CNode(this.token, this.children, this.subscriptions); } public void add(INode newINode) { this.children.add(newINode); } public void remove(INode node) { this.children.remove(node); } CNode addSubscription(Subscription newSubscription) { // if already contains one with same topic and same client, keep that with higher QoS if (subscriptions.contains(newSubscription)) { final Subscription existing = subscriptions.stream() .filter(s -> s.equals(newSubscription)) .findFirst().get(); subscriptions.remove(existing); subscriptions.add(new Subscription(newSubscription)); } else { this.subscriptions.add(new Subscription(newSubscription)); } return this; } boolean containsOnly(String clientId) { for (Subscription sub : this.subscriptions) { if (!sub.getClientId().equals(clientId)) { return false; } } return !this.subscriptions.isEmpty(); } public boolean contains(String clientId) { for (Subscription sub : this.subscriptions) { if (sub.getClientId().equals(clientId)) { return true; } } return false; } void removeSubscriptionsFor(String clientId) { Set toRemove = new HashSet<>(); for (Subscription sub : this.subscriptions) { if (sub.getClientId().equals(clientId)) { toRemove.add(sub); } } this.subscriptions.removeAll(toRemove); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrie.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import java.util.Collections; import java.util.HashSet; import java.util.Optional; import java.util.Set; public class CTrie { interface IVisitor { void visit(CNode node, int deep); T getResult(); } private static final Token ROOT = new Token("root"); private static final INode NO_PARENT = null; private enum Action { OK, REPEAT } INode root; public CTrie() { final CNode mainNode = new CNode(); mainNode.setToken(ROOT); this.root = new INode(mainNode); } public Optional lookup(Topic topic) { INode inode = this.root; Token token = topic.headToken(); while (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) { topic = topic.exceptHeadToken(); inode = inode.mainNode().childOf(token); token = topic.headToken(); } if (inode == null || !topic.isEmpty()) { return Optional.empty(); } return Optional.of(inode.mainNode()); } enum NavigationAction { MATCH, GODEEP, STOP } private NavigationAction evaluate(Topic topic, CNode cnode) { if (Token.MULTI.equals(cnode.getToken())) { return NavigationAction.MATCH; } if (topic.isEmpty()) { return NavigationAction.STOP; } final Token token = topic.headToken(); if (!(Token.SINGLE.equals(cnode.getToken()) || cnode.getToken().equals(token) || ROOT.equals(cnode.getToken()))) { return NavigationAction.STOP; } return NavigationAction.GODEEP; } public Set recursiveMatch(Topic topic) { return recursiveMatch(topic, this.root); } private Set recursiveMatch(Topic topic, INode inode) { CNode cnode = inode.mainNode(); if (cnode instanceof TNode) { return Collections.emptySet(); } NavigationAction action = evaluate(topic, cnode); if (action == NavigationAction.MATCH) { return cnode.subscriptions; } if (action == NavigationAction.STOP) { return Collections.emptySet(); } Topic remainingTopic = (ROOT.equals(cnode.getToken())) ? topic : topic.exceptHeadToken(); Set subscriptions = new HashSet<>(); if (remainingTopic.isEmpty()) { subscriptions.addAll(cnode.subscriptions); } for (INode subInode : cnode.allChildren()) { subscriptions.addAll(recursiveMatch(remainingTopic, subInode)); } return subscriptions; } public void addToTree(Subscription newSubscription) { Action res; do { res = insert(new Topic(newSubscription.getTopic()), this.root, newSubscription); } while (res == Action.REPEAT); } private Action insert(Topic topic, final INode inode, Subscription newSubscription) { Token token = topic.headToken(); if (!topic.isEmpty() && inode.mainNode().anyChildrenMatch(token)) { Topic remainingTopic = topic.exceptHeadToken(); INode nextInode = inode.mainNode().childOf(token); return insert(remainingTopic, nextInode, newSubscription); } else { if (topic.isEmpty()) { return insertSubscription(inode, newSubscription); } else { return createNodeAndInsertSubscription(topic, inode, newSubscription); } } } private Action insertSubscription(INode inode, Subscription newSubscription) { CNode cnode = inode.mainNode(); CNode updatedCnode = cnode.copy().addSubscription(newSubscription); if (inode.compareAndSet(cnode, updatedCnode)) { return Action.OK; } else { return Action.REPEAT; } } private Action createNodeAndInsertSubscription(Topic topic, INode inode, Subscription newSubscription) { INode newInode = createPathRec(topic, newSubscription); CNode cnode = inode.mainNode(); CNode updatedCnode = cnode.copy(); updatedCnode.add(newInode); return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT; } private INode createPathRec(Topic topic, Subscription newSubscription) { Topic remainingTopic = topic.exceptHeadToken(); if (!remainingTopic.isEmpty()) { INode inode = createPathRec(remainingTopic, newSubscription); CNode cnode = new CNode(); cnode.setToken(topic.headToken()); cnode.add(inode); return new INode(cnode); } else { return createLeafNodes(topic.headToken(), newSubscription); } } private INode createLeafNodes(Token token, Subscription newSubscription) { CNode newLeafCnode = new CNode(); newLeafCnode.setToken(token); newLeafCnode.addSubscription(newSubscription); return new INode(newLeafCnode); } public void removeFromTree(Topic topic, String clientID) { Action res; do { res = remove(clientID, topic, this.root, NO_PARENT); } while (res == Action.REPEAT); } private Action remove(String clientId, Topic topic, INode inode, INode iParent) { Token token = topic.headToken(); if (!topic.isEmpty() && (inode.mainNode().anyChildrenMatch(token))) { Topic remainingTopic = topic.exceptHeadToken(); INode nextInode = inode.mainNode().childOf(token); return remove(clientId, remainingTopic, nextInode, inode); } else { final CNode cnode = inode.mainNode(); if (cnode instanceof TNode) { // this inode is a tomb, has no clients and should be cleaned up // Because we implemented cleanTomb below, this should be rare, but possible // Consider calling cleanTomb here too return Action.OK; } if (cnode.containsOnly(clientId) && topic.isEmpty() && cnode.allChildren().isEmpty()) { // last client to leave this node, AND there are no downstream children, remove via TNode tomb if (inode == this.root) { return inode.compareAndSet(cnode, inode.mainNode().copy()) ? Action.OK : Action.REPEAT; } TNode tnode = new TNode(); return inode.compareAndSet(cnode, tnode) ? cleanTomb(inode, iParent) : Action.REPEAT; } else if (cnode.contains(clientId) && topic.isEmpty()) { CNode updatedCnode = cnode.copy(); updatedCnode.removeSubscriptionsFor(clientId); return inode.compareAndSet(cnode, updatedCnode) ? Action.OK : Action.REPEAT; } else { //someone else already removed return Action.OK; } } } /** * * Cleans Disposes of TNode in separate Atomic CAS operation per * http://bravenewgeek.com/breaking-and-entering-lose-the-lock-while-embracing-concurrency/ * * We roughly follow this theory above, but we allow CNode with no Subscriptions to linger (for now). * * * @param inode inode that handle to the tomb node. * @param iParent inode parent. * @return REPEAT if the this methods wasn't successful or OK. */ private Action cleanTomb(INode inode, INode iParent) { CNode updatedCnode = iParent.mainNode().copy(); updatedCnode.remove(inode); return iParent.compareAndSet(iParent.mainNode(), updatedCnode) ? Action.OK : Action.REPEAT; } public int size() { SubscriptionCounterVisitor visitor = new SubscriptionCounterVisitor(); dfsVisit(this.root, visitor, 0); return visitor.getResult(); } public String dumpTree() { DumpTreeVisitor visitor = new DumpTreeVisitor(); dfsVisit(this.root, visitor, 0); return visitor.getResult(); } private void dfsVisit(INode node, IVisitor visitor, int deep) { if (node == null) { return; } visitor.visit(node.mainNode(), deep); ++deep; for (INode child : node.mainNode().allChildren()) { dfsVisit(child, visitor, deep); } } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcher.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import org.jmqtt.support.log.JmqttLogger; import org.slf4j.Logger; import java.util.Set; public class CTrieSubscriptionMatcher implements SubscriptionMatcher { private static final Logger log = JmqttLogger.busLog; private CTrie ctrie; public CTrieSubscriptionMatcher(){ this.ctrie = new CTrie(); } @Override public Set match(String topicFilter) { Topic topic = new Topic(topicFilter); return ctrie.recursiveMatch(topic); } @Override public boolean subscribe(Subscription newSubscription) { ctrie.addToTree(newSubscription); return true; } @Override public boolean unSubscribe(String topicFilter, String clientID) { ctrie.removeFromTree(new Topic(topicFilter), clientID); return true; } @Override public int size() { return ctrie.size(); } @Override public String dumpTree() { return ctrie.dumpTree(); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DefaultSubscriptionTreeMatcher.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.HashSet; import java.util.List; import java.util.Objects; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; public class DefaultSubscriptionTreeMatcher implements SubscriptionMatcher { private static final Logger log = JmqttLogger.mqttLog; private final Object lock = new Object(); private TreeNode root = new TreeNode(new Token("root")); private Token EMPTY = new Token(""); private Token SINGLE = new Token("+"); private Token MULTY = new Token("#"); public DefaultSubscriptionTreeMatcher() { } @Override public boolean subscribe(Subscription subscription) { try { String topic = subscription.getTopic(); TreeNode currentNode = recursionGetTreeNode(topic, root); Set subscriptions = currentNode.getSubscribers(); if (subscriptions.contains(subscription)) { for (Subscription sub : subscriptions) { if (sub.equals(subscription)) { sub.setProperties(subscription.getProperties()); return true; } } } currentNode.addSubscriber(subscription); } catch (Exception ex) { LogUtil.warn(log, "[Subscription] -> Subscribe failed,clientId={},topic={},qos={}", subscription.getClientId(), subscription.getTopic(), subscription.getProperties()); return true; } return true; } @Override public boolean unSubscribe(String topic, String clientId) { TreeNode currentNode = recursionGetTreeNode(topic, root); currentNode.getSubscribers().remove(new Subscription(clientId, topic, null)); return true; } private TreeNode recursionGetTreeNode(String topic, TreeNode node) { String[] tokens = topic.split("/"); Token token = new Token(tokens[0]); TreeNode matchNode = node.getChildNodeByToken(token); if (Objects.isNull(matchNode)) { synchronized (lock) { matchNode = node.getChildNodeByToken(token); if (Objects.isNull(matchNode)) { matchNode = new TreeNode(token); node.addChild(matchNode); } } } if (tokens.length > 1) { String childTopic = topic.substring(topic.indexOf("/") + 1); return recursionGetTreeNode(childTopic, matchNode); } else { return matchNode; } } @Override public Set match(String topic) { Set subscriptions = new HashSet<>(); recursionMatch(topic, root, subscriptions); return subscriptions; } @Override public int size() { return 0; } @Override public String dumpTree() { return null; } private boolean innerIsMatch(String pubTopic, String subTopic) { if (pubTopic.equals(subTopic)) { return true; } String[] pubTokenStr = pubTopic.split("/"); String[] subTokenStr = subTopic.split("/"); int pubLen = pubTokenStr.length; int subLen = subTokenStr.length; if (pubLen != subLen) { Token lastSubToken = new Token(subTokenStr[subLen - 1]); if (subLen > pubLen || (!lastSubToken.equals(MULTY))) { return false; } } for (int i = 0; i < pubLen; i++) { Token pubToken = new Token(pubTokenStr[i]); Token subToken = new Token(subTokenStr[i]); if (subToken.equals(MULTY)) { return true; } else if ((!pubToken.equals(subToken)) && (!subToken.equals(SINGLE))) { return false; } if (i == subLen - 1) { return true; } } return false; } private void recursionMatch(String topic, TreeNode node, Set subscriptions) { String[] topics = topic.split("/"); Token token = new Token(topics[0]); List childNodes = node.getChildren(); if (topics.length > 1) { String nextTopic = topic.substring(topic.indexOf("/") + 1); for (TreeNode itemNode : childNodes) { if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE)) { recursionMatch(nextTopic, itemNode, subscriptions); } if (itemNode.getToken().equals(MULTY)) { subscriptions.addAll(itemNode.getSubscribers()); } } } else { for (TreeNode itemNode : childNodes) { if (itemNode.getToken().equals(token) || itemNode.getToken().equals(SINGLE) || itemNode.getToken().equals(MULTY)) { subscriptions.addAll(itemNode.getSubscribers()); } } } } class Token { String token; public Token(String token) { this.token = token; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Token token1 = (Token) o; return Objects.equals(token, token1.token); } @Override public int hashCode() { return Objects.hash(token); } } class TreeNode { private Token token; private Set subscribers = new CopyOnWriteArraySet<>(); private List children = new CopyOnWriteArrayList<>(); public TreeNode(Token token) { this.token = token; } public void addSubscriber(Subscription subscription) { this.subscribers.add(subscription); } public void addChild(TreeNode treeNode) { this.children.add(treeNode); } public Token getToken() { return token; } public Set getSubscribers() { return subscribers; } public List getChildren() { return this.children; } ; public TreeNode getChildNodeByToken(Token token) { for (TreeNode childNode : this.children) { if (childNode.getToken().equals(token)) { return childNode; } } return null; } } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/DumpTreeVisitor.java ================================================ package org.jmqtt.bus.subscription; import io.netty.util.internal.StringUtil; import org.jmqtt.bus.subscription.model.Subscription; class DumpTreeVisitor implements CTrie.IVisitor { String s = ""; @Override public void visit(CNode node, int deep) { String indentTabs = indentTabs(deep); s += indentTabs + (node.getToken() == null ? "''" : node.getToken().toString()) + prettySubscriptions(node) + "\n"; } private String prettySubscriptions(CNode node) { if (node instanceof TNode) { return "TNode"; } if (node.subscriptions.isEmpty()) { return StringUtil.EMPTY_STRING; } StringBuilder subScriptionsStr = new StringBuilder(" ~~["); int counter = 0; for (Subscription couple : node.subscriptions) { subScriptionsStr .append("{filter=").append(couple.getTopic()).append(", ") .append("properties=").append(couple.getProperties()).append(", ") .append("client='").append(couple.getClientId()).append("'}"); counter++; if (counter < node.subscriptions.size()) { subScriptionsStr.append(";"); } } return subScriptionsStr.append("]").toString(); } private String indentTabs(int deep) { StringBuilder s = new StringBuilder(); if (deep > 0) { s.append(" "); for (int i = 0; i < deep - 1; i++) { s.append("| "); } s.append("|-"); } return s.toString(); } @Override public String getResult() { return s; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/INode.java ================================================ package org.jmqtt.bus.subscription; import java.util.concurrent.atomic.AtomicReference; public class INode { private AtomicReference mainNode = new AtomicReference<>(); INode(CNode mainNode) { this.mainNode.set(mainNode); if (mainNode instanceof TNode) { // this should never happen throw new IllegalStateException("TNode should not be set on mainNnode"); } } boolean compareAndSet(CNode old, CNode newNode) { return mainNode.compareAndSet(old, newNode); } boolean compareAndSet(CNode old, TNode newNode) { return mainNode.compareAndSet(old, newNode); } CNode mainNode() { return this.mainNode.get(); } boolean isTombed() { return this.mainNode() instanceof TNode; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionCounterVisitor.java ================================================ package org.jmqtt.bus.subscription; import java.util.concurrent.atomic.AtomicInteger; class SubscriptionCounterVisitor implements CTrie.IVisitor { private AtomicInteger accumulator = new AtomicInteger(0); @Override public void visit(CNode node, int deep) { accumulator.addAndGet(node.subscriptions.size()); } @Override public Integer getResult() { return accumulator.get(); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/SubscriptionMatcher.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import java.util.Set; /** * Subscription tree * 订阅树处理 * TODO 1.支持共享订阅 * TODO 2.支持 p2p消息(jmqtt 特有) * TODO 2.支持系统topic $SYS -> 需要定时采集本机节点等信息下发 */ public interface SubscriptionMatcher { /** * add subscribe * @return true:new subscribe,dispatcher retain message * false:no need to dispatcher retain message */ boolean subscribe(Subscription subscription); boolean unSubscribe(String topic,String clientId); /** * 获取匹配该topic下的非共享订阅者 */ Set match(String topic); int size(); String dumpTree(); } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/TNode.java ================================================ /* * Copyright (c) 2012-2018 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; class TNode extends CNode { @Override public Token getToken() { throw new IllegalStateException("Can't be invoked on TNode"); } @Override public void setToken(Token token) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override INode childOf(Token token) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override CNode copy() { throw new IllegalStateException("Can't be invoked on TNode"); } @Override public void add(INode newINode) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override CNode addSubscription(Subscription newSubscription) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override boolean containsOnly(String clientId) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override public boolean contains(String clientId) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override void removeSubscriptionsFor(String clientId) { throw new IllegalStateException("Can't be invoked on TNode"); } @Override boolean anyChildrenMatch(Token token) { return false; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Token.java ================================================ package org.jmqtt.bus.subscription; public class Token { public static final Token EMPTY = new Token(""); public static final Token MULTI = new Token("#"); public static final Token SINGLE = new Token("+"); final String name; public Token(String s) { name = s; } protected String name() { return name; } protected boolean match(Token t) { if (MULTI.equals(t) || SINGLE.equals(t)) { return false; } if (MULTI.equals(this) || SINGLE.equals(this)) { return true; } return equals(t); } @Override public int hashCode() { int hash = 7; hash = 29 * hash + (this.name != null ? this.name.hashCode() : 0); return hash; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Token other = (Token) obj; if ((this.name == null) ? (other.name != null) : !this.name.equals(other.name)) { return false; } return true; } @Override public String toString() { return name; } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/Topic.java ================================================ package org.jmqtt.bus.subscription; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.io.Serializable; import java.text.ParseException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; public class Topic implements Serializable , Comparable { private static final Logger log = JmqttLogger.busLog; private static final long serialVersionUID = 2438799283749822L; private final String topic; private transient List tokens; private transient boolean valid; /** * Factory method * * @param s the topic string (es "/a/b"). * @return the created Topic instance. * */ public static Topic asTopic(String s) { return new Topic(s); } public Topic(String topic) { this.topic = topic; } Topic(List tokens) { this.tokens = tokens; List strTokens = tokens.stream().map(Token::toString).collect(Collectors.toList()); this.topic = String.join("/", strTokens); this.valid = true; } public List getTokens() { if (tokens == null) { try { tokens = parseTopic(topic); valid = true; } catch (ParseException e) { valid = false; LogUtil.error(log,"Error parsing the topic: {}, message: {}", topic, e.getMessage()); } } return tokens; } private List parseTopic(String topic) throws ParseException { if (topic.length() == 0) { throw new ParseException("Bad format of topic, topic MUST be at least 1 character [MQTT-4.7.3-1] and " + "this was empty", 0); } List res = new ArrayList<>(); String[] splitted = topic.split("/"); if (splitted.length == 0) { res.add(Token.EMPTY); } if (topic.endsWith("/")) { // Add a fictious space String[] newSplitted = new String[splitted.length + 1]; System.arraycopy(splitted, 0, newSplitted, 0, splitted.length); newSplitted[splitted.length] = ""; splitted = newSplitted; } for (int i = 0; i < splitted.length; i++) { String s = splitted[i]; if (s.isEmpty()) { // if (i != 0) { // throw new ParseException("Bad format of topic, expetec topic name between // separators", i); // } res.add(Token.EMPTY); } else if (s.equals("#")) { // check that multi is the last symbol if (i != splitted.length - 1) { throw new ParseException( "Bad format of topic, the multi symbol (#) has to be the last one after a separator", i); } res.add(Token.MULTI); } else if (s.contains("#")) { throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i); } else if (s.equals("+")) { res.add(Token.SINGLE); } else if (s.contains("+")) { throw new ParseException("Bad format of topic, invalid subtopic name: " + s, i); } else { res.add(new Token(s)); } } return res; } public Token headToken() { final List tokens = getTokens(); if (tokens.isEmpty()) { return null; } return tokens.get(0); } public boolean isEmpty() { final List tokens = getTokens(); return tokens == null || tokens.isEmpty(); } public Topic exceptHeadToken() { List tokens = getTokens(); if (tokens.isEmpty()) { return new Topic(Collections.emptyList()); } List tokensCopy = new ArrayList<>(tokens); tokensCopy.remove(0); return new Topic(tokensCopy); } public boolean isValid() { if (tokens == null) getTokens(); return valid; } public boolean match(Topic subscriptionTopic) { List msgTokens = getTokens(); List subscriptionTokens = subscriptionTopic.getTokens(); int i = 0; for (; i < subscriptionTokens.size(); i++) { Token subToken = subscriptionTokens.get(i); if (!Token.MULTI.equals(subToken) && !Token.SINGLE.equals(subToken)) { if (i >= msgTokens.size()) { return false; } Token msgToken = msgTokens.get(i); if (!msgToken.equals(subToken)) { return false; } } else { if (Token.MULTI.equals(subToken)) { return true; } } } return i == msgTokens.size(); } @Override public String toString() { return topic; } @Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Topic other = (Topic) obj; return Objects.equals(this.topic, other.topic); } @Override public int hashCode() { return topic.hashCode(); } @Override public int compareTo(Topic o) { return topic.compareTo(o.topic); } } ================================================ FILE: jmqtt-bus/src/main/java/org/jmqtt/bus/subscription/model/Subscription.java ================================================ package org.jmqtt.bus.subscription.model; import java.util.Map; import java.util.Objects; /** * 订阅关系 */ public class Subscription { private String clientId; private String topic; private Map properties; public Subscription(String clientId,String topic){ this.clientId = clientId; this.topic = topic; } public Subscription(String clientId,String topic,Map properties){ this.clientId = clientId; this.topic = topic; this.properties = properties; } public Subscription(Subscription origin){ this.clientId = origin.getClientId(); this.topic = origin.getTopic(); this.properties = origin.getProperties(); } public T getProperty(String key){ if (properties == null) { return null; } return (T) properties.get(key); } public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public String getTopic() { return topic; } public void setTopic(String topic) { this.topic = topic; } public Map getProperties() { return properties; } public void setProperties(Map properties) { this.properties = properties; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Subscription that = (Subscription) o; return Objects.equals(clientId, that.clientId) && Objects.equals(topic, that.topic); } @Override public int hashCode() { return Objects.hash(clientId, topic); } @Override public String toString() { return "Subscription{" + "clientId='" + clientId + '\'' + ", topic='" + topic + '\'' + ", properties=" + properties + '}'; } } ================================================ FILE: jmqtt-bus/src/test/java/org/jmqtt/AppTest.java ================================================ package org.jmqtt; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertTrue; /** * Unit test for simple App. */ public class AppTest { /** * Rigorous Test :-) */ @Test public void shouldAnswerWithTrue() { assertTrue( true ); } } ================================================ FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieSubscriptionMatcherMatchingTest.java ================================================ /* * Copyright (c) 2012-2018 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.jmqtt.bus.subscription.CTrieTest.clientSubOnTopic; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class CTrieSubscriptionMatcherMatchingTest { private CTrieSubscriptionMatcher sut; @BeforeEach public void setUp() { sut = new CTrieSubscriptionMatcher(); } @Test public void testMatchSimple() { Subscription slashSub = clientSubOnTopic("TempSensor1", "/"); sut.subscribe(slashSub); assertThat(sut.match("finance")).isEmpty(); Subscription slashFinanceSub = clientSubOnTopic("TempSensor1", "/finance"); sut.subscribe(slashFinanceSub); assertThat(sut.match("finance")).isEmpty(); assertThat(sut.match("/finance")).contains(slashFinanceSub); assertThat(sut.match("/")).contains(slashSub); } @Test public void testMatchSimpleMulti() { Subscription anySub = clientSubOnTopic("TempSensor1", "#"); sut.subscribe(anySub); assertThat(sut.match("finance")).contains(anySub); Subscription financeAnySub = clientSubOnTopic("TempSensor1", "finance/#"); sut.subscribe(financeAnySub); assertThat(sut.match("finance")).containsExactlyInAnyOrder(financeAnySub, anySub); } @Test public void testMatchingDeepMulti_one_layer() { Subscription anySub = clientSubOnTopic("AllSensor1", "#"); Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/#"); sut.subscribe(anySub); sut.subscribe(financeAnySub); // Verify assertThat(sut.match("finance/stock")) .containsExactlyInAnyOrder(financeAnySub, anySub); assertThat(sut.match("finance/stock/ibm")) .containsExactlyInAnyOrder(financeAnySub, anySub); // System.out.println(sut.dumpTree()); } @Test public void testMatchingDeepMulti_two_layer() { Subscription financeAnySub = clientSubOnTopic("FinanceSensor", "finance/stock/#"); sut.subscribe(financeAnySub); // Verify assertThat(sut.match("finance/stock/ibm")).containsExactly(financeAnySub); } @Test public void testMatchSimpleSingle() { Subscription anySub = clientSubOnTopic("AnySensor", "+"); sut.subscribe(anySub); assertThat(sut.match("finance")).containsExactly(anySub); Subscription financeOne = clientSubOnTopic("AnySensor", "finance/+"); sut.subscribe(financeOne); assertThat(sut.match("finance/stock")).containsExactly(financeOne); } @Test public void testMatchManySingle() { Subscription manySub = clientSubOnTopic("AnySensor", "+/+"); sut.subscribe(manySub); // verify assertThat(sut.match("/finance")).contains(manySub); } @Test public void testMatchSlashSingle() { Subscription slashPlusSub = clientSubOnTopic("AnySensor", "/+"); sut.subscribe(slashPlusSub); Subscription anySub = clientSubOnTopic("AnySensor", "+"); sut.subscribe(anySub); // Verify assertThat(sut.match("/finance")).containsOnly(slashPlusSub); assertThat(sut.match("/finance")).doesNotContain(anySub); } @Test public void testMatchManyDeepSingle() { Subscription slashPlusSub = clientSubOnTopic("FinanceSensor1", "/finance/+/ibm"); sut.subscribe(slashPlusSub); Subscription slashPlusDeepSub = clientSubOnTopic("FinanceSensor2", "/+/stock/+"); sut.subscribe(slashPlusDeepSub); // Verify assertThat(sut.match("/finance/stock/ibm")) .containsExactlyInAnyOrder(slashPlusSub, slashPlusDeepSub); } @Test public void testMatchSimpleMulti_allTheTree() { Subscription sub = clientSubOnTopic("AnySensor1", "#"); sut.subscribe(sub); assertThat(sut.match("finance")).isNotEmpty(); assertThat(sut.match("finance/ibm")).isNotEmpty(); } @Test public void rogerLightTopicMatches() { assertMatch("foo/bar", "foo/bar"); assertMatch("foo/bar", "foo/bar"); assertMatch("foo/+", "foo/bar"); assertMatch("foo/+/baz", "foo/bar/baz"); assertMatch("foo/+/#", "foo/bar/baz"); assertMatch("#", "foo/bar/baz"); assertNotMatch("foo/bar", "foo"); assertNotMatch("foo/+", "foo/bar/baz"); assertNotMatch("foo/+/baz", "foo/bar/bar"); assertNotMatch("foo/+/#", "fo2/bar/baz"); assertMatch("#", "/foo/bar"); assertMatch("/#", "/foo/bar"); assertNotMatch("/#", "foo/bar"); assertMatch("foo//bar", "foo//bar"); assertMatch("foo//+", "foo//bar"); assertMatch("foo/+/+/baz", "foo///baz"); assertMatch("foo/bar/+", "foo/bar/"); } private void assertMatch(String s, String t) { sut = new CTrieSubscriptionMatcher(); Subscription sub = clientSubOnTopic("AnySensor1", s); sut.subscribe(sub); assertThat(sut.match(t)).isNotEmpty(); } private void assertNotMatch(String subscription, String topic) { sut = new CTrieSubscriptionMatcher(); Subscription sub = clientSubOnTopic("AnySensor1", subscription); sut.subscribe(sub); assertThat(sut.match(topic)).isEmpty(); } @Test public void testOverlappingSubscriptions() { Subscription genericSub = new Subscription("Sensor1", "a/+", null); sut.subscribe(genericSub); Subscription specificSub = new Subscription("Sensor1", "a/b", null); sut.subscribe(specificSub); //Exercise final Set matchingForSpecific = sut.match("a/b"); // Verify assertThat(matchingForSpecific.size()).isEqualTo(1); } @Test public void removeSubscription_withDifferentClients_subscribedSameTopic() { Subscription slashSub = clientSubOnTopic("Sensor1", "/topic"); sut.subscribe(slashSub); Subscription slashSub2 = clientSubOnTopic("Sensor2", "/topic"); sut.subscribe(slashSub2); // Exercise sut.unSubscribe("/topic", slashSub2.getClientId()); // Verify Subscription remainedSubscription = sut.match("/topic").iterator().next(); assertThat(remainedSubscription.getClientId()).isEqualTo(slashSub.getClientId()); assertEquals(slashSub.getClientId(), remainedSubscription.getClientId()); } @Test public void removeSubscription_sameClients_subscribedSameTopic() { Subscription slashSub = clientSubOnTopic("Sensor1", "/topic"); sut.subscribe(slashSub); // Exercise sut.unSubscribe("/topic", slashSub.getClientId()); // Verify final Set matchingSubscriptions = sut.match("/topic"); assertThat(matchingSubscriptions).isEmpty(); } /* * Test for Issue #49 */ @Test public void duplicatedSubscriptionsWithDifferentQos() { Subscription client2Sub = new Subscription("client2", "client/test/b", null); this.sut.subscribe(client2Sub); Subscription client1SubQoS0 = new Subscription("client1", "client/test/b", null); this.sut.subscribe(client1SubQoS0); Subscription client1SubQoS2 = new Subscription("client1", "client/test/b", null); this.sut.subscribe(client1SubQoS2); // Verify Set subscriptions = this.sut.match("client/test/b"); assertThat(subscriptions).contains(client1SubQoS2); assertThat(subscriptions).contains(client2Sub); final Optional matchingClient1Sub = subscriptions .stream() .filter(s -> s.equals(client1SubQoS0)) .findFirst(); assertTrue(matchingClient1Sub.isPresent()); Subscription client1Sub = matchingClient1Sub.get(); } } ================================================ FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/CTrieTest.java ================================================ /* * Copyright (c) 2012-2018 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package org.jmqtt.bus.subscription; import org.jmqtt.bus.subscription.model.Subscription; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import java.util.Optional; import java.util.Set; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; public class CTrieTest { private CTrie sut; @BeforeEach public void setUp() { sut = new CTrie(); } @Test public void testAddOnSecondLayerWithEmptyTokenOnEmptyTree() { //Exercise sut.addToTree(clientSubOnTopic("TempSensor1", "/")); //Verify final Optional matchedNode = sut.lookup(new Topic("/")); assertTrue(matchedNode.isPresent(), "Node on path / must be present"); //verify structure, only root INode and the first CNode should be present assertThat(this.sut.root.mainNode().subscriptions).isEmpty(); assertThat(this.sut.root.mainNode().allChildren()).isNotEmpty(); INode firstLayer = this.sut.root.mainNode().allChildren().get(0); assertThat(firstLayer.mainNode().subscriptions).isEmpty(); assertThat(firstLayer.mainNode().allChildren()).isNotEmpty(); INode secondLayer = firstLayer.mainNode().allChildren().get(0); assertThat(secondLayer.mainNode().subscriptions).isNotEmpty(); assertThat(secondLayer.mainNode().allChildren()).isEmpty(); } @Test public void testAddFirstLayerNodeOnEmptyTree() { //Exercise sut.addToTree(clientSubOnTopic("TempSensor1", "/temp")); //Verify final Optional matchedNode = sut.lookup(new Topic("/temp")); assertTrue(matchedNode.isPresent(), "Node on path /temp must be present"); assertFalse(matchedNode.get().subscriptions.isEmpty()); } @Test public void testLookup() { final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp"); sut.addToTree(existingSubscription); //Exercise final Optional matchedNode = sut.lookup(new Topic("/humidity")); //Verify assertFalse(matchedNode.isPresent(), "Node on path /humidity can't be present"); } @Test public void testAddNewSubscriptionOnExistingNode() { final Subscription existingSubscription = clientSubOnTopic("TempSensor1", "/temp"); sut.addToTree(existingSubscription); //Exercise final Subscription newSubscription = clientSubOnTopic("TempSensor2", "/temp"); sut.addToTree(newSubscription); //Verify final Optional matchedNode = sut.lookup(new Topic("/temp")); assertTrue(matchedNode.isPresent(), "Node on path /temp must be present"); final Set subscriptions = matchedNode.get().subscriptions; assertTrue(subscriptions.contains(newSubscription)); } @Test public void testAddNewDeepNodes() { sut.addToTree(clientSubOnTopic("TempSensorRM", "/italy/roma/temp")); sut.addToTree(clientSubOnTopic("TempSensorFI", "/italy/firenze/temp")); sut.addToTree(clientSubOnTopic("HumSensorFI", "/italy/roma/humidity")); final Subscription happinessSensor = clientSubOnTopic("HappinessSensor", "/italy/happiness"); sut.addToTree(happinessSensor); //Verify final Optional matchedNode = sut.lookup(new Topic("/italy/happiness")); assertTrue(matchedNode.isPresent(), "Node on path /italy/happiness must be present"); final Set subscriptions = matchedNode.get().subscriptions; assertTrue(subscriptions.contains(happinessSensor)); } static Subscription clientSubOnTopic(String clientID, String topicName) { return new Subscription(clientID, topicName, null); } @Test public void givenTreeWithSomeNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() { sut.addToTree(clientSubOnTopic("TempSensor1", "/temp")); //Exercise sut.removeFromTree(new Topic("/temp"), "TempSensor1"); //Verify final Optional matchedNode = sut.lookup(new Topic("/temp")); assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present"); } @Test public void givenTreeWithSomeNodeUnsubscribeAndResubscribeCleanTomb() { sut.addToTree(clientSubOnTopic("TempSensor1", "test")); sut.removeFromTree(new Topic("test"), "TempSensor1"); sut.addToTree(clientSubOnTopic("TempSensor1", "test")); assertEquals(1, sut.root.mainNode().allChildren().size()); // looking to see if TNode is cleaned up } @Test public void givenTreeWithSomeNodeWhenRemoveMultipleTimes() { sut.addToTree(clientSubOnTopic("TempSensor1", "test")); // make sure no TNode exceptions sut.removeFromTree(new Topic("test"), "TempSensor1"); sut.removeFromTree(new Topic("test"), "TempSensor1"); sut.removeFromTree(new Topic("test"), "TempSensor1"); sut.removeFromTree(new Topic("test"), "TempSensor1"); //Verify final Optional matchedNode = sut.lookup(new Topic("/temp")); assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present"); } @Test public void givenTreeWithSomeDeepNodeWhenRemoveMultipleTimes() { sut.addToTree(clientSubOnTopic("TempSensor1", "/test/me/1/2/3")); // make sure no TNode exceptions sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1"); sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1"); sut.removeFromTree(new Topic("/test/me/1/2/3"), "TempSensor1"); //Verify final Optional matchedNode = sut.lookup(new Topic("/temp")); assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present"); } @Test public void givenTreeWithSomeNodeHierarchyWhenRemoveContainedSubscriptionThenNodeIsUpdated() { sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/1")); sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/2")); //Exercise sut.removeFromTree(new Topic("/temp/1"), "TempSensor1"); sut.removeFromTree(new Topic("/temp/1"), "TempSensor1"); final Set matchingSubs = sut.recursiveMatch(new Topic("/temp/2")); //Verify final Subscription expectedMatchingsub = new Subscription("TempSensor1", "/temp/2"); assertThat(matchingSubs).contains(expectedMatchingsub); } @Test public void givenTreeWithSomeNodeHierarchWhenRemoveContainedSubscriptionSmallerThenNodeIsNotUpdated() { sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/1")); sut.addToTree(clientSubOnTopic("TempSensor1", "/temp/2")); //Exercise sut.removeFromTree(new Topic("/temp"), "TempSensor1"); final Set matchingSubs1 = sut.recursiveMatch(new Topic("/temp/1")); final Set matchingSubs2 = sut.recursiveMatch(new Topic("/temp/2")); //Verify // not clear to me, but I believe /temp unsubscribe should not unsub you from downstream /temp/1 or /temp/2 final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "/temp/1"); assertThat(matchingSubs1).contains(expectedMatchingsub1); final Subscription expectedMatchingsub2 = new Subscription("TempSensor1", "/temp/2"); assertThat(matchingSubs2).contains(expectedMatchingsub2); } @Test public void givenTreeWithDeepNodeWhenRemoveContainedSubscriptionThenNodeIsUpdated() { sut.addToTree(clientSubOnTopic("TempSensor1", "/bah/bin/bash")); sut.removeFromTree(new Topic("/bah/bin/bash"), "TempSensor1"); //Verify final Optional matchedNode = sut.lookup(new Topic("/bah/bin/bash")); assertFalse(matchedNode.isPresent(), "Node on path /temp can't be present"); } @Test public void testMatchSubscriptionNoWildcards() { sut.addToTree(clientSubOnTopic("TempSensor1", "/temp")); //Exercise final Set matchingSubs = sut.recursiveMatch(new Topic("/temp")); //Verify final Subscription expectedMatchingsub = new Subscription("TempSensor1", "/temp", null); assertThat(matchingSubs).contains(expectedMatchingsub); } @Test public void testRemovalInnerTopicOffRootSameClient() { sut.addToTree(clientSubOnTopic("TempSensor1", "temp")); sut.addToTree(clientSubOnTopic("TempSensor1", "temp/1")); //Exercise final Set matchingSubs1 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs2 = sut.recursiveMatch(new Topic("temp/1")); //Verify final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null); final Subscription expectedMatchingsub2 = new Subscription("TempSensor1", "temp/1", null); assertThat(matchingSubs1).contains(expectedMatchingsub1); assertThat(matchingSubs2).contains(expectedMatchingsub2); sut.removeFromTree(new Topic("temp"), "TempSensor1"); //Exercise final Set matchingSubs3 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs4 = sut.recursiveMatch(new Topic("temp/1")); assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1); assertThat(matchingSubs4).contains(expectedMatchingsub2); } @Test public void testRemovalInnerTopicOffRootDiffClient() { sut.addToTree(clientSubOnTopic("TempSensor1", "temp")); sut.addToTree(clientSubOnTopic("TempSensor2", "temp/1")); //Exercise final Set matchingSubs1 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs2 = sut.recursiveMatch(new Topic("temp/1")); //Verify final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null); final Subscription expectedMatchingsub2 = new Subscription("TempSensor2", "temp/1", null); assertThat(matchingSubs1).contains(expectedMatchingsub1); assertThat(matchingSubs2).contains(expectedMatchingsub2); sut.removeFromTree(new Topic("temp"), "TempSensor1"); //Exercise final Set matchingSubs3 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs4 = sut.recursiveMatch(new Topic("temp/1")); assertThat(matchingSubs3).doesNotContain(expectedMatchingsub1); assertThat(matchingSubs4).contains(expectedMatchingsub2); } @Test public void testRemovalOuterTopicOffRootDiffClient() { sut.addToTree(clientSubOnTopic("TempSensor1", "temp")); sut.addToTree(clientSubOnTopic("TempSensor2", "temp/1")); //Exercise final Set matchingSubs1 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs2 = sut.recursiveMatch(new Topic("temp/1")); //Verify final Subscription expectedMatchingsub1 = new Subscription("TempSensor1", "temp", null); final Subscription expectedMatchingsub2 = new Subscription("TempSensor2", "temp/1", null); assertThat(matchingSubs1).contains(expectedMatchingsub1); assertThat(matchingSubs2).contains(expectedMatchingsub2); sut.removeFromTree(new Topic("temp/1"), "TempSensor2"); //Exercise final Set matchingSubs3 = sut.recursiveMatch(new Topic("temp")); final Set matchingSubs4 = sut.recursiveMatch(new Topic("temp/1")); assertThat(matchingSubs3).contains(expectedMatchingsub1); assertThat(matchingSubs4).doesNotContain(expectedMatchingsub2); } } ================================================ FILE: jmqtt-bus/src/test/java/org/jmqtt/bus/subscription/TopicTest.java ================================================ /* * Copyright (c) 2012-2018 The original author or authors * ------------------------------------------------------ * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Apache License v2.0 which accompanies this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Apache License v2.0 is available at * http://www.opensource.org/licenses/apache2.0.php * * You may elect to redistribute this code under either of these licenses. */ package org.jmqtt.bus.subscription; import org.assertj.core.api.AbstractAssert; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; public class TopicTest { @Test public void testParseTopic() { assertThat(new Topic("finance/stock/ibm")).containsToken("finance", "stock", "ibm"); assertThat(new Topic("/finance/stock/ibm")).containsToken(Token.EMPTY, "finance", "stock", "ibm"); assertThat(new Topic("/")).containsToken(Token.EMPTY, Token.EMPTY); } @Test public void testParseTopicMultiValid() { assertThat(new Topic("finance/stock/#")).containsToken("finance", "stock", Token.MULTI); assertThat(new Topic("#")).containsToken(Token.MULTI); } @Test public void testValidationProcess() { // TopicMultiInTheMiddle assertThat(new Topic("finance/#/closingprice")).isInValid(); // MultiNotAfterSeparator assertThat(new Topic("finance#")).isInValid(); // TopicMultiNotAlone assertThat(new Topic("/finance/#closingprice")).isInValid(); // SingleNotAferSeparator assertThat(new Topic("finance+")).isInValid(); assertThat(new Topic("finance/+")).isValid(); } @Test public void testParseTopicSingleValid() { assertThat(new Topic("finance/stock/+")).containsToken("finance", "stock", Token.SINGLE); assertThat(new Topic("+")).containsToken(Token.SINGLE); assertThat(new Topic("finance/+/ibm")).containsToken("finance", Token.SINGLE, "ibm"); } @Test public void testMatchTopics_simple() { assertThat(new Topic("/")).matches("/"); assertThat(new Topic("/finance")).matches("/finance"); } @Test public void testMatchTopics_multi() { assertThat(new Topic("finance")).matches("#"); assertThat(new Topic("finance")).matches("finance/#"); assertThat(new Topic("finance/stock")).matches("finance/#"); assertThat(new Topic("finance/stock/ibm")).matches("finance/#"); } @Test public void testMatchTopics_single() { assertThat(new Topic("finance")).matches("+"); assertThat(new Topic("finance/stock")).matches("finance/+"); assertThat(new Topic("finance")).doesNotMatch("finance/+"); assertThat(new Topic("/finance")).matches("/+"); assertThat(new Topic("/finance")).doesNotMatch("+"); assertThat(new Topic("/finance")).matches("+/+"); assertThat(new Topic("/finance/stock/ibm")).matches("/finance/+/ibm"); assertThat(new Topic("/")).matches("+/+"); assertThat(new Topic("sport/")).matches("sport/+"); assertThat(new Topic("/finance/stock")).doesNotMatch("+"); } @Test public void rogerLightMatchTopics() { assertThat(new Topic("foo/bar")).matches("foo/bar"); assertThat(new Topic("foo/bar")).matches("foo/+"); assertThat(new Topic("foo/bar/baz")).matches("foo/+/baz"); assertThat(new Topic("foo/bar/baz")).matches("foo/+/#"); assertThat(new Topic("foo/bar/baz")).matches("#"); assertThat(new Topic("foo")).doesNotMatch("foo/bar"); assertThat(new Topic("foo/bar/baz")).doesNotMatch("foo/+"); assertThat(new Topic("foo/bar/bar")).doesNotMatch("foo/+/baz"); assertThat(new Topic("fo2/bar/baz")).doesNotMatch("foo/+/#"); assertThat(new Topic("/foo/bar")).matches("#"); assertThat(new Topic("/foo/bar")).matches("/#"); assertThat(new Topic("foo/bar")).doesNotMatch("/#"); assertThat(new Topic("foo//bar")).matches("foo//bar"); assertThat(new Topic("foo//bar")).matches("foo//+"); assertThat(new Topic("foo///baz")).matches("foo/+/+/baz"); assertThat(new Topic("foo/bar/")).matches("foo/bar/+"); } @Test public void exceptHeadToken() { assertEquals(Topic.asTopic("token"), Topic.asTopic("/token").exceptHeadToken()); assertEquals(Topic.asTopic("a/b"), Topic.asTopic("/a/b").exceptHeadToken()); } public static TopicAssert assertThat(Topic topic) { return new TopicAssert(topic); } static class TopicAssert extends AbstractAssert { TopicAssert(Topic actual) { super(actual, TopicAssert.class); } public TopicAssert matches(String topic) { Assertions.assertThat(actual.match(new Topic(topic))).isTrue(); return myself; } public TopicAssert doesNotMatch(String topic) { Assertions.assertThat(actual.match(new Topic(topic))).isFalse(); return myself; } public TopicAssert containsToken(Object... tokens) { Assertions.assertThat(actual.getTokens()).containsExactly(asArray(tokens)); return myself; } private Token[] asArray(Object... l) { Token[] tokens = new Token[l.length]; for (int i = 0; i < l.length; i++) { Object o = l[i]; if (o instanceof Token) { tokens[i] = (Token) o; } else { tokens[i] = new Token(o.toString()); } } return tokens; } public TopicAssert isValid() { Assertions.assertThat(actual.isValid()).isTrue(); return myself; } public TopicAssert isInValid() { Assertions.assertThat(actual.isValid()).isFalse(); return myself; } } } ================================================ FILE: jmqtt-doc/README.md ================================================ 官网搭建指导文件 1. clone本项目 2. 安装npm或yarn,具体参考:https://www.vuepress.cn/guide/getting-started.html 3. 执行:yarn add -D vuepress 4. 本地启动:yarn dev 5. 构建:yarn build ================================================ FILE: jmqtt-doc/docs/README.md ================================================ ## 快速上手 ### 安装主题 1. 下载 [release](https://github.com/Cicizz/jmqtt/releases)(3.x以上版本) 或`clone`本项目: 2. 在jmqtt根目录执行:: ```bash mvn -Ppackage-all -DskipTests clean install -U ``` 3. 配置相应的配置文件,初始化db的sql文件:`/jmqtt-broker/resources/conf`目录下 4. 执行启动命令:`java -jar jmqtt-broker-3.0.0.jar -h ${conf文件目录}` -h后是配置文件目录,里面需要包含jmqtt.properties和log4j2.xml等配置文件 ### 测试 下载客户端:[mqtt客户端](https://mqttx.app/cn/) 或 直接使用websocket测试:`/jmqtt/jmqtt-examples`

 

[我也想为贡献者之一?](https://github.com/Cicizz/jmqtt/pulls)

 

================================================ FILE: jmqtt-doc/docs/TEST_REPORT.md ================================================ # Jmqtt 最新版功能及性能测试报告 线上版连接:https://www.yuque.com/tristan-ku8np/zze/xghq80 最新版链接:[https://github.com/Cicizz/jmqtt](https://github.com/Cicizz/jmqtt) # 一、目标 1. 检测Jmqtt功能及性能运行情况 1. 为使用者提供参考 说明:以下测试为Jmqtt作者闲暇之时进行的测试,仅供参考,再线上使用Jmqtt时,请自行测试,感谢 朱霄 同学提供的服务器等测试资源 # 二、测试机配置 | | 数量 | 操作系统 | 配置 | | --------------- | ----------- | --------------- | ----------------------------- | | Jmqtt运行服务器 | 2台(集群) | linux centos 7 | 8C16G | | Jmqtt压测机 | 2台 | linux centos 7 | 8C16G | | Mysql | 单库单表 | mysql5.7 | 阿里云rds基础版:ESSD OL1云盘 | | SLB | 1 | 支持4层负载均衡 | 支持20万长连接 | 测试脚本: 1. jmeter 1. emqx-bench # 三、功能测试报告 | 功能项 | 是否满足 | 备注 | | --------------------------- | -------- | --------------------------------------------------------- | | 集群连接 | ✅ | | | 集群cleansession为false连接 | ✅ | | | 设备在集群互发消息 | ✅ | | | retain消息 | ✅ | | | will消息 | ✅ | | | qos0 | ✅ | | | qos1 | ✅ | | | qos2 | ✅ | | | 离线消息 | ✅ | 不限离线消息数量 | | 设备信息持久化 | ✅ | 见sql表:jmqtt_session | | 订阅关系持久化 | ✅ | 见sql表:jmqtt_subscription | | 消息持久化 | ✅ | 见sql表:jmqtt_message | | 集群事件消息 | ✅ | 包含集群间转发消息,连接事件,见sql表:jmqtt_cluser_event | | 设备消息接收状态 | ✅ | 见sql表:jmqtt_client_inbox | | 监控topic | ❎ | 不支持,需自行实现 | | 各个消息桥接 | ❎ | 不支持,需自行实现 | | 规则引擎 | ❎ | 不支持,需自行实现 | # 四、单机性能测试报告 ## 4.1 连接数性能报告 连接数单机测试达到10W级连接,连接tps1000,未出现报错, 注意:因测试机资源问题,未压测到上限。详细截图报告见下图 ### 4.1.1 连接数200,连接持续3min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746010873-3806f744-eebd-4a06-b638-a5c9030a913a.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=111&id=u3f625a5c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=222&originWidth=1704&originalType=binary&ratio=1&rotation=0&showTitle=false&size=129250&status=done&style=none&taskId=u1e953a0b-157d-45d3-9bd0-886c7d1982c&title=&width=852) ### 4.1.2 连接数1000,连接持续3min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746046914-ac14a7be-c248-4dfd-9244-3b3b6bf50bfd.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=114&id=ude67f41c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=228&originWidth=1754&originalType=binary&ratio=1&rotation=0&showTitle=false&size=113132&status=done&style=none&taskId=u96d8e2f0-5487-4386-a565-467f58186d3&title=&width=877) ### 4.1.3 连接数2000,连接持续3min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746065295-8a158413-36ac-4e88-b67c-1c6f6f565bc7.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=313&id=u67a9620e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=626&originWidth=2236&originalType=binary&ratio=1&rotation=0&showTitle=false&size=390748&status=done&style=none&taskId=u44a649b4-aa01-4ad7-906e-e55f1b7f21e&title=&width=1118) ### 4.1.4 连接数5000,连接持续3min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746088650-4e2c8fab-4185-41db-b406-89571492fb79.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=279&id=u8a9a70e7&margin=%5Bobject%20Object%5D&name=image.png&originHeight=558&originWidth=2192&originalType=binary&ratio=1&rotation=0&showTitle=false&size=257112&status=done&style=none&taskId=u68f785bb-6e9b-4668-9fa0-23ce4914e1a&title=&width=1096) ### 4.1.5 连接数1W,连接持续3min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746108655-757645fb-6951-4dc4-ac6e-97946f35e980.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=42&id=u4b3542f2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=84&originWidth=945&originalType=binary&ratio=1&rotation=0&showTitle=false&size=49950&status=done&style=none&taskId=ue2cf34cc-2cef-4ef0-88db-3826ecda806&title=&width=472.5) ### 4.1.6 连接数2W,连接持续3min 超过2W采用开源emqx-bench进行性能压测 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746146826-bc37501b-4b08-4566-a461-02ced5d72cfe.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=23&id=ue6ccf234&margin=%5Bobject%20Object%5D&name=image.png&originHeight=46&originWidth=634&originalType=binary&ratio=1&rotation=0&showTitle=false&size=22600&status=done&style=none&taskId=u33aaf8db-a098-40a5-8036-e6f2065e970&title=&width=317) ### 4.1.7 连接数5W,连接持续10min ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746200835-34532619-4370-4a54-9f57-607b6eb20916.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=20&id=ub9254472&margin=%5Bobject%20Object%5D&name=image.png&originHeight=39&originWidth=738&originalType=binary&ratio=1&rotation=0&showTitle=false&size=18207&status=done&style=none&taskId=ud0a3ce29-ca11-482e-83f5-71b4c49f78c&title=&width=369) ### 4.1.8 连接数10W,连接持续10min 压到10W后,未持续压测,尚未压到连接数上限,两台测试机截图如下: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746254255-83829c1c-0ebc-46cb-a252-68febb726b2f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=24&id=u55c0540d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=48&originWidth=1491&originalType=binary&ratio=1&rotation=0&showTitle=false&size=43616&status=done&style=none&taskId=ue7fdb9c3-50d5-4e16-9f5e-c70b6b430b0&title=&width=745.5) 服务器load截图: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642746280704-1784ae08-ffeb-4fdd-94f1-98206c444c54.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=70&id=udcb79ca0&margin=%5Bobject%20Object%5D&name=image.png&originHeight=140&originWidth=716&originalType=binary&ratio=1&rotation=0&showTitle=false&size=71157&status=done&style=none&taskId=u6a9c56d6-706e-4f0e-9813-5c3bf2a1adb&title=&width=358) ## 4.2 发送消息性能报告 ### 4.2.1 设备连接2W,再启动1000连接持续发送消息 消息大小:256byte qos:0 每隔10ms发送1条消息 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747253961-4180a124-1185-4aac-8442-781b044fb609.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ua5ae4796&margin=%5Bobject%20Object%5D&name=image.png&originHeight=302&originWidth=438&originalType=binary&ratio=1&rotation=0&showTitle=false&size=104022&status=done&style=none&taskId=u06b96fe2-eac6-404c-a96b-5f353408b1f&title=&width=364) 对比emq服务(没有2W长连接设备保持):broker.emqx.io ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747299016-50f125b1-fe67-4379-8858-37ac0cdec74b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=u73d17b7e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=255&originWidth=459&originalType=binary&ratio=1&rotation=0&showTitle=false&size=85760&status=done&style=none&taskId=u61141867-fb4f-4047-ae97-9400e851490&title=&width=364.5) ### 4.2.2 设备连接2W,再启动200连接持续发送消息 消息大小:256byte qos:1 每隔10ms发送1条消息 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747478891-b7ec2f4a-012d-4c9d-9dbc-f2bf7112cae8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ue0cc314a&margin=%5Bobject%20Object%5D&name=image.png&originHeight=284&originWidth=403&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91808&status=done&style=none&taskId=u236295c7-4c40-4247-b975-e31b2558b3b&title=&width=287.5) ### 4.2.3 设备连接2W,再启动200连接持续发送消息 消息大小:100byte qos:1 每隔10ms发送1条消息 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747586254-5d5b40f9-4cef-4633-9a3d-94ab46837bd6.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=176&id=uf4feec24&margin=%5Bobject%20Object%5D&name=image.png&originHeight=301&originWidth=561&originalType=binary&ratio=1&rotation=0&showTitle=false&size=99190&status=done&style=none&taskId=u7428afd4-7a26-4449-9433-47fc1af1230&title=&width=327.5) ## 4.3 订阅性能报告 ### 4.3.1 启动2W个设备,订阅2W个topic ### ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642747707443-c70722d5-e4f6-4674-a9ab-3ba8490899d9.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=255&id=u22f47aba&margin=%5Bobject%20Object%5D&name=image.png&originHeight=333&originWidth=285&originalType=binary&ratio=1&rotation=0&showTitle=false&size=80501&status=done&style=none&taskId=u39a72cd5-3b32-4b86-9490-28e3815563d&title=&width=218.5) # 五、集群性能测试报告 ## 5.1 连接数性能报告 Jmqtt服务器两台,设备连接数10W,未压测到上限 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748056231-f1d72eeb-0dac-4e33-8ba2-0111e9f11387.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=314&id=u09e9c4d3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=628&originWidth=1928&originalType=binary&ratio=1&rotation=0&showTitle=false&size=589189&status=done&style=none&taskId=ue4369d89-e4b7-4dac-979b-4514e76acdb&title=&width=964) ## 5.2 发送消息性能报告 发送消息强依赖db进行保存,性能瓶颈在db侧,故tps上不去 ### 5.2.1 设备连接2W,启动200连接持续发送消息 消息大小:256byte qos:1 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748182652-85094ef1-740e-4067-85ac-1d089961994b.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=271&id=u5a016123&margin=%5Bobject%20Object%5D&name=image.png&originHeight=542&originWidth=658&originalType=binary&ratio=1&rotation=0&showTitle=false&size=239858&status=done&style=none&taskId=udb8c34ff-f596-4ef8-bedc-5343205e3ab&title=&width=329) ### 5.2.2 设备连接2W,启动200连接持续发送消息 消息大小:100byte qos:1 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748243207-6bd4b28d-818c-4f71-9759-11edfe7ffbac.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=251&id=ud1655849&margin=%5Bobject%20Object%5D&name=image.png&originHeight=502&originWidth=694&originalType=binary&ratio=1&rotation=0&showTitle=false&size=230671&status=done&style=none&taskId=u9e328faa-fd3f-48f8-884c-7104ba74757&title=&width=347) ## 5.3 订阅性能报告 ### 5.3.1 启动2W个设备,订阅2W个topic ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748307769-aba52973-ef70-4c53-b1d1-b0e8b81ab18f.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=203&id=ua71378ae&margin=%5Bobject%20Object%5D&name=image.png&originHeight=406&originWidth=920&originalType=binary&ratio=1&rotation=0&showTitle=false&size=143298&status=done&style=none&taskId=u0c19253a-4c2b-4ecc-adef-b7ea8893f21&title=&width=460) ### 5.3.2 启动5W个设备,订阅5W个topic ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642748324754-a92522f8-395d-4f70-a2f1-ff143bb3bb37.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=275&id=u048d5dd1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=550&originWidth=718&originalType=binary&ratio=1&rotation=0&showTitle=false&size=183115&status=done&style=none&taskId=ue28001b0-b3a9-407d-9c6d-1fc15a813d3&title=&width=359) # 六、性能测试说明 ## 6.1 关于连接数 1. 单机和集群都未压测到上线,受限于时间和测试机问题 1. 实际使用时,单机连接数不要超过5w,方式服务器重启时,大量重连请求导致不可预知的问题 ## 6.2 关于消息发送tps 1. 整体看消息tps与emq的测试服务器消息tps差不多 1. 为什么集群tps上不去? 1. 因为消息保存强依赖mysql进行存储,mysql存储tps已达上限,这也是消息发送的可优化项 ## 6.3 关于订阅 1. 目前订阅强依赖db存储,不存在订阅关系丢失的问题 1. 本地采用tri树进行订阅关系的管理 ## 6.3 Jmqtt性能可优化项指南 1. 升级mysql 1. 集群事件转发器 用 mq替代(kafka或其他的mq都可以),减少集群服务器从db long pull的模式 1. 消息存储采用其他存储中间件,例如时序数据库,甚至kafka都行 # 七、测试常见问题 ## 7.1 测试机需要修改端口限制,否则无法启动5W长连接 linux centos7默认限制了端口可用范围,需要修改一下,不然连接数无法达到5w 查看端口范围:cat /proc/sys/net/ipv4/ip_local_port_range ## 7.2 jmqtt服务器需要修改文件句柄数 linux 万物皆文件,需要修改文件句柄数,否则无法支持那么大的长连接 linux默认为65535个文件句柄数,需要修改两个地方: ulimit -n 和vim /etc/security/limits.conf ## 7.3 jmqtt集群的负载均衡需要升级 mqtt协议的复杂均衡需要4层的负载均衡代理, 默认购买的SLB一般只支持5W长连接,故需要升级 # 八、附:Jmqtt启动问题 1. 目前jmqtt尽量减少各种依赖,代码简单,很容易进行二次开发和开箱即用 1. 请使用最新发布版本或master 分支代码 1. 建议从源码构建 1. 本地启动,直接在BrokerStartup执行main方法 ## 8.1 结构介绍 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749307002-ed2ad593-44d3-4dac-a62e-f9db215e5fe5.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=312&id=u7cac4a06&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1290&originWidth=1886&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1369802&status=done&style=none&taskId=ua523a209-b0b0-4450-965a-f60ff3ca631&title=&width=456) ## 8.2 在db库中初始化好脚本 t默认使用的是mysql驱动,依赖其他db需要自行修改 1. 在自己的库中,执行jmqtt.sql 1. 执行后如截图所示: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749458236-93fdeb96-9e87-40d7-9152-cdfd8dfbe1bb.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=309&id=ud2bd07e1&margin=%5Bobject%20Object%5D&name=image.png&originHeight=662&originWidth=594&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150608&status=done&style=none&taskId=u74608728-bee1-4b58-acc8-e64fb9d1b8d&title=&width=277) ## 8.3 打包 在broker模块下,执行 :mvn -Ppackage-all -DskipTests clean install -U 打包后: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749594691-b2ac7265-1947-4694-b834-8285f5acc1da.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=360&id=u9e9f6e8c&margin=%5Bobject%20Object%5D&name=image.png&originHeight=720&originWidth=1374&originalType=binary&ratio=1&rotation=0&showTitle=false&size=640584&status=done&style=none&taskId=ua743bfc9-2d04-4761-8c85-d8a5b18b6f5&title=&width=687) ## 8.4 修改配置文件 如截图:这里修改为自己的db连接串 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749722171-23d6363a-9bcd-425a-a682-c36d878cdca2.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=278&id=u60f8b443&margin=%5Bobject%20Object%5D&name=image.png&originHeight=1242&originWidth=2758&originalType=binary&ratio=1&rotation=0&showTitle=false&size=1430587&status=done&style=none&taskId=u54a006e0-9619-468f-9deb-2a45f16b9de&title=&width=617) ## 8.5 上传资源到服务器 1. 将jar,conf下的资源,bin下的脚本都上传到服务器: ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749874476-b29816f0-2c04-4ab5-bac2-2f771e8c4130.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=80&id=u809bdae2&margin=%5Bobject%20Object%5D&name=image.png&originHeight=160&originWidth=1314&originalType=binary&ratio=1&rotation=0&showTitle=false&size=131296&status=done&style=none&taskId=u3491c4a5-645f-4ae6-bc72-10edd16469b&title=&width=657) 其中 config为conf下的配置文件 2. 执行启动命令:./runbroker.sh jmqtt-broker-3.0.0.jar -h config/ ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749926168-c1ff93fb-523f-4569-b6e6-231db8d3c50c.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=62&id=u440600e3&margin=%5Bobject%20Object%5D&name=image.png&originHeight=124&originWidth=1226&originalType=binary&ratio=1&rotation=0&showTitle=false&size=91843&status=done&style=none&taskId=ue93dc841-1df2-4ae5-bbf9-3cf8117dfc0&title=&width=613) 3. 查看启动日志: 1. cd jmqttlogs 1. tailf -200 brokerLog.log : 显示如下截图说明启动成功 ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642749978459-d4df9171-0c11-4e05-a877-dc3eeeed2fa8.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=168&id=ucb54e4fc&margin=%5Bobject%20Object%5D&name=image.png&originHeight=336&originWidth=2268&originalType=binary&ratio=1&rotation=0&showTitle=false&size=259176&status=done&style=none&taskId=uafca868b-afe8-45a8-b2bb-42fa7003c9e&title=&width=1134) ![image.png](https://cdn.nlark.com/yuque/0/2022/png/2726385/1642750011254-87d99be8-40f4-40a8-bbaf-8a3cb67f8f14.png#clientId=u63915b68-034c-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=67&id=u9268067d&margin=%5Bobject%20Object%5D&name=image.png&originHeight=134&originWidth=1308&originalType=binary&ratio=1&rotation=0&showTitle=false&size=61787&status=done&style=none&taskId=ub9ebb91e-a64c-4e63-8601-48e82931c6e&title=&width=654) jmqt ================================================ FILE: jmqtt-doc/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-doc jmqtt-doc ================================================ FILE: jmqtt-example/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-example jmqtt-example UTF-8 1.8 1.8 org.eclipse.paho org.eclipse.paho.client.mqttv3 1.2.5 ================================================ FILE: jmqtt-example/src/main/java/org/jmqtt/java/Consumer.java ================================================ package org.jmqtt.java; import org.eclipse.paho.client.mqttv3.*; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class Consumer { private static final String broker = "tcp://127.0.0.1:1883"; private static final String topic = "MQTT/TOPIC"; private static final String clientId = "MQTT_SUB_CLIENT"; public static void main(String[] args) throws MqttException { MqttClient subClient = getMqttClient(); subClient.setCallback(new MqttCallback() { @Override public void connectionLost(Throwable throwable) { System.out.println("Connect lost,do some thing to solve it"); } @Override public void messageArrived(String s, MqttMessage mqttMessage) { System.out.println("From topic: " + s); System.out.println("Message content: " + new String(mqttMessage.getPayload())); } @Override public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { System.out.println("deliveryComplete"); } }); subClient.subscribe(topic); } private static MqttClient getMqttClient() { try { MqttClient pubClient = new MqttClient(broker, clientId, new MemoryPersistence()); MqttConnectOptions connectOptions = new MqttConnectOptions(); connectOptions.setCleanSession(false); System.out.println("Connecting to broker: " + broker); pubClient.connect(connectOptions); return pubClient; } catch (MqttException e) { e.printStackTrace(); } return null; } } ================================================ FILE: jmqtt-example/src/main/java/org/jmqtt/java/Producer.java ================================================ package org.jmqtt.java; import org.eclipse.paho.client.mqttv3.MqttClient; import org.eclipse.paho.client.mqttv3.MqttConnectOptions; import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; public class Producer { private static final String broker = "tcp://8.142.122.137:1883"; private static final String content = "Message from MqttProducer"; private static final int qos = 1; private static final String topic = "MQTT/TOPIC"; private static final String clientId = "MQTT_PUB_CLIENT"; public static void main(String[] args) throws MqttException, InterruptedException { MqttClient pubClient = getMqttClient(); for (int i = 0; i < 3; i++) { MqttMessage mqttMessage = getMqttMessage(); pubClient.publish(topic, mqttMessage); System.out.println("Send message success."); } } private static MqttMessage getMqttMessage() { MqttMessage mqttMessage = new MqttMessage(content.getBytes()); mqttMessage.setQos(qos); return mqttMessage; } private static MqttClient getMqttClient() { try { MqttClient pubClient = new MqttClient(broker, clientId, new MemoryPersistence()); MqttConnectOptions connectOptions = new MqttConnectOptions(); connectOptions.setWill("lwt", "this is a will message".getBytes(), 1, false); connectOptions.setCleanSession(false); System.out.println("Connecting to broker: " + broker); pubClient.connect(connectOptions); return pubClient; } catch (MqttException e) { e.printStackTrace(); } return null; } } ================================================ FILE: jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt-min.js ================================================ /******************************************************************************* * Copyright (c) 2013, 2014 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * *******************************************************************************/ "undefined"===typeof Paho&&(Paho={}); Paho.MQTT=function(u){function y(a,b,c){b[c++]=a>>8;b[c++]=a%256;return c}function r(a,b,c,h){h=y(b,c,h);F(a,c,h);return h+b}function m(a){for(var b=0,c=0;c=h&&(c++,b++),b+=3):127=e){var d=a.charCodeAt(++h);if(isNaN(d))throw Error(f(g.MALFORMED_UNICODE,[e,d]));e=(e-55296<<10)+(d-56320)+65536}127>=e?b[c++]=e:(2047>=e?b[c++]=e>>6&31| 192:(65535>=e?b[c++]=e>>12&15|224:(b[c++]=e>>18&7|240,b[c++]=e>>12&63|128),b[c++]=e>>6&63|128),b[c++]=e&63|128)}return b}function G(a,b,c){for(var h="",e,d=b;de)){var p=a[d++]-128;if(0>p)throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),""]));if(224>e)e=64*(e-192)+p;else{var t=a[d++]-128;if(0>t)throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),t.toString(16)]));if(240>e)e=4096*(e-224)+64*p+t;else{var l=a[d++]-128;if(0>l)throw Error(f(g.MALFORMED_UTF, [e.toString(16),p.toString(16),t.toString(16),l.toString(16)]));if(248>e)e=262144*(e-240)+4096*p+64*t+l;else throw Error(f(g.MALFORMED_UTF,[e.toString(16),p.toString(16),t.toString(16),l.toString(16)]));}}}65535>10)),e=56320+(e&1023));h+=String.fromCharCode(e)}return h}var A=function(a,b){for(var c in a)if(a.hasOwnProperty(c))if(b.hasOwnProperty(c)){if(typeof a[c]!==b[c])throw Error(f(g.INVALID_TYPE,[typeof a[c],c]));}else{var h="Unknown property, "+c+ ". Valid properties are:";for(c in b)b.hasOwnProperty(c)&&(h=h+" "+c);throw Error(h);}},q=function(a,b){return function(){return a.apply(b,arguments)}},g={OK:{code:0,text:"AMQJSC0000I OK."},CONNECT_TIMEOUT:{code:1,text:"AMQJSC0001E Connect timed out."},SUBSCRIBE_TIMEOUT:{code:2,text:"AMQJS0002E Subscribe timed out."},UNSUBSCRIBE_TIMEOUT:{code:3,text:"AMQJS0003E Unsubscribe timed out."},PING_TIMEOUT:{code:4,text:"AMQJS0004E Ping timed out."},INTERNAL_ERROR:{code:5,text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, CONNACK_RETURNCODE:{code:6,text:"AMQJS0006E Bad Connack return code:{0} {1}."},SOCKET_ERROR:{code:7,text:"AMQJS0007E Socket error:{0}."},SOCKET_CLOSE:{code:8,text:"AMQJS0008I Socket closed."},MALFORMED_UTF:{code:9,text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."},UNSUPPORTED:{code:10,text:"AMQJS0010E {0} is not supported by this browser."},INVALID_STATE:{code:11,text:"AMQJS0011E Invalid state {0}."},INVALID_TYPE:{code:12,text:"AMQJS0012E Invalid type {0} for {1}."},INVALID_ARGUMENT:{code:13,text:"AMQJS0013E Invalid argument {0} for {1}."}, UNSUPPORTED_OPERATION:{code:14,text:"AMQJS0014E Unsupported operation."},INVALID_STORED_DATA:{code:15,text:"AMQJS0015E Invalid data in local storage key={0} value={1}."},INVALID_MQTT_MESSAGE_TYPE:{code:16,text:"AMQJS0016E Invalid MQTT message type {0}."},MALFORMED_UNICODE:{code:17,text:"AMQJS0017E Malformed Unicode string:{0} {1}."}},J={0:"Connection Accepted",1:"Connection Refused: unacceptable protocol version",2:"Connection Refused: identifier rejected",3:"Connection Refused: server unavailable", 4:"Connection Refused: bad user name or password",5:"Connection Refused: not authorized"},f=function(a,b){var c=a.text;if(b)for(var h,e,d=0;d>7;0l);f=d.length+1;b=new ArrayBuffer(b+f);l=new Uint8Array(b); l[0]=a;l.set(d,1);if(3==this.type)f=r(this.payloadMessage.destinationName,h,l,f);else if(1==this.type){switch(this.mqttVersion){case 3:l.set(B,f);f+=B.length;break;case 4:l.set(C,f),f+=C.length}a=0;this.cleanSession&&(a=2);void 0!=this.willMessage&&(a=a|4|this.willMessage.qos<<3,this.willMessage.retained&&(a|=32));void 0!=this.userName&&(a|=128);void 0!=this.password&&(a|=64);l[f++]=a;f=y(this.keepAliveInterval,l,f)}void 0!=this.messageIdentifier&&(f=y(this.messageIdentifier,l,f));switch(this.type){case 1:f= r(this.clientId,m(this.clientId),l,f);void 0!=this.willMessage&&(f=r(this.willMessage.destinationName,m(this.willMessage.destinationName),l,f),f=y(e.byteLength,l,f),l.set(e,f),f+=e.byteLength);void 0!=this.userName&&(f=r(this.userName,m(this.userName),l,f));void 0!=this.password&&r(this.password,m(this.password),l,f);break;case 3:l.set(g,f);break;case 8:for(d=0;dthis.connectOptions.mqttVersion?new WebSocket(a,["mqttv3.1"]):new WebSocket(a,["mqtt"]);this.socket.binaryType= "arraybuffer";this.socket.onopen=q(this._on_socket_open,this);this.socket.onmessage=q(this._on_socket_message,this);this.socket.onerror=q(this._on_socket_error,this);this.socket.onclose=q(this._on_socket_close,this);this.sendPinger=new H(this,window,this.connectOptions.keepAliveInterval);this.receivePinger=new H(this,window,this.connectOptions.keepAliveInterval);this._connectTimeout=new D(this,window,this.connectOptions.timeout,this._disconnected,[g.CONNECT_TIMEOUT.code,f(g.CONNECT_TIMEOUT)])};k.prototype._schedule_message= function(a){this._msg_queue.push(a);this.connected&&this._process_queue()};k.prototype.store=function(a,b){var c={type:b.type,messageIdentifier:b.messageIdentifier,version:1};switch(b.type){case 3:b.pubRecReceived&&(c.pubRecReceived=!0);c.payloadMessage={};for(var h="",e=b.payloadMessage.payloadBytes,d=0;d=e[d]?h+"0"+e[d].toString(16):h+e[d].toString(16);c.payloadMessage.payloadHex=h;c.payloadMessage.qos=b.payloadMessage.qos;c.payloadMessage.destinationName=b.payloadMessage.destinationName; b.payloadMessage.duplicate&&(c.payloadMessage.duplicate=!0);b.payloadMessage.retained&&(c.payloadMessage.retained=!0);0==a.indexOf("Sent:")&&(void 0===b.sequence&&(b.sequence=++this._sequence),c.sequence=b.sequence);break;default:throw Error(f(g.INVALID_STORED_DATA,[key,c]));}localStorage.setItem(a+this._localKey+b.messageIdentifier,JSON.stringify(c))};k.prototype.restore=function(a){var b=localStorage.getItem(a),c=JSON.parse(b),h=new n(c.type,c);switch(c.type){case 3:for(var b=c.payloadMessage.payloadHex, e=new ArrayBuffer(b.length/2),e=new Uint8Array(e),d=0;2<=b.length;){var k=parseInt(b.substring(0,2),16),b=b.substring(2,b.length);e[d++]=k}b=new Paho.MQTT.Message(e);b.qos=c.payloadMessage.qos;b.destinationName=c.payloadMessage.destinationName;c.payloadMessage.duplicate&&(b.duplicate=!0);c.payloadMessage.retained&&(b.retained=!0);h.payloadMessage=b;break;default:throw Error(f(g.INVALID_STORED_DATA,[a,b]));}0==a.indexOf("Sent:"+this._localKey)?(h.payloadMessage.duplicate=!0,this._sentMessages[h.messageIdentifier]= h):0==a.indexOf("Received:"+this._localKey)&&(this._receivedMessages[h.messageIdentifier]=h)};k.prototype._process_queue=function(){for(var a=null,b=this._msg_queue.reverse();a=b.pop();)this._socket_send(a),this._notify_msg_sent[a]&&(this._notify_msg_sent[a](),delete this._notify_msg_sent[a])};k.prototype._requires_ack=function(a){var b=Object.keys(this._sentMessages).length;if(b>this.maxMessageIdentifier)throw Error("Too many messages:"+b);for(;void 0!==this._sentMessages[this._message_identifier];)this._message_identifier++; a.messageIdentifier=this._message_identifier;this._sentMessages[a.messageIdentifier]=a;3===a.type&&this.store("Sent:",a);this._message_identifier===this.maxMessageIdentifier&&(this._message_identifier=1)};k.prototype._on_socket_open=function(){var a=new n(1,this.connectOptions);a.clientId=this.clientId;this._socket_send(a)};k.prototype._on_socket_message=function(a){this._trace("Client._on_socket_message",a.data);this.receivePinger.reset();a=this._deframeMessages(a.data);for(var b=0;b>4,z=t&15,d=d+1,v=void 0,E=0,m=1;do{if(d==e.length){h=[null,k];break a}v=e[d++];E+=(v&127)*m;m*=128}while(0!=(v&128));v=d+E;if(v>e.length)h=[null,k];else{var w=new n(l);switch(l){case 2:e[d++]& 1&&(w.sessionPresent=!0);w.returnCode=e[d++];break;case 3:var k=z>>1&3,r=256*e[d]+e[d+1],d=d+2,u=G(e,d,r),d=d+r;0b)throw Error(f(g.INVALID_TYPE,[typeof b,"port"]));if("string"!==typeof c)throw Error(f(g.INVALID_TYPE,[typeof c,"path"]));e="ws://"+(-1!=a.indexOf(":")&&"["!=a.slice(0,1)&&"]"!=a.slice(-1)?"["+a+"]":a)+":"+b+c}for(var p=d=0;p=m&&p++;d++}if("string"!==typeof h||65535a.mqttVersion)throw Error(f(g.INVALID_ARGUMENT,[a.mqttVersion,"connectOptions.mqttVersion"]));void 0===a.mqttVersion?(a.mqttVersionExplicit=!1,a.mqttVersion=4):a.mqttVersionExplicit=!0;if(void 0===a.password&&void 0!==a.userName)throw Error(f(g.INVALID_ARGUMENT, [a.password,"connectOptions.password"]));if(a.willMessage){if(!(a.willMessage instanceof x))throw Error(f(g.INVALID_TYPE,[a.willMessage,"connectOptions.willMessage"]));a.willMessage.stringPayload;if("undefined"===typeof a.willMessage.destinationName)throw Error(f(g.INVALID_TYPE,[typeof a.willMessage.destinationName,"connectOptions.willMessage.destinationName"]));}"undefined"===typeof a.cleanSession&&(a.cleanSession=!0);if(a.hosts){if(!(a.hosts instanceof Array))throw Error(f(g.INVALID_ARGUMENT,[a.hosts, "connectOptions.hosts"]));if(1>a.hosts.length)throw Error(f(g.INVALID_ARGUMENT,[a.hosts,"connectOptions.hosts"]));for(var b=!1,d=0;da.ports[d])throw Error(f(g.INVALID_TYPE,[typeof a.ports[d],"connectOptions.ports["+d+"]"]));var b=a.hosts[d],h= a.ports[d];e="ws://"+(-1!=b.indexOf(":")?"["+b+"]":b)+":"+h+c;a.uris.push(e)}}}l.connect(a)};this.subscribe=function(a,b){if("string"!==typeof a)throw Error("Invalid argument:"+a);b=b||{};A(b,{qos:"number",invocationContext:"object",onSuccess:"function",onFailure:"function",timeout:"number"});if(b.timeout&&!b.onFailure)throw Error("subscribeOptions.timeout specified with no onFailure callback.");if("undefined"!==typeof b.qos&&0!==b.qos&&1!==b.qos&&2!==b.qos)throw Error(f(g.INVALID_ARGUMENT,[b.qos, "subscribeOptions.qos"]));l.subscribe(a,b)};this.unsubscribe=function(a,b){if("string"!==typeof a)throw Error("Invalid argument:"+a);b=b||{};A(b,{invocationContext:"object",onSuccess:"function",onFailure:"function",timeout:"number"});if(b.timeout&&!b.onFailure)throw Error("unsubscribeOptions.timeout specified with no onFailure callback.");l.unsubscribe(a,b)};this.send=function(a,b,c,d){var e;if(0==arguments.length)throw Error("Invalid argument.length");if(1==arguments.length){if(!(a instanceof x)&& "string"!==typeof a)throw Error("Invalid argument:"+typeof a);e=a;if("undefined"===typeof e.destinationName)throw Error(f(g.INVALID_ARGUMENT,[e.destinationName,"Message.destinationName"]));}else e=new x(b),e.destinationName=a,3<=arguments.length&&(e.qos=c),4<=arguments.length&&(e.retained=d);l.send(e)};this.disconnect=function(){l.disconnect()};this.getTraceLog=function(){return l.getTraceLog()};this.startTrace=function(){l.startTrace()};this.stopTrace=function(){l.stopTrace()};this.isConnected=function(){return l.connected}}; I.prototype={get host(){return this._getHost()},set host(a){this._setHost(a)},get port(){return this._getPort()},set port(a){this._setPort(a)},get path(){return this._getPath()},set path(a){this._setPath(a)},get clientId(){return this._getClientId()},set clientId(a){this._setClientId(a)},get onConnectionLost(){return this._getOnConnectionLost()},set onConnectionLost(a){this._setOnConnectionLost(a)},get onMessageDelivered(){return this._getOnMessageDelivered()},set onMessageDelivered(a){this._setOnMessageDelivered(a)}, get onMessageArrived(){return this._getOnMessageArrived()},set onMessageArrived(a){this._setOnMessageArrived(a)},get trace(){return this._getTrace()},set trace(a){this._setTrace(a)}};var x=function(a){var b;if("string"===typeof a||a instanceof ArrayBuffer||a instanceof Int8Array||a instanceof Uint8Array||a instanceof Int16Array||a instanceof Uint16Array||a instanceof Int32Array||a instanceof Uint32Array||a instanceof Float32Array||a instanceof Float64Array)b=a;else throw f(g.INVALID_ARGUMENT,[a,"newPayload"]); this._getPayloadString=function(){return"string"===typeof b?b:G(b,0,b.length)};this._getPayloadBytes=function(){if("string"===typeof b){var a=new ArrayBuffer(m(b)),a=new Uint8Array(a);F(b,a,0);return a}return b};var c=void 0;this._getDestinationName=function(){return c};this._setDestinationName=function(a){if("string"===typeof a)c=a;else throw Error(f(g.INVALID_ARGUMENT,[a,"newDestinationName"]));};var h=0;this._getQos=function(){return h};this._setQos=function(a){if(0===a||1===a||2===a)h=a;else throw Error("Invalid argument:"+ a);};var e=!1;this._getRetained=function(){return e};this._setRetained=function(a){if("boolean"===typeof a)e=a;else throw Error(f(g.INVALID_ARGUMENT,[a,"newRetained"]));};var d=!1;this._getDuplicate=function(){return d};this._setDuplicate=function(a){d=a}};x.prototype={get payloadString(){return this._getPayloadString()},get payloadBytes(){return this._getPayloadBytes()},get destinationName(){return this._getDestinationName()},set destinationName(a){this._setDestinationName(a)},get qos(){return this._getQos()}, set qos(a){this._setQos(a)},get retained(){return this._getRetained()},set retained(a){this._setRetained(a)},get duplicate(){return this._getDuplicate()},set duplicate(a){this._setDuplicate(a)}};return{Client:I,Message:x}}(window); ================================================ FILE: jmqtt-example/src/main/java/org/jmqtt/websocket/paho-mqtt.js ================================================ /******************************************************************************* * Copyright (c) 2013 IBM Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * and the Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * Andrew Banks - initial API and implementation and initial documentation *******************************************************************************/ // Only expose a single object name in the global namespace. // Everything must go through this module. Global Paho.MQTT module // only has a single public function, client, which returns // a Paho.MQTT client object given connection details. /** * Send and receive messages using web browsers. *

* This programming interface lets a JavaScript client application use the MQTT V3.1 or * V3.1.1 protocol to connect to an MQTT-supporting messaging server. * * The function supported includes: *

    *
  1. Connecting to and disconnecting from a server. The server is identified by its host name and port number. *
  2. Specifying options that relate to the communications link with the server, * for example the frequency of keep-alive heartbeats, and whether SSL/TLS is required. *
  3. Subscribing to and receiving messages from MQTT Topics. *
  4. Publishing messages to MQTT Topics. *
*

* The API consists of two main objects: *

*
{@link Paho.MQTT.Client}
*
This contains methods that provide the functionality of the API, * including provision of callbacks that notify the application when a message * arrives from or is delivered to the messaging server, * or when the status of its connection to the messaging server changes.
*
{@link Paho.MQTT.Message}
*
This encapsulates the payload of the message along with various attributes * associated with its delivery, in particular the destination to which it has * been (or is about to be) sent.
*
*

* The programming interface validates parameters passed to it, and will throw * an Error containing an error message intended for developer use, if it detects * an error with any parameter. *

* Example: * *

 client = new Paho.MQTT.Client(location.hostname, Number(location.port), "clientId");
 client.onConnectionLost = onConnectionLost;
 client.onMessageArrived = onMessageArrived;
 client.connect({onSuccess:onConnect});

 function onConnect() {
  // Once a connection has been made, make a subscription and send a message.
  console.log("onConnect");
  client.subscribe("/World");
  message = new Paho.MQTT.Message("Hello");
  message.destinationName = "/World";
  client.send(message);
};
 function onConnectionLost(responseObject) {
  if (responseObject.errorCode !== 0)
	console.log("onConnectionLost:"+responseObject.errorMessage);
};
 function onMessageArrived(message) {
  console.log("onMessageArrived:"+message.payloadString);
  client.disconnect();
};
 * 
* @namespace Paho.MQTT */ if (typeof Paho === "undefined") { Paho = {}; } Paho.MQTT = (function (global) { // Private variables below, these are only visible inside the function closure // which is used to define the module. var version = "@VERSION@"; var buildLevel = "@BUILDLEVEL@"; /** * Unique message type identifiers, with associated * associated integer values. * @private */ var MESSAGE_TYPE = { CONNECT: 1, CONNACK: 2, PUBLISH: 3, PUBACK: 4, PUBREC: 5, PUBREL: 6, PUBCOMP: 7, SUBSCRIBE: 8, SUBACK: 9, UNSUBSCRIBE: 10, UNSUBACK: 11, PINGREQ: 12, PINGRESP: 13, DISCONNECT: 14 }; // Collection of utility methods used to simplify module code // and promote the DRY pattern. /** * Validate an object's parameter names to ensure they * match a list of expected variables name for this option * type. Used to ensure option object passed into the API don't * contain erroneous parameters. * @param {Object} obj - User options object * @param {Object} keys - valid keys and types that may exist in obj. * @throws {Error} Invalid option parameter found. * @private */ var validate = function(obj, keys) { for (var key in obj) { if (obj.hasOwnProperty(key)) { if (keys.hasOwnProperty(key)) { if (typeof obj[key] !== keys[key]) throw new Error(format(ERROR.INVALID_TYPE, [typeof obj[key], key])); } else { var errorStr = "Unknown property, " + key + ". Valid properties are:"; for (var key in keys) if (keys.hasOwnProperty(key)) errorStr = errorStr+" "+key; throw new Error(errorStr); } } } }; /** * Return a new function which runs the user function bound * to a fixed scope. * @param {function} User function * @param {object} Function scope * @return {function} User function bound to another scope * @private */ var scope = function (f, scope) { return function () { return f.apply(scope, arguments); }; }; /** * Unique message type identifiers, with associated * associated integer values. * @private */ var ERROR = { OK: {code:0, text:"AMQJSC0000I OK."}, CONNECT_TIMEOUT: {code:1, text:"AMQJSC0001E Connect timed out."}, SUBSCRIBE_TIMEOUT: {code:2, text:"AMQJS0002E Subscribe timed out."}, UNSUBSCRIBE_TIMEOUT: {code:3, text:"AMQJS0003E Unsubscribe timed out."}, PING_TIMEOUT: {code:4, text:"AMQJS0004E Ping timed out."}, INTERNAL_ERROR: {code:5, text:"AMQJS0005E Internal error. Error Message: {0}, Stack trace: {1}"}, CONNACK_RETURNCODE: {code:6, text:"AMQJS0006E Bad Connack return code:{0} {1}."}, SOCKET_ERROR: {code:7, text:"AMQJS0007E Socket error:{0}."}, SOCKET_CLOSE: {code:8, text:"AMQJS0008I Socket closed."}, MALFORMED_UTF: {code:9, text:"AMQJS0009E Malformed UTF data:{0} {1} {2}."}, UNSUPPORTED: {code:10, text:"AMQJS0010E {0} is not supported by this browser."}, INVALID_STATE: {code:11, text:"AMQJS0011E Invalid state {0}."}, INVALID_TYPE: {code:12, text:"AMQJS0012E Invalid type {0} for {1}."}, INVALID_ARGUMENT: {code:13, text:"AMQJS0013E Invalid argument {0} for {1}."}, UNSUPPORTED_OPERATION: {code:14, text:"AMQJS0014E Unsupported operation."}, INVALID_STORED_DATA: {code:15, text:"AMQJS0015E Invalid data in local storage key={0} value={1}."}, INVALID_MQTT_MESSAGE_TYPE: {code:16, text:"AMQJS0016E Invalid MQTT message type {0}."}, MALFORMED_UNICODE: {code:17, text:"AMQJS0017E Malformed Unicode string:{0} {1}."}, }; /** CONNACK RC Meaning. */ var CONNACK_RC = { 0:"Connection Accepted", 1:"Connection Refused: unacceptable protocol version", 2:"Connection Refused: identifier rejected", 3:"Connection Refused: server unavailable", 4:"Connection Refused: bad user name or password", 5:"Connection Refused: not authorized" }; /** * Format an error message text. * @private * @param {error} ERROR.KEY value above. * @param {substitutions} [array] substituted into the text. * @return the text with the substitutions made. */ var format = function(error, substitutions) { var text = error.text; if (substitutions) { var field,start; for (var i=0; i 0) { var part1 = text.substring(0,start); var part2 = text.substring(start+field.length); text = part1+substitutions[i]+part2; } } } return text; }; //MQTT protocol and version 6 M Q I s d p 3 var MqttProtoIdentifierv3 = [0x00,0x06,0x4d,0x51,0x49,0x73,0x64,0x70,0x03]; //MQTT proto/version for 311 4 M Q T T 4 var MqttProtoIdentifierv4 = [0x00,0x04,0x4d,0x51,0x54,0x54,0x04]; /** * Construct an MQTT wire protocol message. * @param type MQTT packet type. * @param options optional wire message attributes. * * Optional properties * * messageIdentifier: message ID in the range [0..65535] * payloadMessage: Application Message - PUBLISH only * connectStrings: array of 0 or more Strings to be put into the CONNECT payload * topics: array of strings (SUBSCRIBE, UNSUBSCRIBE) * requestQoS: array of QoS values [0..2] * * "Flag" properties * cleanSession: true if present / false if absent (CONNECT) * willMessage: true if present / false if absent (CONNECT) * isRetained: true if present / false if absent (CONNECT) * userName: true if present / false if absent (CONNECT) * password: true if present / false if absent (CONNECT) * keepAliveInterval: integer [0..65535] (CONNECT) * * @private * @ignore */ var WireMessage = function (type, options) { this.type = type; for (var name in options) { if (options.hasOwnProperty(name)) { this[name] = options[name]; } } }; WireMessage.prototype.encode = function() { // Compute the first byte of the fixed header var first = ((this.type & 0x0f) << 4); /* * Now calculate the length of the variable header + payload by adding up the lengths * of all the component parts */ var remLength = 0; var topicStrLength = new Array(); var destinationNameLength = 0; // if the message contains a messageIdentifier then we need two bytes for that if (this.messageIdentifier != undefined) remLength += 2; switch(this.type) { // If this a Connect then we need to include 12 bytes for its header case MESSAGE_TYPE.CONNECT: switch(this.mqttVersion) { case 3: remLength += MqttProtoIdentifierv3.length + 3; break; case 4: remLength += MqttProtoIdentifierv4.length + 3; break; } remLength += UTF8Length(this.clientId) + 2; if (this.willMessage != undefined) { remLength += UTF8Length(this.willMessage.destinationName) + 2; // Will message is always a string, sent as UTF-8 characters with a preceding length. var willMessagePayloadBytes = this.willMessage.payloadBytes; if (!(willMessagePayloadBytes instanceof Uint8Array)) willMessagePayloadBytes = new Uint8Array(payloadBytes); remLength += willMessagePayloadBytes.byteLength +2; } if (this.userName != undefined) remLength += UTF8Length(this.userName) + 2; if (this.password != undefined) remLength += UTF8Length(this.password) + 2; break; // Subscribe, Unsubscribe can both contain topic strings case MESSAGE_TYPE.SUBSCRIBE: first |= 0x02; // Qos = 1; for ( var i = 0; i < this.topics.length; i++) { topicStrLength[i] = UTF8Length(this.topics[i]); remLength += topicStrLength[i] + 2; } remLength += this.requestedQos.length; // 1 byte for each topic's Qos // QoS on Subscribe only break; case MESSAGE_TYPE.UNSUBSCRIBE: first |= 0x02; // Qos = 1; for ( var i = 0; i < this.topics.length; i++) { topicStrLength[i] = UTF8Length(this.topics[i]); remLength += topicStrLength[i] + 2; } break; case MESSAGE_TYPE.PUBREL: first |= 0x02; // Qos = 1; break; case MESSAGE_TYPE.PUBLISH: if (this.payloadMessage.duplicate) first |= 0x08; first = first |= (this.payloadMessage.qos << 1); if (this.payloadMessage.retained) first |= 0x01; destinationNameLength = UTF8Length(this.payloadMessage.destinationName); remLength += destinationNameLength + 2; var payloadBytes = this.payloadMessage.payloadBytes; remLength += payloadBytes.byteLength; if (payloadBytes instanceof ArrayBuffer) payloadBytes = new Uint8Array(payloadBytes); else if (!(payloadBytes instanceof Uint8Array)) payloadBytes = new Uint8Array(payloadBytes.buffer); break; case MESSAGE_TYPE.DISCONNECT: break; default: ; } // Now we can allocate a buffer for the message var mbi = encodeMBI(remLength); // Convert the length to MQTT MBI format var pos = mbi.length + 1; // Offset of start of variable header var buffer = new ArrayBuffer(remLength + pos); var byteStream = new Uint8Array(buffer); // view it as a sequence of bytes //Write the fixed header into the buffer byteStream[0] = first; byteStream.set(mbi,1); // If this is a PUBLISH then the variable header starts with a topic if (this.type == MESSAGE_TYPE.PUBLISH) pos = writeString(this.payloadMessage.destinationName, destinationNameLength, byteStream, pos); // If this is a CONNECT then the variable header contains the protocol name/version, flags and keepalive time else if (this.type == MESSAGE_TYPE.CONNECT) { switch (this.mqttVersion) { case 3: byteStream.set(MqttProtoIdentifierv3, pos); pos += MqttProtoIdentifierv3.length; break; case 4: byteStream.set(MqttProtoIdentifierv4, pos); pos += MqttProtoIdentifierv4.length; break; } var connectFlags = 0; if (this.cleanSession) connectFlags = 0x02; if (this.willMessage != undefined ) { connectFlags |= 0x04; connectFlags |= (this.willMessage.qos<<3); if (this.willMessage.retained) { connectFlags |= 0x20; } } if (this.userName != undefined) connectFlags |= 0x80; if (this.password != undefined) connectFlags |= 0x40; byteStream[pos++] = connectFlags; pos = writeUint16 (this.keepAliveInterval, byteStream, pos); } // Output the messageIdentifier - if there is one if (this.messageIdentifier != undefined) pos = writeUint16 (this.messageIdentifier, byteStream, pos); switch(this.type) { case MESSAGE_TYPE.CONNECT: pos = writeString(this.clientId, UTF8Length(this.clientId), byteStream, pos); if (this.willMessage != undefined) { pos = writeString(this.willMessage.destinationName, UTF8Length(this.willMessage.destinationName), byteStream, pos); pos = writeUint16(willMessagePayloadBytes.byteLength, byteStream, pos); byteStream.set(willMessagePayloadBytes, pos); pos += willMessagePayloadBytes.byteLength; } if (this.userName != undefined) pos = writeString(this.userName, UTF8Length(this.userName), byteStream, pos); if (this.password != undefined) pos = writeString(this.password, UTF8Length(this.password), byteStream, pos); break; case MESSAGE_TYPE.PUBLISH: // PUBLISH has a text or binary payload, if text do not add a 2 byte length field, just the UTF characters. byteStream.set(payloadBytes, pos); break; // case MESSAGE_TYPE.PUBREC: // case MESSAGE_TYPE.PUBREL: // case MESSAGE_TYPE.PUBCOMP: // break; case MESSAGE_TYPE.SUBSCRIBE: // SUBSCRIBE has a list of topic strings and request QoS for (var i=0; i> 4; var messageInfo = first &= 0x0f; pos += 1; // Decode the remaining length (MBI format) var digit; var remLength = 0; var multiplier = 1; do { if (pos == input.length) { return [null,startingPos]; } digit = input[pos++]; remLength += ((digit & 0x7F) * multiplier); multiplier *= 128; } while ((digit & 0x80) != 0); var endPos = pos+remLength; if (endPos > input.length) { return [null,startingPos]; } var wireMessage = new WireMessage(type); switch(type) { case MESSAGE_TYPE.CONNACK: var connectAcknowledgeFlags = input[pos++]; if (connectAcknowledgeFlags & 0x01) wireMessage.sessionPresent = true; wireMessage.returnCode = input[pos++]; break; case MESSAGE_TYPE.PUBLISH: var qos = (messageInfo >> 1) & 0x03; var len = readUint16(input, pos); pos += 2; var topicName = parseUTF8(input, pos, len); pos += len; // If QoS 1 or 2 there will be a messageIdentifier if (qos > 0) { wireMessage.messageIdentifier = readUint16(input, pos); pos += 2; } var message = new Paho.MQTT.Message(input.subarray(pos, endPos)); if ((messageInfo & 0x01) == 0x01) message.retained = true; if ((messageInfo & 0x08) == 0x08) message.duplicate = true; message.qos = qos; message.destinationName = topicName; wireMessage.payloadMessage = message; break; case MESSAGE_TYPE.PUBACK: case MESSAGE_TYPE.PUBREC: case MESSAGE_TYPE.PUBREL: case MESSAGE_TYPE.PUBCOMP: case MESSAGE_TYPE.UNSUBACK: wireMessage.messageIdentifier = readUint16(input, pos); break; case MESSAGE_TYPE.SUBACK: wireMessage.messageIdentifier = readUint16(input, pos); pos += 2; wireMessage.returnCode = input.subarray(pos, endPos); break; default: ; } return [wireMessage,endPos]; } function writeUint16(input, buffer, offset) { buffer[offset++] = input >> 8; //MSB buffer[offset++] = input % 256; //LSB return offset; } function writeString(input, utf8Length, buffer, offset) { offset = writeUint16(utf8Length, buffer, offset); stringToUTF8(input, buffer, offset); return offset + utf8Length; } function readUint16(buffer, offset) { return 256*buffer[offset] + buffer[offset+1]; } /** * Encodes an MQTT Multi-Byte Integer * @private */ function encodeMBI(number) { var output = new Array(1); var numBytes = 0; do { var digit = number % 128; number = number >> 7; if (number > 0) { digit |= 0x80; } output[numBytes++] = digit; } while ( (number > 0) && (numBytes<4) ); return output; } /** * Takes a String and calculates its length in bytes when encoded in UTF8. * @private */ function UTF8Length(input) { var output = 0; for (var i = 0; i 0x7FF) { // Surrogate pair means its a 4 byte character if (0xD800 <= charCode && charCode <= 0xDBFF) { i++; output++; } output +=3; } else if (charCode > 0x7F) output +=2; else output++; } return output; } /** * Takes a String and writes it into an array as UTF8 encoded bytes. * @private */ function stringToUTF8(input, output, start) { var pos = start; for (var i = 0; i>6 & 0x1F | 0xC0; output[pos++] = charCode & 0x3F | 0x80; } else if (charCode <= 0xFFFF) { output[pos++] = charCode>>12 & 0x0F | 0xE0; output[pos++] = charCode>>6 & 0x3F | 0x80; output[pos++] = charCode & 0x3F | 0x80; } else { output[pos++] = charCode>>18 & 0x07 | 0xF0; output[pos++] = charCode>>12 & 0x3F | 0x80; output[pos++] = charCode>>6 & 0x3F | 0x80; output[pos++] = charCode & 0x3F | 0x80; }; } return output; } function parseUTF8(input, offset, length) { var output = ""; var utf16; var pos = offset; while (pos < offset+length) { var byte1 = input[pos++]; if (byte1 < 128) utf16 = byte1; else { var byte2 = input[pos++]-128; if (byte2 < 0) throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16),""])); if (byte1 < 0xE0) // 2 byte character utf16 = 64*(byte1-0xC0) + byte2; else { var byte3 = input[pos++]-128; if (byte3 < 0) throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16)])); if (byte1 < 0xF0) // 3 byte character utf16 = 4096*(byte1-0xE0) + 64*byte2 + byte3; else { var byte4 = input[pos++]-128; if (byte4 < 0) throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); if (byte1 < 0xF8) // 4 byte character utf16 = 262144*(byte1-0xF0) + 4096*byte2 + 64*byte3 + byte4; else // longer encodings are not supported throw new Error(format(ERROR.MALFORMED_UTF, [byte1.toString(16), byte2.toString(16), byte3.toString(16), byte4.toString(16)])); } } } if (utf16 > 0xFFFF) // 4 byte character - express as a surrogate pair { utf16 -= 0x10000; output += String.fromCharCode(0xD800 + (utf16 >> 10)); // lead character utf16 = 0xDC00 + (utf16 & 0x3FF); // trail character } output += String.fromCharCode(utf16); } return output; } /** * Repeat keepalive requests, monitor responses. * @ignore */ var Pinger = function(client, window, keepAliveInterval) { this._client = client; this._window = window; this._keepAliveInterval = keepAliveInterval*1000; this.isReset = false; var pingReq = new WireMessage(MESSAGE_TYPE.PINGREQ).encode(); var doTimeout = function (pinger) { return function () { return doPing.apply(pinger); }; }; /** @ignore */ var doPing = function() { if (!this.isReset) { this._client._trace("Pinger.doPing", "Timed out"); this._client._disconnected( ERROR.PING_TIMEOUT.code , format(ERROR.PING_TIMEOUT)); } else { this.isReset = false; this._client._trace("Pinger.doPing", "send PINGREQ"); this._client.socket.send(pingReq); this.timeout = this._window.setTimeout(doTimeout(this), this._keepAliveInterval); } } this.reset = function() { this.isReset = true; this._window.clearTimeout(this.timeout); if (this._keepAliveInterval > 0) this.timeout = setTimeout(doTimeout(this), this._keepAliveInterval); } this.cancel = function() { this._window.clearTimeout(this.timeout); } }; /** * Monitor request completion. * @ignore */ var Timeout = function(client, window, timeoutSeconds, action, args) { this._window = window; if (!timeoutSeconds) timeoutSeconds = 30; var doTimeout = function (action, client, args) { return function () { return action.apply(client, args); }; }; this.timeout = setTimeout(doTimeout(action, client, args), timeoutSeconds * 1000); this.cancel = function() { this._window.clearTimeout(this.timeout); } }; /* * Internal implementation of the Websockets MQTT V3.1 client. * * @name Paho.MQTT.ClientImpl @constructor * @param {String} host the DNS nameof the webSocket host. * @param {Number} port the port number for that host. * @param {String} clientId the MQ client identifier. */ var ClientImpl = function (uri, host, port, path, clientId) { // Check dependencies are satisfied in this browser. if (!("WebSocket" in global && global["WebSocket"] !== null)) { throw new Error(format(ERROR.UNSUPPORTED, ["WebSocket"])); } if (!("localStorage" in global && global["localStorage"] !== null)) { throw new Error(format(ERROR.UNSUPPORTED, ["localStorage"])); } if (!("ArrayBuffer" in global && global["ArrayBuffer"] !== null)) { throw new Error(format(ERROR.UNSUPPORTED, ["ArrayBuffer"])); } this._trace("Paho.MQTT.Client", uri, host, port, path, clientId); this.host = host; this.port = port; this.path = path; this.uri = uri; this.clientId = clientId; // Local storagekeys are qualified with the following string. // The conditional inclusion of path in the key is for backward // compatibility to when the path was not configurable and assumed to // be /mqtt this._localKey=host+":"+port+(path!="/mqtt"?":"+path:"")+":"+clientId+":"; // Create private instance-only message queue // Internal queue of messages to be sent, in sending order. this._msg_queue = []; // Messages we have sent and are expecting a response for, indexed by their respective message ids. this._sentMessages = {}; // Messages we have received and acknowleged and are expecting a confirm message for // indexed by their respective message ids. this._receivedMessages = {}; // Internal list of callbacks to be executed when messages // have been successfully sent over web socket, e.g. disconnect // when it doesn't have to wait for ACK, just message is dispatched. this._notify_msg_sent = {}; // Unique identifier for SEND messages, incrementing // counter as messages are sent. this._message_identifier = 1; // Used to determine the transmission sequence of stored sent messages. this._sequence = 0; // Load the local state, if any, from the saved version, only restore state relevant to this client. for (var key in localStorage) if ( key.indexOf("Sent:"+this._localKey) == 0 || key.indexOf("Received:"+this._localKey) == 0) this.restore(key); }; // Messaging Client public instance members. ClientImpl.prototype.host; ClientImpl.prototype.port; ClientImpl.prototype.path; ClientImpl.prototype.uri; ClientImpl.prototype.clientId; // Messaging Client private instance members. ClientImpl.prototype.socket; /* true once we have received an acknowledgement to a CONNECT packet. */ ClientImpl.prototype.connected = false; /* The largest message identifier allowed, may not be larger than 2**16 but * if set smaller reduces the maximum number of outbound messages allowed. */ ClientImpl.prototype.maxMessageIdentifier = 65536; ClientImpl.prototype.connectOptions; ClientImpl.prototype.hostIndex; ClientImpl.prototype.onConnectionLost; ClientImpl.prototype.onMessageDelivered; ClientImpl.prototype.onMessageArrived; ClientImpl.prototype.traceFunction; ClientImpl.prototype._msg_queue = null; ClientImpl.prototype._connectTimeout; /* The sendPinger monitors how long we allow before we send data to prove to the server that we are alive. */ ClientImpl.prototype.sendPinger = null; /* The receivePinger monitors how long we allow before we require evidence that the server is alive. */ ClientImpl.prototype.receivePinger = null; ClientImpl.prototype.receiveBuffer = null; ClientImpl.prototype._traceBuffer = null; ClientImpl.prototype._MAX_TRACE_ENTRIES = 100; ClientImpl.prototype.connect = function (connectOptions) { var connectOptionsMasked = this._traceMask(connectOptions, "password"); this._trace("Client.connect", connectOptionsMasked, this.socket, this.connected); if (this.connected) throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); if (this.socket) throw new Error(format(ERROR.INVALID_STATE, ["already connected"])); this.connectOptions = connectOptions; if (connectOptions.uris) { this.hostIndex = 0; this._doConnect(connectOptions.uris[0]); } else { this._doConnect(this.uri); } }; ClientImpl.prototype.subscribe = function (filter, subscribeOptions) { this._trace("Client.subscribe", filter, subscribeOptions); if (!this.connected) throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); var wireMessage = new WireMessage(MESSAGE_TYPE.SUBSCRIBE); wireMessage.topics=[filter]; if (subscribeOptions.qos != undefined) wireMessage.requestedQos = [subscribeOptions.qos]; else wireMessage.requestedQos = [0]; if (subscribeOptions.onSuccess) { wireMessage.onSuccess = function(grantedQos) {subscribeOptions.onSuccess({invocationContext:subscribeOptions.invocationContext,grantedQos:grantedQos});}; } if (subscribeOptions.onFailure) { wireMessage.onFailure = function(errorCode) {subscribeOptions.onFailure({invocationContext:subscribeOptions.invocationContext,errorCode:errorCode});}; } if (subscribeOptions.timeout) { wireMessage.timeOut = new Timeout(this, window, subscribeOptions.timeout, subscribeOptions.onFailure , [{invocationContext:subscribeOptions.invocationContext, errorCode:ERROR.SUBSCRIBE_TIMEOUT.code, errorMessage:format(ERROR.SUBSCRIBE_TIMEOUT)}]); } // All subscriptions return a SUBACK. this._requires_ack(wireMessage); this._schedule_message(wireMessage); }; /** @ignore */ ClientImpl.prototype.unsubscribe = function(filter, unsubscribeOptions) { this._trace("Client.unsubscribe", filter, unsubscribeOptions); if (!this.connected) throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); var wireMessage = new WireMessage(MESSAGE_TYPE.UNSUBSCRIBE); wireMessage.topics = [filter]; if (unsubscribeOptions.onSuccess) { wireMessage.callback = function() {unsubscribeOptions.onSuccess({invocationContext:unsubscribeOptions.invocationContext});}; } if (unsubscribeOptions.timeout) { wireMessage.timeOut = new Timeout(this, window, unsubscribeOptions.timeout, unsubscribeOptions.onFailure , [{invocationContext:unsubscribeOptions.invocationContext, errorCode:ERROR.UNSUBSCRIBE_TIMEOUT.code, errorMessage:format(ERROR.UNSUBSCRIBE_TIMEOUT)}]); } // All unsubscribes return a SUBACK. this._requires_ack(wireMessage); this._schedule_message(wireMessage); }; ClientImpl.prototype.send = function (message) { this._trace("Client.send", message); if (!this.connected) throw new Error(format(ERROR.INVALID_STATE, ["not connected"])); wireMessage = new WireMessage(MESSAGE_TYPE.PUBLISH); wireMessage.payloadMessage = message; if (message.qos > 0) this._requires_ack(wireMessage); else if (this.onMessageDelivered) this._notify_msg_sent[wireMessage] = this.onMessageDelivered(wireMessage.payloadMessage); this._schedule_message(wireMessage); }; ClientImpl.prototype.disconnect = function () { this._trace("Client.disconnect"); if (!this.socket) throw new Error(format(ERROR.INVALID_STATE, ["not connecting or connected"])); wireMessage = new WireMessage(MESSAGE_TYPE.DISCONNECT); // Run the disconnected call back as soon as the message has been sent, // in case of a failure later on in the disconnect processing. // as a consequence, the _disconected call back may be run several times. this._notify_msg_sent[wireMessage] = scope(this._disconnected, this); this._schedule_message(wireMessage); }; ClientImpl.prototype.getTraceLog = function () { if ( this._traceBuffer !== null ) { this._trace("Client.getTraceLog", new Date()); this._trace("Client.getTraceLog in flight messages", this._sentMessages.length); for (var key in this._sentMessages) this._trace("_sentMessages ",key, this._sentMessages[key]); for (var key in this._receivedMessages) this._trace("_receivedMessages ",key, this._receivedMessages[key]); return this._traceBuffer; } }; ClientImpl.prototype.startTrace = function () { if ( this._traceBuffer === null ) { this._traceBuffer = []; } this._trace("Client.startTrace", new Date(), version); }; ClientImpl.prototype.stopTrace = function () { delete this._traceBuffer; }; ClientImpl.prototype._doConnect = function (wsurl) { // When the socket is open, this client will send the CONNECT WireMessage using the saved parameters. if (this.connectOptions.useSSL) { var uriParts = wsurl.split(":"); uriParts[0] = "wss"; wsurl = uriParts.join(":"); } this.connected = false; if (this.connectOptions.mqttVersion < 4) { this.socket = new WebSocket(wsurl, ["mqttv3.1"]); } else { this.socket = new WebSocket(wsurl, ["mqtt"]); } this.socket.binaryType = 'arraybuffer'; this.socket.onopen = scope(this._on_socket_open, this); this.socket.onmessage = scope(this._on_socket_message, this); this.socket.onerror = scope(this._on_socket_error, this); this.socket.onclose = scope(this._on_socket_close, this); this.sendPinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); this.receivePinger = new Pinger(this, window, this.connectOptions.keepAliveInterval); this._connectTimeout = new Timeout(this, window, this.connectOptions.timeout, this._disconnected, [ERROR.CONNECT_TIMEOUT.code, format(ERROR.CONNECT_TIMEOUT)]); }; // Schedule a new message to be sent over the WebSockets // connection. CONNECT messages cause WebSocket connection // to be started. All other messages are queued internally // until this has happened. When WS connection starts, process // all outstanding messages. ClientImpl.prototype._schedule_message = function (message) { this._msg_queue.push(message); // Process outstanding messages in the queue if we have an open socket, and have received CONNACK. if (this.connected) { this._process_queue(); } }; ClientImpl.prototype.store = function(prefix, wireMessage) { var storedMessage = {type:wireMessage.type, messageIdentifier:wireMessage.messageIdentifier, version:1}; switch(wireMessage.type) { case MESSAGE_TYPE.PUBLISH: if(wireMessage.pubRecReceived) storedMessage.pubRecReceived = true; // Convert the payload to a hex string. storedMessage.payloadMessage = {}; var hex = ""; var messageBytes = wireMessage.payloadMessage.payloadBytes; for (var i=0; i= 2) { var x = parseInt(hex.substring(0, 2), 16); hex = hex.substring(2, hex.length); byteStream[i++] = x; } var payloadMessage = new Paho.MQTT.Message(byteStream); payloadMessage.qos = storedMessage.payloadMessage.qos; payloadMessage.destinationName = storedMessage.payloadMessage.destinationName; if (storedMessage.payloadMessage.duplicate) payloadMessage.duplicate = true; if (storedMessage.payloadMessage.retained) payloadMessage.retained = true; wireMessage.payloadMessage = payloadMessage; break; default: throw Error(format(ERROR.INVALID_STORED_DATA, [key, value])); } if (key.indexOf("Sent:"+this._localKey) == 0) { wireMessage.payloadMessage.duplicate = true; this._sentMessages[wireMessage.messageIdentifier] = wireMessage; } else if (key.indexOf("Received:"+this._localKey) == 0) { this._receivedMessages[wireMessage.messageIdentifier] = wireMessage; } }; ClientImpl.prototype._process_queue = function () { var message = null; // Process messages in order they were added var fifo = this._msg_queue.reverse(); // Send all queued messages down socket connection while ((message = fifo.pop())) { this._socket_send(message); // Notify listeners that message was successfully sent if (this._notify_msg_sent[message]) { this._notify_msg_sent[message](); delete this._notify_msg_sent[message]; } } }; /** * Expect an ACK response for this message. Add message to the set of in progress * messages and set an unused identifier in this message. * @ignore */ ClientImpl.prototype._requires_ack = function (wireMessage) { var messageCount = Object.keys(this._sentMessages).length; if (messageCount > this.maxMessageIdentifier) throw Error ("Too many messages:"+messageCount); while(this._sentMessages[this._message_identifier] !== undefined) { this._message_identifier++; } wireMessage.messageIdentifier = this._message_identifier; this._sentMessages[wireMessage.messageIdentifier] = wireMessage; if (wireMessage.type === MESSAGE_TYPE.PUBLISH) { this.store("Sent:", wireMessage); } if (this._message_identifier === this.maxMessageIdentifier) { this._message_identifier = 1; } }; /** * Called when the underlying websocket has been opened. * @ignore */ ClientImpl.prototype._on_socket_open = function () { // Create the CONNECT message object. var wireMessage = new WireMessage(MESSAGE_TYPE.CONNECT, this.connectOptions); wireMessage.clientId = this.clientId; this._socket_send(wireMessage); }; /** * Called when the underlying websocket has received a complete packet. * @ignore */ ClientImpl.prototype._on_socket_message = function (event) { this._trace("Client._on_socket_message", event.data); // Reset the receive ping timer, we now have evidence the server is alive. this.receivePinger.reset(); var messages = this._deframeMessages(event.data); for (var i = 0; i < messages.length; i+=1) { this._handleMessage(messages[i]); } } ClientImpl.prototype._deframeMessages = function(data) { var byteArray = new Uint8Array(data); if (this.receiveBuffer) { var newData = new Uint8Array(this.receiveBuffer.length+byteArray.length); newData.set(this.receiveBuffer); newData.set(byteArray,this.receiveBuffer.length); byteArray = newData; delete this.receiveBuffer; } try { var offset = 0; var messages = []; while(offset < byteArray.length) { var result = decodeMessage(byteArray,offset); var wireMessage = result[0]; offset = result[1]; if (wireMessage !== null) { messages.push(wireMessage); } else { break; } } if (offset < byteArray.length) { this.receiveBuffer = byteArray.subarray(offset); } } catch (error) { this._disconnected(ERROR.INTERNAL_ERROR.code , format(ERROR.INTERNAL_ERROR, [error.message,error.stack.toString()])); return; } return messages; } ClientImpl.prototype._handleMessage = function(wireMessage) { this._trace("Client._handleMessage", wireMessage); try { switch(wireMessage.type) { case MESSAGE_TYPE.CONNACK: this._connectTimeout.cancel(); // If we have started using clean session then clear up the local state. if (this.connectOptions.cleanSession) { for (var key in this._sentMessages) { var sentMessage = this._sentMessages[key]; localStorage.removeItem("Sent:"+this._localKey+sentMessage.messageIdentifier); } this._sentMessages = {}; for (var key in this._receivedMessages) { var receivedMessage = this._receivedMessages[key]; localStorage.removeItem("Received:"+this._localKey+receivedMessage.messageIdentifier); } this._receivedMessages = {}; } // Client connected and ready for business. if (wireMessage.returnCode === 0) { this.connected = true; // Jump to the end of the list of uris and stop looking for a good host. if (this.connectOptions.uris) this.hostIndex = this.connectOptions.uris.length; } else { this._disconnected(ERROR.CONNACK_RETURNCODE.code , format(ERROR.CONNACK_RETURNCODE, [wireMessage.returnCode, CONNACK_RC[wireMessage.returnCode]])); break; } // Resend messages. var sequencedMessages = new Array(); for (var msgId in this._sentMessages) { if (this._sentMessages.hasOwnProperty(msgId)) sequencedMessages.push(this._sentMessages[msgId]); } // Sort sentMessages into the original sent order. var sequencedMessages = sequencedMessages.sort(function(a,b) {return a.sequence - b.sequence;} ); for (var i=0, len=sequencedMessages.length; i * Most applications will create just one Client object and then call its connect() method, * however applications can create more than one Client object if they wish. * In this case the combination of host, port and clientId attributes must be different for each Client object. *

* The send, subscribe and unsubscribe methods are implemented as asynchronous JavaScript methods * (even though the underlying protocol exchange might be synchronous in nature). * This means they signal their completion by calling back to the application, * via Success or Failure callback functions provided by the application on the method in question. * Such callbacks are called at most once per method invocation and do not persist beyond the lifetime * of the script that made the invocation. *

* In contrast there are some callback functions, most notably onMessageArrived, * that are defined on the {@link Paho.MQTT.Client} object. * These may get called multiple times, and aren't directly related to specific method invocations made by the client. * * @name Paho.MQTT.Client * * @constructor * * @param {string} host - the address of the messaging server, as a fully qualified WebSocket URI, as a DNS name or dotted decimal IP address. * @param {number} port - the port number to connect to - only required if host is not a URI * @param {string} path - the path on the host to connect to - only used if host is not a URI. Default: '/mqtt'. * @param {string} clientId - the Messaging client identifier, between 1 and 23 characters in length. * * @property {string} host - read only the server's DNS hostname or dotted decimal IP address. * @property {number} port - read only the server's port. * @property {string} path - read only the server's path. * @property {string} clientId - read only used when connecting to the server. * @property {function} onConnectionLost - called when a connection has been lost. * after a connect() method has succeeded. * Establish the call back used when a connection has been lost. The connection may be * lost because the client initiates a disconnect or because the server or transfer * cause the client to be disconnected. The disconnect call back may be called without * the connectionComplete call back being invoked if, for example the client fails to * connect. * A single response object parameter is passed to the onConnectionLost callback containing the following fields: *

    *
  1. errorCode *
  2. errorMessage *
* @property {function} onMessageDelivered called when a message has been delivered. * All processing that this Client will ever do has been completed. So, for example, * in the case of a Qos=2 message sent by this client, the PubComp flow has been received from the server * and the message has been removed from persistent storage before this callback is invoked. * Parameters passed to the onMessageDelivered callback are: *
    *
  1. {@link Paho.MQTT.Message} that was delivered. *
* @property {function} onMessageArrived called when a message has arrived in this Paho.MQTT.client. * Parameters passed to the onMessageArrived callback are: *
    *
  1. {@link Paho.MQTT.Message} that has arrived. *
*/ var Client = function (host, port, path, clientId) { var uri; if (typeof host !== "string") throw new Error(format(ERROR.INVALID_TYPE, [typeof host, "host"])); if (arguments.length == 2) { // host: must be full ws:// uri // port: clientId clientId = port; uri = host; var match = uri.match(/^(wss?):\/\/((\[(.+)\])|([^\/]+?))(:(\d+))?(\/.*)$/); if (match) { host = match[4]||match[2]; port = parseInt(match[7]); path = match[8]; } else { throw new Error(format(ERROR.INVALID_ARGUMENT,[host,"host"])); } } else { if (arguments.length == 3) { clientId = path; path = "/mqtt"; } if (typeof port !== "number" || port < 0) throw new Error(format(ERROR.INVALID_TYPE, [typeof port, "port"])); if (typeof path !== "string") throw new Error(format(ERROR.INVALID_TYPE, [typeof path, "path"])); var ipv6AddSBracket = (host.indexOf(":") != -1 && host.slice(0,1) != "[" && host.slice(-1) != "]"); uri = "ws://"+(ipv6AddSBracket?"["+host+"]":host)+":"+port+path; } var clientIdLength = 0; for (var i = 0; i 65535) throw new Error(format(ERROR.INVALID_ARGUMENT, [clientId, "clientId"])); var client = new ClientImpl(uri, host, port, path, clientId); this._getHost = function() { return host; }; this._setHost = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; this._getPort = function() { return port; }; this._setPort = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; this._getPath = function() { return path; }; this._setPath = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; this._getURI = function() { return uri; }; this._setURI = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; this._getClientId = function() { return client.clientId; }; this._setClientId = function() { throw new Error(format(ERROR.UNSUPPORTED_OPERATION)); }; this._getOnConnectionLost = function() { return client.onConnectionLost; }; this._setOnConnectionLost = function(newOnConnectionLost) { if (typeof newOnConnectionLost === "function") client.onConnectionLost = newOnConnectionLost; else throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnConnectionLost, "onConnectionLost"])); }; this._getOnMessageDelivered = function() { return client.onMessageDelivered; }; this._setOnMessageDelivered = function(newOnMessageDelivered) { if (typeof newOnMessageDelivered === "function") client.onMessageDelivered = newOnMessageDelivered; else throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageDelivered, "onMessageDelivered"])); }; this._getOnMessageArrived = function() { return client.onMessageArrived; }; this._setOnMessageArrived = function(newOnMessageArrived) { if (typeof newOnMessageArrived === "function") client.onMessageArrived = newOnMessageArrived; else throw new Error(format(ERROR.INVALID_TYPE, [typeof newOnMessageArrived, "onMessageArrived"])); }; this._getTrace = function() { return client.traceFunction; }; this._setTrace = function(trace) { if(typeof trace === "function"){ client.traceFunction = trace; }else{ throw new Error(format(ERROR.INVALID_TYPE, [typeof trace, "onTrace"])); } }; /** * Connect this Messaging client to its server. * * @name Paho.MQTT.Client#connect * @function * @param {Object} connectOptions - attributes used with the connection. * @param {number} connectOptions.timeout - If the connect has not succeeded within this * number of seconds, it is deemed to have failed. * The default is 30 seconds. * @param {string} connectOptions.userName - Authentication username for this connection. * @param {string} connectOptions.password - Authentication password for this connection. * @param {Paho.MQTT.Message} connectOptions.willMessage - sent by the server when the client * disconnects abnormally. * @param {Number} connectOptions.keepAliveInterval - the server disconnects this client if * there is no activity for this number of seconds. * The default value of 60 seconds is assumed if not set. * @param {boolean} connectOptions.cleanSession - if true(default) the client and server * persistent state is deleted on successful connect. * @param {boolean} connectOptions.useSSL - if present and true, use an SSL Websocket connection. * @param {object} connectOptions.invocationContext - passed to the onSuccess callback or onFailure callback. * @param {function} connectOptions.onSuccess - called when the connect acknowledgement * has been received from the server. * A single response object parameter is passed to the onSuccess callback containing the following fields: *
    *
  1. invocationContext as passed in to the onSuccess method in the connectOptions. *
* @config {function} [onFailure] called when the connect request has failed or timed out. * A single response object parameter is passed to the onFailure callback containing the following fields: *
    *
  1. invocationContext as passed in to the onFailure method in the connectOptions. *
  2. errorCode a number indicating the nature of the error. *
  3. errorMessage text describing the error. *
* @config {Array} [hosts] If present this contains either a set of hostnames or fully qualified * WebSocket URIs (ws://example.com:1883/mqtt), that are tried in order in place * of the host and port paramater on the construtor. The hosts are tried one at at time in order until * one of then succeeds. * @config {Array} [ports] If present the set of ports matching the hosts. If hosts contains URIs, this property * is not used. * @throws {InvalidState} if the client is not in disconnected state. The client must have received connectionLost * or disconnected before calling connect for a second or subsequent time. */ this.connect = function (connectOptions) { connectOptions = connectOptions || {} ; validate(connectOptions, {timeout:"number", userName:"string", password:"string", willMessage:"object", keepAliveInterval:"number", cleanSession:"boolean", useSSL:"boolean", invocationContext:"object", onSuccess:"function", onFailure:"function", hosts:"object", ports:"object", mqttVersion:"number"}); // If no keep alive interval is set, assume 60 seconds. if (connectOptions.keepAliveInterval === undefined) connectOptions.keepAliveInterval = 60; if (connectOptions.mqttVersion > 4 || connectOptions.mqttVersion < 3) { throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.mqttVersion, "connectOptions.mqttVersion"])); } if (connectOptions.mqttVersion === undefined) { connectOptions.mqttVersionExplicit = false; connectOptions.mqttVersion = 4; } else { connectOptions.mqttVersionExplicit = true; } //Check that if password is set, so is username if (connectOptions.password === undefined && connectOptions.userName !== undefined) throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.password, "connectOptions.password"])) if (connectOptions.willMessage) { if (!(connectOptions.willMessage instanceof Message)) throw new Error(format(ERROR.INVALID_TYPE, [connectOptions.willMessage, "connectOptions.willMessage"])); // The will message must have a payload that can be represented as a string. // Cause the willMessage to throw an exception if this is not the case. connectOptions.willMessage.stringPayload; if (typeof connectOptions.willMessage.destinationName === "undefined") throw new Error(format(ERROR.INVALID_TYPE, [typeof connectOptions.willMessage.destinationName, "connectOptions.willMessage.destinationName"])); } if (typeof connectOptions.cleanSession === "undefined") connectOptions.cleanSession = true; if (connectOptions.hosts) { if (!(connectOptions.hosts instanceof Array) ) throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); if (connectOptions.hosts.length <1 ) throw new Error(format(ERROR.INVALID_ARGUMENT, [connectOptions.hosts, "connectOptions.hosts"])); var usingURIs = false; for (var i = 0; i * @param {object} subscribeOptions - used to control the subscription * * @param {number} subscribeOptions.qos - the maiximum qos of any publications sent * as a result of making this subscription. * @param {object} subscribeOptions.invocationContext - passed to the onSuccess callback * or onFailure callback. * @param {function} subscribeOptions.onSuccess - called when the subscribe acknowledgement * has been received from the server. * A single response object parameter is passed to the onSuccess callback containing the following fields: *
    *
  1. invocationContext if set in the subscribeOptions. *
* @param {function} subscribeOptions.onFailure - called when the subscribe request has failed or timed out. * A single response object parameter is passed to the onFailure callback containing the following fields: *
    *
  1. invocationContext - if set in the subscribeOptions. *
  2. errorCode - a number indicating the nature of the error. *
  3. errorMessage - text describing the error. *
* @param {number} subscribeOptions.timeout - which, if present, determines the number of * seconds after which the onFailure calback is called. * The presence of a timeout does not prevent the onSuccess * callback from being called when the subscribe completes. * @throws {InvalidState} if the client is not in connected state. */ this.subscribe = function (filter, subscribeOptions) { if (typeof filter !== "string") throw new Error("Invalid argument:"+filter); subscribeOptions = subscribeOptions || {} ; validate(subscribeOptions, {qos:"number", invocationContext:"object", onSuccess:"function", onFailure:"function", timeout:"number" }); if (subscribeOptions.timeout && !subscribeOptions.onFailure) throw new Error("subscribeOptions.timeout specified with no onFailure callback."); if (typeof subscribeOptions.qos !== "undefined" && !(subscribeOptions.qos === 0 || subscribeOptions.qos === 1 || subscribeOptions.qos === 2 )) throw new Error(format(ERROR.INVALID_ARGUMENT, [subscribeOptions.qos, "subscribeOptions.qos"])); client.subscribe(filter, subscribeOptions); }; /** * Unsubscribe for messages, stop receiving messages sent to destinations described by the filter. * * @name Paho.MQTT.Client#unsubscribe * @function * @param {string} filter - describing the destinations to receive messages from. * @param {object} unsubscribeOptions - used to control the subscription * @param {object} unsubscribeOptions.invocationContext - passed to the onSuccess callback or onFailure callback. * @param {function} unsubscribeOptions.onSuccess - called when the unsubscribe acknowledgement has been received from the server. * A single response object parameter is passed to the * onSuccess callback containing the following fields: *
    *
  1. invocationContext - if set in the unsubscribeOptions. *
* @param {function} unsubscribeOptions.onFailure called when the unsubscribe request has failed or timed out. * A single response object parameter is passed to the onFailure callback containing the following fields: *
    *
  1. invocationContext - if set in the unsubscribeOptions. *
  2. errorCode - a number indicating the nature of the error. *
  3. errorMessage - text describing the error. *
* @param {number} unsubscribeOptions.timeout - which, if present, determines the number of seconds * after which the onFailure callback is called. The presence of * a timeout does not prevent the onSuccess callback from being * called when the unsubscribe completes * @throws {InvalidState} if the client is not in connected state. */ this.unsubscribe = function (filter, unsubscribeOptions) { if (typeof filter !== "string") throw new Error("Invalid argument:"+filter); unsubscribeOptions = unsubscribeOptions || {} ; validate(unsubscribeOptions, {invocationContext:"object", onSuccess:"function", onFailure:"function", timeout:"number" }); if (unsubscribeOptions.timeout && !unsubscribeOptions.onFailure) throw new Error("unsubscribeOptions.timeout specified with no onFailure callback."); client.unsubscribe(filter, unsubscribeOptions); }; /** * Send a message to the consumers of the destination in the Message. * * @name Paho.MQTT.Client#send * @function * @param {string|Paho.MQTT.Message} topic - mandatory The name of the destination to which the message is to be sent. * - If it is the only parameter, used as Paho.MQTT.Message object. * @param {String|ArrayBuffer} payload - The message data to be sent. * @param {number} qos The Quality of Service used to deliver the message. *
*
0 Best effort (default). *
1 At least once. *
2 Exactly once. *
* @param {Boolean} retained If true, the message is to be retained by the server and delivered * to both current and future subscriptions. * If false the server only delivers the message to current subscribers, this is the default for new Messages. * A received message has the retained boolean set to true if the message was published * with the retained boolean set to true * and the subscrption was made after the message has been published. * @throws {InvalidState} if the client is not connected. */ this.send = function (topic,payload,qos,retained) { var message ; if(arguments.length == 0){ throw new Error("Invalid argument."+"length"); }else if(arguments.length == 1) { if (!(topic instanceof Message) && (typeof topic !== "string")) throw new Error("Invalid argument:"+ typeof topic); message = topic; if (typeof message.destinationName === "undefined") throw new Error(format(ERROR.INVALID_ARGUMENT,[message.destinationName,"Message.destinationName"])); client.send(message); }else { //parameter checking in Message object message = new Message(payload); message.destinationName = topic; if(arguments.length >= 3) message.qos = qos; if(arguments.length >= 4) message.retained = retained; client.send(message); } }; /** * Normal disconnect of this Messaging client from its server. * * @name Paho.MQTT.Client#disconnect * @function * @throws {InvalidState} if the client is already disconnected. */ this.disconnect = function () { client.disconnect(); }; /** * Get the contents of the trace log. * * @name Paho.MQTT.Client#getTraceLog * @function * @return {Object[]} tracebuffer containing the time ordered trace records. */ this.getTraceLog = function () { return client.getTraceLog(); } /** * Start tracing. * * @name Paho.MQTT.Client#startTrace * @function */ this.startTrace = function () { client.startTrace(); }; /** * Stop tracing. * * @name Paho.MQTT.Client#stopTrace * @function */ this.stopTrace = function () { client.stopTrace(); }; this.isConnected = function() { return client.connected; }; }; Client.prototype = { get host() { return this._getHost(); }, set host(newHost) { this._setHost(newHost); }, get port() { return this._getPort(); }, set port(newPort) { this._setPort(newPort); }, get path() { return this._getPath(); }, set path(newPath) { this._setPath(newPath); }, get clientId() { return this._getClientId(); }, set clientId(newClientId) { this._setClientId(newClientId); }, get onConnectionLost() { return this._getOnConnectionLost(); }, set onConnectionLost(newOnConnectionLost) { this._setOnConnectionLost(newOnConnectionLost); }, get onMessageDelivered() { return this._getOnMessageDelivered(); }, set onMessageDelivered(newOnMessageDelivered) { this._setOnMessageDelivered(newOnMessageDelivered); }, get onMessageArrived() { return this._getOnMessageArrived(); }, set onMessageArrived(newOnMessageArrived) { this._setOnMessageArrived(newOnMessageArrived); }, get trace() { return this._getTrace(); }, set trace(newTraceFunction) { this._setTrace(newTraceFunction); } }; /** * An application message, sent or received. *

* All attributes may be null, which implies the default values. * * @name Paho.MQTT.Message * @constructor * @param {String|ArrayBuffer} payload The message data to be sent. *

* @property {string} payloadString read only The payload as a string if the payload consists of valid UTF-8 characters. * @property {ArrayBuffer} payloadBytes read only The payload as an ArrayBuffer. *

* @property {string} destinationName mandatory The name of the destination to which the message is to be sent * (for messages about to be sent) or the name of the destination from which the message has been received. * (for messages received by the onMessage function). *

* @property {number} qos The Quality of Service used to deliver the message. *

*
0 Best effort (default). *
1 At least once. *
2 Exactly once. *
*

* @property {Boolean} retained If true, the message is to be retained by the server and delivered * to both current and future subscriptions. * If false the server only delivers the message to current subscribers, this is the default for new Messages. * A received message has the retained boolean set to true if the message was published * with the retained boolean set to true * and the subscrption was made after the message has been published. *

* @property {Boolean} duplicate read only If true, this message might be a duplicate of one which has already been received. * This is only set on messages received from the server. * */ var Message = function (newPayload) { var payload; if ( typeof newPayload === "string" || newPayload instanceof ArrayBuffer || newPayload instanceof Int8Array || newPayload instanceof Uint8Array || newPayload instanceof Int16Array || newPayload instanceof Uint16Array || newPayload instanceof Int32Array || newPayload instanceof Uint32Array || newPayload instanceof Float32Array || newPayload instanceof Float64Array ) { payload = newPayload; } else { throw (format(ERROR.INVALID_ARGUMENT, [newPayload, "newPayload"])); } this._getPayloadString = function () { if (typeof payload === "string") return payload; else return parseUTF8(payload, 0, payload.length); }; this._getPayloadBytes = function() { if (typeof payload === "string") { var buffer = new ArrayBuffer(UTF8Length(payload)); var byteStream = new Uint8Array(buffer); stringToUTF8(payload, byteStream, 0); return byteStream; } else { return payload; }; }; var destinationName = undefined; this._getDestinationName = function() { return destinationName; }; this._setDestinationName = function(newDestinationName) { if (typeof newDestinationName === "string") destinationName = newDestinationName; else throw new Error(format(ERROR.INVALID_ARGUMENT, [newDestinationName, "newDestinationName"])); }; var qos = 0; this._getQos = function() { return qos; }; this._setQos = function(newQos) { if (newQos === 0 || newQos === 1 || newQos === 2 ) qos = newQos; else throw new Error("Invalid argument:"+newQos); }; var retained = false; this._getRetained = function() { return retained; }; this._setRetained = function(newRetained) { if (typeof newRetained === "boolean") retained = newRetained; else throw new Error(format(ERROR.INVALID_ARGUMENT, [newRetained, "newRetained"])); }; var duplicate = false; this._getDuplicate = function() { return duplicate; }; this._setDuplicate = function(newDuplicate) { duplicate = newDuplicate; }; }; Message.prototype = { get payloadString() { return this._getPayloadString(); }, get payloadBytes() { return this._getPayloadBytes(); }, get destinationName() { return this._getDestinationName(); }, set destinationName(newDestinationName) { this._setDestinationName(newDestinationName); }, get qos() { return this._getQos(); }, set qos(newQos) { this._setQos(newQos); }, get retained() { return this._getRetained(); }, set retained(newRetained) { this._setRetained(newRetained); }, get duplicate() { return this._getDuplicate(); }, set duplicate(newDuplicate) { this._setDuplicate(newDuplicate); } }; // Module contents. return { Client: Client, Message: Message }; })(window); ================================================ FILE: jmqtt-example/src/main/java/org/jmqtt/websocket/webSocket.html ================================================ webSocket test

Test WebSocket function

server ip:
webSocket port:
clientId:


Pub Topic:
Message:


Sub Topic:
================================================ FILE: jmqtt-manager ================================================ ================================================ FILE: jmqtt-mqtt/README.md ================================================ Support mqtt gateway ================================================ FILE: jmqtt-mqtt/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-mqtt jar jmqtt-mqtt org.jmqtt jmqtt-bus io.netty netty-all org.apache.logging.log4j log4j-api org.apache.logging.log4j log4j-core ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/ConnectManager.java ================================================ package org.jmqtt.mqtt; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 客户端连接管理器 */ public class ConnectManager { private Map clientCache = new ConcurrentHashMap<>(); private static final ConnectManager INSTANCE = new ConnectManager(); private ConnectManager(){} public static ConnectManager getInstance(){ return INSTANCE; } public Collection getAllConnections(){ return this.clientCache.values(); } public MQTTConnection getClient(String clientId){ return this.clientCache.get(clientId); } public MQTTConnection putClient(String clientId,MQTTConnection clientSession){ return this.clientCache.put(clientId,clientSession); } public boolean containClient(String clientId){ return this.clientCache.containsKey(clientId); } public MQTTConnection removeClient(String clientId){ if (clientId != null) { return this.clientCache.remove(clientId); } return null; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnection.java ================================================ package org.jmqtt.mqtt; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.*; import io.netty.handler.timeout.IdleStateHandler; import org.jmqtt.bus.*; import org.jmqtt.bus.enums.*; import org.jmqtt.bus.model.ClusterEvent; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.model.DeviceSession; import org.jmqtt.bus.model.DeviceSubscription; import org.jmqtt.mqtt.model.MqttTopic; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.mqtt.retain.RetainMessageHandler; import org.jmqtt.mqtt.session.MqttSession; import org.jmqtt.mqtt.utils.MqttMessageUtil; import org.jmqtt.mqtt.utils.MqttMsgHeader; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.helper.Pair; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingHelper; import org.slf4j.Logger; import java.util.*; import java.util.concurrent.ExecutorService; import java.util.concurrent.RejectedExecutionException; import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE; import static io.netty.channel.ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE; /** * handle mqtt connection and process mqtt protocol */ public class MQTTConnection { private static final Logger log = JmqttLogger.mqttLog; private Channel channel; private Map> processorTable; private BrokerConfig brokerConfig; private MqttSession bindedSession; private Authenticator authenticator; private DeviceSessionManager deviceSessionManager; private DeviceSubscriptionManager deviceSubscriptionManager; private DeviceMessageManager deviceMessageManager; private RetainMessageHandler retainMessageHandler; private ClusterEventManager clusterEventManager; private String clientId; public MQTTConnection(Channel channel, Map> processorTable, BrokerConfig brokerConfig, BusController busController,RetainMessageHandler retainMessageHandler) { this.channel = channel; this.processorTable = processorTable; this.brokerConfig = brokerConfig; this.authenticator = busController.getAuthenticator(); this.deviceSessionManager = busController.getDeviceSessionManager(); this.deviceMessageManager = busController.getDeviceMessageManager(); this.deviceSubscriptionManager = busController.getDeviceSubscriptionManager(); this.retainMessageHandler = retainMessageHandler; this.clusterEventManager = busController.getClusterEventManager(); } public void processProtocol(ChannelHandlerContext ctx, MqttMessage mqttMessage) { int protocolType = mqttMessage.fixedHeader().messageType().value(); Runnable runnable = () -> processorTable.get(protocolType).getObject1().processRequest(ctx, mqttMessage); try { processorTable.get(protocolType).getObject2().submit(runnable); } catch (RejectedExecutionException ex) { LogUtil.warn(log, "Reject mqtt request,cause={}", ex.getMessage()); } } public void processPubComp(MqttMessage mqttMessage) { String clientId = getClientId(); int packetId = MqttMessageUtil.getMessageId(mqttMessage); boolean flag = bindedSession.releaseQos2SecFlow(packetId); LogUtil.debug(log,"[PubComp] -> Receive PubCom and remove the flow message,clientId={},msgId={}",clientId,packetId); if(!flag){ LogUtil.warn(log,"[PubComp] -> The message is not in Flow cache,clientId={},msgId={}",clientId,packetId); } } public void processPubRec(MqttMessage mqttMessage){ String clientId = getClientId(); int packetId = MqttMessageUtil.getMessageId(mqttMessage); DeviceMessage stayAckMsg = bindedSession.releaseOutboundFlowMessage(packetId); if (stayAckMsg == null) { LogUtil.warn(log,"[PUBREC] Stay release message is not exist,packetId:{},clientId:{}",packetId,getClientId()); } else { this.deviceMessageManager.ackMessage(getClientId(),stayAckMsg.getId()); } bindedSession.receivePubRec(packetId); LogUtil.debug(log,"[PubRec] -> Receive PubRec message,clientId={},msgId={}",clientId,packetId); MqttMessage pubRelMessage = MqttMessageUtil.getPubRelMessage(packetId); this.channel.writeAndFlush(pubRelMessage); } public void processPubAck(MqttMessage mqttMessage){ int packetId = MqttMessageUtil.getMessageId(mqttMessage); LogUtil.info(log, "[PubAck] -> Receive PubAck message,clientId={},msgId={}", getClientId(),packetId); DeviceMessage deviceMessage = bindedSession.releaseOutboundFlowMessage(packetId); if (deviceMessage == null) { LogUtil.warn(log,"[PUBACK] Stay release message is not exist,packetId:{},clientId:{}",packetId,getClientId()); return; } this.deviceMessageManager.ackMessage(getClientId(),deviceMessage.getId()); } public void processUnSubscribe(MqttUnsubscribeMessage mqttUnsubscribeMessage){ String clientId = getClientId(); MqttUnsubscribePayload unsubscribePayload = mqttUnsubscribeMessage.payload(); List topics = unsubscribePayload.topics(); topics.forEach( topic -> { this.bindedSession.removeSubscription(topic); deviceSubscriptionManager.unSubscribe(clientId,topic); }); MqttUnsubAckMessage unsubAckMessage = MqttMessageUtil.getUnSubAckMessage(MqttMessageUtil.getMessageId(mqttUnsubscribeMessage)); this.channel.writeAndFlush(unsubAckMessage); } public void processSubscribe(MqttSubscribeMessage subscribeMessage) { int packetId = subscribeMessage.variableHeader().messageId(); List validTopicList = validTopics(subscribeMessage.payload().topicSubscriptions()); if (validTopicList == null || validTopicList.size() == 0) { LogUtil.warn(log, "[Subscribe] -> Valid all subscribe topic failure,clientId:{},packetId:{}", getClientId(),packetId); return; } // subscribe List ackQos = new ArrayList<>(validTopicList.size()); for (MqttTopic topic : validTopicList) { DeviceSubscription deviceSubscription = new DeviceSubscription(); deviceSubscription.setTopic(topic.getTopicName()); deviceSubscription.setClientId(getClientId()); deviceSubscription.setSubscribeTime(new Date()); Map properties = new HashMap<>(); properties.put(MqttMsgHeader.QOS,topic.getQos()); deviceSubscription.setProperties(properties); this.bindedSession.addSubscription(deviceSubscription); boolean succ = this.deviceSubscriptionManager.subscribe(deviceSubscription); if (succ) { ackQos.add(topic.getQos()); } else { LogUtil.error(log,"[SUBSCRIBE] subscribe error."); } } MqttMessage subAckMessage = MqttMessageUtil.getSubAckMessage(packetId, ackQos); this.channel.writeAndFlush(subAckMessage).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { if (future.isSuccess()) { // dispatcher retain message List retainMessages = retainMessageHandler.getAllRetatinMessage(); if (retainMessages != null) { retainMessages.forEach(message -> { message.setSource(MessageSourceEnum.DEVICE); // match for (MqttTopic topic : validTopicList) { if (deviceSubscriptionManager.isMatch(message.getTopic(), topic.getTopicName())) { // 1. store stay message Long msgId = deviceMessageManager.storeMessage(message); deviceMessageManager.addClientInBoxMsg(getClientId(),msgId, MessageAckEnum.UN_ACK); // 2. ack message bindedSession.sendMessage(message); } } }); } } else { LogUtil.error(log, "[SUBSCRIBE] suback response error."); } } }); } public void publishMessage(MqttPublishMessage mqttPublishMessage){ this.channel.writeAndFlush(mqttPublishMessage); } /** * 返回校验合法的topic */ private List validTopics(List topics) { List topicList = new ArrayList<>(); for (MqttTopicSubscription subscription : topics) { if (!authenticator.subscribeVerify(getClientId(), subscription.topicName())) { LogUtil.warn(log, "[SubPermission] this clientId:{} have no permission to subscribe this topic:{}", getClientId(), subscription.topicName()); continue; } MqttTopic topic = new MqttTopic(subscription.topicName(), subscription.qualityOfService().value()); topicList.add(topic); } return topicList; } public void processPublishMessage(MqttPublishMessage mqttPublishMessage) { MqttQoS qos = mqttPublishMessage.fixedHeader().qosLevel(); switch (qos) { case AT_MOST_ONCE: dispatcherMessage(mqttPublishMessage); break; case AT_LEAST_ONCE: processQos1(mqttPublishMessage); break; case EXACTLY_ONCE: processQos2(mqttPublishMessage); break; default: LogUtil.warn(log, "[PubMessage] -> Wrong mqtt message,clientId={}", getClientId()); break; } } private void processQos2(MqttPublishMessage mqttPublishMessage) { int originMessageId = mqttPublishMessage.variableHeader().packetId(); LogUtil.debug(log, "[PubMessage] -> Process qos2 message,clientId={}", getClientId()); DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(mqttPublishMessage); bindedSession.receivedPublishQos2(originMessageId, deviceMessage); MqttMessage pubRecMessage = MqttMessageUtil.getPubRecMessage(originMessageId); this.channel.writeAndFlush(pubRecMessage); } private void processQos1(MqttPublishMessage mqttPublishMessage) { int originMessageId = mqttPublishMessage.variableHeader().packetId(); dispatcherMessage(mqttPublishMessage); LogUtil.info(log, "[PubMessage] -> Process qos1 message,clientId={}", getClientId()); MqttPubAckMessage pubAckMessage = MqttMessageUtil.getPubAckMessage(originMessageId); this.channel.writeAndFlush(pubAckMessage); } public void processPubRelMessage(MqttMessage mqttMessage) { int packetId = MqttMessageUtil.getMessageId(mqttMessage); DeviceMessage deviceMessage = bindedSession.receivedPubRelQos2(packetId); if (deviceMessage == null) { LogUtil.error(log, "[PUBREL] receivedPubRelQos2 cached message is not exist,message lost !!!!!,packetId:{},clientId:{}", packetId, getClientId()); } else { dispatcherMessage(deviceMessage); } MqttMessage pubComMessage = MqttMessageUtil.getPubComMessage(packetId); this.channel.writeAndFlush(pubComMessage); } private void dispatcherMessage(DeviceMessage deviceMessage) { // 1. retain消息逻辑 boolean retain = deviceMessage.getProperty(MqttMsgHeader.RETAIN); int qos = deviceMessage.getProperty(MqttMsgHeader.QOS); if (retain) { //qos == 0 or payload is none,then clear previous retain message if (qos == 0 || deviceMessage.getContent() == null || deviceMessage.getContent().length == 0) { this.retainMessageHandler.clearRetainMessage(deviceMessage.getTopic()); } else { this.retainMessageHandler.storeRetainMessage(deviceMessage); } } // 2. 向集群中分发消息:第一阶段 this.deviceMessageManager.dispatcher(deviceMessage); } private void dispatcherMessage(MqttPublishMessage mqttPublishMessage) { boolean retain = mqttPublishMessage.fixedHeader().isRetain(); int qos = mqttPublishMessage.fixedHeader().qosLevel().value(); byte[] payload = MqttMessageUtil.readBytesFromByteBuf(mqttPublishMessage.payload()); String topic = mqttPublishMessage.variableHeader().topicName(); DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(retain, qos, topic, payload); deviceMessage.setSource(MessageSourceEnum.DEVICE); deviceMessage.setFromClientId(clientId); dispatcherMessage(deviceMessage); } public void handleConnectionLost() { String clientID = MqttNettyUtils.clientID(channel); if (clientID == null || clientID.isEmpty()) { return; } if (bindedSession.hasWill()) { dispatcherMessage(bindedSession.getWill()); } if (bindedSession.isCleanSession()) { clearSession(); } offline(); LogUtil.info(log, "[CONNECT INACTIVE] connect lost"); } public boolean createOrReopenSession(MqttConnectMessage mqttConnectMessage) { int mqttVersion = mqttConnectMessage.variableHeader().version(); String clientId = mqttConnectMessage.payload().clientIdentifier(); this.clientId = clientId; boolean cleanSession = mqttConnectMessage.variableHeader().isCleanSession(); this.bindedSession = new MqttSession(); this.bindedSession.setClientId(clientId); this.bindedSession.setCleanSession(cleanSession); this.bindedSession.setMqttVersion(mqttVersion); this.bindedSession.setClientIp(RemotingHelper.getRemoteAddr(this.channel)); this.bindedSession.setServerIp(RemotingHelper.getLocalAddr()); this.bindedSession.setMqttConnection(this); boolean sessionPresent = false; boolean notifyClearOtherSession = true; // 1. 从集群/本服务器中查询是否存在该clientId的设备消息 DeviceSession deviceSession = deviceSessionManager.getSession(clientId); if (deviceSession != null && deviceSession.getOnline() == DeviceOnlineStateEnum.ONLINE) { MQTTConnection previousClient = ConnectManager.getInstance().getClient(clientId); if (previousClient != null) { //clear old session in this node previousClient.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED); ConnectManager.getInstance().removeClient(clientId); notifyClearOtherSession = false; } } if (deviceSession == null) { sessionPresent = false; notifyClearOtherSession = false; } else { if (cleanSession) { sessionPresent = false; clearSession(); } else { sessionPresent = true; reloadSubscriptions(); } } // 2. 清理连接到其它节点的连接 if (notifyClearOtherSession) { ClusterEvent clusterEvent = new ClusterEvent(); clusterEvent.setClusterEventCode(ClusterEventCodeEnum.MQTT_CLEAR_SESSION); clusterEvent.setContent(getClientId()); clusterEvent.setGmtCreate(new Date()); clusterEvent.setNodeIp(bindedSession.getServerIp()); clusterEventManager.sendEvent(clusterEvent); } // 3. 处理will 消息 boolean willFlag = mqttConnectMessage.variableHeader().isWillFlag(); if (willFlag) { boolean willRetain = mqttConnectMessage.variableHeader().isWillRetain(); int willQos = mqttConnectMessage.variableHeader().willQos(); String willTopic = mqttConnectMessage.payload().willTopic(); byte[] willPayload = mqttConnectMessage.payload().willMessageInBytes(); DeviceMessage deviceMessage = MqttMsgHeader.buildDeviceMessage(willRetain, willQos, willTopic, willPayload); deviceMessage.setSource(MessageSourceEnum.DEVICE); deviceMessage.setFromClientId(clientId); bindedSession.setWill(deviceMessage); } return sessionPresent; } public void storeSession() { ConnectManager.getInstance().putClient(getClientId(),this); DeviceSession deviceSession = new DeviceSession(); deviceSession.setTransportProtocol(TransportProtocolEnum.MQTT); deviceSession.setServerIp(bindedSession.getServerIp()); deviceSession.setClientIp(bindedSession.getClientIp()); deviceSession.setOnlineTime(new Date()); deviceSession.setClientId(getClientId()); deviceSession.setOnline(DeviceOnlineStateEnum.ONLINE); Map properties = new HashMap<>(); properties.put(MqttMsgHeader.CLEAN_SESSION,bindedSession.isCleanSession()); deviceSession.setProperties(properties); deviceSessionManager.storeSession(deviceSession); } public void reSendMessage2Client() { int limit = 100; // per 100 boolean hasUnAckMessages = true; while (hasUnAckMessages) { List deviceInboxMessageList = this.deviceMessageManager.queryUnAckMessages(getClientId(),limit); if (deviceInboxMessageList == null) { return; } if (deviceInboxMessageList.size() < 100) { hasUnAckMessages = false; } // resendMessage deviceInboxMessageList.forEach(deviceMessage -> { bindedSession.sendMessage(deviceMessage); }); } } public boolean keepAlive(int heatbeatSec) { int keepAlive = (int) (heatbeatSec * 1.5f); if (this.channel.pipeline().names().contains("idleStateHandler")) { this.channel.pipeline().remove("idleStateHandler"); } this.channel.pipeline().addFirst("idleStateHandler", new IdleStateHandler(keepAlive, 0, 0)); return true; } public boolean login(String clientId, String username, byte[] password) { return this.authenticator.login(clientId, username, password); } public boolean onBlackList(String remoteAddr, String clientId) { return this.authenticator.onBlackList(clientId, remoteAddr); } public boolean clientIdVerify(String clientId) { return this.authenticator.clientIdVerify(clientId); } public void abortConnection(MqttConnectReturnCode returnCode) { MqttConnAckMessage badProto = MqttMessageBuilders.connAck() .returnCode(returnCode) .sessionPresent(false).build(); this.channel.writeAndFlush(badProto).addListener(FIRE_EXCEPTION_ON_FAILURE); this.channel.close().addListener(CLOSE_ON_FAILURE); } public void processDisconnect() { // 1. 清理will消息 bindedSession.clearWill(); // 2. 下线 offline(); } private void clearSession() { deviceSubscriptionManager.deleteAllSubscription(getClientId()); deviceMessageManager.clearUnAckMessage(getClientId()); } private void reloadSubscriptions() { Set deviceSubscriptions = deviceSubscriptionManager.getAllSubscription(getClientId()); if (deviceSubscriptions != null) { deviceSubscriptions.forEach(item -> { deviceSubscriptionManager.onlySubscribe2Tree(item); }); } } private void offline() { this.deviceSessionManager.offline(getClientId()); ConnectManager.getInstance().removeClient(getClientId()); } public String getClientId() { return this.clientId; } public Channel getChannel() { return channel; } public Map> getProcessorTable() { return processorTable; } public BrokerConfig getBrokerConfig() { return brokerConfig; } public MqttSession getBindedSession() { return bindedSession; } public Authenticator getAuthenticator() { return authenticator; } public DeviceSessionManager getDeviceSessionManager() { return deviceSessionManager; } public DeviceSubscriptionManager getDeviceSubscriptionManager() { return deviceSubscriptionManager; } public DeviceMessageManager getDeviceMessageManager() { return deviceMessageManager; } public RetainMessageHandler getRetainMessageHandler() { return retainMessageHandler; } public ClusterEventManager getClusterEventManager() { return clusterEventManager; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTConnectionFactory.java ================================================ package org.jmqtt.mqtt; import io.netty.channel.Channel; import io.netty.handler.codec.mqtt.MqttMessageType; import org.jmqtt.bus.BusController; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.mqtt.protocol.impl.*; import org.jmqtt.mqtt.retain.RetainMessageHandler; import org.jmqtt.mqtt.retain.impl.RetainMessageHandlerImpl; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.helper.Pair; import org.jmqtt.support.helper.RejectHandler; import org.jmqtt.support.helper.ThreadFactoryImpl; import java.util.HashMap; import java.util.Map; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; /** * mqtt connection factory */ public class MQTTConnectionFactory { private BrokerConfig brokerConfig; /** mqtt protocol processor */ private ExecutorService connectExecutor; private ExecutorService pubExecutor; private ExecutorService subExecutor; private ExecutorService pingExecutor; private LinkedBlockingQueue connectQueue; private LinkedBlockingQueue pubQueue; private LinkedBlockingQueue subQueue; private LinkedBlockingQueue pingQueue; private Map> processorTable; private RetainMessageHandler retainMessageHandler; /** bus dependency */ private BusController busController; public MQTTConnectionFactory(BrokerConfig brokerConfig, BusController busController) { this.brokerConfig = brokerConfig; this.busController = busController; this.retainMessageHandler = new RetainMessageHandlerImpl(); this.connectQueue = new LinkedBlockingQueue<>(100000); this.pubQueue = new LinkedBlockingQueue<>(100000); this.subQueue = new LinkedBlockingQueue<>(100000); this.pingQueue = new LinkedBlockingQueue<>(10000); int coreThreadNum = Runtime.getRuntime().availableProcessors(); this.connectExecutor = new ThreadPoolExecutor(coreThreadNum * 2, coreThreadNum * 2, 60000, TimeUnit.MILLISECONDS, connectQueue, new ThreadFactoryImpl("ConnectThread"), new RejectHandler("connect", 100000)); this.pubExecutor = new ThreadPoolExecutor(coreThreadNum * 2, coreThreadNum * 2, 60000, TimeUnit.MILLISECONDS, pubQueue, new ThreadFactoryImpl("PubThread"), new RejectHandler("pub", 100000)); this.subExecutor = new ThreadPoolExecutor(coreThreadNum * 2, coreThreadNum * 2, 60000, TimeUnit.MILLISECONDS, subQueue, new ThreadFactoryImpl("SubThread"), new RejectHandler("sub", 100000)); this.pingExecutor = new ThreadPoolExecutor(coreThreadNum, coreThreadNum, 60000, TimeUnit.MILLISECONDS, pingQueue, new ThreadFactoryImpl("PingThread"), new RejectHandler("heartbeat", 100000)); RequestProcessor connectProcessor = new ConnectProcessor(); RequestProcessor disconnectProcessor = new DisconnectProcessor(); RequestProcessor pingProcessor = new PingProcessor(); RequestProcessor publishProcessor = new PublishProcessor(); RequestProcessor pubRelProcessor = new PubRelProcessor(); RequestProcessor subscribeProcessor = new SubscribeProcessor(); RequestProcessor unSubscribeProcessor = new UnSubscribeProcessor(); RequestProcessor pubRecProcessor = new PubRecProcessor(); RequestProcessor pubAckProcessor = new PubAckProcessor(); RequestProcessor pubCompProcessor = new PubCompProcessor(); processorTable = new HashMap<>(); registerProcessor(MqttMessageType.CONNECT.value(), connectProcessor, connectExecutor); registerProcessor(MqttMessageType.DISCONNECT.value(), disconnectProcessor, connectExecutor); registerProcessor(MqttMessageType.PINGREQ.value(), pingProcessor, pingExecutor); registerProcessor(MqttMessageType.PUBLISH.value(), publishProcessor, pubExecutor); registerProcessor(MqttMessageType.PUBACK.value(), pubAckProcessor, pubExecutor); registerProcessor(MqttMessageType.PUBREL.value(), pubRelProcessor, pubExecutor); registerProcessor(MqttMessageType.SUBSCRIBE.value(), subscribeProcessor, subExecutor); registerProcessor(MqttMessageType.UNSUBSCRIBE.value(), unSubscribeProcessor, subExecutor); registerProcessor(MqttMessageType.PUBREC.value(), pubRecProcessor, subExecutor); registerProcessor(MqttMessageType.PUBCOMP.value(), pubCompProcessor, subExecutor); } public void registerProcessor(int mqttMessageType, RequestProcessor requestProcessor, ExecutorService executorService){ processorTable.put(mqttMessageType,new Pair<>(requestProcessor,executorService)); } public MQTTConnection create(Channel channel){ MQTTConnection mqttConnection = new MQTTConnection(channel,processorTable,brokerConfig,busController,retainMessageHandler); return mqttConnection; } public void shutdown(){ this.connectExecutor.shutdown(); this.pubExecutor.shutdown(); this.subExecutor.shutdown(); this.pingExecutor.shutdown(); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/MQTTServer.java ================================================ package org.jmqtt.mqtt; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import org.jmqtt.bus.BusController; import org.jmqtt.mqtt.event.MqttEventListener; import org.jmqtt.mqtt.netty.MqttRemotingServer; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.config.NettyConfig; import org.jmqtt.support.helper.MixAll; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.Collection; /** * mqtt服务 */ public class MQTTServer { private static final Logger log = JmqttLogger.mqttLog; private BusController busController; private BrokerConfig brokerConfig; private NettyConfig nettyConfig; private MqttRemotingServer mqttRemotingServer; private MqttEventListener mqttEventListener; public MQTTServer(BusController busController, BrokerConfig brokerConfig, NettyConfig nettyConfig){ this.busController = busController; this.brokerConfig = brokerConfig; this.nettyConfig = nettyConfig; this.mqttRemotingServer = new MqttRemotingServer(brokerConfig,nettyConfig,busController); this.mqttEventListener = new MqttEventListener(busController.getDeviceMessageManager()); } public void start(){ this.busController.getClusterEventManager().registerEventListener(mqttEventListener); this.mqttRemotingServer.start(); LogUtil.info(log,"MQTT server start success."); } public void shutdown(){ this.mqttRemotingServer.shutdown(); Collection mqttConnections = ConnectManager.getInstance().getAllConnections(); if (!MixAll.isEmpty(mqttConnections)) { for (MQTTConnection mqttConnection : mqttConnections) { mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE); } } } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/ByteBuf2WebSocketEncoder.java ================================================ package org.jmqtt.mqtt.codec; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import java.util.List; public class ByteBuf2WebSocketEncoder extends MessageToMessageEncoder { @Override protected void encode(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf, List list) throws Exception { BinaryWebSocketFrame fram = new BinaryWebSocketFrame(); fram.content().writeBytes(byteBuf); list.add(fram); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/codec/WebSocket2ByteBufDecoder.java ================================================ package org.jmqtt.mqtt.codec; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.MessageToMessageDecoder; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import java.util.List; public class WebSocket2ByteBufDecoder extends MessageToMessageDecoder { @Override protected void decode(ChannelHandlerContext channelHandlerContext, BinaryWebSocketFrame binaryWebSocketFrame, List list) throws Exception { ByteBuf byteBuf = binaryWebSocketFrame.content(); byteBuf.retain(); list.add(byteBuf); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/event/MqttEventListener.java ================================================ package org.jmqtt.mqtt.event; import com.alibaba.fastjson.JSONObject; import com.google.common.eventbus.Subscribe; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import org.jmqtt.bus.DeviceMessageManager; import org.jmqtt.bus.enums.ClusterEventCodeEnum; import org.jmqtt.bus.enums.MessageAckEnum; import org.jmqtt.bus.event.GatewayListener; import org.jmqtt.bus.model.ClusterEvent; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.model.DeviceSubscription; import org.jmqtt.bus.subscription.model.Subscription; import org.jmqtt.mqtt.ConnectManager; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.utils.MqttMessageUtil; import org.jmqtt.mqtt.utils.MqttMsgHeader; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingHelper; import org.slf4j.Logger; import java.util.Map; public class MqttEventListener implements GatewayListener { private static final Logger log = JmqttLogger.mqttLog; private static final String currentIp = RemotingHelper.getLocalAddr(); private DeviceMessageManager deviceMessageManager; public MqttEventListener(DeviceMessageManager deviceMessageManager) { this.deviceMessageManager = deviceMessageManager; } /** * 分发到网关的消息,bus上检查设备是在线的,若不在线,说明是断开了 * mqtt网关处理两类事件: * 1. 集群消息:分发给设备 * 2. 清理session:设备连接到其它服务器了,需要清理当前服务器上该设备的连接session(clientId 保持唯一) * @param clusterEvent */ @Override @Subscribe public void consume(ClusterEvent clusterEvent) { ClusterEventCodeEnum eventCode = clusterEvent.getClusterEventCode(); switch (eventCode){ case MQTT_CLEAR_SESSION: clearCurrentNodeSession(clusterEvent); break; case DISPATCHER_CLIENT_MESSAGE: sendMessage2Client(clusterEvent); break; default: LogUtil.warn(log,"Mqtt gateway not support this event:{}",eventCode); break; } } private void sendMessage2Client(ClusterEvent clusterEvent) { DeviceMessage deviceMessage = JSONObject.parseObject(clusterEvent.getContent(),DeviceMessage.class); Subscription subscription = clusterEvent.getSubscription(); if (deviceMessage == null ||subscription == null) { LogUtil.error(log,"[EVENT] Send event error, message or subscription is null,message:{},subscription:{}",deviceMessage,subscription); return; } MQTTConnection mqttConnection = ConnectManager.getInstance().getClient(subscription.getClientId()); Integer subQos = subscription.getProperty(MqttMsgHeader.QOS); Integer msgQos = deviceMessage.getProperty(MqttMsgHeader.QOS); if (subQos == null) { subQos = 1; } if (msgQos == null) { msgQos = 1; } int qos = MqttMessageUtil.getMinQos(subQos,msgQos); // add to client inbox if (qos == 0) { this.deviceMessageManager.addClientInBoxMsg(subscription.getClientId(),deviceMessage.getId(), MessageAckEnum.ACK); } else { this.deviceMessageManager.addClientInBoxMsg(subscription.getClientId(),deviceMessage.getId(),MessageAckEnum.UN_ACK); } // offline can be optimize ,can carry to bus if (mqttConnection == null) { return; } mqttConnection.getBindedSession().sendMessage(deviceMessage); } private void clearCurrentNodeSession(ClusterEvent clusterEvent){ if (currentIp.equals(clusterEvent.getNodeIp())) { return; } String clientId = clusterEvent.getContent(); MQTTConnection mqttConnection = ConnectManager.getInstance().getClient(clientId); if (mqttConnection == null) { LogUtil.info(log,"MQTT connection is not exist.{}",clientId); return; } // 1. clear subscription tree Map subscriptionSet = mqttConnection.getBindedSession().getSubscriptions(); if (subscriptionSet != null && subscriptionSet.size() > 0) { for (String topic:subscriptionSet.keySet()) { mqttConnection.getDeviceSubscriptionManager().onlyUnUnSubscribeFromTree(clientId,topic); LogUtil.debug(log,"[CLUSTER SESSION] clear other node's session.un subscribe clientId:{},topic:{}",clientId,topic); } } // 2. close channel mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE); // 3. remove connection in cache ConnectManager.getInstance().removeClient(clientId); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/model/MqttTopic.java ================================================ package org.jmqtt.mqtt.model; public class MqttTopic { private String topicName; private int qos; public MqttTopic(String topicName, int qos) { this.topicName = topicName; this.qos = qos; } public String getTopicName() { return topicName; } public void setTopicName(String topicName) { this.topicName = topicName; } public int getQos() { return qos; } public void setQos(int qos) { this.qos = qos; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttNettyUtils.java ================================================ package org.jmqtt.mqtt.netty; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.util.Attribute; import io.netty.util.AttributeKey; import org.jmqtt.mqtt.MQTTConnection; import java.io.IOException; public class MqttNettyUtils { public static final String ATTR_USERNAME = "username"; private static final String ATTR_CLIENTID = "ClientID"; private static final String CLEAN_SESSION = "removeTemporaryQoS2"; private static final String KEEP_ALIVE = "keepAlive"; private static final String CONNECTION = "connection"; private static final AttributeKey ATTR_KEY_CONNECTION = AttributeKey.valueOf(CONNECTION); private static final AttributeKey ATTR_KEY_KEEPALIVE = AttributeKey.valueOf(KEEP_ALIVE); private static final AttributeKey ATTR_KEY_CLEANSESSION = AttributeKey.valueOf(CLEAN_SESSION); private static final AttributeKey ATTR_KEY_CLIENTID = AttributeKey.valueOf(ATTR_CLIENTID); private static final AttributeKey ATTR_KEY_USERNAME = AttributeKey.valueOf(ATTR_USERNAME); public static Object getAttribute(ChannelHandlerContext ctx, AttributeKey key) { Attribute attr = ctx.channel().attr(key); return attr.get(); } public static void keepAlive(Channel channel, int keepAlive) { channel.attr(ATTR_KEY_KEEPALIVE).set(keepAlive); } public static void cleanSession(Channel channel, boolean cleanSession) { channel.attr(ATTR_KEY_CLEANSESSION).set(cleanSession); } public static boolean cleanSession(Channel channel) { return (Boolean) channel.attr(ATTR_KEY_CLEANSESSION).get(); } public static void clientID(Channel channel, String clientID) { channel.attr(ATTR_KEY_CLIENTID).set(clientID); } public static String clientID(Channel channel) { return (String) channel.attr(ATTR_KEY_CLIENTID).get(); } public static void mqttConnection(Channel channel, MQTTConnection mqttConnection) { channel.attr(ATTR_KEY_CONNECTION).set(mqttConnection); } public static MQTTConnection mqttConnection(Channel channel) { return (MQTTConnection) channel.attr(ATTR_KEY_CONNECTION).get(); } public static void userName(Channel channel, String username) { channel.attr(ATTR_KEY_USERNAME).set(username); } public static String userName(Channel channel) { return (String) channel.attr(ATTR_KEY_USERNAME).get(); } public static MqttMessage validateMessage(Object message) throws IOException, ClassCastException { MqttMessage msg = (MqttMessage) message; if (msg.decoderResult() != null && msg.decoderResult().isFailure()) { throw new IOException("invalid massage", msg.decoderResult().cause()); } if (msg.fixedHeader() == null) { throw new IOException("Unknown packet, no fixedHeader present, no cause provided"); } return msg; } private MqttNettyUtils() { } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/MqttRemotingServer.java ================================================ package org.jmqtt.mqtt.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.PooledByteBufAllocator; import io.netty.channel.*; import io.netty.channel.epoll.EpollEventLoopGroup; import io.netty.channel.epoll.EpollServerSocketChannel; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.codec.http.HttpContentCompressor; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.mqtt.MqttDecoder; import io.netty.handler.codec.mqtt.MqttEncoder; import io.netty.handler.timeout.IdleStateHandler; import org.jmqtt.bus.BusController; import org.jmqtt.mqtt.MQTTConnectionFactory; import org.jmqtt.mqtt.codec.ByteBuf2WebSocketEncoder; import org.jmqtt.mqtt.codec.WebSocket2ByteBufDecoder; import org.jmqtt.support.config.BrokerConfig; import org.jmqtt.support.config.NettyConfig; import org.jmqtt.support.helper.MixAll; import org.jmqtt.support.helper.ThreadFactoryImpl; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingService; import org.slf4j.Logger; public class MqttRemotingServer implements RemotingService { int coreThreadNum = Runtime.getRuntime().availableProcessors(); private static final Logger log = JmqttLogger.remotingLog; private NettyConfig nettyConfig; private EventLoopGroup selectorGroup; private EventLoopGroup ioGroup; private Class clazz; private BrokerConfig brokerConfig; private MQTTConnectionFactory mqttConnectionFactory; public MqttRemotingServer(BrokerConfig brokerConfig, NettyConfig nettyConfig, BusController busController) { this.nettyConfig = nettyConfig; this.brokerConfig = brokerConfig; this.mqttConnectionFactory = new MQTTConnectionFactory(brokerConfig,busController); if (!nettyConfig.getUseEpoll()) { selectorGroup = new NioEventLoopGroup(coreThreadNum, new ThreadFactoryImpl("SelectorEventGroup")); ioGroup = new NioEventLoopGroup(coreThreadNum * 2, new ThreadFactoryImpl("IOEventGroup")); clazz = NioServerSocketChannel.class; } else { selectorGroup = new EpollEventLoopGroup(coreThreadNum, new ThreadFactoryImpl("SelectorEventGroup")); ioGroup = new EpollEventLoopGroup(coreThreadNum * 2, new ThreadFactoryImpl("IOEventGroup")); clazz = EpollServerSocketChannel.class; } } @Override public void start() { // start TCP server if (nettyConfig.getStartTcp()) { startTcpServer(false, nettyConfig.getTcpPort()); } if (nettyConfig.getStartSslTcp()) { startTcpServer(true, nettyConfig.getSslTcpPort()); } // start Websocket server if (nettyConfig.getStartWebsocket()) { startWebsocketServer(false, nettyConfig.getWebsocketPort()); } if (nettyConfig.getStartSslWebsocket()) { startWebsocketServer(true, nettyConfig.getSslWebsocketPort()); } } private void startWebsocketServer(boolean useSsl, Integer port) { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(selectorGroup, ioGroup) .channel(clazz) .option(ChannelOption.SO_BACKLOG, nettyConfig.getTcpBackLog()) .childOption(ChannelOption.TCP_NODELAY, nettyConfig.getTcpNoDelay()) .childOption(ChannelOption.SO_SNDBUF, nettyConfig.getTcpSndBuf()) .option(ChannelOption.SO_RCVBUF, nettyConfig.getTcpRcvBuf()) .option(ChannelOption.SO_REUSEADDR, nettyConfig.getTcpReuseAddr()) .childOption(ChannelOption.SO_KEEPALIVE, nettyConfig.getTcpKeepAlive()) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); if (useSsl) { pipeline.addLast("ssl", NettySslHandler.getSslHandler( socketChannel, nettyConfig.getUseClientCA(), nettyConfig.getSslKeyStoreType(), brokerConfig.getJmqttHome() + nettyConfig.getSslKeyFilePath(), nettyConfig.getSslManagerPwd(), nettyConfig.getSslStorePwd() )); } pipeline.addLast("idleStateHandler", new IdleStateHandler(0, 0, 60)) .addLast("httpCodec", new HttpServerCodec()) .addLast("aggregator", new HttpObjectAggregator(65535)) .addLast("compressor ", new HttpContentCompressor()) .addLast("webSocketHandler", new WebSocketServerProtocolHandler("/mqtt", MixAll.MQTT_VERSION_SUPPORT, false,65536)) .addLast("webSocket2ByteBufDecoder", new WebSocket2ByteBufDecoder()) .addLast("byteBuf2WebSocketEncoder", new ByteBuf2WebSocketEncoder()) .addLast("mqttDecoder", new MqttDecoder(nettyConfig.getMaxMsgSize())) .addLast("mqttEncoder", MqttEncoder.INSTANCE) .addLast("nettyMqttHandler", new NettyMqttHandler(mqttConnectionFactory)); } }); if (nettyConfig.getPooledByteBufAllocatorEnable()) { bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } try { ChannelFuture future = bootstrap.bind(port).sync(); LogUtil.info(log,"Start webSocket server {} success,port = {}", useSsl ? "with ssl" : "", port); } catch (InterruptedException ex) { LogUtil.error(log,"Start webSocket server {} failure.cause={}", useSsl ? "with ssl" : "", ex); } } private void startTcpServer(boolean useSsl, Integer port) { ServerBootstrap bootstrap = new ServerBootstrap(); bootstrap.group(selectorGroup, ioGroup) .channel(clazz) .option(ChannelOption.SO_BACKLOG, nettyConfig.getTcpBackLog()) .childOption(ChannelOption.TCP_NODELAY, nettyConfig.getTcpNoDelay()) .childOption(ChannelOption.SO_SNDBUF, nettyConfig.getTcpSndBuf()) .option(ChannelOption.SO_RCVBUF, nettyConfig.getTcpRcvBuf()) .option(ChannelOption.SO_REUSEADDR, nettyConfig.getTcpReuseAddr()) .childOption(ChannelOption.SO_KEEPALIVE, nettyConfig.getTcpKeepAlive()) .childHandler(new ChannelInitializer() { @Override protected void initChannel(SocketChannel socketChannel) throws Exception { ChannelPipeline pipeline = socketChannel.pipeline(); if (useSsl) { pipeline.addLast("ssl", NettySslHandler.getSslHandler( socketChannel, nettyConfig.getUseClientCA(), nettyConfig.getSslKeyStoreType(), brokerConfig.getJmqttHome() + nettyConfig.getSslKeyFilePath(), nettyConfig.getSslManagerPwd(), nettyConfig.getSslStorePwd() )); } pipeline.addLast("idleStateHandler", new IdleStateHandler(60, 0, 0)) .addLast("mqttEncoder", MqttEncoder.INSTANCE) .addLast("mqttDecoder", new MqttDecoder(nettyConfig.getMaxMsgSize())) .addLast("nettyMqttHandler", new NettyMqttHandler(mqttConnectionFactory)); } }); if (nettyConfig.getPooledByteBufAllocatorEnable()) { bootstrap.childOption(ChannelOption.ALLOCATOR, PooledByteBufAllocator.DEFAULT); } try { ChannelFuture future = bootstrap.bind(port).sync(); LogUtil.info(log,"Start tcp server {} success,port = {}", useSsl ? "with ssl" : "", port); } catch (InterruptedException ex) { LogUtil.error(log,"Start tcp server {} failure.cause={}", useSsl ? "with ssl" : "", ex); } } @Override public void shutdown() { this.mqttConnectionFactory.shutdown(); if (selectorGroup != null) { selectorGroup.shutdownGracefully(); } if (ioGroup != null) { ioGroup.shutdownGracefully(); } } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettyMqttHandler.java ================================================ package org.jmqtt.mqtt.netty; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.MQTTConnectionFactory; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import static io.netty.channel.ChannelFutureListener.CLOSE_ON_FAILURE; public class NettyMqttHandler extends ChannelDuplexHandler { private static final Logger log = JmqttLogger.remotingLog; private MQTTConnectionFactory connectionFactory; NettyMqttHandler(MQTTConnectionFactory connectionFactory) { this.connectionFactory = connectionFactory; } @Override public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception { MqttMessage msg = MqttNettyUtils.validateMessage(message); final MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); try { mqttConnection.processProtocol(ctx,msg); } catch (Throwable ex) { LogUtil.error(log,"Error processing protocol message: {}", msg.fixedHeader().messageType(), ex); ctx.channel().close().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { LogUtil.info(log,"Closed client channel due to exception in processing"); } }); } } @Override public void channelActive(ChannelHandlerContext ctx) { MQTTConnection connection = connectionFactory.create(ctx.channel()); MqttNettyUtils.mqttConnection(ctx.channel(), connection); } @Override public void channelInactive(ChannelHandlerContext ctx) { final MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.handleConnectionLost(); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { LogUtil.error(log,"Unexpected exception while processing MQTT message. Closing Netty channel. CId={}", MqttNettyUtils.clientID(ctx.channel()), cause); ctx.close().addListener(CLOSE_ON_FAILURE); } @Override public void channelWritabilityChanged(ChannelHandlerContext ctx) { ctx.fireChannelWritabilityChanged(); } @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) { //if (evt instanceof InflightResender.ResendNotAckedPublishes) { // final MQTTConnection mqttConnection = mqttConnection(ctx.channel()); // mqttConnection.resendNotAckedPublishes(); //} ctx.fireUserEventTriggered(evt); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/netty/NettySslHandler.java ================================================ package org.jmqtt.mqtt.netty; import io.netty.channel.ChannelHandler; import io.netty.channel.socket.SocketChannel; import io.netty.handler.ssl.*; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManagerFactory; import java.io.FileInputStream; import java.io.InputStream; import java.security.KeyStore; public class NettySslHandler { private static final Logger log = JmqttLogger.remotingLog; public static ChannelHandler getSslHandler(SocketChannel channel, boolean useClientCA, String sslKeyStoreType, String sslKeyFilePath, String sslManagerPwd, String sslStorePwd) { SslContext sslContext = createSSLContext(useClientCA, sslKeyStoreType, sslKeyFilePath, sslManagerPwd, sslStorePwd); SSLEngine sslEngine = sslContext.newEngine( channel.alloc(), channel.remoteAddress().getHostString(), channel.remoteAddress().getPort()); // server mode sslEngine.setUseClientMode(false); if (useClientCA) { sslEngine.setNeedClientAuth(true); } return new SslHandler(sslEngine); } private static SslContext createSSLContext(boolean useClientCA, String sslKeyStoreType, String sslKeyFilePath, String sslManagerPwd, String sslStorePwd) { try { InputStream ksInputStream = new FileInputStream(sslKeyFilePath); KeyStore ks = KeyStore.getInstance(sslKeyStoreType); ks.load(ksInputStream, sslStorePwd.toCharArray()); final KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(ks, sslManagerPwd.toCharArray()); SslContextBuilder contextBuilder = SslContextBuilder.forServer(kmf); // whether need client CA(two-way authentication) if (useClientCA) { contextBuilder.clientAuth(ClientAuth.REQUIRE); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(ks); contextBuilder.trustManager(tmf); } return contextBuilder.sslProvider(SslProvider.valueOf("JDK")).build(); } catch (Exception ex) { LogUtil.error(log,"Create ssl context failure.cause={}", ex); return null; } } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/RequestProcessor.java ================================================ package org.jmqtt.mqtt.protocol; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; public interface RequestProcessor { /** * handle mqtt message processor * ps:阅读mqtt协议代码从这里的实现开始 */ void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage); } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/ConnectProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttConnAckMessage; import io.netty.handler.codec.mqtt.MqttConnectMessage; import io.netty.handler.codec.mqtt.MqttConnectReturnCode; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.mqtt.utils.MqttMessageUtil; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingHelper; import org.slf4j.Logger; /** * mqtt 客户端连接逻辑处理 强制约束:jmqtt在未返回conAck之前,不接收其它任何mqtt协议报文(mqtt协议可以允许) TODO mqtt5 协议支持 */ public class ConnectProcessor implements RequestProcessor { private static final Logger log = JmqttLogger.mqttLog; @Override public void processRequest(ChannelHandlerContext ctx,MqttMessage mqttMessage) { MqttConnectMessage connectMessage = (MqttConnectMessage) mqttMessage; MqttConnectReturnCode returnCode = null; int mqttVersion = connectMessage.variableHeader().version(); String clientId = connectMessage.payload().clientIdentifier(); String userName = connectMessage.payload().userName(); byte[] password = connectMessage.payload().passwordInBytes(); boolean cleanSession = connectMessage.variableHeader().isCleanSession(); MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); boolean sessionPresent = false; try { if (!versionValid(mqttVersion)) { returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION; } else if (!mqttConnection.clientIdVerify(clientId)) { returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED; } else if (mqttConnection.onBlackList(RemotingHelper.getRemoteAddr(ctx.channel()), clientId)) { returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_NOT_AUTHORIZED; } else if (!mqttConnection.login(clientId, userName, password)) { returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD; } else { // 1. 设置心跳 int heartbeatSec = connectMessage.variableHeader().keepAliveTimeSeconds(); if (!mqttConnection.keepAlive(heartbeatSec)) { LogUtil.warn(log,"[CONNECT] -> set heartbeat failure,clientId:{},heartbeatSec:{}", clientId, heartbeatSec); throw new Exception("set heartbeat failure"); } // 2. 处理连接 sessionPresent = mqttConnection.createOrReopenSession(connectMessage); returnCode = MqttConnectReturnCode.CONNECTION_ACCEPTED; MqttNettyUtils.clientID(ctx.channel(), clientId); } if (returnCode != MqttConnectReturnCode.CONNECTION_ACCEPTED) { ctx.close(); LogUtil.warn(log,"[CONNECT] -> {} connect failure,returnCode={}", clientId, returnCode); } MqttConnAckMessage ackMessage = MqttMessageUtil.getConnectAckMessage(returnCode, sessionPresent); final boolean hasOldSession = sessionPresent; ctx.writeAndFlush(ackMessage).addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) throws Exception { LogUtil.info(log,"[CONNECT] -> {} connect to this mqtt server", clientId); if (future.isSuccess()) { // store session mqttConnection.storeSession(); // send unAckMessages if (!cleanSession && hasOldSession) { mqttConnection.reSendMessage2Client(); } } else { LogUtil.error(log,"[CONNECT] -> send connect ack error."); mqttConnection.abortConnection(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE); } } }); //LogUtil.warn(log,"Connect Cost:{}",(System.currentTimeMillis() - start)); } catch (Exception ex) { LogUtil.warn(log,"[CONNECT] -> Service Unavailable: cause={}", ex); returnCode = MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE; MqttConnAckMessage ackMessage = MqttMessageUtil.getConnectAckMessage(returnCode, sessionPresent); ctx.writeAndFlush(ackMessage); ctx.close(); } } private boolean versionValid(int mqttVersion) { if (mqttVersion == 3 || mqttVersion == 4) { return true; } return false; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/DisconnectProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; /** * 客户端主动发起断开连接:正常断连 */ public class DisconnectProcessor implements RequestProcessor { private static final Logger log = JmqttLogger.mqttLog; @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); String clientId = MqttNettyUtils.clientID(ctx.channel()); if (mqttConnection == null) { LogUtil.error(log,"[DISCONNECT] -> {} hasn't connect before",clientId ); return; } mqttConnection.processDisconnect(); ctx.close(); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PingProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.mqtt.utils.MqttMessageUtil; /** * 心跳 */ public class PingProcessor implements RequestProcessor { @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MqttMessage pingRespMessage = MqttMessageUtil.getPingRespMessage(); ctx.writeAndFlush(pingRespMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubAckProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.support.log.JmqttLogger; import org.slf4j.Logger; /** * 出栈消息接收到的客户端 pubAck报文:释放缓存的出栈消息 */ public class PubAckProcessor implements RequestProcessor { private static final Logger log = JmqttLogger.messageTraceLog; @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processPubAck(mqttMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubCompProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; /** * 出栈消息接收到的qos2消息的pubComp报文:清除缓存的出栈消息 */ public class PubCompProcessor implements RequestProcessor { @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processPubComp(mqttMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRecProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; /** * 出栈消息接收到qos2第一阶段的的pubRec报文: 丢弃消息,存储报文标志符 * 发送pubRel报文 */ public class PubRecProcessor implements RequestProcessor { @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processPubRec(mqttMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PubRelProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.jmqtt.support.remoting.RemotingHelper; import org.slf4j.Logger; /** * 客户端的pubRel报文:入栈消息qos2的第二阶段: * 1. 消息所有者转换为broker,开始分发消息 * 2. 返回pubCom报文 */ public class PubRelProcessor implements RequestProcessor { private static final Logger log = JmqttLogger.messageTraceLog; @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); if(mqttConnection != null){ mqttConnection.processPubRelMessage(mqttMessage); }else{ LogUtil.warn(log,"[PubRelMessage] -> the client:{} disconnect to this server.",mqttConnection.getClientId()); RemotingHelper.closeChannel(ctx.channel()); } } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/PublishProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.util.ReferenceCountUtil; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; /** * 客户端publish消息到jmqtt broker TODO mqtt5实现,流控处理 */ public class PublishProcessor implements RequestProcessor { private static final Logger log = JmqttLogger.mqttLog; @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { try { MqttPublishMessage publishMessage = (MqttPublishMessage) mqttMessage; MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processPublishMessage(publishMessage); } catch (Throwable tr) { LogUtil.warn(log, "[PubMessage] -> Solve mqtt pub message exception:{}", tr); } finally { ReferenceCountUtil.release(mqttMessage.payload()); } } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/SubscribeProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttSubscribeMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; /** * 订阅报文逻辑处理 * TODO mqtt5协议支持 */ public class SubscribeProcessor implements RequestProcessor { @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MqttSubscribeMessage subscribeMessage = (MqttSubscribeMessage) mqttMessage; MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processSubscribe(subscribeMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/protocol/impl/UnSubscribeProcessor.java ================================================ package org.jmqtt.mqtt.protocol.impl; import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.mqtt.MqttMessage; import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.netty.MqttNettyUtils; import org.jmqtt.mqtt.protocol.RequestProcessor; /** * 取消订阅 */ public class UnSubscribeProcessor implements RequestProcessor { @Override public void processRequest(ChannelHandlerContext ctx, MqttMessage mqttMessage) { MqttUnsubscribeMessage unsubscribeMessage = (MqttUnsubscribeMessage) mqttMessage; MQTTConnection mqttConnection = MqttNettyUtils.mqttConnection(ctx.channel()); mqttConnection.processUnSubscribe(unsubscribeMessage); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/RetainMessageHandler.java ================================================ package org.jmqtt.mqtt.retain; import org.jmqtt.bus.model.DeviceMessage; import java.util.List; public interface RetainMessageHandler { List getAllRetatinMessage(); void clearRetainMessage(String topic); void storeRetainMessage(DeviceMessage deviceMessage); } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/retain/impl/RetainMessageHandlerImpl.java ================================================ package org.jmqtt.mqtt.retain.impl; import org.apache.ibatis.session.SqlSession; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.store.DBCallback; import org.jmqtt.bus.store.DBUtils; import org.jmqtt.bus.store.daoobject.RetainMessageDO; import org.jmqtt.mqtt.retain.RetainMessageHandler; import org.jmqtt.support.helper.MixAll; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.ArrayList; import java.util.List; public class RetainMessageHandlerImpl implements RetainMessageHandler { private static final Logger log = JmqttLogger.mqttLog; @Override public List getAllRetatinMessage() { // If has too much retain message ,can optimize with paging query List retainMessageDOList = DBUtils.operate(new DBCallback() { @Override public List operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).getAllRetainMessage(); } }); if (retainMessageDOList == null) { return null; } List deviceMessageList = new ArrayList<>(); retainMessageDOList.forEach(item -> { DeviceMessage deviceMessage = new DeviceMessage(); deviceMessage.setId(item.getId()); deviceMessage.setProperties(MixAll.propToMap(item.getProperties())); deviceMessage.setContent(item.getContent()); deviceMessage.setFromClientId(item.getFromClientId()); deviceMessage.setStoredTime(item.getStoredTime()); deviceMessage.setTopic(item.getTopic()); deviceMessageList.add(deviceMessage); }); return deviceMessageList; } @Override public void clearRetainMessage(String topic) { DBUtils.operate(new DBCallback() { @Override public Integer operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).delRetainMessage(topic); } }); } @Override public void storeRetainMessage(DeviceMessage deviceMessage) { RetainMessageDO retainMessageDO = convert(deviceMessage); DBUtils.operate(new DBCallback() { @Override public Long operate(SqlSession sqlSession) { return DBUtils.getMapper(sqlSession,DBUtils.retainMessageMapperClass).storeRetainMessage(retainMessageDO); } }); if (retainMessageDO.getId() == null) { LogUtil.error(log,"[RETAIN MESSAGE] store retain message index error."); } } private RetainMessageDO convert(DeviceMessage deviceMessage) { RetainMessageDO retainMessageDO = new RetainMessageDO(); retainMessageDO.setTopic(deviceMessage.getTopic()); retainMessageDO.setFromClientId(deviceMessage.getFromClientId()); retainMessageDO.setContent(deviceMessage.getContent()); retainMessageDO.setStoredTime(deviceMessage.getStoredTime()); retainMessageDO.setProperties(MixAll.propToStr(deviceMessage.getProperties())); return retainMessageDO; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/session/MqttSession.java ================================================ package org.jmqtt.mqtt.session; import io.netty.handler.codec.mqtt.MqttPublishMessage; import io.netty.handler.codec.mqtt.MqttQoS; import org.jmqtt.bus.model.DeviceMessage; import org.jmqtt.bus.model.DeviceSubscription; import org.jmqtt.mqtt.MQTTConnection; import org.jmqtt.mqtt.utils.MqttMessageUtil; import org.jmqtt.mqtt.utils.MqttMsgHeader; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArraySet; import java.util.concurrent.atomic.AtomicInteger; /** * mqtt session */ public class MqttSession { private static final Logger log = JmqttLogger.mqttLog; /** * clientId uniqu in a cluseter */ private String clientId; private boolean cleanSession; private transient AtomicInteger lastPacketId = new AtomicInteger(1); private int mqttVersion; private DeviceMessage will; private final Map qos2Receiving = new ConcurrentHashMap<>(); private MQTTConnection mqttConnection; private final Map outboundFlowMessages = new ConcurrentHashMap<>(); private final Set qos2OutboundFlowPacketIds = new CopyOnWriteArraySet<>(); private String clientIp; private String serverIp; private Map subscriptions = new HashMap<>(); public void addSubscription(DeviceSubscription deviceSubscription) { this.subscriptions.put(deviceSubscription.getTopic(),deviceSubscription); } public void removeSubscription(String topic) { this.subscriptions.remove(topic); } public void receivePubRec(int packetId){ this.qos2OutboundFlowPacketIds.add(packetId); } public boolean releaseQos2SecFlow(int packetId){ return this.qos2OutboundFlowPacketIds.remove(packetId); } public DeviceMessage releaseOutboundFlowMessage(int packetId){ DeviceMessage deviceMessage = outboundFlowMessages.remove(packetId); if (deviceMessage == null) { LogUtil.error(log,"[PUBACK Or PUBREC] Release cache flow message,message is not success,packetId:{}",packetId); } return deviceMessage; } public void sendMessage(DeviceMessage deviceMessage) { Integer qos = deviceMessage.getProperty(MqttMsgHeader.QOS); MqttQoS mqttQoS = qos == null ? MqttQoS.AT_LEAST_ONCE : MqttQoS.valueOf(qos); switch (mqttQoS){ case AT_MOST_ONCE: sendQos0Message(deviceMessage); break; case AT_LEAST_ONCE: sendQos1Or2Message(deviceMessage); break; case EXACTLY_ONCE: sendQos1Or2Message(deviceMessage); break; default: LogUtil.error(log,"[Outbound publish] unsupport qos.",qos); break; } } private void sendQos0Message(DeviceMessage deviceMessage) { MqttPublishMessage mqttPublishMessage = MqttMessageUtil.getPubMessage(deviceMessage,0); mqttConnection.publishMessage(mqttPublishMessage); } private void sendQos1Or2Message(DeviceMessage deviceMessage) { int packetId = nextPacketId(); // cache outboundFlowMessages.put(packetId,deviceMessage); MqttPublishMessage mqttPublishMessage = MqttMessageUtil.getPubMessage(deviceMessage,packetId); mqttConnection.publishMessage(mqttPublishMessage); } public void receivedPublishQos2(int originPacketId,DeviceMessage deviceMessage) { this.qos2Receiving.put(originPacketId,deviceMessage); } public DeviceMessage receivedPubRelQos2(int packgetId) { return this.qos2Receiving.remove(packgetId); } public void clearWill(){ this.will = null; } public boolean hasWill(){ return will != null; } public int nextPacketId() { return lastPacketId.updateAndGet(v -> v == 65535 ? 1 : v + 1); } /** setter getter */ public String getClientId() { return clientId; } public void setClientId(String clientId) { this.clientId = clientId; } public boolean isCleanSession() { return cleanSession; } public void setCleanSession(boolean cleanSession) { this.cleanSession = cleanSession; } public int getMqttVersion() { return mqttVersion; } public void setMqttVersion(int mqttVersion) { this.mqttVersion = mqttVersion; } public DeviceMessage getWill() { return will; } public void setWill(DeviceMessage will) { this.will = will; } public MQTTConnection getMqttConnection() { return mqttConnection; } public void setMqttConnection(MQTTConnection mqttConnection) { this.mqttConnection = mqttConnection; } public String getClientIp() { return clientIp; } public void setClientIp(String clientIp) { this.clientIp = clientIp; } public String getServerIp() { return serverIp; } public void setServerIp(String serverIp) { this.serverIp = serverIp; } public Map getSubscriptions() { return subscriptions; } public void setSubscriptions(Map subscriptions) { this.subscriptions = subscriptions; } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMessageUtil.java ================================================ package org.jmqtt.mqtt.utils; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.*; import org.jmqtt.bus.model.DeviceMessage; import java.util.List; /** * transfer message from Message and MqttMessage */ public class MqttMessageUtil { public static byte[] readBytesFromByteBuf(ByteBuf byteBuf){ byte[] bytes = new byte[byteBuf.readableBytes()]; byteBuf.readBytes(bytes); return bytes; } public static MqttUnsubAckMessage getUnSubAckMessage(int messageId){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.UNSUBACK,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttUnsubAckMessage(fixedHeader,idVariableHeader); } public static int getMessageId(MqttMessage mqttMessage){ MqttMessageIdVariableHeader idVariableHeader = (MqttMessageIdVariableHeader) mqttMessage.variableHeader(); return idVariableHeader.messageId(); } public static int getMinQos(int qos1,int qos2){ if(qos1 < qos2){ return qos1; } return qos2; } public static MqttMessage getPubRelMessage(int messageId){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREL,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttMessage(fixedHeader,idVariableHeader); } public static MqttPublishMessage getPubMessage(DeviceMessage message,int packetId){ Integer qos = message.getProperty(MqttMsgHeader.QOS); qos = qos == null ? 1 : qos; Boolean dup = message.getProperty(MqttMsgHeader.DUP); dup = dup == null ? false : dup; MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBLISH,dup,MqttQoS.valueOf(qos),false,0); MqttPublishVariableHeader publishVariableHeader = new MqttPublishVariableHeader(message.getTopic(),packetId); ByteBuf heapBuf; if(message.getContent() == null){ heapBuf = Unpooled.EMPTY_BUFFER; }else{ heapBuf = Unpooled.wrappedBuffer(message.getContent()); } return new MqttPublishMessage(fixedHeader,publishVariableHeader,heapBuf); } public static MqttMessage getSubAckMessage(int messageId, List qos){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.SUBACK,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId); MqttSubAckPayload subAckPayload = new MqttSubAckPayload(qos); return new MqttSubAckMessage(fixedHeader,idVariableHeader,subAckPayload); } public static MqttMessage getPingRespMessage(){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PINGRESP,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessage mqttMessage = new MqttMessage(fixedHeader); return mqttMessage; } public static MqttMessage getPubComMessage(int messageId){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBCOMP,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId)); return mqttMessage; } public static MqttMessage getPubRecMessage(int messageId){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId)); return mqttMessage; } public static MqttMessage getPubRecMessage(int messageId,boolean isDup){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBREC,isDup,MqttQoS.AT_MOST_ONCE,false,0); MqttMessage mqttMessage = new MqttMessage(fixedHeader,MqttMessageIdVariableHeader.from(messageId)); return mqttMessage; } public static MqttPubAckMessage getPubAckMessage(int messageId){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.PUBACK,false,MqttQoS.AT_MOST_ONCE,false,0); MqttMessageIdVariableHeader idVariableHeader = MqttMessageIdVariableHeader.from(messageId); return new MqttPubAckMessage(fixedHeader,idVariableHeader); } public static MqttConnAckMessage getConnectAckMessage(MqttConnectReturnCode returnCode,boolean sessionPresent){ MqttFixedHeader fixedHeader = new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0); MqttConnAckVariableHeader variableHeade = new MqttConnAckVariableHeader(returnCode,sessionPresent); return new MqttConnAckMessage(fixedHeader,variableHeade); } } ================================================ FILE: jmqtt-mqtt/src/main/java/org/jmqtt/mqtt/utils/MqttMsgHeader.java ================================================ package org.jmqtt.mqtt.utils; import io.netty.handler.codec.mqtt.MqttPublishMessage; import org.jmqtt.bus.enums.MessageSourceEnum; import org.jmqtt.bus.model.DeviceMessage; import java.util.Date; import java.util.HashMap; import java.util.Map; public class MqttMsgHeader { public static final String QOS = "qos"; public static final String RETAIN = "retain"; public static final String DUP = "dup"; public static final String CLEAN_SESSION = "cleansession"; public static final DeviceMessage buildDeviceMessage(boolean retain,int qos,String topic,byte[] content){ DeviceMessage deviceMessage = new DeviceMessage(); deviceMessage.setId(null); deviceMessage.setContent(content); deviceMessage.setSource(MessageSourceEnum.DEVICE); deviceMessage.setStoredTime(new Date()); deviceMessage.setTopic(topic); Map properties = new HashMap<>(); properties.put(QOS,qos); properties.put(RETAIN,retain); deviceMessage.setProperties(properties); return deviceMessage; } public static final DeviceMessage buildDeviceMessage(MqttPublishMessage mqttPublishMessage){ return buildDeviceMessage(mqttPublishMessage.fixedHeader().isRetain(),mqttPublishMessage.fixedHeader().qosLevel().value(), mqttPublishMessage.variableHeader().topicName(),MqttMessageUtil.readBytesFromByteBuf(mqttPublishMessage.content())); } } ================================================ FILE: jmqtt-support/README.md ================================================ 通用支持,支持消息总线模块Bus及各协议网关处理 ================================================ FILE: jmqtt-support/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-support jmqtt-support io.netty netty-all ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/config/BrokerConfig.java ================================================ package org.jmqtt.support.config; import java.io.File; public class BrokerConfig { // 配置conf文件的所在位置,logback,properties文件等所在位置 private String jmqttHome = System.getenv("JMQTT_HOME") != null ? System.getenv("JMQTT_HOME") : System.getProperty("user.dir") + File.separator + "jmqtt-broker" + File.separator + "src" + File.separator + "main" + File.separator + "resources" + File.separator + "conf"; private String logLevel = "INFO"; private String version = "3.0.2"; private boolean anonymousEnable = true; private int pollThreadNum = Runtime.getRuntime().availableProcessors() * 2; // 采用拉消息方式时,一次最多拉的消息数目 private int maxPollEventNum = 10; private int pollWaitInterval = 10;//ms /* db相关配置 */ private String driver = "com.mysql.jdbc.Driver"; private String url = "jdbc:mysql://localhost:3306/jmqtt?characterEncoding=utf8&autoReconnect=true&failOverReadOnly=false" + "&maxReconnects=10&useSSL=false"; private String username = "root"; private String password = "CallmeZ2013"; public int getPollThreadNum() { return pollThreadNum; } public void setPollThreadNum(int pollThreadNum) { this.pollThreadNum = pollThreadNum; } public String getJmqttHome() { return jmqttHome; } public void setJmqttHome(String jmqttHome) { this.jmqttHome = jmqttHome; } public String getVersion() { return version; } public void setVersion(String version) { this.version = version; } public boolean getAnonymousEnable() { return anonymousEnable; } public void setAnonymousEnable(boolean anonymousEnable) { this.anonymousEnable = anonymousEnable; } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public boolean isAnonymousEnable() { return anonymousEnable; } public int getMaxPollEventNum() { return maxPollEventNum; } public void setMaxPollEventNum(int maxPollEventNum) { this.maxPollEventNum = maxPollEventNum; } public int getPollWaitInterval() { return pollWaitInterval; } public void setPollWaitInterval(int pollWaitInterval) { this.pollWaitInterval = pollWaitInterval; } public String getLogLevel() { return logLevel; } public void setLogLevel(String logLevel) { this.logLevel = logLevel; } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/config/NettyConfig.java ================================================ package org.jmqtt.support.config; public class NettyConfig { private int tcpBackLog = 1024; private boolean tcpNoDelay = false; private boolean tcpReuseAddr = true; private boolean tcpKeepAlive = false; private int tcpSndBuf = 65536; private int tcpRcvBuf = 65536; private boolean useEpoll = false; private boolean pooledByteBufAllocatorEnable = false; /** * tcp port default 1883 */ private boolean startTcp = true; private int tcpPort = 1883; /** * websocket port default 1884 */ private boolean startWebsocket = true; private int websocketPort = 1884; /** * http port default 1881 */ private boolean startHttp = true; private int httpPort = 1881; /** * tcp port with ssl default 8883 */ private boolean startSslTcp = true; private int sslTcpPort = 8883; /** * websocket port with ssl default 8884 */ private boolean startSslWebsocket = true; private int sslWebsocketPort = 8884; /** * SSL setting */ private boolean useClientCA = false; private String sslKeyStoreType = "PKCS12"; private String sslKeyFilePath = "/conf/server.pfx"; private String sslManagerPwd = "654321"; private String sslStorePwd = "654321"; /** * max mqtt message size */ private int maxMsgSize = 512 * 1024; public int getTcpBackLog() { return tcpBackLog; } public void setTcpBackLog(int tcpBackLog) { this.tcpBackLog = tcpBackLog; } public boolean getTcpNoDelay() { return tcpNoDelay; } public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; } public boolean getTcpReuseAddr() { return tcpReuseAddr; } public void setTcpReuseAddr(boolean tcpReuseAddr) { this.tcpReuseAddr = tcpReuseAddr; } public boolean getTcpKeepAlive() { return tcpKeepAlive; } public void setTcpKeepAlive(boolean tcpKeepAlive) { this.tcpKeepAlive = tcpKeepAlive; } public int getTcpSndBuf() { return tcpSndBuf; } public void setTcpSndBuf(int tcpSndBuf) { this.tcpSndBuf = tcpSndBuf; } public int getTcpRcvBuf() { return tcpRcvBuf; } public void setTcpRcvBuf(int tcpRcvBuf) { this.tcpRcvBuf = tcpRcvBuf; } public int getTcpPort() { return tcpPort; } public void setTcpPort(int tcpPort) { this.tcpPort = tcpPort; } public int getMaxMsgSize() { return maxMsgSize; } public void setMaxMsgSize(int maxMsgSize) { this.maxMsgSize = maxMsgSize; } public boolean getUseEpoll() { return useEpoll; } public void setUseEpoll(boolean useEpoll) { this.useEpoll = useEpoll; } public boolean getPooledByteBufAllocatorEnable() { return pooledByteBufAllocatorEnable; } public void setPooledByteBufAllocatorEnable(boolean pooledByteBufAllocatorEnable) { this.pooledByteBufAllocatorEnable = pooledByteBufAllocatorEnable; } public boolean getStartWebsocket() { return startWebsocket; } public void setStartWebsocket(boolean startWebsocket) { this.startWebsocket = startWebsocket; } public int getWebsocketPort() { return websocketPort; } public void setWebsocketPort(int websocketPort) { this.websocketPort = websocketPort; } public boolean getStartTcp() { return startTcp; } public void setStartTcp(boolean startTcp) { this.startTcp = startTcp; } public boolean getStartSslTcp() { return startSslTcp; } public void setStartSslTcp(boolean startSslTcp) { this.startSslTcp = startSslTcp; } public int getSslTcpPort() { return sslTcpPort; } public void setSslTcpPort(int sslTcpPort) { this.sslTcpPort = sslTcpPort; } public boolean getStartSslWebsocket() { return startSslWebsocket; } public void setStartSslWebsocket(boolean startSslWebsocket) { this.startSslWebsocket = startSslWebsocket; } public int getSslWebsocketPort() { return sslWebsocketPort; } public void setSslWebsocketPort(int sslWebsocketPort) { this.sslWebsocketPort = sslWebsocketPort; } public boolean getUseClientCA() { return useClientCA; } public void setUseClientCA(boolean useClientCA) { this.useClientCA = useClientCA; } public String getSslKeyStoreType() { return sslKeyStoreType; } public void setSslKeyStoreType(String sslKeyStoreType) { this.sslKeyStoreType = sslKeyStoreType; } public String getSslKeyFilePath() { return sslKeyFilePath; } public void setSslKeyFilePath(String sslKeyFilePath) { this.sslKeyFilePath = sslKeyFilePath; } public String getSslManagerPwd() { return sslManagerPwd; } public void setSslManagerPwd(String sslManagerPwd) { this.sslManagerPwd = sslManagerPwd; } public String getSslStorePwd() { return sslStorePwd; } public void setSslStorePwd(String sslStorePwd) { this.sslStorePwd = sslStorePwd; } public boolean getStartHttp() { return startHttp; } public void setStartHttp(boolean startHttp) { this.startHttp = startHttp; } public int getHttpPort() { return httpPort; } public void setHttpPort(int httpPort) { this.httpPort = httpPort; } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingConnectException.java ================================================ package org.jmqtt.support.exception; import java.io.Serializable; public class RemotingConnectException extends RemotingException implements Serializable { private static final long serialVersionUID = -412312312370505110L; public RemotingConnectException(String addr) { this(addr,null); } public RemotingConnectException(String addr, Throwable throwable) { super("connect to <" + addr + "> failed", throwable); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingException.java ================================================ package org.jmqtt.support.exception; import java.io.Serializable; public class RemotingException extends Exception implements Serializable { private static final long serialVersionUID = -46545454570505110L; public RemotingException(String message){ super(message); } public RemotingException(String message,Throwable throwable){ super(message,throwable); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingSendRequestException.java ================================================ package org.jmqtt.support.exception; import java.io.Serializable; public class RemotingSendRequestException extends RemotingException implements Serializable { public RemotingSendRequestException(String message) { this(message,null); } public RemotingSendRequestException(String message, Throwable throwable) { super(message, throwable); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTimeoutException.java ================================================ package org.jmqtt.support.exception; import java.io.Serializable; public class RemotingTimeoutException extends RemotingException implements Serializable { private static final long serialVersionUID = -56656565470505110L; public RemotingTimeoutException(long timeoutMillis) { this(timeoutMillis,null); } public RemotingTimeoutException(long timeoutMillis, Throwable throwable) { super("Send timeout time: <" + timeoutMillis + ">", throwable); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/exception/RemotingTooMuchRequestException.java ================================================ package org.jmqtt.support.exception; import java.io.Serializable; public class RemotingTooMuchRequestException extends RemotingException implements Serializable { private static final long serialVersionUID = -865546545670505110L; public RemotingTooMuchRequestException(String message) { super(message); } public RemotingTooMuchRequestException(String message, Throwable throwable) { super(message, throwable); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/MixAll.java ================================================ package org.jmqtt.support.helper; import com.alibaba.fastjson.JSONObject; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.net.InetAddress; import java.net.UnknownHostException; import java.text.SimpleDateFormat; import java.util.Collection; import java.util.Map; import java.util.Properties; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import java.util.zip.InflaterInputStream; public class MixAll { public static String MQTT_VERSION_SUPPORT = "mqtt,mqtt3.1,mqttv3.1.1"; public static boolean createIfNotExistsDir(File file) { return file != null && (file.exists() ? file.isDirectory() : file.mkdirs()); } public static String dateFormater(long time) { SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); return dateFormat.format(time); } public static T pluginInit(String classFullName){ try { Class clazz = Class.forName(classFullName); return (T) clazz.newInstance(); } catch (Exception e) { System.err.println("Load class and init error," + e); System.exit(-1); } return null; } public static Map propToMap(String propStr) { if (propStr == null) { return null; } return JSONObject.parseObject(propStr,Map.class); } public static String propToStr(Map properties) { if (properties == null) { return null; } return JSONObject.toJSONString(properties); } public static boolean isEmpty(Collection collection) { if (collection == null || collection.size() == 0) { return true; } return false; } public static String getLocalIp(){ try { return InetAddress.getLocalHost().getHostAddress(); } catch (UnknownHostException e) { e.printStackTrace(); } return "UN_KNOW"; } public static byte[] uncompress(final byte[] src) throws IOException { byte[] result = src; byte[] uncompressData = new byte[src.length]; ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(src); InflaterInputStream inflaterInputStream = new InflaterInputStream(byteArrayInputStream); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); try { while (true) { int len = inflaterInputStream.read(uncompressData, 0, uncompressData.length); if (len <= 0) { break; } byteArrayOutputStream.write(uncompressData, 0, len); } byteArrayOutputStream.flush(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { throw e; } finally { try { byteArrayInputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { inflaterInputStream.close(); } catch (IOException e) { e.printStackTrace(); } try { byteArrayOutputStream.close(); } catch (IOException e) { e.printStackTrace(); } } return result; } public static byte[] compress(final byte[] src, final int level) throws IOException { byte[] result = src; ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(src.length); Deflater deflater = new Deflater(level); DeflaterOutputStream deflaterOutputStream = new DeflaterOutputStream(byteArrayOutputStream, deflater); try { deflaterOutputStream.write(src); deflaterOutputStream.flush(); deflaterOutputStream.close(); result = byteArrayOutputStream.toByteArray(); } catch (IOException e) { deflater.end(); throw e; } finally { try { byteArrayOutputStream.close(); } catch (IOException ignored) { } deflater.end(); } return result; } public static void printProperties(Logger log, Object obj) { Class clazz = obj.getClass(); Field[] fields = clazz.getDeclaredFields(); if (fields != null) { for (Field field : fields) { try { field.setAccessible(true); String key = field.getName(); Object value = field.get(obj); LogUtil.info(log,"{} = {}", key, value); } catch (IllegalAccessException e) { e.printStackTrace(); } } } } /** * transfer properties 2 pojo */ public static void properties2POJO(Properties properties, Object obj) { Method[] methods = obj.getClass().getMethods(); for (Method method : methods) { String methodName = method.getName(); if (methodName.startsWith("set")) { try { String tmp = methodName.substring(4); String firstChar = methodName.substring(3, 4); String key = firstChar.toLowerCase() + tmp; String value = properties.getProperty(key); if (value != null) { Class[] types = method.getParameterTypes(); if (types != null && types.length > 0) { String type = types[0].getSimpleName(); Object arg = null; if (type.equals("int") || type.equals("Integer")) { arg = Integer.parseInt(value); } else if (type.equals("float") || type.equals("Float")) { arg = Float.parseFloat(value); } else if (type.equals("double") || type.equals("Double")) { arg = Double.parseDouble(value); } else if (type.equals("long") || type.equals("Long")) { arg = Long.parseLong(value); } else if (type.equals("boolean") || type.equals("Boolean")) { arg = Boolean.parseBoolean(value); } else if (type.equals("String")) { arg = value; } else { continue; } method.invoke(obj, arg); } } } catch (Exception e) { e.printStackTrace(); } } } } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/Pair.java ================================================ package org.jmqtt.support.helper; public class Pair { private T object1; private K object2; public Pair(T object1, K object2) { this.object1 = object1; this.object2 = object2; } public T getObject1() { return object1; } public void setObject1(T object1) { this.object1 = object1; } public K getObject2() { return object2; } public void setObject2(K object2) { this.object2 = object2; } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/RejectHandler.java ================================================ package org.jmqtt.support.helper; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.RejectedExecutionHandler; import java.util.concurrent.ThreadPoolExecutor; public class RejectHandler implements RejectedExecutionHandler { private String task; private int maxBlockQueueSize; public RejectHandler(String task,int maxBlockQueueSize){ this.task = task; this.maxBlockQueueSize = maxBlockQueueSize; }; @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { throw new RejectedExecutionException("Task:" + task + ",maxBlockQueueSize:" + maxBlockQueueSize + ",Thread:" + r.toString()); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/helper/ThreadFactoryImpl.java ================================================ package org.jmqtt.support.helper; import java.util.concurrent.ThreadFactory; import java.util.concurrent.atomic.AtomicInteger; public class ThreadFactoryImpl implements ThreadFactory { private AtomicInteger counter = new AtomicInteger(0); private String threadName; public ThreadFactoryImpl(String threadName){ this.threadName = threadName; } @Override public Thread newThread(Runnable r) { return new Thread(r,threadName + "_" + this.counter.incrementAndGet()); } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/log/JmqttLogger.java ================================================ package org.jmqtt.support.log; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public interface JmqttLogger { Logger brokerLog = LoggerFactory.getLogger("brokerLog"); Logger mqttLog = LoggerFactory.getLogger("mqttLog"); Logger busLog = LoggerFactory.getLogger("busLog"); Logger messageTraceLog = LoggerFactory.getLogger("messageTraceLog"); Logger remotingLog = LoggerFactory.getLogger("remotingLog"); Logger storeLog = LoggerFactory.getLogger("storeLog"); Logger otherLog = LoggerFactory.getLogger("otherLog"); Logger monitorLog = LoggerFactory.getLogger("monitorLog"); } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/log/LogUtil.java ================================================ package org.jmqtt.support.log; import org.slf4j.Logger; public class LogUtil { public static void debug(Logger log,String desc,Object... param){ if (log != null) { if (log.isDebugEnabled()) { log.debug(desc,param); } } } public static void info(Logger log,String desc,Object... param){ if (log != null) { if (log.isInfoEnabled()) { log.info(desc,param); } } } public static void warn(Logger log,String desc,Object... param){ if (log != null) { if (log.isWarnEnabled()) { log.warn(desc,param); } } } public static void error(Logger log,String desc,Object... param){ if (log != null) { if (log.isErrorEnabled()) { log.error(desc,param); } } } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingHelper.java ================================================ package org.jmqtt.support.remoting; import io.netty.channel.Channel; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import org.jmqtt.support.log.JmqttLogger; import org.jmqtt.support.log.LogUtil; import org.slf4j.Logger; import java.net.*; import java.util.ArrayList; import java.util.Enumeration; public class RemotingHelper { private static final Logger log = JmqttLogger.remotingLog; public static void closeChannel(Channel channel){ String remoteAddr = getRemoteAddr(channel); channel.close().addListener(new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture channelFuture) throws Exception { LogUtil.info(log,"[closeChannel] -> close the connection,addr={},result={}",remoteAddr,channelFuture.isSuccess()); } }); } public static SocketAddress string2SocketAddress(final String addr){ String[] s = addr.split(":"); InetSocketAddress isa = new InetSocketAddress(s[0],Integer.parseInt(s[1])); return isa; } public static String getLocalAddr(){ try { Enumeration enumeration = NetworkInterface.getNetworkInterfaces(); ArrayList ipv4Result = new ArrayList(); ArrayList ipv6Result = new ArrayList(); while (enumeration.hasMoreElements()) { final NetworkInterface networkInterface = enumeration.nextElement(); final Enumeration en = networkInterface.getInetAddresses(); while (en.hasMoreElements()) { final InetAddress address = en.nextElement(); if (!address.isLoopbackAddress()) { if (address instanceof Inet6Address) { ipv6Result.add(normalizeHostAddress(address)); } else { ipv4Result.add(normalizeHostAddress(address)); } } } } // prefer ipv4 if (!ipv4Result.isEmpty()) { for (String ip : ipv4Result) { if (ip.startsWith("127.0") || ip.startsWith("192.168")) { continue; } return ip; } return ipv4Result.get(ipv4Result.size() - 1); } else if (!ipv6Result.isEmpty()) { return ipv6Result.get(0); } //If failed to find,fall back to localhost final InetAddress localHost = InetAddress.getLocalHost(); return normalizeHostAddress(localHost); } catch (Exception e) { LogUtil.error(log,"failed to get local addr",e); } return null; } private static String normalizeHostAddress(final InetAddress localHost) { if (localHost instanceof Inet6Address) { return "[" + localHost.getHostAddress() + "]"; } else { return localHost.getHostAddress(); } } public static String getRemoteAddr(Channel channel){ if (null == channel) { return ""; } SocketAddress remote = channel.remoteAddress(); final String addr = remote != null ? remote.toString() : ""; if (addr.length() > 0) { int index = addr.lastIndexOf("/"); if (index >= 0) { return addr.substring(index + 1); } return addr; } return ""; } } ================================================ FILE: jmqtt-support/src/main/java/org/jmqtt/support/remoting/RemotingService.java ================================================ package org.jmqtt.support.remoting; public interface RemotingService { /** * remoting start */ void start(); /** * remoting shutdown */ void shutdown(); } ================================================ FILE: jmqtt-tcp/README.md ================================================ 自定义tcp协议网关 待实现 ================================================ FILE: jmqtt-tcp/pom.xml ================================================ jmqtt org.jmqtt 3.0.0 4.0.0 jmqtt-tcp jmqtt-tcp ================================================ FILE: jmqtt-tcp/src/main/java/org/jmqtt/App.java ================================================ package org.jmqtt; /** * Hello world! * */ public class App { public static void main( String[] args ) { System.out.println( "Hello World!" ); } } ================================================ FILE: pom.xml ================================================ 4.0.0 org.springframework.boot spring-boot-starter-parent 2.4.2 org.jmqtt jmqtt pom 3.0.0 jmqtt-broker jmqtt-example jmqtt-admin jmqtt-acceptance jmqtt-mqtt jmqtt-tcp jmqtt-support jmqtt-bus jmqtt-doc jmqtt https://github.com/Cicizz/jmqtt The Apache Software License, Version 2.0 http://www.apache.org/licenses/LICENSE-2.0.txt repo Cicizz UTF-8 1.8 1.8 4.1.54.Final 1.2.75 1.7.30 1.4 3.7 5.7.1 4.0.3 2.9.46 3.5.6 1.2.4 8.0.17 2.17.1 31.0.1-jre 2.6.0 com.google.guava guava ${guava.version} org.jmqtt jmqtt-broker ${project.version} org.jmqtt jmqtt-bus ${project.version} org.jmqtt jmqtt-mqtt ${project.version} org.jmqtt jmqtt-support ${project.version} org.jmqtt jmqtt-tcp ${project.version} org.jmqtt jmqtt-admin ${project.version} io.netty netty-all ${netty.version} org.mybatis mybatis ${mybatis.version} com.alibaba druid ${druid.version} mysql mysql-connector-java ${jdbc.version} org.apache.logging.log4j log4j-core ${log4j2.version} org.apache.logging.log4j log4j-api ${log4j2.version} top.hserver HServer ${HServer.version} io.netty netty-all org.assertj assertj-core ${assertj.version} test org.junit.jupiter junit-jupiter-api ${junit.version} test com.alibaba fastjson ${fastjson.version} org.slf4j slf4j-api ${slf4j.version} org.apache.logging.log4j log4j-slf4j-impl ${log4j2.version} org.apache.logging.log4j log4j-core ${log4j2.version} org.apache.logging.log4j log4j-api ${log4j2.version} commons-cli commons-cli ${commons-cli.version} org.apache.commons commons-lang3 ${commons-lang3.version} alibaba true alimaven aliyun maven http://maven.aliyun.com/nexus/content/groups/public/ default false spring-milestones Spring Milestones https://repo.spring.io/libs-milestone false