├── .gitignore ├── LICENSE ├── README.md ├── README_EN.md ├── mqtt-auth ├── pom.xml └── src │ └── main │ ├── java │ └── cn │ │ └── wizzer │ │ └── iot │ │ └── mqtt │ │ └── server │ │ └── auth │ │ ├── service │ │ └── AuthService.java │ │ └── util │ │ ├── PwdUtil.java │ │ └── RsaKeyUtil.java │ └── resources │ └── keystore │ └── auth-private.key ├── mqtt-broker ├── pom.xml └── src │ └── main │ ├── java │ └── cn │ │ └── wizzer │ │ └── iot │ │ └── mqtt │ │ └── server │ │ └── broker │ │ ├── MainLauncher.java │ │ ├── cluster │ │ └── RedisCluster.java │ │ ├── codec │ │ └── MqttWebSocketCodec.java │ │ ├── config │ │ └── BrokerProperties.java │ │ ├── handler │ │ └── BrokerHandler.java │ │ ├── internal │ │ ├── InternalCommunication.java │ │ ├── InternalMessage.java │ │ └── InternalSendServer.java │ │ ├── protocol │ │ ├── Connect.java │ │ ├── DisConnect.java │ │ ├── PingReq.java │ │ ├── ProtocolProcess.java │ │ ├── PubAck.java │ │ ├── PubComp.java │ │ ├── PubRec.java │ │ ├── PubRel.java │ │ ├── Publish.java │ │ ├── Subscribe.java │ │ └── UnSubscribe.java │ │ ├── server │ │ └── BrokerServer.java │ │ ├── service │ │ └── KafkaService.java │ │ └── webapi │ │ └── WebApiController.java │ └── resources │ ├── META-INF │ └── nutz │ │ └── org.nutz.boot.config.ConfigureLoader │ ├── application.yaml │ ├── banner.txt │ ├── keystore │ └── server.pfx │ └── logback.xml ├── mqtt-client ├── pom.xml └── src │ └── main │ ├── java │ └── cn │ │ └── wizzer │ │ └── iot │ │ ├── ClientMainLauncher.java │ │ ├── model │ │ └── IotDev.java │ │ └── mqtt │ │ ├── MqttRecieveCallback.java │ │ ├── MqttReciever.java │ │ ├── MqttSender.java │ │ └── MqttSenderCallback.java │ └── resources │ ├── application.properties │ ├── banner.txt │ └── logback.xml ├── mqtt-common ├── pom.xml └── src │ └── main │ └── java │ └── cn │ └── wizzer │ └── iot │ └── mqtt │ └── server │ └── common │ ├── auth │ └── IAuthService.java │ ├── message │ ├── DupPubRelMessageStore.java │ ├── DupPublishMessageStore.java │ ├── IDupPubRelMessageStoreService.java │ ├── IDupPublishMessageStoreService.java │ ├── IMessageIdService.java │ ├── IRetainMessageStoreService.java │ └── RetainMessageStore.java │ ├── session │ ├── ISessionStoreService.java │ └── SessionStore.java │ └── subscribe │ ├── ISubscribeStoreService.java │ └── SubscribeStore.java ├── mqtt-store ├── pom.xml └── src │ └── main │ ├── java │ └── cn │ │ └── wizzer │ │ └── iot │ │ └── mqtt │ │ └── server │ │ └── store │ │ ├── cache │ │ ├── DupPubRelMessageCache.java │ │ ├── DupPublishMessageCache.java │ │ ├── RetainMessageCache.java │ │ ├── SubscribeNotWildcardCache.java │ │ └── SubscribeWildcardCache.java │ │ ├── kafka │ │ └── SimplePartitioner.java │ │ ├── message │ │ ├── DupPubRelMessageStoreService.java │ │ ├── DupPublishMessageStoreService.java │ │ ├── MessageIdService.java │ │ └── RetainMessageStoreService.java │ │ ├── session │ │ └── SessionStoreService.java │ │ ├── starter │ │ └── StoreStarter.java │ │ ├── subscribe │ │ └── SubscribeStoreService.java │ │ └── util │ │ └── StoreUtil.java │ └── resources │ └── META-INF │ └── nutz │ └── org.nutz.boot.starter.NbStarter ├── mqtt-zoo ├── README.md ├── keystore │ ├── server.cer │ ├── server.crt │ ├── server.jks │ ├── server.key │ └── server.pfx ├── linux_sysctl.txt ├── mqtt-test-kafka │ ├── pom.xml │ └── src │ │ └── main │ │ ├── java │ │ └── cn │ │ │ └── wizzer │ │ │ └── iot │ │ │ └── mqtt │ │ │ └── server │ │ │ └── test │ │ │ ├── InternalMessage.java │ │ │ └── KafkaLauncher.java │ │ └── resources │ │ ├── META-INF │ │ └── nutz │ │ │ └── org.nutz.boot.config.ConfigureLoader │ │ ├── application.yaml │ │ ├── banner.txt │ │ └── logback.xml ├── mqtt-test-websocket │ └── src │ │ └── mqtt.html └── test.png └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | .project 4 | .vscode/ 5 | .settings/ 6 | .idea/ 7 | .git/ 8 | .DS_Store 9 | 10 | # Log file 11 | *.log 12 | 13 | # BlueJ files 14 | *.ctxt 15 | 16 | # Mobile Tools for Java (J2ME) 17 | .mtj.tmp/ 18 | 19 | # Package Files # 20 | *.jar 21 | *.war 22 | *.nar 23 | *.ear 24 | *.zip 25 | *.tar.gz 26 | *.rar 27 | 28 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 29 | hs_err_pid* 30 | 31 | npm-debug.log* 32 | yarn-debug.log* 33 | yarn-error.log* 34 | 35 | # Runtime data 36 | pids 37 | *.pid 38 | *.seed 39 | *.pid.lock 40 | 41 | # Directory for instrumented libs generated by jscoverage/JSCover 42 | lib-cov 43 | 44 | # Coverage directory used by tools like istanbul 45 | coverage 46 | 47 | # nyc test coverage 48 | .nyc_output 49 | 50 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 51 | .grunt 52 | 53 | # Bower dependency directory (https://bower.io/) 54 | bower_components 55 | 56 | # node-waf configuration 57 | .lock-wscript 58 | 59 | # Compiled binary addons (https://nodejs.org/api/addons.html) 60 | build/Release 61 | 62 | # Dependency directories 63 | node_modules/ 64 | jspm_packages/ 65 | 66 | # TypeScript v1 declaration files 67 | typings/ 68 | 69 | # Optional npm cache directory 70 | .npm 71 | 72 | # Optional eslint cache 73 | .eslintcache 74 | 75 | # Optional stylelint cache 76 | .stylelintcache 77 | 78 | # Optional REPL history 79 | .node_repl_history 80 | 81 | # Output of 'npm pack' 82 | *.tgz 83 | 84 | # Yarn Integrity file 85 | .yarn-integrity 86 | 87 | # dotenv environment variables file 88 | #.env 89 | 90 | # parcel-bundler cache (https://parceljs.org/) 91 | .cache 92 | 93 | # next.js build output 94 | .next 95 | 96 | # nuxt.js build output 97 | .nuxt 98 | 99 | # Nuxt generate 100 | dist 101 | 102 | # vuepress build output 103 | .vuepress/dist 104 | 105 | # Serverless directories 106 | .serverless 107 | 108 | # IDE 109 | .idea 110 | 111 | # Service worker 112 | sw.* -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

MqttWk - by netty

4 | 5 | [![GitHub release](https://img.shields.io/github/release/Wizzercn/MqttWk.svg)](https://github.com/Wizzercn/MqttWk/releases) 6 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 7 | [![PowerByNutz](https://img.shields.io/badge/PowerBy-NutzBoot-green.svg)](https://github.com/nutzam/nutzboot) 8 |
9 | 10 | 中文 | [English](README_EN.md) 11 | 12 | > 本项目开源免费,欢迎交流学习、贡献代码。 13 | 14 | #### MqttWk 15 | * QQ群号:225991747 16 | 17 | # 开发指南 18 | 19 | #### 技术体系 20 | 21 | 1. 使用 netty 实现通信及协议解析 22 | 2. 使用 nutzboot 提供依赖注入及属性配置 23 | 3. 使用 redis 实现消息缓存,集群 24 | 4. 使用 kafka 实现消息转发(可选) 25 | 26 | #### 项目结构 27 | ``` 28 | MqttWk 29 | ├── mqtt-auth -- MQTT服务连接时用户名和密码认证 30 | ├── mqtt-broker -- MQTT服务器功能的核心实现 31 | ├── mqtt-common -- 公共类及其他模块使用的服务接口及对象 32 | ├── mqtt-store -- MQTT服务器会话信息(redis缓存及kafka加载) 33 | ├── mqtt-client -- MQTT客户端示例代码(配置文件修改数据库连接启动之) 34 | ├── mqtt-zoo -- 教程文档或文件 35 | ├── mqtt-test-kafka -- kafka消费者接收消息 36 | ├── mqtt-test-websocket -- websocket通信测试示例 37 | ``` 38 | 39 | #### 功能说明 40 | 1. 参考MQTT3.1.1规范实现 41 | 2. 完整的QoS服务质量等级实现 42 | 3. 遗嘱消息, 保留消息及消息分发重试 43 | 4. 心跳机制 44 | 5. MQTT连接认证(可选择是否开启) 45 | 6. SSL方式连接(可选择是否开启) 46 | 7. 主题过滤(支持单主题订阅如 `test_topic` `/mqtt/test` --不能以/结尾, 通配符订阅 `#` `/mqtt/#` --以#结尾) 47 | 8. Websocket支持(可选择是否开启) 48 | 9. 集群功能(可选择是否开启) 49 | 10. Kafka消息转发功能(可选择是否开启) 50 | 11. 启动后查看统计数据 `http://127.0.0.1:8922/open/api/mqttwk/info` 51 | 52 | #### 快速开始 53 | - JDK1.8 54 | - 项目根目录执行 `mvn install` 55 | - mqtt-broker 下执行 `mvn clean package nutzboot:shade` 进行打包 56 | - 运行并加载jar内部yaml配置文件 `java -jar mqtt-broker-xxx.jar -Dnutz.profiles.active=prod` [此时加载application-prod.yaml配置文件] 57 | - 部署并加载指定文件夹下yaml配置文件 `nohup java -Dnutz.boot.configure.yaml.dir=/data -jar mqtt-broker-xxx.jar >/dev/null 2>&1 & ` 58 | - 打开mqtt-spy客户端, 填写相应配置[下载](https://github.com/eclipse/paho.mqtt-spy/wiki/Downloads) 59 | - 连接端口: 8885, websocket 端口: 9995 websocket 60 | - 连接使用的用户名: demo 61 | - 连接使用的密码: 8F3B8DE2FDC8BD3D792BE77EAC412010971765E5BDD6C499ADCEE840CE441BDEF17E30684BD95CA708F55022222CC6161D0D23C2DFCB12F8AC998F59E7213393 62 | - 连接使用的证书在 `mqtt-zoo`\keystore\server.cer 63 | 64 | #### 集群使用 65 | - 多机环境集群: 66 | 67 | ```yaml 68 | mqttwk: 69 | broker: 70 | cluster-on: true 71 | kafka: 72 | # 是否启用kafka消息转发 73 | broker-enabled: false 74 | bootstrap: 75 | servers: 192.168.1.101:9092,192.168.1.102:9093 76 | redis: 77 | mode: cluster 78 | nodes: 192.168.1.103:16379,192.168.1.104:26379,192.168.1.103:36379 79 | ``` 80 | - 单机环境集群: 81 | 82 | ```yaml 83 | mqttwk: 84 | broker: 85 | cluster-on: true 86 | kafka: 87 | # 是否启用kafka消息转发 88 | broker-enabled: false 89 | bootstrap: 90 | servers: 192.168.1.101:9092,192.168.1.102:9093 91 | redis: 92 | mode: normal 93 | host: 127.0.0.1 94 | port: 6379 95 | ``` 96 | 97 | #### 自定义 - 连接认证 98 | - 默认只是简单使用对用户名进行RSA密钥对加密生成密码, 连接认证时对密码进行解密和相应用户名进行匹配认证 99 | - 使用中如果需要实现连接数据库或其他方式进行连接认证, 只需要重写`mqtt-auth`模块下的相应方法即可 100 | 101 | #### 自定义 - 服务端证书 102 | - 服务端证书存储在`mqtt-broker`的`resources/keystore/server.pfx` 103 | - 用户可以制作自己的证书, 但存储位置和文件名必须使用上述描述的位置及文件名 104 | 105 | #### 生产环境部署 106 | - 多机环境集群 107 | - 负载均衡: 富人用 L4 Switch,穷人用 Linux HAProxy 108 | 109 | #### 示例截图 110 | ![示例截图](mqtt-zoo/test.png) 111 | 112 | 113 | # 参考项目 114 | 115 | * [https://github.com/netty/netty](https://github.com/netty/netty) 116 | * [https://gitee.com/recallcode/iot-mqtt-server](https://gitee.com/recallcode/iot-mqtt-server) 117 | 118 | > 如果您觉得还不错请在右上角点一下 star,帮忙转发,谢谢 ��🙏🙏 大家的支持是开源最大动力 119 | -------------------------------------------------------------------------------- /README_EN.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

MqttWk - powered by netty

4 | 5 | [![GitHub release](https://img.shields.io/github/release/Wizzercn/MqttWk.svg)](https://github.com/Wizzercn/MqttWk/releases) 6 | [![License](https://img.shields.io/badge/license-Apache%202-4EB1BA.svg)](https://www.apache.org/licenses/LICENSE-2.0.html) 7 | [![PowerByNutz](https://img.shields.io/badge/PowerBy-NutzBoot-green.svg)](https://github.com/nutzam/nutzboot) 8 |
9 | 10 | [中文](README.md) | English 11 | 12 | > This project is open source and free. Welcome to exchange, learn, and contribute code. 13 | 14 | #### MqttWk 15 | * QQ Group: 225991747 16 | 17 | # Development Guide 18 | 19 | #### Technology Stack 20 | 21 | 1. Using netty for communication and protocol parsing 22 | 2. Using nutzboot for dependency injection and property configuration 23 | 3. Using redis for message caching and clustering 24 | 4. Using kafka for message forwarding (optional) 25 | 26 | #### Project Structure 27 | ``` 28 | MqttWk 29 | ├── mqtt-auth -- MQTT service username and password authentication 30 | ├── mqtt-broker -- Core implementation of MQTT server functionality 31 | ├── mqtt-common -- Common classes and service interfaces used by other modules 32 | ├── mqtt-store -- MQTT server session information (redis cache and kafka loading) 33 | ├── mqtt-client -- MQTT client example code (modify database connection in config file to start) 34 | ├── mqtt-zoo -- Tutorials, documents, or files 35 | ├── mqtt-test-kafka -- Kafka consumer for receiving messages 36 | ├── mqtt-test-websocket -- Websocket communication test example 37 | ``` 38 | 39 | #### Features 40 | 1. Implemented according to MQTT 3.1.1 specification 41 | 2. Complete QoS service quality level implementation 42 | 3. Last will messages, retained messages, and message distribution retry 43 | 4. Heartbeat mechanism 44 | 5. MQTT connection authentication (optional) 45 | 6. SSL connection (optional) 46 | 7. Topic filtering (supports single topic subscription like `test_topic` or `/mqtt/test` --cannot end with /, wildcard subscription `#` or `/mqtt/#` --must end with #) 47 | 8. Websocket support (optional) 48 | 9. Clustering functionality (optional) 49 | 10. Kafka message forwarding (optional) 50 | 11. View statistics after startup at `http://127.0.0.1:8922/open/api/mqttwk/info` 51 | 52 | #### Quick Start 53 | - JDK 1.8 54 | - Execute `mvn install` in the project root directory 55 | - Execute `mvn clean package nutzboot:shade` in mqtt-broker for packaging 56 | - Run and load internal yaml configuration file: `java -jar mqtt-broker-xxx.jar -Dnutz.profiles.active=prod` [This loads the application-prod.yaml configuration file] 57 | - Deploy and load yaml configuration file from a specified folder: `nohup java -Dnutz.boot.configure.yaml.dir=/data -jar mqtt-broker-xxx.jar >/dev/null 2>&1 &` 58 | - Open mqtt-spy client and fill in the corresponding configuration [Download](https://github.com/eclipse/paho.mqtt-spy/wiki/Downloads) 59 | - Connection port: 8885, websocket port: 9995 60 | - Username for connection: demo 61 | - Password for connection: 8F3B8DE2FDC8BD3D792BE77EAC412010971765E5BDD6C499ADCEE840CE441BDEF17E30684BD95CA708F55022222CC6161D0D23C2DFCB12F8AC998F59E7213393 62 | - The certificate used for connection is in `mqtt-zoo`\keystore\server.cer 63 | 64 | #### Cluster Usage 65 | - Multi-machine environment cluster: 66 | 67 | ```yaml 68 | mqttwk: 69 | broker: 70 | cluster-on: true 71 | kafka: 72 | # Whether to enable kafka message forwarding 73 | broker-enabled: false 74 | bootstrap: 75 | servers: 192.168.1.101:9092,192.168.1.102:9093 76 | redis: 77 | mode: cluster 78 | nodes: 192.168.1.103:16379,192.168.1.104:26379,192.168.1.103:36379 79 | ``` 80 | - Single machine environment cluster: 81 | 82 | ```yaml 83 | mqttwk: 84 | broker: 85 | cluster-on: true 86 | kafka: 87 | # Whether to enable kafka message forwarding 88 | broker-enabled: false 89 | bootstrap: 90 | servers: 192.168.1.101:9092,192.168.1.102:9093 91 | redis: 92 | mode: normal 93 | host: 127.0.0.1 94 | port: 6379 95 | ``` 96 | 97 | #### Customization - Connection Authentication 98 | - By default, it simply uses RSA key pair encryption on the username to generate a password, and decrypts the password during connection authentication to match with the corresponding username 99 | - If you need to implement connection authentication using a database or other methods, you only need to override the corresponding methods in the `mqtt-auth` module 100 | 101 | #### Customization - Server Certificate 102 | - The server certificate is stored in `mqtt-broker`'s `resources/keystore/server.pfx` 103 | - Users can create their own certificates, but the storage location and filename must be as described above 104 | 105 | #### Production Environment Deployment 106 | - Multi-machine environment cluster 107 | - Load balancing: Rich people use L4 Switch, poor people use Linux HAProxy 108 | 109 | #### Example Screenshot 110 | ![Example Screenshot](mqtt-zoo/test.png) 111 | 112 | 113 | # Reference Projects 114 | 115 | * [https://github.com/netty/netty](https://github.com/netty/netty) 116 | * [https://gitee.com/recallcode/iot-mqtt-server](https://gitee.com/recallcode/iot-mqtt-server) 117 | 118 | > If you find this project helpful, please give it a star in the upper right corner and help spread the word. Thank you 🙏🙏🙏 Your support is the biggest motivation for open source -------------------------------------------------------------------------------- /mqtt-auth/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mqtt-wk 7 | cn.wizzer 8 | 1.4.0-netty 9 | 10 | 4.0.0 11 | jar 12 | mqtt-auth 13 | 14 | 15 | cn.wizzer 16 | mqtt-common 17 | ${mqttwk.version} 18 | 19 | 20 | org.nutz 21 | nutzboot-core 22 | 23 | 24 | cn.hutool 25 | hutool-crypto 26 | ${hutool.version} 27 | 28 | 29 | -------------------------------------------------------------------------------- /mqtt-auth/src/main/java/cn/wizzer/iot/mqtt/server/auth/service/AuthService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.auth.service; 6 | 7 | import cn.hutool.core.io.IoUtil; 8 | import cn.hutool.core.util.StrUtil; 9 | import cn.hutool.crypto.asymmetric.KeyType; 10 | import cn.hutool.crypto.asymmetric.RSA; 11 | import cn.wizzer.iot.mqtt.server.common.auth.IAuthService; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | 14 | import java.security.interfaces.RSAPrivateKey; 15 | 16 | /** 17 | * 用户名和密码认证服务 18 | */ 19 | @IocBean(create = "init") 20 | public class AuthService implements IAuthService { 21 | 22 | private RSAPrivateKey privateKey; 23 | 24 | @Override 25 | public boolean checkValid(String username, String password) { 26 | if (StrUtil.isBlank(username)) return false; 27 | if (StrUtil.isBlank(password)) return false; 28 | RSA rsa = new RSA(privateKey, null); 29 | String value = rsa.encryptBcd(username, KeyType.PrivateKey); 30 | return value.equals(password) ? true : false; 31 | } 32 | 33 | public void init() { 34 | privateKey = IoUtil.readObj(AuthService.class.getClassLoader().getResourceAsStream("keystore/auth-private.key")); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /mqtt-auth/src/main/java/cn/wizzer/iot/mqtt/server/auth/util/PwdUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.auth.util; 6 | 7 | import cn.hutool.core.io.IoUtil; 8 | import cn.hutool.crypto.asymmetric.KeyType; 9 | import cn.hutool.crypto.asymmetric.RSA; 10 | 11 | import java.io.IOException; 12 | import java.security.interfaces.RSAPrivateKey; 13 | import java.util.Scanner; 14 | 15 | /** 16 | * 密码 17 | */ 18 | public class PwdUtil { 19 | 20 | /** 21 | * 通过用户名和私钥生成密码 22 | */ 23 | public static void main(String[] args) throws IOException { 24 | System.out.println(); 25 | System.out.print("输入需要获取密码的用户名: "); 26 | Scanner scanner = new Scanner(System.in); 27 | String value = scanner.nextLine(); 28 | RSAPrivateKey privateKey = IoUtil.readObj(PwdUtil.class.getClassLoader().getResourceAsStream("keystore/auth-private.key")); 29 | RSA rsa = new RSA(privateKey, null); 30 | System.out.println("用户名: " + value + " 对应生成的密码为: " + rsa.encryptBcd(value, KeyType.PrivateKey)); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /mqtt-auth/src/main/java/cn/wizzer/iot/mqtt/server/auth/util/RsaKeyUtil.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.auth.util; 6 | 7 | import cn.hutool.core.io.FileUtil; 8 | import cn.hutool.core.io.IoUtil; 9 | import cn.hutool.crypto.SecureUtil; 10 | 11 | import java.security.KeyPair; 12 | import java.security.interfaces.RSAPrivateKey; 13 | import java.time.LocalDateTime; 14 | import java.util.Scanner; 15 | 16 | /** 17 | * 私钥 18 | */ 19 | public class RsaKeyUtil { 20 | 21 | /** 22 | * 生成私钥文件 23 | */ 24 | public static void main(String[] args) { 25 | System.out.println(); 26 | System.out.print("输入保存密钥文件的路径(如: D:/keystore/ ): "); 27 | Scanner scanner = new Scanner(System.in); 28 | String path = scanner.nextLine(); 29 | KeyPair keyPair = SecureUtil.generateKeyPair("RSA", 512, LocalDateTime.now().toString().getBytes()); 30 | RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); 31 | String privatePath = path + "auth-private.key"; 32 | IoUtil.writeObjects(FileUtil.getOutputStream(privatePath), true, privateKey); 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /mqtt-auth/src/main/resources/keystore/auth-private.key: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wizzercn/MqttWk/addfd0ad452a1917e8a95e3cf06096696ce3fc38/mqtt-auth/src/main/resources/keystore/auth-private.key -------------------------------------------------------------------------------- /mqtt-broker/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mqtt-wk 7 | cn.wizzer 8 | 1.4.0-netty 9 | 10 | 4.0.0 11 | jar 12 | mqtt-broker 13 | 14 | 15 | cn.wizzer 16 | mqtt-common 17 | ${mqttwk.version} 18 | 19 | 20 | cn.wizzer 21 | mqtt-store 22 | ${mqttwk.version} 23 | 24 | 25 | cn.wizzer 26 | mqtt-auth 27 | ${mqttwk.version} 28 | 29 | 30 | org.nutz 31 | nutzboot-core 32 | 33 | 34 | io.netty 35 | netty-codec-mqtt 36 | ${netty.version} 37 | 38 | 39 | io.netty 40 | netty-codec-http 41 | ${netty.version} 42 | 43 | 44 | io.netty 45 | netty-handler 46 | ${netty.version} 47 | 48 | 49 | io.netty 50 | netty-transport-native-epoll 51 | linux-x86_64 52 | ${netty.version} 53 | 54 | 55 | cn.hutool 56 | hutool-crypto 57 | ${hutool.version} 58 | 59 | 60 | org.nutz 61 | nutzboot-starter-nutz-mvc 62 | ${nutzboot.version} 63 | 64 | 65 | org.nutz 66 | nutzboot-starter-jetty 67 | ${nutzboot.version} 68 | 69 | 70 | 71 | org.slf4j 72 | slf4j-api 73 | ${slf4j.version} 74 | 75 | 76 | ch.qos.logback 77 | logback-core 78 | ${logback.version} 79 | 80 | 81 | ch.qos.logback 82 | logback-classic 83 | ${logback.version} 84 | 85 | 86 | 87 | 88 | 89 | org.nutz.boot 90 | nutzboot-maven-plugin 91 | ${nutzboot.version} 92 | 93 | 94 | 95 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/MainLauncher.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.broker; 2 | 3 | import cn.wizzer.iot.mqtt.server.broker.server.BrokerServer; 4 | import org.nutz.boot.NbApp; 5 | import org.nutz.integration.jedis.RedisService; 6 | import org.nutz.ioc.Ioc; 7 | import org.nutz.ioc.impl.PropertiesProxy; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | import org.nutz.log.Log; 11 | import org.nutz.log.Logs; 12 | import org.nutz.mvc.annotation.Modules; 13 | 14 | /** 15 | * Created by wizzer on 2018 16 | */ 17 | @IocBean 18 | @Modules(packages = "cn.wizzer.iot") 19 | public class MainLauncher { 20 | private static final Log log = Logs.get(); 21 | @Inject("refer:$ioc") 22 | private Ioc ioc; 23 | @Inject 24 | private PropertiesProxy conf; 25 | @Inject 26 | private RedisService redisService; 27 | @Inject 28 | private BrokerServer brokerServer; 29 | 30 | public static void main(String[] args) throws Exception { 31 | NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true); 32 | nb.setMainPackage("cn.wizzer.iot"); 33 | nb.run(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/cluster/RedisCluster.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.broker.cluster; 2 | 3 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 4 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalMessage; 5 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalSendServer; 6 | import com.alibaba.fastjson.JSONObject; 7 | import io.netty.handler.codec.mqtt.MqttQoS; 8 | import org.nutz.aop.interceptor.async.Async; 9 | import org.nutz.integration.jedis.pubsub.PubSub; 10 | import org.nutz.integration.jedis.pubsub.PubSubService; 11 | import org.nutz.ioc.loader.annotation.Inject; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | import org.nutz.lang.Lang; 14 | import org.slf4j.Logger; 15 | import org.slf4j.LoggerFactory; 16 | 17 | /** 18 | * Created by wizzer on 2018 19 | */ 20 | @IocBean(create = "init") 21 | public class RedisCluster implements PubSub { 22 | private static final Logger LOGGER = LoggerFactory.getLogger(RedisCluster.class); 23 | private static final String CLUSTER_TOPIC = "mqttwk:cluster"; 24 | @Inject 25 | private PubSubService pubSubService; 26 | @Inject 27 | private BrokerProperties brokerProperties; 28 | @Inject 29 | private InternalSendServer internalSendServer; 30 | 31 | public void init() { 32 | pubSubService.reg(CLUSTER_TOPIC, this); 33 | } 34 | 35 | @Override 36 | public void onMessage(String channel, String message) { 37 | InternalMessage internalMessage = JSONObject.parseObject(message, InternalMessage.class); 38 | //判断进程ID是否是自身实例,若相同则不发送,否则集群模式下重复发消息 39 | if (!brokerProperties.getId().equals(internalMessage.getBrokerId()) && !Lang.JdkTool.getProcessId("0").equals(internalMessage.getProcessId())) 40 | internalSendServer.sendPublishMessage(internalMessage.getClientId(), internalMessage.getTopic(), MqttQoS.valueOf(internalMessage.getMqttQoS()), internalMessage.getMessageBytes(), internalMessage.isRetain(), internalMessage.isDup()); 41 | } 42 | 43 | @Async 44 | public void sendMessage(InternalMessage internalMessage) { 45 | pubSubService.fire(CLUSTER_TOPIC, JSONObject.toJSONString(internalMessage)); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/codec/MqttWebSocketCodec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.codec; 6 | 7 | import io.netty.buffer.ByteBuf; 8 | import io.netty.channel.ChannelHandlerContext; 9 | import io.netty.handler.codec.MessageToMessageCodec; 10 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 11 | 12 | import java.util.List; 13 | 14 | /** 15 | * WebSocket Mqtt消息编解码器 16 | */ 17 | public class MqttWebSocketCodec extends MessageToMessageCodec { 18 | 19 | @Override 20 | protected void encode(ChannelHandlerContext ctx, ByteBuf msg, List out) throws Exception { 21 | out.add(new BinaryWebSocketFrame(msg.retain())); 22 | } 23 | 24 | @Override 25 | protected void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame msg, List out) throws Exception { 26 | out.add(msg.retain().content()); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/config/BrokerProperties.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.config; 6 | 7 | import org.nutz.boot.annotation.PropDoc; 8 | import org.nutz.ioc.impl.PropertiesProxy; 9 | import org.nutz.ioc.loader.annotation.Inject; 10 | import org.nutz.ioc.loader.annotation.IocBean; 11 | 12 | /** 13 | * 服务配置 14 | */ 15 | @IocBean(create = "init") 16 | public class BrokerProperties { 17 | @Inject 18 | private PropertiesProxy conf; 19 | protected static final String PRE = "mqttwk.broker."; 20 | /** 21 | * Broker唯一标识, 默认mqttwk 22 | */ 23 | private String id; 24 | 25 | // @PropDoc(group = "broker", value = "Broker唯一标识", need = true, defaultValue = "mqttwk") 26 | public static final String _id = PRE + "id"; 27 | 28 | /** 29 | * SSL启动的IP地址, 默认127.0.0.1 30 | */ 31 | private String host; 32 | @PropDoc(group = "broker", value = "服务启动的IP", defaultValue = "127.0.0.1") 33 | public static final String PROP_HOST = PRE + "host"; 34 | /** 35 | * SSL端口号, 默认8885端口 36 | */ 37 | private int port; 38 | @PropDoc(group = "broker", value = "端口号, 默认8885端口", type = "int", defaultValue = "8885") 39 | public static final String PROP_PORT = PRE + "port"; 40 | /** 41 | * 是否开启集群 42 | */ 43 | private boolean clusterEnabled; 44 | @PropDoc(group = "broker", value = "是否开启集群模式, 默认false", type = "boolean", defaultValue = "false") 45 | public static final String PROP_CLUSTERON = PRE + "cluster-on"; 46 | /** 47 | * WebSocket SSL端口号, 默认9995端口 48 | */ 49 | private int websocketPort; 50 | @PropDoc(group = "broker", value = "WebSocket 端口号, 默认9995端口", type = "int", defaultValue = "9995") 51 | public static final String PROP_WEBSOCKETPORT = PRE + "websocket-port"; 52 | /** 53 | * WebSocket 是否启用 54 | */ 55 | private boolean websocketEnabled; 56 | @PropDoc(group = "broker", value = "WebSocket 是否启用", type = "boolean", defaultValue = "false") 57 | public static final String PROP_WEBSOCKETENABLED = PRE + "websocket-enabled"; 58 | /** 59 | * WebSocket 路径 60 | */ 61 | private String websocketPath; 62 | @PropDoc(group = "broker", value = "WebSocket 访问路径, 默认 /mqtt", defaultValue = "/mqtt") 63 | public static final String PROP_WEBSOCKETPATH = PRE + "websocket-path"; 64 | /** 65 | * SSL是否启用 66 | */ 67 | private boolean sslEnabled; 68 | @PropDoc(group = "broker", value = "SSL是否启用", type = "boolean", defaultValue = "true") 69 | public static final String PROP_SSLENABLED = PRE + "ssl-enabled"; 70 | /** 71 | * SSL密钥文件密码 72 | */ 73 | private String sslPassword; 74 | @PropDoc(group = "broker", value = "SSL密钥文件密码") 75 | public static final String PROP_SSLPASSWORD = PRE + "ssl-password"; 76 | /** 77 | * 心跳时间(秒), 默认60秒, 该值可被客户端连接时相应配置覆盖 78 | */ 79 | private int keepAlive; 80 | @PropDoc(group = "broker", value = "心跳时间(秒), 默认60秒, 该值可被客户端连接时相应配置覆盖", type = "int", defaultValue = "60") 81 | public static final String PROP_KEEPALIVE = PRE + "keep-alive"; 82 | /** 83 | * 是否开启Epoll模式, 默认关闭 84 | */ 85 | private boolean useEpoll; 86 | @PropDoc(group = "broker", value = "是否开启Epoll模式, 默认关闭", type = "boolean", defaultValue = "false") 87 | public static final String PROP_USEEPOLL = PRE + "use-epoll"; 88 | /** 89 | * Sokcet参数, 存放已完成三次握手请求的队列最大长度, 默认511长度 90 | */ 91 | private int soBacklog; 92 | @PropDoc(group = "broker", value = "Sokcet参数, 存放已完成三次握手请求的队列最大长度, 默认511长度", type = "int", defaultValue = "511") 93 | public static final String PROP_SOBACKLOG = PRE + "so-backlog"; 94 | /** 95 | * Socket参数, 是否开启心跳保活机制, 默认开启 96 | */ 97 | private boolean soKeepAlive; 98 | @PropDoc(group = "broker", value = "是否开启心跳保活机制, 默认开启", type = "boolean", defaultValue = "true") 99 | public static final String PROP_SOKEEPALIVE = PRE + "so-keep-alive"; 100 | /** 101 | * 转发kafka主题 102 | */ 103 | private String producerTopic; 104 | @PropDoc(group = "broker", value = "kafka转发topic", defaultValue = "mqtt_publish") 105 | public static final String PROP_KAFKA_PRODUCERTOPIC = PRE + "kafka.producer.topic"; 106 | /** 107 | * MQTT.Connect消息必须通过用户名密码验证 108 | */ 109 | private boolean mqttPasswordMust; 110 | @PropDoc(group = "broker", value = "Connect消息必须通过用户名密码验证, 默认true", type = "boolean", defaultValue = "true") 111 | public static final String PROP_MQTTPASSWORDMUST = PRE + "mqtt-password-must"; 112 | /** 113 | * 是否启用kafka消息转发 114 | */ 115 | private boolean kafkaBrokerEnabled; 116 | public static final String PROP_KAFKA_BROKER_ENABLED = PRE + "kafka.broker-enabled"; 117 | 118 | private int bossGroup_nThreads; 119 | @PropDoc(group = "broker", value = "bossGroup线程数", type = "int", defaultValue = "4") 120 | public static final String PROP_BOSSGROUP_NTHREADS = PRE + "bossGroup-nThreads"; 121 | private int workerGroup_nThreads; 122 | @PropDoc(group = "broker", value = "bossGroup线程数", type = "int", defaultValue = "20") 123 | public static final String PROP_WORKERGROUP_NTHREADS = PRE + "workerGroup-nThreads"; 124 | 125 | public void init() { 126 | this.id = conf.get(_id, "mqttwk"); 127 | this.host = conf.get(PROP_HOST, "127.0.0.1"); 128 | this.port = conf.getInt(PROP_PORT, 8885); 129 | this.websocketPort = conf.getInt(PROP_WEBSOCKETPORT, 9995); 130 | this.websocketEnabled = conf.getBoolean(PROP_WEBSOCKETENABLED, false); 131 | this.websocketPath = conf.get(PROP_WEBSOCKETPATH, "/mqtt"); 132 | this.sslEnabled = conf.getBoolean(PROP_SSLENABLED, true); 133 | this.sslPassword = conf.get(PROP_SSLPASSWORD); 134 | this.keepAlive = conf.getInt(PROP_KEEPALIVE, 60); 135 | this.producerTopic = conf.get(PROP_KAFKA_PRODUCERTOPIC, "mqtt_publish"); 136 | this.mqttPasswordMust = conf.getBoolean(PROP_MQTTPASSWORDMUST, true); 137 | this.clusterEnabled = conf.getBoolean(PROP_CLUSTERON, false); 138 | this.kafkaBrokerEnabled = conf.getBoolean(PROP_KAFKA_BROKER_ENABLED, false); 139 | this.useEpoll = conf.getBoolean(PROP_USEEPOLL, false); 140 | this.soBacklog = conf.getInt(PROP_SOBACKLOG, 511); 141 | this.soKeepAlive = conf.getBoolean(PROP_SOKEEPALIVE, true); 142 | this.bossGroup_nThreads = conf.getInt(PROP_BOSSGROUP_NTHREADS, 4); 143 | this.workerGroup_nThreads = conf.getInt(PROP_WORKERGROUP_NTHREADS, 20); 144 | } 145 | 146 | public String getId() { 147 | return id; 148 | } 149 | 150 | public BrokerProperties setId(String id) { 151 | this.id = id; 152 | return this; 153 | } 154 | 155 | public String getHost() { 156 | return host; 157 | } 158 | 159 | public BrokerProperties setHost(String host) { 160 | this.host = host; 161 | return this; 162 | } 163 | 164 | public int getPort() { 165 | return port; 166 | } 167 | 168 | public BrokerProperties setPort(int port) { 169 | this.port = port; 170 | return this; 171 | } 172 | 173 | public boolean getClusterEnabled() { 174 | return clusterEnabled; 175 | } 176 | 177 | public BrokerProperties setClusterEnabled(boolean clusterEnabled) { 178 | this.clusterEnabled = clusterEnabled; 179 | return this; 180 | } 181 | 182 | public int getWebsocketPort() { 183 | return websocketPort; 184 | } 185 | 186 | public BrokerProperties setWebsocketPort(int websocketPort) { 187 | this.websocketPort = websocketPort; 188 | return this; 189 | } 190 | 191 | public boolean getWebsocketEnabled() { 192 | return websocketEnabled; 193 | } 194 | 195 | public BrokerProperties setWebsocketEnabled(boolean websocketEnabled) { 196 | this.websocketEnabled = websocketEnabled; 197 | return this; 198 | } 199 | 200 | public String getWebsocketPath() { 201 | return websocketPath; 202 | } 203 | 204 | public BrokerProperties setWebsocketPath(String websocketPath) { 205 | this.websocketPath = websocketPath; 206 | return this; 207 | } 208 | 209 | public boolean getSslEnabled() { 210 | return sslEnabled; 211 | } 212 | 213 | public BrokerProperties setSslEnabled(boolean sslEnabled) { 214 | this.sslEnabled = sslEnabled; 215 | return this; 216 | } 217 | 218 | public String getSslPassword() { 219 | return sslPassword; 220 | } 221 | 222 | public BrokerProperties setSslPassword(String sslPassword) { 223 | this.sslPassword = sslPassword; 224 | return this; 225 | } 226 | 227 | public int getKeepAlive() { 228 | return keepAlive; 229 | } 230 | 231 | public BrokerProperties setKeepAlive(int keepAlive) { 232 | this.keepAlive = keepAlive; 233 | return this; 234 | } 235 | 236 | public String getProducerTopic() { 237 | return producerTopic; 238 | } 239 | 240 | public BrokerProperties setProducerTopic(String producerTopic) { 241 | this.producerTopic = producerTopic; 242 | return this; 243 | } 244 | 245 | public boolean getMqttPasswordMust() { 246 | return mqttPasswordMust; 247 | } 248 | 249 | public BrokerProperties setMqttPasswordMust(boolean mqttPasswordMust) { 250 | this.mqttPasswordMust = mqttPasswordMust; 251 | return this; 252 | } 253 | 254 | public boolean getKafkaBrokerEnabled() { 255 | return kafkaBrokerEnabled; 256 | } 257 | 258 | public BrokerProperties setKafkaBrokerEnabled(boolean kafkaBrokerEnabled) { 259 | this.kafkaBrokerEnabled = kafkaBrokerEnabled; 260 | return this; 261 | } 262 | 263 | public boolean getUseEpoll() { 264 | return useEpoll; 265 | } 266 | 267 | public BrokerProperties setUseEpoll(boolean useEpoll) { 268 | this.useEpoll = useEpoll; 269 | return this; 270 | } 271 | 272 | public int getSoBacklog() { 273 | return soBacklog; 274 | } 275 | 276 | public BrokerProperties setSoBacklog(int soBacklog) { 277 | this.soBacklog = soBacklog; 278 | return this; 279 | } 280 | 281 | public boolean getSoKeepAlive() { 282 | return soKeepAlive; 283 | } 284 | 285 | public BrokerProperties setSoKeepAlive(boolean soKeepAlive) { 286 | this.soKeepAlive = soKeepAlive; 287 | return this; 288 | } 289 | 290 | public int getBossGroup_nThreads() { 291 | return bossGroup_nThreads; 292 | } 293 | 294 | public BrokerProperties setBossGroup_nThreads(int bossGroup_nThreads) { 295 | this.bossGroup_nThreads = bossGroup_nThreads; 296 | return this; 297 | } 298 | 299 | public int getWorkerGroup_nThreads() { 300 | return workerGroup_nThreads; 301 | } 302 | 303 | public BrokerProperties setWorkerGroup_nThreads(int workerGroup_nThreads) { 304 | this.workerGroup_nThreads = workerGroup_nThreads; 305 | return this; 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/handler/BrokerHandler.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.handler; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 8 | import cn.wizzer.iot.mqtt.server.broker.protocol.ProtocolProcess; 9 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 10 | import io.netty.channel.*; 11 | import io.netty.channel.group.ChannelGroup; 12 | import io.netty.handler.codec.mqtt.*; 13 | import io.netty.handler.timeout.IdleState; 14 | import io.netty.handler.timeout.IdleStateEvent; 15 | import io.netty.util.AttributeKey; 16 | import org.nutz.ioc.loader.annotation.Inject; 17 | import org.nutz.ioc.loader.annotation.IocBean; 18 | 19 | import java.io.IOException; 20 | import java.util.Map; 21 | 22 | /** 23 | * MQTT消息处理 24 | */ 25 | @IocBean 26 | @ChannelHandler.Sharable 27 | public class BrokerHandler extends SimpleChannelInboundHandler { 28 | @Inject 29 | private ProtocolProcess protocolProcess; 30 | @Inject 31 | private BrokerProperties brokerProperties; 32 | @Inject 33 | private ChannelGroup channelGroup; 34 | @Inject 35 | private Map channelIdMap; 36 | 37 | @Override 38 | public void channelActive(ChannelHandlerContext ctx) throws Exception { 39 | super.channelActive(ctx); 40 | this.channelGroup.add(ctx.channel()); 41 | this.channelIdMap.put(brokerProperties.getId() + "_" + ctx.channel().id().asLongText(), ctx.channel().id()); 42 | } 43 | 44 | @Override 45 | public void channelInactive(ChannelHandlerContext ctx) throws Exception { 46 | super.channelInactive(ctx); 47 | this.channelGroup.remove(ctx.channel()); 48 | this.channelIdMap.remove(brokerProperties.getId() + "_" + ctx.channel().id().asLongText()); 49 | } 50 | 51 | @Override 52 | protected void channelRead0(ChannelHandlerContext ctx, MqttMessage msg) throws Exception { 53 | if (msg.decoderResult().isFailure()) { 54 | Throwable cause = msg.decoderResult().cause(); 55 | if (cause instanceof MqttUnacceptableProtocolVersionException) { 56 | ctx.writeAndFlush(MqttMessageFactory.newMessage( 57 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 58 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION, false), 59 | null)); 60 | } else if (cause instanceof MqttIdentifierRejectedException) { 61 | ctx.writeAndFlush(MqttMessageFactory.newMessage( 62 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 63 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED, false), 64 | null)); 65 | } 66 | ctx.close(); 67 | return; 68 | } 69 | 70 | switch (msg.fixedHeader().messageType()) { 71 | case CONNECT: 72 | protocolProcess.connect().processConnect(ctx.channel(), (MqttConnectMessage) msg); 73 | break; 74 | case CONNACK: 75 | break; 76 | case PUBLISH: 77 | protocolProcess.publish().processPublish(ctx.channel(), (MqttPublishMessage) msg); 78 | break; 79 | case PUBACK: 80 | protocolProcess.pubAck().processPubAck(ctx.channel(), (MqttMessageIdVariableHeader) msg.variableHeader()); 81 | break; 82 | case PUBREC: 83 | protocolProcess.pubRec().processPubRec(ctx.channel(), (MqttMessageIdVariableHeader) msg.variableHeader()); 84 | break; 85 | case PUBREL: 86 | protocolProcess.pubRel().processPubRel(ctx.channel(), (MqttMessageIdVariableHeader) msg.variableHeader()); 87 | break; 88 | case PUBCOMP: 89 | protocolProcess.pubComp().processPubComp(ctx.channel(), (MqttMessageIdVariableHeader) msg.variableHeader()); 90 | break; 91 | case SUBSCRIBE: 92 | protocolProcess.subscribe().processSubscribe(ctx.channel(), (MqttSubscribeMessage) msg); 93 | break; 94 | case SUBACK: 95 | break; 96 | case UNSUBSCRIBE: 97 | protocolProcess.unSubscribe().processUnSubscribe(ctx.channel(), (MqttUnsubscribeMessage) msg); 98 | break; 99 | case UNSUBACK: 100 | break; 101 | case PINGREQ: 102 | protocolProcess.pingReq().processPingReq(ctx.channel(), msg); 103 | break; 104 | case PINGRESP: 105 | break; 106 | case DISCONNECT: 107 | protocolProcess.disConnect().processDisConnect(ctx.channel(), msg); 108 | break; 109 | default: 110 | break; 111 | } 112 | } 113 | 114 | @Override 115 | public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 116 | if (cause instanceof IOException) { 117 | // 远程主机强迫关闭了一个现有的连接的异常 118 | ctx.close(); 119 | } else { 120 | super.exceptionCaught(ctx, cause); 121 | } 122 | } 123 | 124 | @Override 125 | public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { 126 | if (evt instanceof IdleStateEvent) { 127 | IdleStateEvent idleStateEvent = (IdleStateEvent) evt; 128 | if (idleStateEvent.state() == IdleState.ALL_IDLE) { 129 | Channel channel = ctx.channel(); 130 | String clientId = (String) channel.attr(AttributeKey.valueOf("clientId")).get(); 131 | // 发送遗嘱消息 132 | if (this.protocolProcess.getSessionStoreService().containsKey(clientId)) { 133 | SessionStore sessionStore = this.protocolProcess.getSessionStoreService().get(clientId); 134 | if (sessionStore.getWillMessage() != null) { 135 | this.protocolProcess.publish().processPublish(ctx.channel(), sessionStore.getWillMessage()); 136 | } 137 | } 138 | ctx.close(); 139 | } 140 | } else { 141 | super.userEventTriggered(ctx, evt); 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/internal/InternalCommunication.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.internal; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.cluster.RedisCluster; 8 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 9 | import cn.wizzer.iot.mqtt.server.broker.service.KafkaService; 10 | import org.nutz.ioc.loader.annotation.Inject; 11 | import org.nutz.ioc.loader.annotation.IocBean; 12 | import org.nutz.lang.Lang; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * 消息转发,基于kafka 18 | */ 19 | @IocBean 20 | public class InternalCommunication { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(InternalCommunication.class); 22 | @Inject 23 | private BrokerProperties brokerProperties; 24 | @Inject 25 | private KafkaService kafkaService; 26 | @Inject 27 | private RedisCluster redisCluster; 28 | 29 | public void internalSend(InternalMessage internalMessage) { 30 | String processId = Lang.JdkTool.getProcessId("0"); 31 | //broker唯一标识 mqttwk.broker.id 32 | internalMessage.setBrokerId(brokerProperties.getId()); 33 | internalMessage.setProcessId(processId); 34 | //如果开启kafka消息转发 35 | if (brokerProperties.getKafkaBrokerEnabled()) { 36 | kafkaService.send(internalMessage); 37 | } 38 | //如果开启集群功能 39 | if (brokerProperties.getClusterEnabled()) { 40 | redisCluster.sendMessage(internalMessage); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/internal/InternalMessage.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.internal; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 内部消息 11 | */ 12 | public class InternalMessage implements Serializable { 13 | 14 | private static final long serialVersionUID = -1L; 15 | 16 | private String brokerId; 17 | 18 | private String processId; 19 | 20 | //当前频道clientId 21 | private String clientId; 22 | 23 | private String topic; 24 | 25 | private int mqttQoS; 26 | 27 | private byte[] messageBytes; 28 | 29 | private boolean retain; 30 | 31 | private boolean dup; 32 | 33 | public String getBrokerId() { 34 | return brokerId; 35 | } 36 | 37 | public void setBrokerId(String brokerId) { 38 | this.brokerId = brokerId; 39 | } 40 | 41 | public String getProcessId() { 42 | return processId; 43 | } 44 | 45 | public void setProcessId(String processId) { 46 | this.processId = processId; 47 | } 48 | 49 | public String getClientId() { 50 | return clientId; 51 | } 52 | 53 | public InternalMessage setClientId(String clientId) { 54 | this.clientId = clientId; 55 | return this; 56 | } 57 | 58 | public String getTopic() { 59 | return topic; 60 | } 61 | 62 | public InternalMessage setTopic(String topic) { 63 | this.topic = topic; 64 | return this; 65 | } 66 | 67 | public int getMqttQoS() { 68 | return mqttQoS; 69 | } 70 | 71 | public InternalMessage setMqttQoS(int mqttQoS) { 72 | this.mqttQoS = mqttQoS; 73 | return this; 74 | } 75 | 76 | public byte[] getMessageBytes() { 77 | return messageBytes; 78 | } 79 | 80 | public InternalMessage setMessageBytes(byte[] messageBytes) { 81 | this.messageBytes = messageBytes; 82 | return this; 83 | } 84 | 85 | public boolean isRetain() { 86 | return retain; 87 | } 88 | 89 | public InternalMessage setRetain(boolean retain) { 90 | this.retain = retain; 91 | return this; 92 | } 93 | 94 | public boolean isDup() { 95 | return dup; 96 | } 97 | 98 | public InternalMessage setDup(boolean dup) { 99 | this.dup = dup; 100 | return this; 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/internal/InternalSendServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.internal; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 8 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 9 | import cn.wizzer.iot.mqtt.server.common.subscribe.SubscribeStore; 10 | import cn.wizzer.iot.mqtt.server.store.message.MessageIdService; 11 | import cn.wizzer.iot.mqtt.server.store.session.SessionStoreService; 12 | import cn.wizzer.iot.mqtt.server.store.subscribe.SubscribeStoreService; 13 | import io.netty.buffer.Unpooled; 14 | import io.netty.channel.Channel; 15 | import io.netty.channel.ChannelId; 16 | import io.netty.channel.group.ChannelGroup; 17 | import io.netty.handler.codec.mqtt.*; 18 | import org.nutz.ioc.loader.annotation.Inject; 19 | import org.nutz.ioc.loader.annotation.IocBean; 20 | import org.slf4j.Logger; 21 | import org.slf4j.LoggerFactory; 22 | 23 | import java.util.List; 24 | import java.util.Map; 25 | 26 | /** 27 | * 消息发送 28 | */ 29 | @IocBean 30 | public class InternalSendServer { 31 | private static final Logger LOGGER = LoggerFactory.getLogger(InternalSendServer.class); 32 | @Inject 33 | private BrokerProperties brokerProperties; 34 | @Inject 35 | private SubscribeStoreService subscribeStoreService; 36 | @Inject 37 | private SessionStoreService sessionStoreService; 38 | @Inject 39 | private MessageIdService messageIdService; 40 | @Inject 41 | private ChannelGroup channelGroup; 42 | @Inject 43 | private Map channelIdMap; 44 | 45 | /** 46 | * 发送消息到Topic(排除自身clientId) 47 | * 48 | * @param clientId 49 | * @param topic 50 | * @param mqttQoS 51 | * @param messageBytes 52 | * @param retain 53 | * @param dup 54 | */ 55 | public void sendPublishMessage(String clientId, String topic, MqttQoS mqttQoS, byte[] messageBytes, boolean retain, boolean dup) { 56 | List subscribeStores = subscribeStoreService.search(topic); 57 | subscribeStores.forEach(subscribeStore -> { 58 | if (!clientId.equals(subscribeStore.getClientId()) && sessionStoreService.containsKey(subscribeStore.getClientId())) { 59 | // 订阅者收到MQTT消息的QoS级别, 最终取决于发布消息的QoS和主题订阅的QoS 60 | MqttQoS respQoS = mqttQoS.value() > subscribeStore.getMqttQoS() ? MqttQoS.valueOf(subscribeStore.getMqttQoS()) : mqttQoS; 61 | if (respQoS == MqttQoS.AT_MOST_ONCE) { 62 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 63 | new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0), 64 | new MqttPublishVariableHeader(topic, 0), Unpooled.buffer().writeBytes(messageBytes)); 65 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}", subscribeStore.getClientId(), topic, respQoS.value()); 66 | SessionStore sessionStore = sessionStoreService.get(subscribeStore.getClientId()); 67 | ChannelId channelId = channelIdMap.get(sessionStore.getBrokerId() + "_" + sessionStore.getChannelId()); 68 | if (channelId != null) { 69 | Channel channel = channelGroup.find(channelId); 70 | if (channel != null) channel.writeAndFlush(publishMessage); 71 | } 72 | } 73 | if (respQoS == MqttQoS.AT_LEAST_ONCE) { 74 | int messageId = messageIdService.getNextMessageId(); 75 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 76 | new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0), 77 | new MqttPublishVariableHeader(topic, messageId), Unpooled.buffer().writeBytes(messageBytes)); 78 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", subscribeStore.getClientId(), topic, respQoS.value(), messageId); 79 | SessionStore sessionStore = sessionStoreService.get(subscribeStore.getClientId()); 80 | ChannelId channelId = channelIdMap.get(sessionStore.getBrokerId() + "_" + sessionStore.getChannelId()); 81 | if (channelId != null) { 82 | Channel channel = channelGroup.find(channelId); 83 | if (channel != null) channel.writeAndFlush(publishMessage); 84 | } 85 | } 86 | if (respQoS == MqttQoS.EXACTLY_ONCE) { 87 | int messageId = messageIdService.getNextMessageId(); 88 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 89 | new MqttFixedHeader(MqttMessageType.PUBLISH, dup, respQoS, retain, 0), 90 | new MqttPublishVariableHeader(topic, messageId), Unpooled.buffer().writeBytes(messageBytes)); 91 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", subscribeStore.getClientId(), topic, respQoS.value(), messageId); 92 | SessionStore sessionStore = sessionStoreService.get(subscribeStore.getClientId()); 93 | ChannelId channelId = channelIdMap.get(sessionStore.getBrokerId() + "_" + sessionStore.getChannelId()); 94 | if (channelId != null) { 95 | Channel channel = channelGroup.find(channelId); 96 | if (channel != null) channel.writeAndFlush(publishMessage); 97 | } 98 | } 99 | } 100 | }); 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/Connect.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 9 | import cn.wizzer.iot.mqtt.server.common.auth.IAuthService; 10 | import cn.wizzer.iot.mqtt.server.common.message.DupPubRelMessageStore; 11 | import cn.wizzer.iot.mqtt.server.common.message.DupPublishMessageStore; 12 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 13 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 14 | import cn.wizzer.iot.mqtt.server.common.session.ISessionStoreService; 15 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 16 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 17 | import io.netty.buffer.Unpooled; 18 | import io.netty.channel.Channel; 19 | import io.netty.channel.ChannelId; 20 | import io.netty.channel.group.ChannelGroup; 21 | import io.netty.handler.codec.mqtt.*; 22 | import io.netty.handler.timeout.IdleStateHandler; 23 | import io.netty.util.AttributeKey; 24 | import io.netty.util.CharsetUtil; 25 | import org.slf4j.Logger; 26 | import org.slf4j.LoggerFactory; 27 | 28 | import java.util.List; 29 | import java.util.Map; 30 | 31 | /** 32 | * CONNECT连接处理 33 | */ 34 | public class Connect { 35 | 36 | private static final Logger LOGGER = LoggerFactory.getLogger(Connect.class); 37 | 38 | private ISessionStoreService sessionStoreService; 39 | 40 | private ISubscribeStoreService subscribeStoreService; 41 | 42 | private IDupPublishMessageStoreService dupPublishMessageStoreService; 43 | 44 | private IDupPubRelMessageStoreService dupPubRelMessageStoreService; 45 | 46 | private IAuthService authService; 47 | 48 | private BrokerProperties brokerProperties; 49 | 50 | private ChannelGroup channelGroup; 51 | 52 | private Map channelIdMap; 53 | 54 | public Connect(ISessionStoreService sessionStoreService, ISubscribeStoreService subscribeStoreService, IDupPublishMessageStoreService dupPublishMessageStoreService, IDupPubRelMessageStoreService dupPubRelMessageStoreService, IAuthService authService, BrokerProperties brokerProperties, ChannelGroup channelGroup, Map channelIdMap) { 55 | this.sessionStoreService = sessionStoreService; 56 | this.subscribeStoreService = subscribeStoreService; 57 | this.dupPublishMessageStoreService = dupPublishMessageStoreService; 58 | this.dupPubRelMessageStoreService = dupPubRelMessageStoreService; 59 | this.authService = authService; 60 | this.brokerProperties = brokerProperties; 61 | this.channelGroup = channelGroup; 62 | this.channelIdMap = channelIdMap; 63 | } 64 | 65 | public void processConnect(Channel channel, MqttConnectMessage msg) { 66 | // 消息解码器出现异常 67 | if (msg.decoderResult().isFailure()) { 68 | Throwable cause = msg.decoderResult().cause(); 69 | if (cause instanceof MqttUnacceptableProtocolVersionException) { 70 | // 不支持的协议版本 71 | MqttConnAckMessage connAckMessage = (MqttConnAckMessage) MqttMessageFactory.newMessage( 72 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 73 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_UNACCEPTABLE_PROTOCOL_VERSION, false), null); 74 | channel.writeAndFlush(connAckMessage); 75 | channel.close(); 76 | return; 77 | } else if (cause instanceof MqttIdentifierRejectedException) { 78 | // 不合格的clientId 79 | MqttConnAckMessage connAckMessage = (MqttConnAckMessage) MqttMessageFactory.newMessage( 80 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 81 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED, false), null); 82 | channel.writeAndFlush(connAckMessage); 83 | channel.close(); 84 | return; 85 | } 86 | channel.close(); 87 | return; 88 | } 89 | // clientId为空或null的情况, 这里要求客户端必须提供clientId, 不管cleanSession是否为1, 此处没有参考标准协议实现 90 | if (StrUtil.isBlank(msg.payload().clientIdentifier())) { 91 | MqttConnAckMessage connAckMessage = (MqttConnAckMessage) MqttMessageFactory.newMessage( 92 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 93 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_IDENTIFIER_REJECTED, false), null); 94 | channel.writeAndFlush(connAckMessage); 95 | channel.close(); 96 | return; 97 | } 98 | if (brokerProperties.getMqttPasswordMust()) { 99 | // 用户名和密码验证, 这里要求客户端连接时必须提供用户名和密码, 不管是否设置用户名标志和密码标志为1, 此处没有参考标准协议实现 100 | String username = msg.payload().userName(); 101 | String password = msg.payload().passwordInBytes() == null ? null : new String(msg.payload().passwordInBytes(), CharsetUtil.UTF_8); 102 | if (!authService.checkValid(username, password)) { 103 | MqttConnAckMessage connAckMessage = (MqttConnAckMessage) MqttMessageFactory.newMessage( 104 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 105 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_REFUSED_BAD_USER_NAME_OR_PASSWORD, false), null); 106 | channel.writeAndFlush(connAckMessage); 107 | channel.close(); 108 | return; 109 | } 110 | } 111 | // 如果会话中已存储这个新连接的clientId, 就关闭之前该clientId的连接 112 | if (sessionStoreService.containsKey(msg.payload().clientIdentifier())) { 113 | SessionStore sessionStore = sessionStoreService.get(msg.payload().clientIdentifier()); 114 | boolean cleanSession = sessionStore.isCleanSession(); 115 | if (cleanSession) { 116 | sessionStoreService.remove(msg.payload().clientIdentifier()); 117 | subscribeStoreService.removeForClient(msg.payload().clientIdentifier()); 118 | dupPublishMessageStoreService.removeByClient(msg.payload().clientIdentifier()); 119 | dupPubRelMessageStoreService.removeByClient(msg.payload().clientIdentifier()); 120 | } 121 | try { 122 | ChannelId channelId = channelIdMap.get(sessionStore.getBrokerId() + "_" + sessionStore.getChannelId()); 123 | if (channelId != null) { 124 | Channel previous = channelGroup.find(channelId); 125 | if (previous != null) previous.close(); 126 | } 127 | } catch (Exception e) { 128 | //e.printStackTrace(); 129 | } 130 | } else { 131 | //如果不存在session,则清除之前的其他缓存 132 | subscribeStoreService.removeForClient(msg.payload().clientIdentifier()); 133 | dupPublishMessageStoreService.removeByClient(msg.payload().clientIdentifier()); 134 | dupPubRelMessageStoreService.removeByClient(msg.payload().clientIdentifier()); 135 | } 136 | // 处理连接心跳包 137 | int expire = 0; 138 | if (msg.variableHeader().keepAliveTimeSeconds() > 0) { 139 | if (channel.pipeline().names().contains("idle")) { 140 | channel.pipeline().remove("idle"); 141 | } 142 | expire = Math.round(msg.variableHeader().keepAliveTimeSeconds() * 1.5f); 143 | channel.pipeline().addFirst("idle", new IdleStateHandler(0, 0, expire)); 144 | } 145 | // 处理遗嘱信息 146 | SessionStore sessionStore = new SessionStore(brokerProperties.getId(), msg.payload().clientIdentifier(), channel.id().asLongText(), msg.variableHeader().isCleanSession(), null, expire); 147 | if (msg.variableHeader().isWillFlag()) { 148 | MqttPublishMessage willMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 149 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, MqttQoS.valueOf(msg.variableHeader().willQos()), msg.variableHeader().isWillRetain(), 0), 150 | new MqttPublishVariableHeader(msg.payload().willTopic(), 0), Unpooled.buffer().writeBytes(msg.payload().willMessageInBytes())); 151 | sessionStore.setWillMessage(willMessage); 152 | } 153 | // 至此存储会话信息及返回接受客户端连接 154 | sessionStoreService.put(msg.payload().clientIdentifier(), sessionStore, expire); 155 | // 将clientId存储到channel的map中 156 | channel.attr(AttributeKey.valueOf("clientId")).set(msg.payload().clientIdentifier()); 157 | boolean sessionPresent = sessionStoreService.containsKey(msg.payload().clientIdentifier()) && !msg.variableHeader().isCleanSession(); 158 | MqttConnAckMessage okResp = (MqttConnAckMessage) MqttMessageFactory.newMessage( 159 | new MqttFixedHeader(MqttMessageType.CONNACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 160 | new MqttConnAckVariableHeader(MqttConnectReturnCode.CONNECTION_ACCEPTED, sessionPresent), null); 161 | channel.writeAndFlush(okResp); 162 | LOGGER.debug("CONNECT - clientId: {}, cleanSession: {}", msg.payload().clientIdentifier(), msg.variableHeader().isCleanSession()); 163 | // 如果cleanSession为0, 需要重发同一clientId存储的未完成的QoS1和QoS2的DUP消息 164 | if (!msg.variableHeader().isCleanSession()) { 165 | List dupPublishMessageStoreList = dupPublishMessageStoreService.get(msg.payload().clientIdentifier()); 166 | List dupPubRelMessageStoreList = dupPubRelMessageStoreService.get(msg.payload().clientIdentifier()); 167 | dupPublishMessageStoreList.forEach(dupPublishMessageStore -> { 168 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 169 | new MqttFixedHeader(MqttMessageType.PUBLISH, true, MqttQoS.valueOf(dupPublishMessageStore.getMqttQoS()), false, 0), 170 | new MqttPublishVariableHeader(dupPublishMessageStore.getTopic(), dupPublishMessageStore.getMessageId()), Unpooled.buffer().writeBytes(dupPublishMessageStore.getMessageBytes())); 171 | channel.writeAndFlush(publishMessage); 172 | }); 173 | dupPubRelMessageStoreList.forEach(dupPubRelMessageStore -> { 174 | MqttMessage pubRelMessage = MqttMessageFactory.newMessage( 175 | new MqttFixedHeader(MqttMessageType.PUBREL, true, MqttQoS.AT_MOST_ONCE, false, 0), 176 | MqttMessageIdVariableHeader.from(dupPubRelMessageStore.getMessageId()), null); 177 | channel.writeAndFlush(pubRelMessage); 178 | }); 179 | } 180 | } 181 | 182 | } 183 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/DisConnect.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 8 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.session.ISessionStoreService; 10 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 11 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 12 | import io.netty.channel.Channel; 13 | import io.netty.handler.codec.mqtt.MqttMessage; 14 | import io.netty.util.AttributeKey; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | 19 | /** 20 | * DISCONNECT连接处理 21 | */ 22 | public class DisConnect { 23 | 24 | private static final Logger LOGGER = LoggerFactory.getLogger(DisConnect.class); 25 | 26 | private ISessionStoreService sessionStoreService; 27 | 28 | private ISubscribeStoreService subscribeStoreService; 29 | 30 | private IDupPublishMessageStoreService dupPublishMessageStoreService; 31 | 32 | private IDupPubRelMessageStoreService dupPubRelMessageStoreService; 33 | 34 | public DisConnect(ISessionStoreService sessionStoreService, ISubscribeStoreService subscribeStoreService, IDupPublishMessageStoreService dupPublishMessageStoreService, IDupPubRelMessageStoreService dupPubRelMessageStoreService) { 35 | this.sessionStoreService = sessionStoreService; 36 | this.subscribeStoreService = subscribeStoreService; 37 | this.dupPublishMessageStoreService = dupPublishMessageStoreService; 38 | this.dupPubRelMessageStoreService = dupPubRelMessageStoreService; 39 | } 40 | 41 | public void processDisConnect(Channel channel, MqttMessage msg) { 42 | String clientId = (String) channel.attr(AttributeKey.valueOf("clientId")).get(); 43 | SessionStore sessionStore = sessionStoreService.get(clientId); 44 | if (sessionStore != null && sessionStore.isCleanSession()) { 45 | subscribeStoreService.removeForClient(clientId); 46 | dupPublishMessageStoreService.removeByClient(clientId); 47 | dupPubRelMessageStoreService.removeByClient(clientId); 48 | } 49 | LOGGER.debug("DISCONNECT - clientId: {}, cleanSession: {}", clientId, sessionStore.isCleanSession()); 50 | sessionStoreService.remove(clientId); 51 | channel.close(); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/PingReq.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 8 | import cn.wizzer.iot.mqtt.server.common.session.ISessionStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 10 | import io.netty.channel.Channel; 11 | import io.netty.channel.ChannelId; 12 | import io.netty.channel.group.ChannelGroup; 13 | import io.netty.handler.codec.mqtt.*; 14 | import io.netty.util.AttributeKey; 15 | import org.slf4j.Logger; 16 | import org.slf4j.LoggerFactory; 17 | 18 | import java.util.Map; 19 | 20 | /** 21 | * PINGREQ连接处理 22 | */ 23 | public class PingReq { 24 | 25 | private static final Logger LOGGER = LoggerFactory.getLogger(PingReq.class); 26 | private ISessionStoreService sessionStoreService; 27 | private BrokerProperties brokerProperties; 28 | private ChannelGroup channelGroup; 29 | private Map channelIdMap; 30 | 31 | public PingReq(ISessionStoreService sessionStoreService, BrokerProperties brokerProperties, ChannelGroup channelGroup, Map channelIdMap) { 32 | this.sessionStoreService = sessionStoreService; 33 | this.brokerProperties = brokerProperties; 34 | this.channelGroup = channelGroup; 35 | this.channelIdMap = channelIdMap; 36 | } 37 | 38 | public void processPingReq(Channel channel, MqttMessage msg) { 39 | String clientId = (String) channel.attr(AttributeKey.valueOf("clientId")).get(); 40 | if (sessionStoreService.containsKey(clientId)) { 41 | SessionStore sessionStore = sessionStoreService.get(clientId); 42 | ChannelId channelId = channelIdMap.get(sessionStore.getBrokerId() + "_" + sessionStore.getChannelId()); 43 | if (brokerProperties.getId().equals(sessionStore.getBrokerId()) && channelId != null) { 44 | sessionStoreService.expire(clientId, sessionStore.getExpire()); 45 | MqttMessage pingRespMessage = MqttMessageFactory.newMessage( 46 | new MqttFixedHeader(MqttMessageType.PINGRESP, false, MqttQoS.AT_MOST_ONCE, false, 0), null, null); 47 | LOGGER.debug("PINGREQ - clientId: {}", clientId); 48 | channel.writeAndFlush(pingRespMessage); 49 | } 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/ProtocolProcess.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 8 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalCommunication; 9 | import cn.wizzer.iot.mqtt.server.common.auth.IAuthService; 10 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 11 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 12 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 13 | import cn.wizzer.iot.mqtt.server.common.message.IRetainMessageStoreService; 14 | import cn.wizzer.iot.mqtt.server.common.session.ISessionStoreService; 15 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 16 | import io.netty.channel.ChannelId; 17 | import io.netty.channel.group.ChannelGroup; 18 | import org.nutz.ioc.loader.annotation.Inject; 19 | import org.nutz.ioc.loader.annotation.IocBean; 20 | 21 | import java.util.Map; 22 | 23 | /** 24 | * 协议处理 25 | */ 26 | @IocBean 27 | public class ProtocolProcess { 28 | 29 | @Inject 30 | private ISessionStoreService sessionStoreService; 31 | 32 | @Inject 33 | private ISubscribeStoreService subscribeStoreService; 34 | 35 | @Inject 36 | private IAuthService authService; 37 | 38 | @Inject 39 | private IMessageIdService messageIdService; 40 | 41 | @Inject 42 | private IRetainMessageStoreService messageStoreService; 43 | 44 | @Inject 45 | private IDupPublishMessageStoreService dupPublishMessageStoreService; 46 | 47 | @Inject 48 | private IDupPubRelMessageStoreService dupPubRelMessageStoreService; 49 | 50 | @Inject 51 | private InternalCommunication internalCommunication; 52 | 53 | @Inject 54 | private BrokerProperties brokerProperties; 55 | 56 | @Inject 57 | private ChannelGroup channelGroup; 58 | 59 | @Inject 60 | private Map channelIdMap; 61 | 62 | private Connect connect; 63 | 64 | private Subscribe subscribe; 65 | 66 | private UnSubscribe unSubscribe; 67 | 68 | private Publish publish; 69 | 70 | private DisConnect disConnect; 71 | 72 | private PingReq pingReq; 73 | 74 | private PubRel pubRel; 75 | 76 | private PubAck pubAck; 77 | 78 | private PubRec pubRec; 79 | 80 | private PubComp pubComp; 81 | 82 | public Connect connect() { 83 | if (connect == null) { 84 | connect = new Connect(sessionStoreService, subscribeStoreService, dupPublishMessageStoreService, dupPubRelMessageStoreService, authService, brokerProperties, channelGroup, channelIdMap); 85 | } 86 | return connect; 87 | } 88 | 89 | public Subscribe subscribe() { 90 | if (subscribe == null) { 91 | subscribe = new Subscribe(subscribeStoreService, messageIdService, messageStoreService); 92 | } 93 | return subscribe; 94 | } 95 | 96 | public UnSubscribe unSubscribe() { 97 | if (unSubscribe == null) { 98 | unSubscribe = new UnSubscribe(subscribeStoreService); 99 | } 100 | return unSubscribe; 101 | } 102 | 103 | public Publish publish() { 104 | if (publish == null) { 105 | publish = new Publish(sessionStoreService, subscribeStoreService, messageIdService, messageStoreService, dupPublishMessageStoreService, internalCommunication, channelGroup, channelIdMap, brokerProperties); 106 | } 107 | return publish; 108 | } 109 | 110 | public DisConnect disConnect() { 111 | if (disConnect == null) { 112 | disConnect = new DisConnect(sessionStoreService, subscribeStoreService, dupPublishMessageStoreService, dupPubRelMessageStoreService); 113 | } 114 | return disConnect; 115 | } 116 | 117 | public PingReq pingReq() { 118 | if (pingReq == null) { 119 | pingReq = new PingReq(sessionStoreService, brokerProperties, channelGroup, channelIdMap); 120 | } 121 | return pingReq; 122 | } 123 | 124 | public PubRel pubRel() { 125 | if (pubRel == null) { 126 | pubRel = new PubRel(); 127 | } 128 | return pubRel; 129 | } 130 | 131 | public PubAck pubAck() { 132 | if (pubAck == null) { 133 | pubAck = new PubAck(dupPublishMessageStoreService); 134 | } 135 | return pubAck; 136 | } 137 | 138 | public PubRec pubRec() { 139 | if (pubRec == null) { 140 | pubRec = new PubRec(dupPublishMessageStoreService, dupPubRelMessageStoreService); 141 | } 142 | return pubRec; 143 | } 144 | 145 | public PubComp pubComp() { 146 | if (pubComp == null) { 147 | pubComp = new PubComp(dupPubRelMessageStoreService); 148 | } 149 | return pubComp; 150 | } 151 | 152 | public ISessionStoreService getSessionStoreService() { 153 | return sessionStoreService; 154 | } 155 | 156 | } 157 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/PubAck.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 8 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 9 | import io.netty.channel.Channel; 10 | import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; 11 | import io.netty.util.AttributeKey; 12 | import org.slf4j.Logger; 13 | import org.slf4j.LoggerFactory; 14 | 15 | /** 16 | * PUBACK连接处理 17 | */ 18 | public class PubAck { 19 | 20 | private static final Logger LOGGER = LoggerFactory.getLogger(PubAck.class); 21 | 22 | private IDupPublishMessageStoreService dupPublishMessageStoreService; 23 | 24 | public PubAck(IDupPublishMessageStoreService dupPublishMessageStoreService) { 25 | this.dupPublishMessageStoreService = dupPublishMessageStoreService; 26 | } 27 | 28 | public void processPubAck(Channel channel, MqttMessageIdVariableHeader variableHeader) { 29 | int messageId = variableHeader.messageId(); 30 | LOGGER.debug("PUBACK - clientId: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), messageId); 31 | dupPublishMessageStoreService.remove((String) channel.attr(AttributeKey.valueOf("clientId")).get(), messageId); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/PubComp.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 8 | import io.netty.channel.Channel; 9 | import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; 10 | import io.netty.util.AttributeKey; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | /** 15 | * PUBCOMP连接处理 16 | */ 17 | public class PubComp { 18 | 19 | private static final Logger LOGGER = LoggerFactory.getLogger(PubComp.class); 20 | 21 | private IDupPubRelMessageStoreService dupPubRelMessageStoreService; 22 | 23 | public PubComp(IDupPubRelMessageStoreService dupPubRelMessageStoreService) { 24 | this.dupPubRelMessageStoreService = dupPubRelMessageStoreService; 25 | } 26 | 27 | public void processPubComp(Channel channel, MqttMessageIdVariableHeader variableHeader) { 28 | int messageId = variableHeader.messageId(); 29 | LOGGER.debug("PUBCOMP - clientId: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), messageId); 30 | dupPubRelMessageStoreService.remove((String) channel.attr(AttributeKey.valueOf("clientId")).get(), variableHeader.messageId()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/PubRec.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.DupPubRelMessageStore; 8 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 10 | import io.netty.channel.Channel; 11 | import io.netty.handler.codec.mqtt.*; 12 | import io.netty.util.AttributeKey; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * PUBREC连接处理 18 | */ 19 | public class PubRec { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(PubRel.class); 22 | 23 | private IDupPublishMessageStoreService dupPublishMessageStoreService; 24 | 25 | private IDupPubRelMessageStoreService dupPubRelMessageStoreService; 26 | 27 | public PubRec(IDupPublishMessageStoreService dupPublishMessageStoreService, IDupPubRelMessageStoreService dupPubRelMessageStoreService) { 28 | this.dupPublishMessageStoreService = dupPublishMessageStoreService; 29 | this.dupPubRelMessageStoreService = dupPubRelMessageStoreService; 30 | } 31 | 32 | public void processPubRec(Channel channel, MqttMessageIdVariableHeader variableHeader) { 33 | MqttMessage pubRelMessage = MqttMessageFactory.newMessage( 34 | new MqttFixedHeader(MqttMessageType.PUBREL, false, MqttQoS.AT_MOST_ONCE, false, 0), 35 | MqttMessageIdVariableHeader.from(variableHeader.messageId()), null); 36 | LOGGER.debug("PUBREC - clientId: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), variableHeader.messageId()); 37 | dupPublishMessageStoreService.remove((String) channel.attr(AttributeKey.valueOf("clientId")).get(), variableHeader.messageId()); 38 | DupPubRelMessageStore dupPubRelMessageStore = new DupPubRelMessageStore().setClientId((String) channel.attr(AttributeKey.valueOf("clientId")).get()) 39 | .setMessageId(variableHeader.messageId()); 40 | dupPubRelMessageStoreService.put((String) channel.attr(AttributeKey.valueOf("clientId")).get(), dupPubRelMessageStore); 41 | channel.writeAndFlush(pubRelMessage); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/PubRel.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import io.netty.channel.Channel; 8 | import io.netty.handler.codec.mqtt.*; 9 | import io.netty.util.AttributeKey; 10 | import org.slf4j.Logger; 11 | import org.slf4j.LoggerFactory; 12 | 13 | /** 14 | * PUBREL连接处理 15 | */ 16 | public class PubRel { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(PubRel.class); 19 | 20 | public void processPubRel(Channel channel, MqttMessageIdVariableHeader variableHeader) { 21 | MqttMessage pubCompMessage = MqttMessageFactory.newMessage( 22 | new MqttFixedHeader(MqttMessageType.PUBCOMP, false, MqttQoS.AT_MOST_ONCE, false, 0), 23 | MqttMessageIdVariableHeader.from(variableHeader.messageId()), null); 24 | LOGGER.debug("PUBREL - clientId: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), variableHeader.messageId()); 25 | channel.writeAndFlush(pubCompMessage); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/Subscribe.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 9 | import cn.wizzer.iot.mqtt.server.common.message.IRetainMessageStoreService; 10 | import cn.wizzer.iot.mqtt.server.common.message.RetainMessageStore; 11 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 12 | import cn.wizzer.iot.mqtt.server.common.subscribe.SubscribeStore; 13 | import io.netty.buffer.Unpooled; 14 | import io.netty.channel.Channel; 15 | import io.netty.handler.codec.mqtt.*; 16 | import io.netty.util.AttributeKey; 17 | import org.slf4j.Logger; 18 | import org.slf4j.LoggerFactory; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | /** 24 | * SUBSCRIBE连接处理 25 | */ 26 | public class Subscribe { 27 | 28 | private static final Logger LOGGER = LoggerFactory.getLogger(Subscribe.class); 29 | 30 | private ISubscribeStoreService subscribeStoreService; 31 | 32 | private IMessageIdService messageIdService; 33 | 34 | private IRetainMessageStoreService retainMessageStoreService; 35 | 36 | public Subscribe(ISubscribeStoreService subscribeStoreService, IMessageIdService messageIdService, IRetainMessageStoreService retainMessageStoreService) { 37 | this.subscribeStoreService = subscribeStoreService; 38 | this.messageIdService = messageIdService; 39 | this.retainMessageStoreService = retainMessageStoreService; 40 | } 41 | 42 | public void processSubscribe(Channel channel, MqttSubscribeMessage msg) { 43 | List topicSubscriptions = msg.payload().topicSubscriptions(); 44 | if (this.validTopicFilter(topicSubscriptions)) { 45 | String clientId = (String) channel.attr(AttributeKey.valueOf("clientId")).get(); 46 | List mqttQoSList = new ArrayList(); 47 | topicSubscriptions.forEach(topicSubscription -> { 48 | String topicFilter = topicSubscription.topicName(); 49 | MqttQoS mqttQoS = topicSubscription.qualityOfService(); 50 | SubscribeStore subscribeStore = new SubscribeStore(clientId, topicFilter, mqttQoS.value()); 51 | subscribeStoreService.put(topicFilter, subscribeStore); 52 | mqttQoSList.add(mqttQoS.value()); 53 | LOGGER.debug("SUBSCRIBE - clientId: {}, topFilter: {}, QoS: {}", clientId, topicFilter, mqttQoS.value()); 54 | }); 55 | MqttSubAckMessage subAckMessage = (MqttSubAckMessage) MqttMessageFactory.newMessage( 56 | new MqttFixedHeader(MqttMessageType.SUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 57 | MqttMessageIdVariableHeader.from(msg.variableHeader().messageId()), 58 | new MqttSubAckPayload(mqttQoSList)); 59 | channel.writeAndFlush(subAckMessage); 60 | // 发布保留消息 61 | topicSubscriptions.forEach(topicSubscription -> { 62 | String topicFilter = topicSubscription.topicName(); 63 | MqttQoS mqttQoS = topicSubscription.qualityOfService(); 64 | this.sendRetainMessage(channel, topicFilter, mqttQoS); 65 | }); 66 | } else { 67 | channel.close(); 68 | } 69 | } 70 | 71 | private boolean validTopicFilter(List topicSubscriptions) { 72 | for (MqttTopicSubscription topicSubscription : topicSubscriptions) { 73 | String topicFilter = topicSubscription.topicName(); 74 | // 以#或+符号开头的、以/符号结尾的订阅按非法订阅处理, 这里没有参考标准协议 75 | if (StrUtil.startWith(topicFilter, '+') || StrUtil.endWith(topicFilter, '/')) 76 | return false; 77 | if (StrUtil.contains(topicFilter, '#')) { 78 | // 如果出现多个#符号的订阅按非法订阅处理 79 | if (StrUtil.count(topicFilter, '#') > 1) return false; 80 | } 81 | if (StrUtil.contains(topicFilter, '+')) { 82 | //如果+符号和/+字符串出现的次数不等的情况按非法订阅处理 83 | if (StrUtil.count(topicFilter, '+') != StrUtil.count(topicFilter, "/+")) return false; 84 | } 85 | } 86 | return true; 87 | } 88 | 89 | private void sendRetainMessage(Channel channel, String topicFilter, MqttQoS mqttQoS) { 90 | List retainMessageStores = retainMessageStoreService.search(topicFilter); 91 | retainMessageStores.forEach(retainMessageStore -> { 92 | MqttQoS respQoS = retainMessageStore.getMqttQoS() > mqttQoS.value() ? mqttQoS : MqttQoS.valueOf(retainMessageStore.getMqttQoS()); 93 | if (respQoS == MqttQoS.AT_MOST_ONCE) { 94 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 95 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, respQoS, false, 0), 96 | new MqttPublishVariableHeader(retainMessageStore.getTopic(), 0), Unpooled.buffer().writeBytes(retainMessageStore.getMessageBytes())); 97 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), retainMessageStore.getTopic(), respQoS.value()); 98 | channel.writeAndFlush(publishMessage); 99 | } 100 | if (respQoS == MqttQoS.AT_LEAST_ONCE) { 101 | int messageId = messageIdService.getNextMessageId(); 102 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 103 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, respQoS, false, 0), 104 | new MqttPublishVariableHeader(retainMessageStore.getTopic(), messageId), Unpooled.buffer().writeBytes(retainMessageStore.getMessageBytes())); 105 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), retainMessageStore.getTopic(), respQoS.value(), messageId); 106 | channel.writeAndFlush(publishMessage); 107 | } 108 | if (respQoS == MqttQoS.EXACTLY_ONCE) { 109 | int messageId = messageIdService.getNextMessageId(); 110 | MqttPublishMessage publishMessage = (MqttPublishMessage) MqttMessageFactory.newMessage( 111 | new MqttFixedHeader(MqttMessageType.PUBLISH, false, respQoS, false, 0), 112 | new MqttPublishVariableHeader(retainMessageStore.getTopic(), messageId), Unpooled.buffer().writeBytes(retainMessageStore.getMessageBytes())); 113 | LOGGER.debug("PUBLISH - clientId: {}, topic: {}, Qos: {}, messageId: {}", (String) channel.attr(AttributeKey.valueOf("clientId")).get(), retainMessageStore.getTopic(), respQoS.value(), messageId); 114 | channel.writeAndFlush(publishMessage); 115 | } 116 | }); 117 | } 118 | 119 | } 120 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/protocol/UnSubscribe.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.protocol; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 8 | import io.netty.channel.Channel; 9 | import io.netty.handler.codec.mqtt.*; 10 | import io.netty.util.AttributeKey; 11 | import org.slf4j.Logger; 12 | import org.slf4j.LoggerFactory; 13 | 14 | import java.util.List; 15 | 16 | /** 17 | * UNSUBSCRIBE连接处理 18 | */ 19 | public class UnSubscribe { 20 | 21 | private static final Logger LOGGER = LoggerFactory.getLogger(UnSubscribe.class); 22 | 23 | private ISubscribeStoreService subscribeStoreService; 24 | 25 | public UnSubscribe(ISubscribeStoreService subscribeStoreService) { 26 | this.subscribeStoreService = subscribeStoreService; 27 | } 28 | 29 | public void processUnSubscribe(Channel channel, MqttUnsubscribeMessage msg) { 30 | List topicFilters = msg.payload().topics(); 31 | String clinetId = (String) channel.attr(AttributeKey.valueOf("clientId")).get(); 32 | topicFilters.forEach(topicFilter -> { 33 | subscribeStoreService.remove(topicFilter, clinetId); 34 | LOGGER.debug("UNSUBSCRIBE - clientId: {}, topicFilter: {}", clinetId, topicFilter); 35 | }); 36 | MqttUnsubAckMessage unsubAckMessage = (MqttUnsubAckMessage) MqttMessageFactory.newMessage( 37 | new MqttFixedHeader(MqttMessageType.UNSUBACK, false, MqttQoS.AT_MOST_ONCE, false, 0), 38 | MqttMessageIdVariableHeader.from(msg.variableHeader().messageId()), null); 39 | channel.writeAndFlush(unsubAckMessage); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/server/BrokerServer.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.broker.server; 6 | 7 | import cn.wizzer.iot.mqtt.server.broker.codec.MqttWebSocketCodec; 8 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 9 | import cn.wizzer.iot.mqtt.server.broker.handler.BrokerHandler; 10 | import io.netty.bootstrap.ServerBootstrap; 11 | import io.netty.channel.*; 12 | import io.netty.channel.epoll.EpollEventLoopGroup; 13 | import io.netty.channel.epoll.EpollServerSocketChannel; 14 | import io.netty.channel.group.ChannelGroup; 15 | import io.netty.channel.group.DefaultChannelGroup; 16 | import io.netty.channel.nio.NioEventLoopGroup; 17 | import io.netty.channel.socket.SocketChannel; 18 | import io.netty.channel.socket.nio.NioServerSocketChannel; 19 | import io.netty.handler.codec.http.HttpContentCompressor; 20 | import io.netty.handler.codec.http.HttpObjectAggregator; 21 | import io.netty.handler.codec.http.HttpServerCodec; 22 | import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; 23 | import io.netty.handler.codec.mqtt.MqttDecoder; 24 | import io.netty.handler.codec.mqtt.MqttEncoder; 25 | import io.netty.handler.logging.LogLevel; 26 | import io.netty.handler.logging.LoggingHandler; 27 | import io.netty.handler.ssl.SslContext; 28 | import io.netty.handler.ssl.SslContextBuilder; 29 | import io.netty.handler.ssl.SslHandler; 30 | import io.netty.handler.timeout.IdleStateHandler; 31 | import io.netty.util.concurrent.GlobalEventExecutor; 32 | import org.nutz.boot.starter.ServerFace; 33 | import org.nutz.ioc.Ioc; 34 | import org.nutz.ioc.loader.annotation.Inject; 35 | import org.nutz.ioc.loader.annotation.IocBean; 36 | import org.nutz.lang.Strings; 37 | import org.slf4j.Logger; 38 | import org.slf4j.LoggerFactory; 39 | 40 | import javax.net.ssl.KeyManagerFactory; 41 | import javax.net.ssl.SSLEngine; 42 | import java.io.InputStream; 43 | import java.security.KeyStore; 44 | import java.util.HashMap; 45 | import java.util.Map; 46 | 47 | /** 48 | * 启动Broker 49 | */ 50 | @IocBean 51 | public class BrokerServer implements ServerFace { 52 | private static final Logger LOGGER = LoggerFactory.getLogger(BrokerServer.class); 53 | @Inject 54 | private BrokerProperties brokerProperties; 55 | @Inject("refer:$ioc") 56 | private Ioc ioc; 57 | private EventLoopGroup bossGroup; 58 | private EventLoopGroup workerGroup; 59 | private SslContext sslContext; 60 | private Channel channel; 61 | private Channel websocketChannel; 62 | private ChannelGroup channelGroup; 63 | private Map channelIdMap; 64 | 65 | public void start() throws Exception { 66 | LOGGER.info("Initializing {} MQTT Broker ...", "[" + brokerProperties.getId() + "]"); 67 | channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 68 | channelIdMap = new HashMap<>(); 69 | bossGroup = brokerProperties.getUseEpoll() ? new EpollEventLoopGroup(brokerProperties.getBossGroup_nThreads()) : new NioEventLoopGroup(brokerProperties.getBossGroup_nThreads()); 70 | workerGroup = brokerProperties.getUseEpoll() ? new EpollEventLoopGroup(brokerProperties.getWorkerGroup_nThreads()) : new NioEventLoopGroup(brokerProperties.getWorkerGroup_nThreads()); 71 | if (brokerProperties.getSslEnabled()) { 72 | KeyStore keyStore = KeyStore.getInstance("PKCS12"); 73 | InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("keystore/server.pfx"); 74 | keyStore.load(inputStream, brokerProperties.getSslPassword().toCharArray()); 75 | KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509"); 76 | kmf.init(keyStore, brokerProperties.getSslPassword().toCharArray()); 77 | sslContext = SslContextBuilder.forServer(kmf).build(); 78 | } 79 | mqttServer(); 80 | if (brokerProperties.getWebsocketEnabled()) { 81 | websocketServer(); 82 | LOGGER.info("MQTT Broker {} is up and running. Open Port: {} WebSocketPort: {}", "[" + brokerProperties.getId() + "]", brokerProperties.getPort(), brokerProperties.getWebsocketPort()); 83 | } else { 84 | LOGGER.info("MQTT Broker {} is up and running. Open Port: {} ", "[" + brokerProperties.getId() + "]", brokerProperties.getPort()); 85 | } 86 | } 87 | 88 | public void stop() { 89 | LOGGER.info("Shutdown {} MQTT Broker ...", "[" + brokerProperties.getId() + "]"); 90 | channelGroup = null; 91 | channelIdMap = null; 92 | bossGroup.shutdownGracefully(); 93 | bossGroup = null; 94 | workerGroup.shutdownGracefully(); 95 | workerGroup = null; 96 | channel.closeFuture().syncUninterruptibly(); 97 | channel = null; 98 | websocketChannel.closeFuture().syncUninterruptibly(); 99 | websocketChannel = null; 100 | LOGGER.info("MQTT Broker {} shutdown finish.", "[" + brokerProperties.getId() + "]"); 101 | } 102 | 103 | @IocBean(name = "channelGroup") 104 | public ChannelGroup getChannels() { 105 | return this.channelGroup; 106 | } 107 | 108 | @IocBean(name = "channelIdMap") 109 | public Map getChannelIdMap() { 110 | return this.channelIdMap; 111 | } 112 | 113 | private void mqttServer() throws Exception { 114 | ServerBootstrap sb = new ServerBootstrap(); 115 | sb.group(bossGroup, workerGroup) 116 | .channel(brokerProperties.getUseEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) 117 | // handler在初始化时就会执行 118 | .handler(new LoggingHandler(LogLevel.INFO)) 119 | // childHandler会在客户端成功connect后才执行 120 | .childHandler(new ChannelInitializer() { 121 | @Override 122 | protected void initChannel(SocketChannel socketChannel) throws Exception { 123 | ChannelPipeline channelPipeline = socketChannel.pipeline(); 124 | // Netty提供的心跳检测 125 | channelPipeline.addFirst("idle", new IdleStateHandler(0, 0, brokerProperties.getKeepAlive())); 126 | // Netty提供的SSL处理 127 | if (brokerProperties.getSslEnabled()) { 128 | SSLEngine sslEngine = sslContext.newEngine(socketChannel.alloc()); 129 | sslEngine.setUseClientMode(false); // 服务端模式 130 | sslEngine.setNeedClientAuth(false); // 不需要验证客户端 131 | channelPipeline.addLast("ssl", new SslHandler(sslEngine)); 132 | } 133 | channelPipeline.addLast("decoder", new MqttDecoder()); 134 | channelPipeline.addLast("encoder", MqttEncoder.INSTANCE); 135 | channelPipeline.addLast("broker", ioc.get(BrokerHandler.class)); 136 | } 137 | }) 138 | .option(ChannelOption.SO_BACKLOG, brokerProperties.getSoBacklog()) 139 | .childOption(ChannelOption.SO_KEEPALIVE, brokerProperties.getSoKeepAlive()); 140 | if (Strings.isNotBlank(brokerProperties.getHost())) { 141 | channel = sb.bind(brokerProperties.getHost(), brokerProperties.getPort()).sync().channel(); 142 | } else { 143 | channel = sb.bind(brokerProperties.getPort()).sync().channel(); 144 | } 145 | } 146 | 147 | private void websocketServer() throws Exception { 148 | ServerBootstrap sb = new ServerBootstrap(); 149 | sb.group(bossGroup, workerGroup) 150 | .channel(brokerProperties.getUseEpoll() ? EpollServerSocketChannel.class : NioServerSocketChannel.class) 151 | // handler在初始化时就会执行 152 | .handler(new LoggingHandler(LogLevel.INFO)) 153 | .childHandler(new ChannelInitializer() { 154 | @Override 155 | protected void initChannel(SocketChannel socketChannel) throws Exception { 156 | ChannelPipeline channelPipeline = socketChannel.pipeline(); 157 | // Netty提供的心跳检测 158 | channelPipeline.addFirst("idle", new IdleStateHandler(0, 0, brokerProperties.getKeepAlive())); 159 | // Netty提供的SSL处理 160 | if (brokerProperties.getSslEnabled()) { 161 | SSLEngine sslEngine = sslContext.newEngine(socketChannel.alloc()); 162 | sslEngine.setUseClientMode(false); // 服务端模式 163 | sslEngine.setNeedClientAuth(false); // 不需要验证客户端 164 | channelPipeline.addLast("ssl", new SslHandler(sslEngine)); 165 | } 166 | // 将请求和应答消息编码或解码为HTTP消息 167 | channelPipeline.addLast("http-codec", new HttpServerCodec()); 168 | // 将HTTP消息的多个部分合成一条完整的HTTP消息 169 | channelPipeline.addLast("aggregator", new HttpObjectAggregator(1048576)); 170 | // 将HTTP消息进行压缩编码 171 | channelPipeline.addLast("compressor ", new HttpContentCompressor()); 172 | channelPipeline.addLast("protocol", new WebSocketServerProtocolHandler(brokerProperties.getWebsocketPath(), "mqtt,mqttv3.1,mqttv3.1.1", true, 65536)); 173 | channelPipeline.addLast("mqttWebSocket", new MqttWebSocketCodec()); 174 | channelPipeline.addLast("decoder", new MqttDecoder()); 175 | channelPipeline.addLast("encoder", MqttEncoder.INSTANCE); 176 | channelPipeline.addLast("broker", ioc.get(BrokerHandler.class)); 177 | } 178 | }) 179 | .option(ChannelOption.SO_BACKLOG, brokerProperties.getSoBacklog()) 180 | .childOption(ChannelOption.SO_KEEPALIVE, brokerProperties.getSoKeepAlive()); 181 | if (Strings.isNotBlank(brokerProperties.getHost())) { 182 | websocketChannel = sb.bind(brokerProperties.getHost(), brokerProperties.getWebsocketPort()).sync().channel(); 183 | } else { 184 | websocketChannel = sb.bind(brokerProperties.getWebsocketPort()).sync().channel(); 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/service/KafkaService.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.broker.service; 2 | 3 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 4 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalMessage; 5 | import com.alibaba.fastjson.JSONObject; 6 | import org.apache.kafka.clients.producer.Callback; 7 | import org.apache.kafka.clients.producer.KafkaProducer; 8 | import org.apache.kafka.clients.producer.ProducerRecord; 9 | import org.apache.kafka.clients.producer.RecordMetadata; 10 | import org.nutz.aop.interceptor.async.Async; 11 | import org.nutz.ioc.loader.annotation.Inject; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | import org.slf4j.Logger; 14 | import org.slf4j.LoggerFactory; 15 | 16 | /** 17 | * Created by wizzer on 2018 18 | */ 19 | @IocBean 20 | public class KafkaService { 21 | private static final Logger LOGGER = LoggerFactory.getLogger(KafkaService.class); 22 | @Inject 23 | private KafkaProducer kafkaProducer; 24 | @Inject 25 | private BrokerProperties brokerProperties; 26 | 27 | @Async 28 | @SuppressWarnings("unchecked") 29 | public void send(InternalMessage internalMessage) { 30 | try { 31 | //消息体转换为Hex字符串进行转发 32 | ProducerRecord data = new ProducerRecord<>(brokerProperties.getProducerTopic(), internalMessage.getTopic(), JSONObject.toJSONString(internalMessage)); 33 | kafkaProducer.send(data, 34 | new Callback() { 35 | public void onCompletion(RecordMetadata metadata, Exception e) { 36 | if (e != null) { 37 | e.printStackTrace(); 38 | LOGGER.error(e.getMessage(), e); 39 | } else { 40 | LOGGER.info("The offset of the record we just sent is: " + metadata.offset()); 41 | } 42 | } 43 | }); 44 | } catch (Exception e) { 45 | LOGGER.error("kafka没有连接成功.."); 46 | } 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/cn/wizzer/iot/mqtt/server/broker/webapi/WebApiController.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.broker.webapi; 2 | 3 | import cn.wizzer.iot.mqtt.server.broker.cluster.RedisCluster; 4 | import cn.wizzer.iot.mqtt.server.broker.config.BrokerProperties; 5 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalMessage; 6 | import cn.wizzer.iot.mqtt.server.broker.internal.InternalSendServer; 7 | import io.netty.handler.codec.mqtt.MqttQoS; 8 | import org.nutz.http.Request; 9 | import org.nutz.http.Response; 10 | import org.nutz.http.Sender; 11 | import org.nutz.integration.jedis.JedisAgent; 12 | import org.nutz.integration.jedis.RedisService; 13 | import org.nutz.ioc.loader.annotation.Inject; 14 | import org.nutz.ioc.loader.annotation.IocBean; 15 | import org.nutz.json.Json; 16 | import org.nutz.lang.Lang; 17 | import org.nutz.lang.Streams; 18 | import org.nutz.lang.random.R; 19 | import org.nutz.lang.util.NutMap; 20 | import org.nutz.log.Log; 21 | import org.nutz.log.Logs; 22 | import org.nutz.mvc.adaptor.JsonAdaptor; 23 | import org.nutz.mvc.annotation.AdaptBy; 24 | import org.nutz.mvc.annotation.At; 25 | import org.nutz.mvc.annotation.Ok; 26 | import redis.clients.jedis.*; 27 | 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | /** 32 | * Created by wizzer on 2019/5/24 33 | */ 34 | @IocBean 35 | @At("/open/api/mqttwk") 36 | public class WebApiController { 37 | private static final Log log = Logs.get(); 38 | private static final String CACHE_SESSION_PRE = "mqttwk:session:"; 39 | private static final String CACHE_CLIENT_PRE = "mqttwk:client:"; 40 | @Inject 41 | private RedisService redisService; 42 | @Inject 43 | private InternalSendServer internalSendServer; 44 | @Inject 45 | private BrokerProperties brokerProperties; 46 | @Inject 47 | private RedisCluster redisCluster; 48 | @Inject 49 | private JedisAgent jedisAgent; 50 | 51 | /** 52 | * 向设备发送数据,发送格式见 test_send 方法实例代码 53 | * @param data 54 | * @return 55 | */ 56 | @At("/send") 57 | @Ok("json") 58 | @AdaptBy(type = JsonAdaptor.class) 59 | public Object send(NutMap data) { 60 | NutMap nutMap = NutMap.NEW(); 61 | try { 62 | String processId = Lang.JdkTool.getProcessId("0"); 63 | InternalMessage message = new InternalMessage(); 64 | message.setBrokerId(brokerProperties.getId()); 65 | message.setProcessId(processId); 66 | message.setClientId(R.UU32()); 67 | message.setTopic(data.getString("topic", "")); 68 | message.setRetain(data.getBoolean("retain")); 69 | message.setDup(data.getBoolean("dup")); 70 | message.setMqttQoS(data.getInt("qos")); 71 | message.setMessageBytes(data.getString("message", "").getBytes()); 72 | log.debug("send:::" + Json.toJson(message)); 73 | //如果开启集群功能 74 | if (brokerProperties.getClusterEnabled()) { 75 | redisCluster.sendMessage(message); 76 | } else { 77 | internalSendServer.sendPublishMessage(message.getClientId(), message.getTopic(), MqttQoS.valueOf(message.getMqttQoS()), message.getMessageBytes(), message.isRetain(), message.isDup()); 78 | } 79 | nutMap.put("code", 0); 80 | nutMap.put("msg", "success"); 81 | } catch (Exception e) { 82 | log.error(e); 83 | nutMap.put("code", -1); 84 | nutMap.put("msg", e.getMessage()); 85 | } 86 | return nutMap; 87 | } 88 | 89 | @At("/test_send") 90 | @Ok("json") 91 | public Object test_send() { 92 | NutMap nutMap = NutMap.NEW(); 93 | try { 94 | Request req = Request.create("http://127.0.0.1:8922/open/api/mqttwk/send", Request.METHOD.POST); 95 | NutMap message = NutMap.NEW(); 96 | message.addv("topic", "/topic/mqttwk"); 97 | message.addv("retain", true); 98 | message.addv("dup", true); 99 | message.addv("qos", 1); 100 | message.addv("message", "wizzer"); 101 | req.setData(Json.toJson(message)); 102 | Response resp = Sender.create(req).send(); 103 | if (resp.isOK()) { 104 | nutMap.put("code", 0); 105 | } 106 | } catch (Exception e) { 107 | log.error(e); 108 | nutMap.put("code", -1); 109 | } 110 | return nutMap; 111 | } 112 | 113 | /** 114 | * 获取在线设备数量、客户端名称、订阅主题 115 | * example: {"code":0,"msg":"","data":{"total":0,"list":[{"clientId":"pc-web","topics":["/topic_back"]}]}} 116 | */ 117 | @At("/info") 118 | @Ok("json") 119 | public Object info() { 120 | NutMap nutMap = NutMap.NEW(); 121 | try { 122 | NutMap data = NutMap.NEW(); 123 | ScanParams match = new ScanParams().match(CACHE_SESSION_PRE + "*"); 124 | List keys = new ArrayList<>(); 125 | if (jedisAgent.isClusterMode()) { 126 | JedisCluster jedisCluster = jedisAgent.getJedisClusterWrapper().getJedisCluster(); 127 | for (JedisPool pool : jedisCluster.getClusterNodes().values()) { 128 | try (Jedis jedis = pool.getResource()) { 129 | ScanResult scan = null; 130 | do { 131 | scan = jedis.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match); 132 | keys.addAll(scan.getResult()); 133 | } while (!scan.isCompleteIteration()); 134 | } 135 | } 136 | } else { 137 | Jedis jedis = null; 138 | try { 139 | jedis =jedisAgent.jedis(); 140 | ScanResult scan = null; 141 | do { 142 | scan = jedis.scan(scan == null ? ScanParams.SCAN_POINTER_START : scan.getStringCursor(), match); 143 | keys.addAll(scan.getResult()); 144 | } while (!scan.isCompleteIteration()); 145 | } finally { 146 | Streams.safeClose(jedis); 147 | } 148 | } 149 | List dataList = new ArrayList<>(); 150 | for (String k : keys) { 151 | dataList.add(NutMap.NEW() 152 | .addv("clientId", k.substring(k.lastIndexOf(":") + 1)) 153 | .addv("topics", redisService.smembers(CACHE_CLIENT_PRE + k.substring(k.lastIndexOf(":") + 1))) 154 | ); 155 | } 156 | data.addv("total", keys.size()); 157 | data.addv("list", dataList); 158 | nutMap.put("code", 0); 159 | nutMap.put("msg", ""); 160 | nutMap.put("data", data); 161 | } catch (Exception e) { 162 | log.error(e); 163 | nutMap.put("code", -1); 164 | nutMap.put("msg", e.getMessage()); 165 | } 166 | return nutMap; 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/resources/META-INF/nutz/org.nutz.boot.config.ConfigureLoader: -------------------------------------------------------------------------------- 1 | org.nutz.boot.config.impl.YamlConfigureLoader -------------------------------------------------------------------------------- /mqtt-broker/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | nutz: 2 | application: 3 | name: mqttwk 4 | propdoc: 5 | packages: cn.wizzer.iot.mqtt.server.broker 6 | 7 | redis: 8 | host: 127.0.0.1 9 | port: 6379 10 | timeout: 2000 11 | max_redir: 10 12 | database: 0 13 | maxTotal: 100 14 | #password: 123 15 | pool: 16 | maxTotal: 200 17 | maxIdle: 50 18 | minIdle: 10 19 | # 集群模式=cluster 单机模式=normal 20 | mode: normal 21 | # 集群模式配置节点 22 | #nodes: 23 | 24 | mqttwk: 25 | broker: 26 | # broker唯一标识,用于集群实例判断 27 | id: mqttwk 28 | host: 29 | port: 8885 30 | # websocket 端口 31 | websocket-port: 9995 32 | # websocket 是否启用 33 | websocket-enabled: true 34 | # bossGroup线程数 35 | bossGroup-nThreads: 4 36 | # workerGroup线程数 37 | workerGroup-nThreads: 200 38 | # 是否开启集群功能 39 | cluster-on: false 40 | # 启用ssl验证(含websocket) 41 | ssl-enabled: false 42 | # SSL密钥文件密码 43 | ssl-password: 123456 44 | # MQTT服务端是否强制要求Connect消息必须通过用户名密码验证 45 | mqtt-password-must: true 46 | # 默认心跳时间(秒)(会被客户端参数覆盖) 47 | keep-alive: 60 48 | # 是否开启Epoll模式, 默认关闭 49 | use-epoll: false 50 | # Sokcet参数, 存放已完成三次握手请求的队列最大长度, 默认511长度 51 | so-backlog: 511 52 | # Socket参数, 是否开启心跳保活机制, 默认开启 53 | so-keep-alive: true 54 | kafka: 55 | # 是否启用kafka消息转发 56 | broker-enabled: false 57 | bootstrap: 58 | # kafka地址 127.0.0.1:9092,127.0.0.1:9093 59 | servers: 127.0.0.1:9092 60 | # acks回令 如果必须等待回令,那么设置acks为all,否则,设置为-1,等待回令会有性能损耗 61 | acks: -1 62 | # 重试次数 63 | retries: 3 64 | batch: 65 | # 批量提交大小 66 | size: 16384 67 | linger: 68 | # 提交延迟等待时间(等待时间内可以追加提交) 69 | ms: 1 70 | buffer: 71 | # 缓存大小 72 | memory: 33554432 73 | key: 74 | # 序列化方式 75 | serializer: org.apache.kafka.common.serialization.StringSerializer 76 | value: 77 | # 序列化方式 78 | serializer: org.apache.kafka.common.serialization.StringSerializer 79 | partitioner: 80 | class: cn.wizzer.iot.mqtt.server.store.kafka.SimplePartitioner 81 | producer: 82 | # kafka转发的主题 83 | topic: mqtt_publish 84 | 85 | 86 | # web api 服务相关配置 87 | server: 88 | port: 8922 89 | jetty: 90 | contextPath: / 91 | threadpool: 92 | idleTimeout: 60000 93 | minThreads: 10 94 | maxThreads: 200 -------------------------------------------------------------------------------- /mqtt-broker/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | :: MqttWk by https://wizzer.cn 2 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/resources/keystore/server.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wizzercn/MqttWk/addfd0ad452a1917e8a95e3cf06096696ce3fc38/mqtt-broker/src/main/resources/keystore/server.pfx -------------------------------------------------------------------------------- /mqtt-broker/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %highlight(%date{yyyy-MM-dd HH:mm:ss.SSS}) %cyan([%thread]) %yellow(%-5level) %green(%logger{36}).%gray(%M)-%boldMagenta(%line) - %blue(%msg%n) 7 | 8 | 9 | 10 | 11 | 12 | 13 | logs/mqttwk-%d{yyyy-MM-dd}.%i.txt 14 | 15 | 50MB 16 | 30 17 | 1GB 18 | 19 | 20 | [%-5level] %d{HH:mm:ss.SSS} %logger - %msg%n 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 30 | 256 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /mqtt-client/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | cn.wizzer 7 | mqtt-client 8 | hy-1.0 9 | MqttClient 10 | 11 | 2.4.2-SNAPSHOT 12 | 1.2.2 13 | 8.0.22 14 | 2.3.1 15 | 1.7.25 16 | 1.2.9 17 | UTF-8 18 | UTF-8 19 | 20 | 21 | 22 | 23 | org.nutz 24 | nutzboot-core 25 | 26 | 27 | org.nutz 28 | nutzboot-starter-nutz-dao 29 | 30 | 31 | org.nutz 32 | nutzboot-starter-jdbc 33 | 34 | 35 | mysql 36 | mysql-connector-java 37 | ${mysql-connector-java.version} 38 | 39 | 40 | org.slf4j 41 | slf4j-api 42 | ${slf4j.version} 43 | 44 | 45 | ch.qos.logback 46 | logback-core 47 | ${logback.version} 48 | 49 | 50 | ch.qos.logback 51 | logback-classic 52 | ${logback.version} 53 | 54 | 55 | org.eclipse.paho 56 | org.eclipse.paho.client.mqttv3 57 | ${mqttv3.version} 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.nutz 65 | nutzboot-parent 66 | ${nutzboot.version} 67 | pom 68 | import 69 | 70 | 71 | 72 | 73 | 74 | nutz 75 | http://jfrog.nutz.cn/artifactory/libs-release 76 | 77 | 78 | nutz-snapshots 79 | http://jfrog.nutz.cn/artifactory/snapshots 80 | 81 | true 82 | always 83 | 84 | 85 | false 86 | 87 | 88 | 89 | 90 | 91 | nutz-snapshots 92 | http://jfrog.nutz.cn/artifactory/snapshots 93 | 94 | true 95 | always 96 | 97 | 98 | false 99 | 100 | 101 | 102 | 103 | 104 | 105 | org.apache.maven.plugins 106 | maven-compiler-plugin 107 | 3.7.0 108 | 109 | 1.8 110 | 1.8 111 | 112 | -parameters 113 | 114 | false 115 | 116 | 117 | 118 | org.nutz.boot 119 | nutzboot-maven-plugin 120 | ${nutzboot.version} 121 | 122 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/ClientMainLauncher.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot; 2 | 3 | import cn.wizzer.iot.mqtt.MqttReciever; 4 | import cn.wizzer.iot.mqtt.MqttSender; 5 | import org.nutz.boot.NbApp; 6 | import org.nutz.dao.Dao; 7 | import org.nutz.dao.util.Daos; 8 | import org.nutz.ioc.Ioc; 9 | import org.nutz.ioc.impl.PropertiesProxy; 10 | import org.nutz.ioc.loader.annotation.Inject; 11 | import org.nutz.ioc.loader.annotation.IocBean; 12 | import org.nutz.log.Log; 13 | import org.nutz.log.Logs; 14 | 15 | /** 16 | * @author wizzer@qq.com 17 | */ 18 | @IocBean(create = "init") 19 | public class ClientMainLauncher { 20 | private static final Log log = Logs.get(); 21 | @Inject("refer:$ioc") 22 | private Ioc ioc; 23 | @Inject 24 | private PropertiesProxy conf; 25 | @Inject 26 | private Dao dao; 27 | 28 | 29 | public static void main(String[] args) throws Exception { 30 | NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true); 31 | nb.getAppContext().setMainPackage("cn.wizzer"); 32 | nb.run(); 33 | } 34 | 35 | public void init() { 36 | try { 37 | //通过POJO类创建表结构 38 | Daos.createTablesInPackage(dao, "cn.wizzer", false); 39 | //通过POJO类修改表结构 40 | Daos.migration(dao, "cn.wizzer", true, false); 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } 44 | // 初始化MQTT接收者,并订阅topic 45 | ioc.get(MqttReciever.class);//MQTT订阅 46 | // 模拟MQTT发送消息 47 | this.test_send(); 48 | } 49 | 50 | public void test_send() { 51 | try { 52 | MqttSender myMqttClient = ioc.get(MqttSender.class); 53 | int i=0; 54 | while (true) { 55 | //topic一般包含设备ID 56 | myMqttClient.publishMessage("/mqtt/dev/dev00001", "hello"+i, 1); 57 | Thread.sleep(2500); 58 | i++; 59 | } 60 | } catch (Exception e) { 61 | e.printStackTrace(); 62 | } 63 | } 64 | 65 | } 66 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/model/IotDev.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.model; 2 | 3 | import org.nutz.dao.entity.annotation.*; 4 | import org.nutz.dao.interceptor.annotation.PrevInsert; 5 | 6 | import java.io.Serializable; 7 | 8 | /** 9 | * @author wizzer@qq.com 10 | */ 11 | @Table("iot_dev") 12 | public class IotDev implements Serializable { 13 | private static final long serialVersionUID = 1L; 14 | 15 | @Column 16 | @Name 17 | @Comment("ID") 18 | @ColDefine(type = ColType.VARCHAR, width = 32) 19 | @PrevInsert(els = {@EL("uuid()")}) 20 | private String id; 21 | 22 | @Column 23 | @Comment("设备ID") 24 | @ColDefine(type = ColType.VARCHAR, width = 255) 25 | private String devId; 26 | 27 | @Column 28 | @Comment("设备状态(0-在线/1-离线/2-异常") 29 | @ColDefine(type = ColType.INT) 30 | private int devStatus; 31 | 32 | @Column 33 | @Comment("电池容量") 34 | @ColDefine(type = ColType.VARCHAR, width = 32) 35 | private String devBattery; 36 | 37 | @Column 38 | @Comment("JSON") 39 | @ColDefine(type = ColType.VARCHAR, width = 500) 40 | private String devData; 41 | 42 | @Column 43 | @Comment("创建时间") 44 | @PrevInsert(now = true) 45 | private Long createdAt; 46 | 47 | public String getId() { 48 | return id; 49 | } 50 | 51 | public void setId(String id) { 52 | this.id = id; 53 | } 54 | 55 | public String getDevId() { 56 | return devId; 57 | } 58 | 59 | public void setDevId(String devId) { 60 | this.devId = devId; 61 | } 62 | 63 | public int getDevStatus() { 64 | return devStatus; 65 | } 66 | 67 | public void setDevStatus(int devStatus) { 68 | this.devStatus = devStatus; 69 | } 70 | 71 | public String getDevBattery() { 72 | return devBattery; 73 | } 74 | 75 | public void setDevBattery(String devBattery) { 76 | this.devBattery = devBattery; 77 | } 78 | 79 | public String getDevData() { 80 | return devData; 81 | } 82 | 83 | public void setDevData(String devData) { 84 | this.devData = devData; 85 | } 86 | 87 | public Long getCreatedAt() { 88 | return createdAt; 89 | } 90 | 91 | public void setCreatedAt(Long createdAt) { 92 | this.createdAt = createdAt; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/mqtt/MqttRecieveCallback.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt; 2 | 3 | import cn.wizzer.iot.model.IotDev; 4 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 5 | import org.eclipse.paho.client.mqttv3.MqttCallback; 6 | import org.eclipse.paho.client.mqttv3.MqttMessage; 7 | import org.nutz.aop.interceptor.async.Async; 8 | import org.nutz.dao.Dao; 9 | import org.nutz.ioc.loader.annotation.Inject; 10 | import org.nutz.ioc.loader.annotation.IocBean; 11 | import org.nutz.log.Log; 12 | import org.nutz.log.Logs; 13 | 14 | /** 15 | * 接收者消息回调 16 | * @author wizzer@qq.com 17 | */ 18 | @IocBean 19 | public class MqttRecieveCallback implements MqttCallback { 20 | private static final Log log = Logs.get(); 21 | @Inject 22 | private Dao dao; 23 | 24 | public void connectionLost(Throwable cause) { 25 | log.error(cause); 26 | } 27 | 28 | public void messageArrived(String topic, MqttMessage message) throws Exception { 29 | System.out.println("Client 接收消息主题 : " + topic); 30 | System.out.println("Client 接收消息Qos : " + message.getQos()); 31 | System.out.println("Client 接收消息内容 : " + new String(message.getPayload())); 32 | String devId = topic; 33 | if(topic.contains("/")){ 34 | devId=topic.substring(topic.lastIndexOf("/")+1); 35 | System.out.println("设备ID : " + devId); 36 | IotDev iotDev=new IotDev(); 37 | iotDev.setDevId(devId); 38 | iotDev.setDevData(new String(message.getPayload())); 39 | insert(iotDev); 40 | } 41 | 42 | } 43 | 44 | public void deliveryComplete(IMqttDeliveryToken token) { 45 | 46 | } 47 | 48 | // 异步插入数据库 49 | @Async 50 | public void insert(IotDev iotDev){ 51 | dao.fastInsert(iotDev); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/mqtt/MqttReciever.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt; 2 | 3 | import org.eclipse.paho.client.mqttv3.MqttClient; 4 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 5 | import org.eclipse.paho.client.mqttv3.MqttException; 6 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 7 | import org.nutz.ioc.Ioc; 8 | import org.nutz.ioc.impl.PropertiesProxy; 9 | import org.nutz.ioc.loader.annotation.Inject; 10 | import org.nutz.ioc.loader.annotation.IocBean; 11 | import org.nutz.log.Log; 12 | import org.nutz.log.Logs; 13 | 14 | /** 15 | * @author wizzer@qq.com 16 | */ 17 | @IocBean(create = "init") 18 | public class MqttReciever { 19 | private static final Log log = Logs.get(); 20 | @Inject("refer:$ioc") 21 | private Ioc ioc; 22 | @Inject 23 | private PropertiesProxy conf; 24 | 25 | private static int QoS = 1; 26 | public static MqttClient mqttClient = null; 27 | private static MemoryPersistence memoryPersistence = null; 28 | private static MqttConnectOptions mqttConnectOptions = null; 29 | 30 | private static String mqtt_host; 31 | private static String mqtt_clientId; 32 | private static String mqtt_topic; 33 | private static String mqtt_username; 34 | private static String mqtt_password; 35 | 36 | public void init() { 37 | mqtt_host = conf.get("mqtt.host", ""); 38 | mqtt_clientId = conf.get("mqtt.recieverClientId", ""); 39 | mqtt_topic = conf.get("mqtt.topic", ""); 40 | mqtt_username = conf.get("mqtt.username", ""); 41 | mqtt_password = conf.get("mqtt.password", ""); 42 | memoryPersistence = new MemoryPersistence(); 43 | try { 44 | mqttClient = new MqttClient(mqtt_host, mqtt_clientId, memoryPersistence); 45 | } catch (MqttException e) { 46 | log.errorf("mqttClient %s", e.getMessage()); 47 | } 48 | mqttConnectOptions = new MqttConnectOptions(); 49 | mqttConnectOptions.setCleanSession(false); 50 | mqttConnectOptions.setAutomaticReconnect(true); 51 | //超时时间 60秒 52 | mqttConnectOptions.setConnectionTimeout(60); 53 | //心跳时间 超时断开连接 54 | mqttConnectOptions.setKeepAliveInterval(0); 55 | mqttConnectOptions.setUserName(mqtt_username); 56 | mqttConnectOptions.setPassword(mqtt_password.toCharArray()); 57 | if (null != mqttClient && !mqttClient.isConnected()) { 58 | try { 59 | mqttClient.setCallback(ioc.get(MqttRecieveCallback.class)); 60 | mqttClient.connect(mqttConnectOptions); 61 | mqttClient.subscribe(mqtt_topic, QoS); 62 | } catch (MqttException e) { 63 | log.errorf("mqttClient %s", e.getMessage()); 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/mqtt/MqttSender.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt; 2 | 3 | import org.eclipse.paho.client.mqttv3.*; 4 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 5 | import org.nutz.ioc.Ioc; 6 | import org.nutz.ioc.impl.PropertiesProxy; 7 | import org.nutz.ioc.loader.annotation.Inject; 8 | import org.nutz.ioc.loader.annotation.IocBean; 9 | import org.nutz.log.Log; 10 | import org.nutz.log.Logs; 11 | 12 | /** 13 | * @author wizzer@qq.com 14 | */ 15 | @IocBean(create = "init") 16 | public class MqttSender { 17 | private static final Log log = Logs.get(); 18 | @Inject("refer:$ioc") 19 | private Ioc ioc; 20 | @Inject 21 | private PropertiesProxy conf; 22 | 23 | public static MqttClient mqttClient = null; 24 | private static MemoryPersistence memoryPersistence = null; 25 | private static MqttConnectOptions mqttConnectOptions = null; 26 | 27 | private static String mqtt_host; 28 | private static String mqtt_clientId; 29 | private static String mqtt_topic; 30 | private static String mqtt_username; 31 | private static String mqtt_password; 32 | 33 | public void init() { 34 | mqtt_host = conf.get("mqtt.host", ""); 35 | mqtt_clientId = conf.get("mqtt.senderClientId", ""); 36 | mqtt_topic = conf.get("mqtt.topic", ""); 37 | mqtt_username = conf.get("mqtt.username", ""); 38 | mqtt_password = conf.get("mqtt.password", ""); 39 | //初始化连接设置对象 40 | mqttConnectOptions = new MqttConnectOptions(); 41 | //初始化MqttClient 42 | 43 | //true可以安全地使用内存持久性作为客户端断开连接时清除的所有状态 44 | mqttConnectOptions.setCleanSession(true); 45 | //设置连接超时 46 | mqttConnectOptions.setConnectionTimeout(30); 47 | mqttConnectOptions.setUserName(mqtt_username); 48 | mqttConnectOptions.setPassword(mqtt_password.toCharArray()); 49 | //设置持久化方式 50 | memoryPersistence = new MemoryPersistence(); 51 | try { 52 | mqttClient = new MqttClient(mqtt_host, mqtt_clientId, memoryPersistence); 53 | } catch (MqttException e) { 54 | e.printStackTrace(); 55 | } 56 | //设置连接和回调 57 | if (null != mqttClient) { 58 | if (!mqttClient.isConnected()) { 59 | //客户端添加回调函数 60 | mqttClient.setCallback(ioc.get(MqttSenderCallback.class)); 61 | //创建连接 62 | try { 63 | log.info("创建连接"); 64 | mqttClient.connect(mqttConnectOptions); 65 | } catch (MqttException e) { 66 | log.errorf("创建连接失败 %s", e.getMessage()); 67 | } 68 | 69 | } 70 | } else { 71 | log.error("mqttClient 为空"); 72 | } 73 | } 74 | 75 | public void closeConnect() { 76 | //关闭存储方式 77 | if (null != memoryPersistence) { 78 | try { 79 | memoryPersistence.close(); 80 | } catch (MqttPersistenceException e) { 81 | e.printStackTrace(); 82 | } 83 | } 84 | 85 | //关闭连接 86 | if (null != mqttClient) { 87 | if (mqttClient.isConnected()) { 88 | try { 89 | mqttClient.disconnect(); 90 | mqttClient.close(); 91 | } catch (MqttException e) { 92 | e.printStackTrace(); 93 | } 94 | } 95 | } 96 | } 97 | 98 | public void publishMessage(String pubTopic, String message, int qos) { 99 | if (null != mqttClient && mqttClient.isConnected()) { 100 | MqttMessage mqttMessage = new MqttMessage(); 101 | mqttMessage.setQos(qos); 102 | mqttMessage.setPayload(message.getBytes()); 103 | MqttTopic topic = mqttClient.getTopic(pubTopic); 104 | if (null != topic) { 105 | try { 106 | MqttDeliveryToken publish = topic.publish(mqttMessage); 107 | if (!publish.isComplete()) { 108 | log.infof("消息发布成功::%s - %s", pubTopic, message); 109 | } 110 | } catch (MqttException e) { 111 | log.errorf("消息发布失败::%s", e.getMessage()); 112 | } 113 | } 114 | 115 | } else { 116 | reConnect(); 117 | publishMessage(pubTopic, message, qos); 118 | } 119 | 120 | } 121 | 122 | //重新连接 123 | public void reConnect() { 124 | if (null != mqttClient) { 125 | if (!mqttClient.isConnected()) { 126 | if (null != mqttConnectOptions) { 127 | try { 128 | mqttClient.connect(mqttConnectOptions); 129 | } catch (MqttException e) { 130 | e.printStackTrace(); 131 | } 132 | } 133 | } 134 | } else { 135 | init(); 136 | } 137 | 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /mqtt-client/src/main/java/cn/wizzer/iot/mqtt/MqttSenderCallback.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt; 2 | 3 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 4 | import org.eclipse.paho.client.mqttv3.MqttCallback; 5 | import org.eclipse.paho.client.mqttv3.MqttMessage; 6 | import org.nutz.ioc.loader.annotation.IocBean; 7 | 8 | /** 9 | * 发送者消息回调 10 | * @author wizzer@qq.com 11 | */ 12 | @IocBean 13 | public class MqttSenderCallback implements MqttCallback { 14 | 15 | public void connectionLost(Throwable cause) { 16 | 17 | } 18 | 19 | public void messageArrived(String topic, MqttMessage message) throws Exception { 20 | System.out.println("Client 接收消息主题 : " + topic); 21 | System.out.println("Client 接收消息Qos : " + message.getQos()); 22 | System.out.println("Client 接收消息内容 : " + new String(message.getPayload())); 23 | } 24 | 25 | public void deliveryComplete(IMqttDeliveryToken token) { 26 | 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /mqtt-client/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | # 开发环境配置 2 | nutz.application.name=mqtt_client 3 | 4 | jdbc.url=jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai 5 | jdbc.username=root 6 | jdbc.password=root 7 | jdbc.validationQuery=select 1 8 | jdbc.maxActive=150 9 | jdbc.testWhileIdle=true 10 | jdbc.connectionProperties=druid.stat.slowSqlMillis=2000 11 | jdbc.defaultAutoCommit=true 12 | 13 | mqtt.host=tcp://127.0.0.1:8885 14 | mqtt.senderClientId=mqtt_sender 15 | mqtt.recieverClientId=mqtt_reciever 16 | mqtt.topic=/mqtt/dev/# 17 | mqtt.username=demo 18 | mqtt.password=8F3B8DE2FDC8BD3D792BE77EAC412010971765E5BDD6C499ADCEE840CE441BDEF17E30684BD95CA708F55022222CC6161D0D23C2DFCB12F8AC998F59E7213393 19 | 20 | 21 | # rabbitmq 队列转发配置 22 | # 10万以内设备或单机部署无需启用 23 | rabbitmq.enabled=false 24 | rabbitmq.host=127.0.0.1 25 | rabbitmq.port=5672 26 | rabbitmq.username=aaa 27 | rabbitmq.password=bbb 28 | # 并发取消息数量 29 | rabbitmq.concurrency=50 30 | # 上行队列名,设备数据转发到队里 31 | rabbitmq.up_queue=mqtt_up -------------------------------------------------------------------------------- /mqtt-client/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | :: MqttWk by https://wizzer.cn 2 | -------------------------------------------------------------------------------- /mqtt-client/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | [%-5level] %d{HH:mm:ss.SSS} [%thread] %logger - %msg%n 7 | 8 | 9 | 10 | 11 | 12 | /data/mqtt/logs/client-%d{yyyy-MM-dd}.log 13 | 15 14 | 15 | 16 | [%-5level] %d{HH:mm:ss.SSS} %logger - %msg%n 17 | 18 | 19 | 20 | 21 | 22 | 23 | 0 24 | 25 | 256 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /mqtt-common/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mqtt-wk 7 | cn.wizzer 8 | 1.4.0-netty 9 | 10 | 11 | 4.0.0 12 | jar 13 | mqtt-common 14 | 15 | 16 | io.netty 17 | netty-codec-mqtt 18 | ${netty.version} 19 | 20 | 21 | io.netty 22 | netty-codec-http 23 | ${netty.version} 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/auth/IAuthService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.auth; 6 | 7 | /** 8 | * 用户和密码认证服务接口 9 | */ 10 | public interface IAuthService { 11 | 12 | /** 13 | * 验证用户名和密码是否正确 14 | */ 15 | boolean checkValid(String username, String password); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/DupPubRelMessageStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * PUBREL重发消息存储 11 | */ 12 | public class DupPubRelMessageStore implements Serializable { 13 | 14 | private static final long serialVersionUID = -4111642532532950980L; 15 | 16 | private String clientId; 17 | 18 | private int messageId; 19 | 20 | public String getClientId() { 21 | return clientId; 22 | } 23 | 24 | public DupPubRelMessageStore setClientId(String clientId) { 25 | this.clientId = clientId; 26 | return this; 27 | } 28 | 29 | public int getMessageId() { 30 | return messageId; 31 | } 32 | 33 | public DupPubRelMessageStore setMessageId(int messageId) { 34 | this.messageId = messageId; 35 | return this; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/DupPublishMessageStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * PUBLISH重发消息存储 11 | */ 12 | public class DupPublishMessageStore implements Serializable { 13 | 14 | private static final long serialVersionUID = -8112511377194421600L; 15 | 16 | private String clientId; 17 | 18 | private String topic; 19 | 20 | private int mqttQoS; 21 | 22 | private int messageId; 23 | 24 | private byte[] messageBytes; 25 | 26 | 27 | public String getClientId() { 28 | return clientId; 29 | } 30 | 31 | public DupPublishMessageStore setClientId(String clientId) { 32 | this.clientId = clientId; 33 | return this; 34 | } 35 | 36 | public String getTopic() { 37 | return topic; 38 | } 39 | 40 | public DupPublishMessageStore setTopic(String topic) { 41 | this.topic = topic; 42 | return this; 43 | } 44 | 45 | public int getMqttQoS() { 46 | return mqttQoS; 47 | } 48 | 49 | public DupPublishMessageStore setMqttQoS(int mqttQoS) { 50 | this.mqttQoS = mqttQoS; 51 | return this; 52 | } 53 | 54 | public int getMessageId() { 55 | return messageId; 56 | } 57 | 58 | public DupPublishMessageStore setMessageId(int messageId) { 59 | this.messageId = messageId; 60 | return this; 61 | } 62 | 63 | public byte[] getMessageBytes() { 64 | return messageBytes; 65 | } 66 | 67 | public DupPublishMessageStore setMessageBytes(byte[] messageBytes) { 68 | this.messageBytes = messageBytes; 69 | return this; 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/IDupPubRelMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * PUBREL重发消息存储服务接口, 当QoS=2时存在该重发机制 11 | */ 12 | public interface IDupPubRelMessageStoreService { 13 | 14 | /** 15 | * 存储消息 16 | */ 17 | void put(String clientId, DupPubRelMessageStore dupPubRelMessageStore); 18 | 19 | /** 20 | * 获取消息集合 21 | */ 22 | List get(String clientId); 23 | 24 | /** 25 | * 删除消息 26 | */ 27 | void remove(String clientId, int messageId); 28 | 29 | /** 30 | * 删除消息 31 | */ 32 | void removeByClient(String clientId); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/IDupPublishMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * PUBLISH重发消息存储服务接口, 当QoS=1和QoS=2时存在该重发机制 11 | */ 12 | public interface IDupPublishMessageStoreService { 13 | 14 | /** 15 | * 存储消息 16 | */ 17 | void put(String clientId, DupPublishMessageStore dupPublishMessageStore); 18 | 19 | /** 20 | * 获取消息集合 21 | */ 22 | List get(String clientId); 23 | 24 | /** 25 | * 删除消息 26 | */ 27 | void remove(String clientId, int messageId); 28 | 29 | /** 30 | * 删除消息 31 | */ 32 | void removeByClient(String clientId); 33 | 34 | } 35 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/IMessageIdService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | /** 8 | * 分布式生成报文标识符 9 | */ 10 | public interface IMessageIdService { 11 | 12 | /** 13 | * 获取报文标识符 14 | */ 15 | int getNextMessageId(); 16 | } 17 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/IRetainMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 消息存储服务接口 11 | */ 12 | public interface IRetainMessageStoreService { 13 | 14 | /** 15 | * 存储retain标志消息 16 | */ 17 | void put(String topic, RetainMessageStore retainMessageStore); 18 | 19 | /** 20 | * 获取retain消息 21 | */ 22 | RetainMessageStore get(String topic); 23 | 24 | /** 25 | * 删除retain标志消息 26 | */ 27 | void remove(String topic); 28 | 29 | /** 30 | * 判断指定topic的retain消息是否存在 31 | */ 32 | boolean containsKey(String topic); 33 | 34 | /** 35 | * 获取retain消息集合 36 | */ 37 | List search(String topicFilter); 38 | 39 | } 40 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/message/RetainMessageStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.message; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * Retain标志消息存储 11 | */ 12 | public class RetainMessageStore implements Serializable { 13 | 14 | private static final long serialVersionUID = -7548204047370972779L; 15 | 16 | private String topic; 17 | 18 | private byte[] messageBytes; 19 | 20 | private int mqttQoS; 21 | 22 | public String getTopic() { 23 | return topic; 24 | } 25 | 26 | public RetainMessageStore setTopic(String topic) { 27 | this.topic = topic; 28 | return this; 29 | } 30 | 31 | public byte[] getMessageBytes() { 32 | return messageBytes; 33 | } 34 | 35 | public RetainMessageStore setMessageBytes(byte[] messageBytes) { 36 | this.messageBytes = messageBytes; 37 | return this; 38 | } 39 | 40 | public int getMqttQoS() { 41 | return mqttQoS; 42 | } 43 | 44 | public RetainMessageStore setMqttQoS(int mqttQoS) { 45 | this.mqttQoS = mqttQoS; 46 | return this; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/session/ISessionStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.session; 6 | 7 | /** 8 | * 会话存储服务接口 9 | */ 10 | public interface ISessionStoreService { 11 | 12 | /** 13 | * 存储会话 14 | */ 15 | void put(String clientId, SessionStore sessionStore, int expire); 16 | 17 | /** 18 | * 设置session失效时间 19 | */ 20 | void expire(String clientId, int expire); 21 | 22 | /** 23 | * 获取会话 24 | */ 25 | SessionStore get(String clientId); 26 | 27 | /** 28 | * clientId的会话是否存在 29 | */ 30 | boolean containsKey(String clientId); 31 | 32 | /** 33 | * 删除会话 34 | */ 35 | void remove(String clientId); 36 | 37 | } 38 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/session/SessionStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.session; 6 | 7 | 8 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 9 | 10 | import java.io.Serializable; 11 | 12 | /** 13 | * 会话存储 14 | */ 15 | public class SessionStore implements Serializable { 16 | private static final long serialVersionUID = -1L; 17 | 18 | private String brokerId; 19 | 20 | private String clientId; 21 | 22 | private String channelId; 23 | 24 | private int expire; 25 | 26 | private boolean cleanSession; 27 | 28 | private MqttPublishMessage willMessage; 29 | 30 | public SessionStore() { 31 | 32 | } 33 | 34 | public SessionStore(String brokerId, String clientId, String channelId, boolean cleanSession, MqttPublishMessage willMessage, int expire) { 35 | this.brokerId = brokerId; 36 | this.clientId = clientId; 37 | this.channelId = channelId; 38 | this.cleanSession = cleanSession; 39 | this.willMessage = willMessage; 40 | this.expire = expire; 41 | } 42 | 43 | public String getBrokerId() { 44 | return brokerId; 45 | } 46 | 47 | public void setBrokerId(String brokerId) { 48 | this.brokerId = brokerId; 49 | } 50 | 51 | public String getClientId() { 52 | return clientId; 53 | } 54 | 55 | public SessionStore setClientId(String clientId) { 56 | this.clientId = clientId; 57 | return this; 58 | } 59 | 60 | public String getChannelId() { 61 | return channelId; 62 | } 63 | 64 | public void setChannelId(String channelId) { 65 | this.channelId = channelId; 66 | } 67 | 68 | public boolean isCleanSession() { 69 | return cleanSession; 70 | } 71 | 72 | public int getExpire() { 73 | return expire; 74 | } 75 | 76 | public void setExpire(int expire) { 77 | this.expire = expire; 78 | } 79 | 80 | public SessionStore setCleanSession(boolean cleanSession) { 81 | this.cleanSession = cleanSession; 82 | return this; 83 | } 84 | 85 | public MqttPublishMessage getWillMessage() { 86 | return willMessage; 87 | } 88 | 89 | public SessionStore setWillMessage(MqttPublishMessage willMessage) { 90 | this.willMessage = willMessage; 91 | return this; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/subscribe/ISubscribeStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.subscribe; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * 订阅存储服务接口 11 | */ 12 | public interface ISubscribeStoreService { 13 | 14 | /** 15 | * 存储订阅 16 | */ 17 | void put(String topicFilter, SubscribeStore subscribeStore); 18 | 19 | /** 20 | * 删除订阅 21 | */ 22 | void remove(String topicFilter, String clientId); 23 | 24 | /** 25 | * 删除clientId的订阅 26 | */ 27 | void removeForClient(String clientId); 28 | 29 | /** 30 | * 获取订阅存储集 31 | */ 32 | List search(String topic); 33 | } 34 | -------------------------------------------------------------------------------- /mqtt-common/src/main/java/cn/wizzer/iot/mqtt/server/common/subscribe/SubscribeStore.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.common.subscribe; 6 | 7 | import java.io.Serializable; 8 | 9 | /** 10 | * 订阅存储 11 | */ 12 | public class SubscribeStore implements Serializable { 13 | 14 | private static final long serialVersionUID = 1276156087085594264L; 15 | 16 | private String clientId; 17 | 18 | private String topicFilter; 19 | 20 | private int mqttQoS; 21 | 22 | public SubscribeStore(String clientId, String topicFilter, int mqttQoS) { 23 | this.clientId = clientId; 24 | this.topicFilter = topicFilter; 25 | this.mqttQoS = mqttQoS; 26 | } 27 | 28 | public String getClientId() { 29 | return clientId; 30 | } 31 | 32 | public SubscribeStore setClientId(String clientId) { 33 | this.clientId = clientId; 34 | return this; 35 | } 36 | 37 | public String getTopicFilter() { 38 | return topicFilter; 39 | } 40 | 41 | public SubscribeStore setTopicFilter(String topicFilter) { 42 | this.topicFilter = topicFilter; 43 | return this; 44 | } 45 | 46 | public int getMqttQoS() { 47 | return mqttQoS; 48 | } 49 | 50 | public SubscribeStore setMqttQoS(int mqttQoS) { 51 | this.mqttQoS = mqttQoS; 52 | return this; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-store/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mqtt-wk 7 | cn.wizzer 8 | 1.4.0-netty 9 | 10 | 4.0.0 11 | jar 12 | mqtt-store 13 | 14 | 15 | 16 | cn.wizzer 17 | mqtt-common 18 | ${mqttwk.version} 19 | 20 | 21 | org.nutz 22 | nutzboot-core 23 | 24 | 25 | org.apache.kafka 26 | kafka_2.12 27 | ${kafka_2.12.version} 28 | 29 | 30 | org.nutz 31 | nutzboot-starter-redis 32 | ${nutzboot.version} 33 | 34 | 35 | cn.hutool 36 | hutool-crypto 37 | ${hutool.version} 38 | 39 | 40 | com.alibaba 41 | fastjson 42 | ${fastjson.version} 43 | 44 | 45 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/cache/DupPubRelMessageCache.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.cache; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.message.DupPubRelMessageStore; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.nutz.aop.interceptor.async.Async; 6 | import org.nutz.integration.jedis.RedisService; 7 | import org.nutz.ioc.impl.PropertiesProxy; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * Created by wizzer on 2018 16 | */ 17 | @IocBean 18 | public class DupPubRelMessageCache { 19 | private final static String CACHE_PRE = "mqttwk:pubrel:"; 20 | @Inject 21 | private RedisService redisService; 22 | @Inject 23 | private PropertiesProxy conf; 24 | 25 | public DupPubRelMessageStore put(String clientId, Integer messageId, DupPubRelMessageStore dupPubRelMessageStore) { 26 | redisService.hset(CACHE_PRE + clientId, String.valueOf(messageId), JSONObject.toJSONString(dupPubRelMessageStore)); 27 | return dupPubRelMessageStore; 28 | } 29 | 30 | public ConcurrentHashMap get(String clientId) { 31 | ConcurrentHashMap map = new ConcurrentHashMap<>(); 32 | Map map1 = redisService.hgetAll(CACHE_PRE + clientId); 33 | if (map1 != null && !map1.isEmpty()) { 34 | map1.forEach((k, v) -> { 35 | map.put(Integer.valueOf(k), JSONObject.parseObject(v, DupPubRelMessageStore.class)); 36 | }); 37 | } 38 | return map; 39 | } 40 | 41 | public boolean containsKey(String clientId) { 42 | return redisService.exists(CACHE_PRE + clientId); 43 | } 44 | 45 | @Async 46 | public void remove(String clientId, Integer messageId) { 47 | redisService.hdel(CACHE_PRE + clientId, String.valueOf(messageId)); 48 | } 49 | 50 | @Async 51 | public void remove(String clientId) { 52 | redisService.del(CACHE_PRE + clientId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/cache/DupPublishMessageCache.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.cache; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.message.DupPublishMessageStore; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.nutz.aop.interceptor.async.Async; 6 | import org.nutz.integration.jedis.RedisService; 7 | import org.nutz.ioc.impl.PropertiesProxy; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | 11 | import java.util.Map; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * Created by wizzer on 2018 16 | */ 17 | @IocBean 18 | public class DupPublishMessageCache { 19 | private final static String CACHE_PRE = "mqttwk:publish:"; 20 | @Inject 21 | private RedisService redisService; 22 | @Inject 23 | private PropertiesProxy conf; 24 | 25 | public DupPublishMessageStore put(String clientId, Integer messageId, DupPublishMessageStore dupPublishMessageStore) { 26 | redisService.hset(CACHE_PRE + clientId, String.valueOf(messageId), JSONObject.toJSONString(dupPublishMessageStore)); 27 | return dupPublishMessageStore; 28 | } 29 | 30 | public ConcurrentHashMap get(String clientId) { 31 | ConcurrentHashMap map = new ConcurrentHashMap<>(); 32 | Map map1 = redisService.hgetAll(CACHE_PRE + clientId); 33 | if (map1 != null && !map1.isEmpty()) { 34 | map1.forEach((k, v) -> { 35 | map.put(Integer.valueOf(k), JSONObject.parseObject(v, DupPublishMessageStore.class)); 36 | }); 37 | } 38 | return map; 39 | } 40 | 41 | public boolean containsKey(String clientId) { 42 | return redisService.exists(CACHE_PRE + clientId); 43 | } 44 | 45 | @Async 46 | public void remove(String clientId, Integer messageId) { 47 | redisService.hdel(CACHE_PRE + clientId, String.valueOf(messageId)); 48 | } 49 | 50 | @Async 51 | public void remove(String clientId) { 52 | redisService.del(CACHE_PRE + clientId); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/cache/RetainMessageCache.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.cache; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.message.RetainMessageStore; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.nutz.aop.interceptor.async.Async; 6 | import org.nutz.integration.jedis.RedisService; 7 | import org.nutz.ioc.impl.PropertiesProxy; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | 11 | import java.util.HashMap; 12 | import java.util.Map; 13 | import java.util.Set; 14 | 15 | /** 16 | * Created by wizzer on 2018 17 | */ 18 | @IocBean 19 | public class RetainMessageCache { 20 | private final static String CACHE_PRE = "mqttwk:retain:"; 21 | private final static String CACHE_TOPIC = "mqttwk:retain_topic"; 22 | @Inject 23 | private RedisService redisService; 24 | @Inject 25 | private PropertiesProxy conf; 26 | 27 | public RetainMessageStore put(String topic, RetainMessageStore obj) { 28 | redisService.set(CACHE_PRE + topic, JSONObject.toJSONString(obj)); 29 | redisService.sadd(CACHE_TOPIC, topic); 30 | return obj; 31 | } 32 | 33 | public RetainMessageStore get(String topic) { 34 | return JSONObject.parseObject(redisService.get(CACHE_PRE + topic), RetainMessageStore.class); 35 | } 36 | 37 | public boolean containsKey(String topic) { 38 | return redisService.exists(CACHE_PRE + topic); 39 | } 40 | 41 | @Async 42 | public void remove(String topic) { 43 | redisService.del(CACHE_PRE + topic); 44 | redisService.srem(CACHE_TOPIC, topic); 45 | } 46 | 47 | public Map all() { 48 | Map map = new HashMap<>(); 49 | Set topics = redisService.smembers(CACHE_TOPIC); 50 | for (String topic : topics) { 51 | map.put(topic, JSONObject.parseObject(redisService.get(CACHE_PRE + topic), RetainMessageStore.class)); 52 | } 53 | return map; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/cache/SubscribeNotWildcardCache.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.cache; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.subscribe.SubscribeStore; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.nutz.aop.interceptor.async.Async; 6 | import org.nutz.integration.jedis.JedisAgent; 7 | import org.nutz.integration.jedis.RedisService; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | 11 | import java.util.*; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * Created by wizzer on 2018 16 | */ 17 | @IocBean 18 | public class SubscribeNotWildcardCache { 19 | private final static String CACHE_PRE = "mqttwk:subnotwildcard:"; 20 | private final static String CACHE_CLIENT_PRE = "mqttwk:client:"; 21 | private final static String CACHE_TOPIC = "mqttwk:subnotwildcard_topic"; 22 | @Inject 23 | private RedisService redisService; 24 | @Inject 25 | private JedisAgent jedisAgent; 26 | 27 | public SubscribeStore put(String topic, String clientId, SubscribeStore subscribeStore) { 28 | redisService.hset(CACHE_PRE + topic, clientId, JSONObject.toJSONString(subscribeStore)); 29 | redisService.sadd(CACHE_CLIENT_PRE + clientId, topic); 30 | redisService.sadd(CACHE_TOPIC, topic); 31 | return subscribeStore; 32 | } 33 | 34 | public SubscribeStore get(String topic, String clientId) { 35 | return JSONObject.parseObject(redisService.hget(CACHE_PRE + topic, clientId), SubscribeStore.class); 36 | } 37 | 38 | public boolean containsKey(String topic, String clientId) { 39 | return redisService.hexists(CACHE_PRE + topic, clientId); 40 | } 41 | 42 | @Async 43 | public void remove(String topic, String clientId) { 44 | redisService.srem(CACHE_CLIENT_PRE + clientId, topic); 45 | redisService.hdel(CACHE_PRE + topic, clientId); 46 | if (redisService.hlen(CACHE_PRE + topic) == 0) { 47 | redisService.srem(CACHE_TOPIC, topic); 48 | } 49 | } 50 | 51 | @Async 52 | public void removeForClient(String clientId) { 53 | for (String topic : redisService.smembers(CACHE_CLIENT_PRE + clientId)) { 54 | redisService.hdel(CACHE_PRE + topic, clientId); 55 | if (redisService.hlen(CACHE_PRE + topic) == 0) { 56 | redisService.srem(CACHE_TOPIC, topic); 57 | } 58 | } 59 | redisService.del(CACHE_CLIENT_PRE + clientId); 60 | } 61 | 62 | public Map> all() { 63 | Map> map = new HashMap<>(); 64 | Set topics = redisService.smembers(CACHE_TOPIC); 65 | for (String topic : topics) { 66 | ConcurrentHashMap map1 = new ConcurrentHashMap<>(); 67 | Map map2 = redisService.hgetAll(CACHE_PRE + topic); 68 | if (map2 != null && !map2.isEmpty()) { 69 | map2.forEach((k, v) -> { 70 | map1.put(k, JSONObject.parseObject(v, SubscribeStore.class)); 71 | }); 72 | map.put(topic, map1); 73 | } 74 | } 75 | return map; 76 | } 77 | 78 | public List all(String topic) { 79 | List list = new ArrayList<>(); 80 | Map map = redisService.hgetAll(CACHE_PRE + topic); 81 | if (map != null && !map.isEmpty()) { 82 | map.forEach((k, v) -> { 83 | list.add(JSONObject.parseObject(v, SubscribeStore.class)); 84 | }); 85 | } 86 | return list; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/cache/SubscribeWildcardCache.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.cache; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.subscribe.SubscribeStore; 4 | import com.alibaba.fastjson.JSONObject; 5 | import org.nutz.aop.interceptor.async.Async; 6 | import org.nutz.integration.jedis.JedisAgent; 7 | import org.nutz.integration.jedis.RedisService; 8 | import org.nutz.ioc.loader.annotation.Inject; 9 | import org.nutz.ioc.loader.annotation.IocBean; 10 | 11 | import java.util.*; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * Created by wizzer on 2018 16 | */ 17 | @IocBean 18 | public class SubscribeWildcardCache { 19 | private final static String CACHE_PRE = "mqttwk:subwildcard:"; 20 | private final static String CACHE_CLIENT_PRE = "mqttwk:client:"; 21 | private final static String CACHE_TOPIC = "mqttwk:subwildcard_topic"; 22 | 23 | @Inject 24 | private RedisService redisService; 25 | @Inject 26 | private JedisAgent jedisAgent; 27 | 28 | public SubscribeStore put(String topic, String clientId, SubscribeStore subscribeStore) { 29 | redisService.hset(CACHE_PRE + topic, clientId, JSONObject.toJSONString(subscribeStore)); 30 | redisService.sadd(CACHE_CLIENT_PRE + clientId, topic); 31 | redisService.sadd(CACHE_TOPIC, topic); 32 | return subscribeStore; 33 | } 34 | 35 | public SubscribeStore get(String topic, String clientId) { 36 | return JSONObject.parseObject(redisService.hget(CACHE_PRE + topic, clientId), SubscribeStore.class); 37 | } 38 | 39 | public boolean containsKey(String topic, String clientId) { 40 | return redisService.hexists(CACHE_PRE + topic, clientId); 41 | } 42 | 43 | @Async 44 | public void remove(String topic, String clientId) { 45 | redisService.srem(CACHE_CLIENT_PRE + clientId, topic); 46 | redisService.hdel(CACHE_PRE + topic, clientId); 47 | if (redisService.hlen(CACHE_PRE + topic) == 0) { 48 | redisService.srem(CACHE_TOPIC, topic); 49 | } 50 | } 51 | 52 | @Async 53 | public void removeForClient(String clientId) { 54 | for (String topic : redisService.smembers(CACHE_CLIENT_PRE + clientId)) { 55 | redisService.hdel(CACHE_PRE + topic, clientId); 56 | if (redisService.hlen(CACHE_PRE + topic) == 0) { 57 | redisService.srem(CACHE_TOPIC, topic); 58 | } 59 | } 60 | redisService.del(CACHE_CLIENT_PRE + clientId); 61 | } 62 | 63 | public Map> all() { 64 | Map> map = new HashMap<>(); 65 | Set topics = redisService.smembers(CACHE_TOPIC); 66 | for (String topic : topics) { 67 | ConcurrentHashMap map1 = new ConcurrentHashMap<>(); 68 | Map map2 = redisService.hgetAll(CACHE_PRE + topic); 69 | if (map2 != null && !map2.isEmpty()) { 70 | map2.forEach((k, v) -> { 71 | map1.put(k, JSONObject.parseObject(v, SubscribeStore.class)); 72 | }); 73 | map.put(topic, map1); 74 | } 75 | } 76 | return map; 77 | } 78 | 79 | public List all(String topic) { 80 | List list = new ArrayList<>(); 81 | Map map = redisService.hgetAll(CACHE_PRE + topic); 82 | if (map != null && !map.isEmpty()) { 83 | map.forEach((k, v) -> { 84 | list.add(JSONObject.parseObject(v, SubscribeStore.class)); 85 | }); 86 | } 87 | return list; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/kafka/SimplePartitioner.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.kafka; 2 | 3 | import org.apache.kafka.clients.producer.Partitioner; 4 | import org.apache.kafka.common.Cluster; 5 | import org.apache.kafka.common.PartitionInfo; 6 | 7 | import java.util.List; 8 | import java.util.Map; 9 | 10 | /** 11 | * Created by wizzer on 2018 12 | */ 13 | public class SimplePartitioner implements Partitioner { 14 | 15 | @Override 16 | public void configure(Map configs) { 17 | // TODO Auto-generated method stub 18 | 19 | } 20 | 21 | @Override 22 | public int partition(String topic, Object key, byte[] keyBytes, 23 | Object value, byte[] valueBytes, Cluster cluster) { 24 | int partition = 0; 25 | List partitions = cluster.partitionsForTopic(topic); 26 | int numPartitions = partitions.size(); 27 | String stringKey = (String) key; 28 | int offset = stringKey.lastIndexOf('.'); 29 | if (offset > 0) { 30 | partition = Integer.parseInt(stringKey.substring(offset + 1)) % numPartitions; 31 | } 32 | return partition; 33 | } 34 | 35 | @Override 36 | public void close() { 37 | // TODO Auto-generated method stub 38 | 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/message/DupPubRelMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.store.message; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.DupPubRelMessageStore; 8 | import cn.wizzer.iot.mqtt.server.common.message.IDupPubRelMessageStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 10 | import cn.wizzer.iot.mqtt.server.store.cache.DupPubRelMessageCache; 11 | import org.nutz.ioc.loader.annotation.Inject; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.List; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | 19 | @IocBean 20 | public class DupPubRelMessageStoreService implements IDupPubRelMessageStoreService { 21 | 22 | @Inject 23 | private IMessageIdService messageIdService; 24 | 25 | @Inject 26 | private DupPubRelMessageCache dupPubRelMessageCache; 27 | 28 | @Override 29 | public void put(String clientId, DupPubRelMessageStore dupPubRelMessageStore) { 30 | dupPubRelMessageCache.put(clientId, dupPubRelMessageStore.getMessageId(), dupPubRelMessageStore); 31 | } 32 | 33 | @Override 34 | public List get(String clientId) { 35 | if (dupPubRelMessageCache.containsKey(clientId)) { 36 | ConcurrentHashMap map = dupPubRelMessageCache.get(clientId); 37 | Collection collection = map.values(); 38 | return new ArrayList<>(collection); 39 | } 40 | return new ArrayList<>(); 41 | } 42 | 43 | @Override 44 | public void remove(String clientId, int messageId) { 45 | dupPubRelMessageCache.remove(clientId, messageId); 46 | } 47 | 48 | @Override 49 | public void removeByClient(String clientId) { 50 | if (dupPubRelMessageCache.containsKey(clientId)) { 51 | dupPubRelMessageCache.remove(clientId); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/message/DupPublishMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.store.message; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.message.DupPublishMessageStore; 8 | import cn.wizzer.iot.mqtt.server.common.message.IDupPublishMessageStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 10 | import cn.wizzer.iot.mqtt.server.store.cache.DupPublishMessageCache; 11 | import org.nutz.ioc.loader.annotation.Inject; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | 14 | import java.util.ArrayList; 15 | import java.util.Collection; 16 | import java.util.List; 17 | import java.util.concurrent.ConcurrentHashMap; 18 | 19 | @IocBean 20 | public class DupPublishMessageStoreService implements IDupPublishMessageStoreService { 21 | 22 | @Inject 23 | private IMessageIdService messageIdService; 24 | @Inject 25 | private DupPublishMessageCache dupPublishMessageCache; 26 | 27 | @Override 28 | public void put(String clientId, DupPublishMessageStore dupPublishMessageStore) { 29 | dupPublishMessageCache.put(clientId, dupPublishMessageStore.getMessageId(), dupPublishMessageStore); 30 | } 31 | 32 | @Override 33 | public List get(String clientId) { 34 | if (dupPublishMessageCache.containsKey(clientId)) { 35 | ConcurrentHashMap map = dupPublishMessageCache.get(clientId); 36 | Collection collection = map.values(); 37 | return new ArrayList<>(collection); 38 | } 39 | return new ArrayList<>(); 40 | } 41 | 42 | @Override 43 | public void remove(String clientId, int messageId) { 44 | dupPublishMessageCache.remove(clientId, messageId); 45 | } 46 | 47 | @Override 48 | public void removeByClient(String clientId) { 49 | dupPublishMessageCache.remove(clientId); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/message/MessageIdService.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.message; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.message.IMessageIdService; 4 | import org.nutz.integration.jedis.RedisService; 5 | import org.nutz.ioc.loader.annotation.Inject; 6 | import org.nutz.ioc.loader.annotation.IocBean; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | 10 | /** 11 | * Created by wizzer on 2018 12 | */ 13 | @IocBean(create = "init") 14 | public class MessageIdService implements IMessageIdService { 15 | private static final Logger LOGGER = LoggerFactory.getLogger(MessageIdService.class); 16 | 17 | @Inject 18 | private RedisService redisService; 19 | 20 | @Override 21 | public int getNextMessageId() { 22 | try { 23 | while (true) { 24 | int nextMsgId = (int) (redisService.incr("mqttwk:messageid:num") % 65536); 25 | if (nextMsgId > 0) { 26 | return nextMsgId; 27 | } 28 | } 29 | } catch (Exception e) { 30 | LOGGER.error(e.getMessage(), e); 31 | } 32 | return 0; 33 | } 34 | 35 | /** 36 | * 每次重启的时候初始化 37 | */ 38 | public void init() { 39 | redisService.del("mqttwk:messageid:num"); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/message/RetainMessageStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.store.message; 6 | 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.wizzer.iot.mqtt.server.common.message.IRetainMessageStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.message.RetainMessageStore; 10 | import cn.wizzer.iot.mqtt.server.store.cache.RetainMessageCache; 11 | import org.nutz.ioc.loader.annotation.Inject; 12 | import org.nutz.ioc.loader.annotation.IocBean; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | @IocBean 18 | public class RetainMessageStoreService implements IRetainMessageStoreService { 19 | 20 | @Inject 21 | private RetainMessageCache retainMessageCache; 22 | 23 | @Override 24 | public void put(String topic, RetainMessageStore retainMessageStore) { 25 | retainMessageCache.put(topic, retainMessageStore); 26 | } 27 | 28 | @Override 29 | public RetainMessageStore get(String topic) { 30 | return retainMessageCache.get(topic); 31 | } 32 | 33 | @Override 34 | public void remove(String topic) { 35 | retainMessageCache.remove(topic); 36 | } 37 | 38 | @Override 39 | public boolean containsKey(String topic) { 40 | return retainMessageCache.containsKey(topic); 41 | } 42 | 43 | @Override 44 | public List search(String topicFilter) { 45 | List retainMessageStores = new ArrayList(); 46 | if (!StrUtil.contains(topicFilter, '#') && !StrUtil.contains(topicFilter, '+')) { 47 | if (retainMessageCache.containsKey(topicFilter)) { 48 | retainMessageStores.add(retainMessageCache.get(topicFilter)); 49 | } 50 | } else { 51 | retainMessageCache.all().forEach((topic, val) -> { 52 | if (StrUtil.split(topic, '/').size() >= StrUtil.split(topicFilter, '/').size()) { 53 | List splitTopics = StrUtil.split(topic, '/'); 54 | List spliteTopicFilters = StrUtil.split(topicFilter, '/'); 55 | String newTopicFilter = ""; 56 | for (int i = 0; i < spliteTopicFilters.size(); i++) { 57 | String value = spliteTopicFilters.get(i); 58 | if (value.equals("+")) { 59 | newTopicFilter = newTopicFilter + "+/"; 60 | } else if (value.equals("#")) { 61 | newTopicFilter = newTopicFilter + "#/"; 62 | break; 63 | } else { 64 | newTopicFilter = newTopicFilter + splitTopics.get(i) + "/"; 65 | } 66 | } 67 | newTopicFilter = StrUtil.removeSuffix(newTopicFilter, "/"); 68 | if (topicFilter.equals(newTopicFilter)) { 69 | retainMessageStores.add(val); 70 | } 71 | } 72 | }); 73 | } 74 | return retainMessageStores; 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/session/SessionStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by wizzer on 2018 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.store.session; 6 | 7 | import cn.wizzer.iot.mqtt.server.common.session.ISessionStoreService; 8 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 9 | import cn.wizzer.iot.mqtt.server.store.util.StoreUtil; 10 | import com.alibaba.fastjson.JSON; 11 | import org.nutz.aop.interceptor.async.Async; 12 | import org.nutz.integration.jedis.RedisService; 13 | import org.nutz.ioc.loader.annotation.Inject; 14 | import org.nutz.ioc.loader.annotation.IocBean; 15 | import org.nutz.lang.Strings; 16 | import org.nutz.lang.util.NutMap; 17 | 18 | /** 19 | * 会话存储服务 20 | */ 21 | @IocBean 22 | public class SessionStoreService implements ISessionStoreService { 23 | private final static String CACHE_PRE = "mqttwk:session:"; 24 | @Inject 25 | private RedisService redisService; 26 | 27 | @Override 28 | public void put(String clientId, SessionStore sessionStore, int expire) { 29 | //SessionStore对象不能正常转为JSON,使用工具类类解决 30 | NutMap nutMap = StoreUtil.transPublishToMapBeta(sessionStore); 31 | if (nutMap != null) { 32 | if (expire > 0) { 33 | redisService.setex(CACHE_PRE + clientId, expire, JSON.toJSONString(nutMap)); 34 | } else { 35 | redisService.set(CACHE_PRE + clientId, JSON.toJSONString(nutMap)); 36 | } 37 | } 38 | 39 | } 40 | 41 | @Override 42 | public void expire(String clientId, int expire) { 43 | redisService.expire(CACHE_PRE + clientId, expire); 44 | } 45 | 46 | 47 | @Override 48 | public SessionStore get(String clientId) { 49 | String jsonObj = redisService.get(CACHE_PRE + clientId); 50 | if (Strings.isNotBlank(jsonObj)) { 51 | NutMap nutMap = JSON.parseObject(jsonObj, NutMap.class); 52 | return StoreUtil.mapTransToPublishMsgBeta(nutMap); 53 | } 54 | return null; 55 | } 56 | 57 | @Override 58 | public boolean containsKey(String clientId) { 59 | return redisService.exists(CACHE_PRE + clientId); 60 | } 61 | 62 | 63 | @Override 64 | @Async 65 | public void remove(String clientId) { 66 | redisService.del(CACHE_PRE + clientId); 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/starter/StoreStarter.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.starter; 2 | 3 | import org.apache.kafka.clients.producer.KafkaProducer; 4 | import org.nutz.boot.annotation.PropDoc; 5 | import org.nutz.ioc.impl.PropertiesProxy; 6 | import org.nutz.ioc.loader.annotation.Inject; 7 | import org.nutz.ioc.loader.annotation.IocBean; 8 | 9 | import java.util.Properties; 10 | 11 | /** 12 | * Created by wizzer on 2018 13 | */ 14 | @IocBean(create = "init", depose = "close") 15 | public class StoreStarter { 16 | @Inject 17 | protected PropertiesProxy conf; 18 | protected KafkaProducer kafkaProducer; 19 | protected static final String PRE = "mqttwk.broker."; 20 | 21 | @PropDoc(group = "broker", value = "实例名称", need = true, defaultValue = "mqttwk") 22 | public static final String PROP_INSTANCENAME = PRE + "id"; 23 | 24 | @PropDoc(group = "broker", value = "是否启用kafka消息转发", need = true, defaultValue = "false") 25 | public static final String PROP_KAFKA_BROKER_ENABLED = PRE + "kafka.broker-enabled"; 26 | 27 | @PropDoc(group = "broker", value = "kafka地址 127.0.0.1:9092,127.0.0.1:9093", need = true, defaultValue = "127.0.0.1:9092") 28 | public static final String PROP_KAFKA_SERVERS = PRE + "kafka.bootstrap.servers"; 29 | 30 | @PropDoc(group = "broker", value = "all:必须等待回令 -1:不等待", defaultValue = "all") 31 | public static final String PROP_KAFKA_ACKS = PRE + "kafka.acks"; 32 | 33 | @PropDoc(group = "broker", value = "重试次数", type = "int", defaultValue = "0") 34 | public static final String PROP_KAFKA_RETRIES = PRE + "kafka.retries"; 35 | 36 | @PropDoc(group = "broker", value = "批量提交大小", type = "int", defaultValue = "16384") 37 | public static final String PROP_KAFKA_BATCHSIZE = PRE + "kafka.batch.size"; 38 | 39 | @PropDoc(group = "broker", value = "提交延迟等待时间(等待时间内可以追加提交)", type = "int", defaultValue = "1") 40 | public static final String PROP_KAFKA_LINGERMS = PRE + "kafka.linger.ms"; 41 | 42 | @PropDoc(group = "broker", value = "缓存大小(Bit) 默认:64MB", type = "int", defaultValue = "67108864") 43 | public static final String PROP_KAFKA_BUFFERMEMORY = PRE + "kafka.buffer.memory"; 44 | 45 | @PropDoc(group = "broker", value = "key序列化方法", defaultValue = "org.apache.kafka.common.serialization.StringSerializer") 46 | public static final String PROP_KAFKA_KEYSERIALIZER = PRE + "kafka.key.serializer"; 47 | 48 | @PropDoc(group = "broker", value = "value序列化方法", defaultValue = "org.apache.kafka.common.serialization.StringSerializer") 49 | public static final String PROP_KAFKA_VALUESERIALIZER = PRE + "kafka.value.serializer"; 50 | 51 | @PropDoc(group = "broker", value = "分发策略", defaultValue = "cn.wizzer.iot.mqtt.server.store.kafka.SimplePartitioner") 52 | public static final String PROP_KAFKA_PARTITIONERCLASS = PRE + "kafka.partitioner.class"; 53 | 54 | public Properties getProperties() { 55 | Properties properties = new Properties(); 56 | for (String key : conf.keySet()) { 57 | if (key.startsWith("mqttwk.broker.kafka.")) { 58 | properties.put(key.substring("mqttwk.broker.kafka.".length()), conf.get(key)); 59 | } 60 | } 61 | return properties; 62 | } 63 | 64 | @IocBean 65 | public KafkaProducer kafkaProducer() { 66 | return this.kafkaProducer; 67 | } 68 | 69 | public void init() throws Exception { 70 | if (conf.getBoolean(PROP_KAFKA_BROKER_ENABLED, false)) { 71 | this.kafkaProducer = new KafkaProducer(getProperties()); 72 | } 73 | } 74 | 75 | public void close() throws Exception { 76 | if (this.kafkaProducer != null) { 77 | this.kafkaProducer.close(); 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/subscribe/SubscribeStoreService.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2018, Mr.Wang (recallcode@aliyun.com) All rights reserved. 3 | */ 4 | 5 | package cn.wizzer.iot.mqtt.server.store.subscribe; 6 | 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.wizzer.iot.mqtt.server.common.subscribe.ISubscribeStoreService; 9 | import cn.wizzer.iot.mqtt.server.common.subscribe.SubscribeStore; 10 | import cn.wizzer.iot.mqtt.server.store.cache.SubscribeNotWildcardCache; 11 | import cn.wizzer.iot.mqtt.server.store.cache.SubscribeWildcardCache; 12 | import org.nutz.aop.interceptor.async.Async; 13 | import org.nutz.ioc.loader.annotation.Inject; 14 | import org.nutz.ioc.loader.annotation.IocBean; 15 | 16 | import java.util.ArrayList; 17 | import java.util.Collection; 18 | import java.util.List; 19 | 20 | /** 21 | * 订阅存储服务 22 | */ 23 | @IocBean 24 | public class SubscribeStoreService implements ISubscribeStoreService { 25 | 26 | @Inject 27 | private SubscribeNotWildcardCache subscribeNotWildcardCache; 28 | 29 | @Inject 30 | private SubscribeWildcardCache subscribeWildcardCache; 31 | 32 | @Override 33 | public void put(String topicFilter, SubscribeStore subscribeStore) { 34 | if (StrUtil.contains(topicFilter, '#') || StrUtil.contains(topicFilter, '+')) { 35 | subscribeWildcardCache.put(topicFilter, subscribeStore.getClientId(), subscribeStore); 36 | } else { 37 | subscribeNotWildcardCache.put(topicFilter, subscribeStore.getClientId(), subscribeStore); 38 | } 39 | } 40 | 41 | @Override 42 | public void remove(String topicFilter, String clientId) { 43 | if (StrUtil.contains(topicFilter, '#') || StrUtil.contains(topicFilter, '+')) { 44 | subscribeWildcardCache.remove(topicFilter, clientId); 45 | } else { 46 | subscribeNotWildcardCache.remove(topicFilter, clientId); 47 | } 48 | } 49 | 50 | @Override 51 | public void removeForClient(String clientId) { 52 | subscribeNotWildcardCache.removeForClient(clientId); 53 | subscribeWildcardCache.removeForClient(clientId); 54 | } 55 | 56 | @Override 57 | public List search(String topic) { 58 | List subscribeStores = new ArrayList(); 59 | List list = subscribeNotWildcardCache.all(topic); 60 | if (list.size() > 0) { 61 | subscribeStores.addAll(list); 62 | } 63 | subscribeWildcardCache.all().forEach((topicFilter, map) -> { 64 | if (StrUtil.split(topic, '/').size() >= StrUtil.split(topicFilter, '/').size()) { 65 | List splitTopics = StrUtil.split(topic, '/');//a 66 | List spliteTopicFilters = StrUtil.split(topicFilter, '/');//# 67 | String newTopicFilter = ""; 68 | for (int i = 0; i < spliteTopicFilters.size(); i++) { 69 | String value = spliteTopicFilters.get(i); 70 | if (value.equals("+")) { 71 | newTopicFilter = newTopicFilter + "+/"; 72 | } else if (value.equals("#")) { 73 | newTopicFilter = newTopicFilter + "#/"; 74 | break; 75 | } else { 76 | newTopicFilter = newTopicFilter + splitTopics.get(i) + "/"; 77 | } 78 | } 79 | newTopicFilter = StrUtil.removeSuffix(newTopicFilter, "/"); 80 | if (topicFilter.equals(newTopicFilter)) { 81 | Collection collection = map.values(); 82 | List list2 = new ArrayList(collection); 83 | subscribeStores.addAll(list2); 84 | } 85 | } 86 | }); 87 | return subscribeStores; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /mqtt-store/src/main/java/cn/wizzer/iot/mqtt/server/store/util/StoreUtil.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.store.util; 2 | 3 | import cn.wizzer.iot.mqtt.server.common.session.SessionStore; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.buffer.Unpooled; 6 | import io.netty.handler.codec.mqtt.*; 7 | import org.nutz.lang.util.NutMap; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | 11 | import java.util.Base64; 12 | 13 | /** 14 | * Created by cs on 2018 15 | */ 16 | public class StoreUtil { 17 | 18 | private static final Logger LOGGER = LoggerFactory.getLogger(StoreUtil.class); 19 | 20 | public static NutMap transPublishToMapBeta(SessionStore store) { 21 | try { 22 | NutMap sessionStore = new NutMap(); 23 | sessionStore.put("clientId", store.getClientId()); 24 | sessionStore.put("channelId", store.getChannelId()); 25 | sessionStore.put("cleanSession", store.isCleanSession()); 26 | sessionStore.put("brokerId", store.getBrokerId()); 27 | sessionStore.put("expire", store.getExpire()); 28 | MqttPublishMessage msg = store.getWillMessage(); 29 | if (null != msg) { 30 | sessionStore.addv("payload", Base64.getEncoder().encodeToString(msg.payload().array())); 31 | sessionStore.addv("messageType", msg.fixedHeader().messageType().value()); 32 | sessionStore.addv("isDup", msg.fixedHeader().isDup()); 33 | sessionStore.addv("qosLevel", msg.fixedHeader().qosLevel().value()); 34 | sessionStore.addv("isRetain", msg.fixedHeader().isRetain()); 35 | sessionStore.addv("remainingLength", msg.fixedHeader().remainingLength()); 36 | sessionStore.addv("topicName", msg.variableHeader().topicName()); 37 | sessionStore.addv("packetId", msg.variableHeader().packetId()); 38 | sessionStore.addv("hasWillMessage", true); 39 | } 40 | 41 | return sessionStore; 42 | } catch (Exception e) { 43 | LOGGER.error(e.getMessage(), e); 44 | } 45 | return null; 46 | } 47 | 48 | 49 | public static SessionStore mapTransToPublishMsgBeta(NutMap store) { 50 | SessionStore sessionStore = new SessionStore(); 51 | if (store.getBoolean("hasWillMessage", false)) { 52 | byte[] payloads = Base64.getDecoder().decode(store.getString("payload")); 53 | ByteBuf buf = null; 54 | try { 55 | buf = Unpooled.wrappedBuffer(payloads); 56 | MqttFixedHeader mqttFixedHeader = new MqttFixedHeader( 57 | MqttMessageType.valueOf(store.getInt("messageType")), 58 | store.getBoolean("isDup"), 59 | MqttQoS.valueOf(store.getInt("qosLevel")), 60 | store.getBoolean("isRetain"), 61 | store.getInt("remainingLength")); 62 | 63 | MqttPublishVariableHeader mqttPublishVariableHeader = new MqttPublishVariableHeader(store.getString("topicName"), 64 | store.getInt("packetId")); 65 | MqttPublishMessage mqttPublishMessage = new MqttPublishMessage(mqttFixedHeader, mqttPublishVariableHeader, buf); 66 | sessionStore.setWillMessage(mqttPublishMessage); 67 | } finally { 68 | if (buf != null) { 69 | buf.release(); 70 | } 71 | } 72 | } 73 | sessionStore.setChannelId(store.getString("channelId")); 74 | sessionStore.setClientId(store.getString("clientId")); 75 | sessionStore.setCleanSession(store.getBoolean("cleanSession")); 76 | sessionStore.setBrokerId(store.getString("brokerId")); 77 | sessionStore.setExpire(store.getInt("expire")); 78 | return sessionStore; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /mqtt-store/src/main/resources/META-INF/nutz/org.nutz.boot.starter.NbStarter: -------------------------------------------------------------------------------- 1 | cn.wizzer.iot.mqtt.server.store.starter.StoreStarter 2 | -------------------------------------------------------------------------------- /mqtt-zoo/README.md: -------------------------------------------------------------------------------- 1 | ### 证书生成教程 2 | * keystore 文件夹下为所有密码为 123456 生成的证书文件 3 | 4 | 1、生成.key文件 5 | 6 | ``` 7 | openssl genrsa -des3 -out server.key 2048 8 | ``` 9 | 中间会提示输入密码(重复输入两次),要记住这个密码; 10 | 11 | 2、生成.crt文件 12 | ``` 13 | openssl req -new -x509 -key server.key -out server.crt -days 3650 14 | ``` 15 | 会提示输入server.key的密码 16 | 开始输入Country Name:CN 17 | State or Province Name:SH 18 | Locality Name:shanghai 19 | Organization Name:这个可以忽略 20 | Organizational Unit Name:这个可以忽略 21 | Common Name:这个可以忽略 22 | Email Address:填写一个非QQ的邮箱地址 23 | 24 | 3、生成.pfx文件 25 | ``` 26 | openssl pkcs12 -export -out server.pfx -inkey server.key -in server.crt 27 | ``` 28 | 提示输入server.key文件的密码 29 | 提示输入即将生成的.pfx文件的密码(需要输入两次) 30 | 31 | 4、生成.cer文件 32 | ``` 33 | openssl pkcs12 -nodes -nokeys -in server.pfx -passin pass:证书密码 -out server.cer 34 | ``` 35 | 如无需加密pem中私钥,可以添加选项-nodes; 36 | 37 | 如无需导出私钥,可以添加选项-nokeys; 38 | 39 | 5、生成.jks文件 40 | ``` 41 | keytool -importkeystore -srckeystore server.pfx -destkeystore server.jks -srcstoretype PKCS12 -deststoretype JKS 42 | ``` 43 | 输入证书密码,六位长度,输入原始密码; -------------------------------------------------------------------------------- /mqtt-zoo/keystore/server.cer: -------------------------------------------------------------------------------- 1 | Bag Attributes 2 | localKeyID: B4 61 AD 8D BC 4A 60 23 FC 43 F6 85 FD BF 7F 15 ED 8C 92 89 3 | subject=/C=CN/ST=AH/L=HF/O=Default Company Ltd/emailAddress=wizzer.cn@gmail.com 4 | issuer=/C=CN/ST=AH/L=HF/O=Default Company Ltd/emailAddress=wizzer.cn@gmail.com 5 | -----BEGIN CERTIFICATE----- 6 | MIIDpTCCAo2gAwIBAgIJALVF3pbqpxANMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV 7 | BAYTAkNOMQswCQYDVQQIDAJBSDELMAkGA1UEBwwCSEYxHDAaBgNVBAoME0RlZmF1 8 | bHQgQ29tcGFueSBMdGQxIjAgBgkqhkiG9w0BCQEWE3dpenplci5jbkBnbWFpbC5j 9 | b20wHhcNMTgwNzE2MDMyMzEwWhcNMjgwNzEzMDMyMzEwWjBpMQswCQYDVQQGEwJD 10 | TjELMAkGA1UECAwCQUgxCzAJBgNVBAcMAkhGMRwwGgYDVQQKDBNEZWZhdWx0IENv 11 | bXBhbnkgTHRkMSIwIAYJKoZIhvcNAQkBFhN3aXp6ZXIuY25AZ21haWwuY29tMIIB 12 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2fhyzYHQvJcSs4Zrq1JIC+qO 13 | 1VIF2dVs2pxrJSZjCZpQDTQ4QjCsYp3cZEDM3BFViwNHx2/hd+fHH4NOhepBZGsf 14 | w/3Vv+b26jyiNfFUuSsW+uJqxbN2goxRm53WpYKKCyA3Qoz5o2RlCDGU3gN333B+ 15 | 5L3ItVJrILeKSMij6c5I460QyiS9lG+tVLRvl7bzde7t4ZhAVkslitN5CuZ9N3Q0 16 | G1b64NWeKPNqFORtet8QzVH0PZpB584hpFI/INv1gJM1gOtPpthnyW1dEHjrjmSz 17 | 4NZI2l7VT36yjELrzgjW5RW5Z5i3usmLMKiIYKE8hbLMXbBWalG6mG2pGF7TSQID 18 | AQABo1AwTjAdBgNVHQ4EFgQUTAiod22+1CyJNKmOz7vnleHIHSUwHwYDVR0jBBgw 19 | FoAUTAiod22+1CyJNKmOz7vnleHIHSUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B 20 | AQUFAAOCAQEAC456dmAhIHM8CmHsVkMv0GvycTC1q78cM63X5YQc4kktKJfgqqLM 21 | yqYeKlWuJav/aezuP16H9wfhZ/bHw410Qxp5656oeSJNXie5fUelfdx2O7l5rniJ 22 | +NPM7eyhOWW8l2PrLNXPO+ZD/GslcAjcxwo5IPm4HL7e/F74e26jLtmPBXvkmnCc 23 | 0woMbRT+fTTHL8KXPxPr1NqYmgGFtjMzw9MyQ82zeyBpe+hxZS4hDOP6FhCHm5KV 24 | Q4dS0UlFaBIX29VQ9V2enEJ8i5SsnhU/+ezZR5/W+sRQaqDciJmNvHqOxFX0Wsab 25 | jQW9Z7CedivZg32Sj6QCmt21fNAfwRxzww== 26 | -----END CERTIFICATE----- 27 | -------------------------------------------------------------------------------- /mqtt-zoo/keystore/server.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDpTCCAo2gAwIBAgIJALVF3pbqpxANMA0GCSqGSIb3DQEBBQUAMGkxCzAJBgNV 3 | BAYTAkNOMQswCQYDVQQIDAJBSDELMAkGA1UEBwwCSEYxHDAaBgNVBAoME0RlZmF1 4 | bHQgQ29tcGFueSBMdGQxIjAgBgkqhkiG9w0BCQEWE3dpenplci5jbkBnbWFpbC5j 5 | b20wHhcNMTgwNzE2MDMyMzEwWhcNMjgwNzEzMDMyMzEwWjBpMQswCQYDVQQGEwJD 6 | TjELMAkGA1UECAwCQUgxCzAJBgNVBAcMAkhGMRwwGgYDVQQKDBNEZWZhdWx0IENv 7 | bXBhbnkgTHRkMSIwIAYJKoZIhvcNAQkBFhN3aXp6ZXIuY25AZ21haWwuY29tMIIB 8 | IjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA2fhyzYHQvJcSs4Zrq1JIC+qO 9 | 1VIF2dVs2pxrJSZjCZpQDTQ4QjCsYp3cZEDM3BFViwNHx2/hd+fHH4NOhepBZGsf 10 | w/3Vv+b26jyiNfFUuSsW+uJqxbN2goxRm53WpYKKCyA3Qoz5o2RlCDGU3gN333B+ 11 | 5L3ItVJrILeKSMij6c5I460QyiS9lG+tVLRvl7bzde7t4ZhAVkslitN5CuZ9N3Q0 12 | G1b64NWeKPNqFORtet8QzVH0PZpB584hpFI/INv1gJM1gOtPpthnyW1dEHjrjmSz 13 | 4NZI2l7VT36yjELrzgjW5RW5Z5i3usmLMKiIYKE8hbLMXbBWalG6mG2pGF7TSQID 14 | AQABo1AwTjAdBgNVHQ4EFgQUTAiod22+1CyJNKmOz7vnleHIHSUwHwYDVR0jBBgw 15 | FoAUTAiod22+1CyJNKmOz7vnleHIHSUwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0B 16 | AQUFAAOCAQEAC456dmAhIHM8CmHsVkMv0GvycTC1q78cM63X5YQc4kktKJfgqqLM 17 | yqYeKlWuJav/aezuP16H9wfhZ/bHw410Qxp5656oeSJNXie5fUelfdx2O7l5rniJ 18 | +NPM7eyhOWW8l2PrLNXPO+ZD/GslcAjcxwo5IPm4HL7e/F74e26jLtmPBXvkmnCc 19 | 0woMbRT+fTTHL8KXPxPr1NqYmgGFtjMzw9MyQ82zeyBpe+hxZS4hDOP6FhCHm5KV 20 | Q4dS0UlFaBIX29VQ9V2enEJ8i5SsnhU/+ezZR5/W+sRQaqDciJmNvHqOxFX0Wsab 21 | jQW9Z7CedivZg32Sj6QCmt21fNAfwRxzww== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /mqtt-zoo/keystore/server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wizzercn/MqttWk/addfd0ad452a1917e8a95e3cf06096696ce3fc38/mqtt-zoo/keystore/server.jks -------------------------------------------------------------------------------- /mqtt-zoo/keystore/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | Proc-Type: 4,ENCRYPTED 3 | DEK-Info: DES-EDE3-CBC,84C2FB70C070F7EC 4 | 5 | w/vD8i02WgJMqJum+KToXMWRE3sJ91Ku1vokvZQPc6cs1MX/A36bRb/3cLkv2KCH 6 | FKqoeLrKqMlsoxj4nF8e0EMMzdsDSfmTT/kgmN3HoSWWEtKGewOTrVFRnngsQlT4 7 | TRMLWQ2uHCGM1qvEvIO+Edap4WXTEKNWrjjv3BLZ04TJh5/Q73djaeEc/X1SA3DU 8 | /CKHxV+WgHY20pWDGAhHC017qm2LD4a4cR8lyNbPfCK4+g7N+O92PMKRFIsAIqTh 9 | x+t23F3mOQZc9hgqKguqB+PXGsr7U4AToa6OafTV0xmMsdOLqVqxbFnLT45+8FM9 10 | tEuBx9teFIrKh+CjOBKY6R0sxP2PAosWA76LGCF7sQOmQqbR/U4+qxAjvgzCuIhw 11 | OnmTfXmVPrRQxCgw4DXKO4jJkhepA/r62L6tTXybR4Pae+bIjVewjpcLQ3Q2mh0w 12 | jbsob8XAYZCpbKyirfMJ/DOCD6+bc8S01iF0f6z9XZOhh8yJiti7H6dwEQ02pFJT 13 | 0xc7rt2UVsgWQOo8FEDGOA0Zoh8zhJqaBWTesjPoNGyAJM4HE2E8qrObTr4vloJn 14 | UkyBl1J8aNCdD2Lfzqb0qtMPOSmJMC7l2lwO6mLrnngIeW/S04A9nZkhgkYXJONo 15 | fPqNvWOE6BIvo8gxMdDqnCnINY6L7qOq+6YqVgJtn6w/OO639ZHCDt0LwnMvolef 16 | lvoW7comHVE9D13jzvL/k3Jb2m3i4NM1A8Ljy3BalcGb45RJaM5JhFes9jbur04R 17 | dm871RNVFrY5VFBCLaplraS1e69R8UYz18G7wzGua0skWEm/QpACOsUxXC2hxfbQ 18 | /XyDNF5BhdkZi1quMIQhVjkl3Chvf2qYGbBV1o4vdRcqS+M3r/qPJMXsg2lXzuyB 19 | yc+eStE4eFW40egBYKnIFWMk1CKoyKetOAA04RDe9vfzHn/u5PyOd05rJcw2C44r 20 | KjOQNOoI01r5kPUUjiUSRS4OGXGLta8CTbpl4qSwqOIklGLh8YKalDUfHZs2kknR 21 | L1imBT3MlJ6pyVxJBT0JLeeR3rKdkDVd+NLEsYquSkMLW5HbXk/zDBOSXuYSI988 22 | 1yNbc+pMLR94zC9A+rJf8UM3gpecrT898gAZTePPcjInSzF6TC0gjOiJ+I1bsTFZ 23 | ruH2dobcYEPTPmWVJrD0coFlXls7Wo43q8vBsXTWAbxIjLjEmPB5H+VhtHHTHMF4 24 | b3ZyerCvpNTUyXtmgzVFDTqZbGVv2gsl0aqpQy83R/UPK6p1zmQ+anAiSr0lSpul 25 | bNXEmJd8DX6uynlFG2kQcTApbv9hhdtdZkSB3UBk1YVLrecHXYioQNcaYCYTH1HH 26 | AqDuEhWPw+lseAjVFIRVkPxhMLZMJ3CQdrELBahOnKtKYE2skkXpNxNi6Jble7hb 27 | 3kHWQ08JLXALSUDf+bBZIvYtywZHmEVXPkrVb3ltV7cyvNAWjFHU9Nknh9XRBj8X 28 | 4aJIynEVb6qqbpqRWSV+ZOx77Eti3Di65qkiOAL4D9ump6eCeaSzhD4c5MlY4G6U 29 | Gd90uaoNK8XIXymeWLwZROBd9lhrCIr/QeRY6r66AN1yl1p0LYKNZg== 30 | -----END RSA PRIVATE KEY----- 31 | -------------------------------------------------------------------------------- /mqtt-zoo/keystore/server.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wizzercn/MqttWk/addfd0ad452a1917e8a95e3cf06096696ce3fc38/mqtt-zoo/keystore/server.pfx -------------------------------------------------------------------------------- /mqtt-zoo/linux_sysctl.txt: -------------------------------------------------------------------------------- 1 | #修改/etc/sysctl.conf 2 | 3 | net.ipv4.icmp_echo_ignore_broadcasts = 1 4 | 5 | fs.file-max = 1048576 6 | 7 | net.ipv4.tcp_max_tw_buckets=1048576 8 | 9 | net.ipv4.tcp_fin_timeout = 15 10 | 11 | net.nf_conntrack_max=1000000 12 | 13 | net.netfilter.nf_conntrack_tcp_timeout_time_wait=30 14 | 15 | fs.nr_open=2097152 16 | 17 | net.core.somaxconn=32768 18 | 19 | net.ipv4.tcp_max_syn_backlog=16384 20 | 21 | net.core.netdev_max_backlog=16384 22 | 23 | net.ipv4.ip_local_port_range=1000 65535 24 | 25 | net.core.rmem_default=262144 26 | 27 | net.core.wmem_default=262144 28 | 29 | net.core.rmem_max=16777216 30 | 31 | net.core.wmem_max=16777216 32 | 33 | net.core.optmem_max=16777216 34 | 35 | net.ipv4.tcp_rmem=1024 4096 16777216 36 | 37 | net.ipv4.tcp_wmem=1024 4096 16777216 -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | cn.wizzer 6 | mqtt-test-kafka 7 | 1.4.0-netty 8 | 4.0.0 9 | jar 10 | 11 | 1.4.0-netty 12 | 2.4.1.v20201014 13 | 4.1.77.Final 14 | 2.0.4 15 | 5.8.1 16 | 2.0.0 17 | 1.7.25 18 | 1.2.9 19 | UTF-8 20 | UTF-8 21 | 22 | 23 | 24 | org.nutz 25 | nutzboot-parent 26 | ${nutzboot.version} 27 | pom 28 | import 29 | 30 | 31 | cn.wizzer 32 | mqtt-common 33 | ${mqttwk.version} 34 | 35 | 36 | cn.wizzer 37 | mqtt-store 38 | ${mqttwk.version} 39 | 40 | 41 | cn.wizzer 42 | mqtt-auth 43 | ${mqttwk.version} 44 | 45 | 46 | org.nutz 47 | nutzboot-core 48 | ${nutzboot.version} 49 | 50 | 51 | cn.hutool 52 | hutool-crypto 53 | ${hutool.version} 54 | 55 | 56 | org.apache.kafka 57 | kafka_2.12 58 | ${kafka_2.12.version} 59 | 60 | 61 | 62 | org.slf4j 63 | slf4j-api 64 | ${slf4j.version} 65 | 66 | 67 | ch.qos.logback 68 | logback-core 69 | ${logback.version} 70 | 71 | 72 | ch.qos.logback 73 | logback-classic 74 | ${logback.version} 75 | 76 | 77 | 78 | 79 | nutz 80 | http://jfrog.nutz.cn/artifactory/libs-release 81 | 82 | 83 | nutz-snapshots 84 | http://jfrog.nutz.cn/artifactory/snapshots 85 | 86 | true 87 | always 88 | 89 | 90 | false 91 | 92 | 93 | 94 | 95 | 96 | nutz-snapshots 97 | http://jfrog.nutz.cn/artifactory/snapshots 98 | 99 | true 100 | always 101 | 102 | 103 | false 104 | 105 | 106 | 107 | 108 | 109 | 110 | org.apache.maven.plugins 111 | maven-compiler-plugin 112 | 3.7.0 113 | 114 | 1.8 115 | 1.8 116 | 117 | -parameters 118 | 119 | false 120 | 121 | 122 | 123 | org.nutz.boot 124 | nutzboot-maven-plugin 125 | ${nutzboot.version} 126 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/java/cn/wizzer/iot/mqtt/server/test/InternalMessage.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.test; 2 | 3 | import java.io.Serializable; 4 | /** 5 | * 内部消息 6 | */ 7 | public class InternalMessage implements Serializable { 8 | 9 | private static final long serialVersionUID = -1L; 10 | 11 | //当前频道clientId 12 | private String clientId; 13 | 14 | private String topic; 15 | 16 | private int mqttQoS; 17 | 18 | private byte[] messageBytes; 19 | 20 | private boolean retain; 21 | 22 | private boolean dup; 23 | 24 | public String getClientId() { 25 | return clientId; 26 | } 27 | 28 | public InternalMessage setClientId(String clientId) { 29 | this.clientId = clientId; 30 | return this; 31 | } 32 | 33 | public String getTopic() { 34 | return topic; 35 | } 36 | 37 | public InternalMessage setTopic(String topic) { 38 | this.topic = topic; 39 | return this; 40 | } 41 | 42 | public int getMqttQoS() { 43 | return mqttQoS; 44 | } 45 | 46 | public InternalMessage setMqttQoS(int mqttQoS) { 47 | this.mqttQoS = mqttQoS; 48 | return this; 49 | } 50 | 51 | public byte[] getMessageBytes() { 52 | return messageBytes; 53 | } 54 | 55 | public InternalMessage setMessageBytes(byte[] messageBytes) { 56 | this.messageBytes = messageBytes; 57 | return this; 58 | } 59 | 60 | public boolean isRetain() { 61 | return retain; 62 | } 63 | 64 | public InternalMessage setRetain(boolean retain) { 65 | this.retain = retain; 66 | return this; 67 | } 68 | 69 | public boolean isDup() { 70 | return dup; 71 | } 72 | 73 | public InternalMessage setDup(boolean dup) { 74 | this.dup = dup; 75 | return this; 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/java/cn/wizzer/iot/mqtt/server/test/KafkaLauncher.java: -------------------------------------------------------------------------------- 1 | package cn.wizzer.iot.mqtt.server.test; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import org.apache.kafka.clients.consumer.ConsumerRecord; 5 | import org.apache.kafka.clients.consumer.ConsumerRecords; 6 | import org.apache.kafka.clients.consumer.KafkaConsumer; 7 | import org.nutz.boot.NbApp; 8 | import org.nutz.ioc.impl.PropertiesProxy; 9 | import org.nutz.ioc.loader.annotation.Inject; 10 | import org.nutz.ioc.loader.annotation.IocBean; 11 | import org.nutz.log.Log; 12 | import org.nutz.log.Logs; 13 | import org.nutz.mvc.annotation.Modules; 14 | 15 | import java.time.Duration; 16 | import java.util.Arrays; 17 | import java.util.Properties; 18 | 19 | /** 20 | * Created by wizzer on 2018 21 | */ 22 | @IocBean(create = "init") 23 | @Modules(packages = "cn.wizzer.iot") 24 | public class KafkaLauncher { 25 | private static final Log log = Logs.get(); 26 | @Inject 27 | private PropertiesProxy conf; 28 | 29 | public static void main(String[] args) throws Exception { 30 | NbApp nb = new NbApp().setArgs(args).setPrintProcDoc(true); 31 | nb.setMainPackage("cn.wizzer.iot"); 32 | nb.run(); 33 | } 34 | 35 | public Properties getProperties() { 36 | Properties properties = new Properties(); 37 | for (String key : conf.keySet()) { 38 | if (key.startsWith("mqttwk.broker.kafka.")) { 39 | properties.put(key.substring("mqttwk.broker.kafka.".length()), conf.get(key)); 40 | } 41 | } 42 | return properties; 43 | } 44 | 45 | public void init() { 46 | KafkaConsumer kafkaConsumer = new KafkaConsumer(getProperties()); 47 | //kafka消费消息,接收MQTT发来的消息 48 | kafkaConsumer.subscribe(Arrays.asList(conf.get("mqttwk.broker.kafka.producer.topic"))); 49 | int sum = 0; 50 | while (true) { 51 | ConsumerRecords records = kafkaConsumer.poll(Duration.ofMillis(500)); 52 | for (ConsumerRecord record : records) { 53 | log.debugf("offset = %d, key = %s, value = %s%n", record.offset(), record.key(), JSONObject.parseObject(record.value(), InternalMessage.class)); 54 | log.debugf("总计收到 %s条", ++sum); 55 | } 56 | } 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/resources/META-INF/nutz/org.nutz.boot.config.ConfigureLoader: -------------------------------------------------------------------------------- 1 | org.nutz.boot.config.impl.YamlConfigureLoader -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/resources/application.yaml: -------------------------------------------------------------------------------- 1 | nutz: 2 | application: 3 | name: test 4 | 5 | mqttwk: 6 | broker: 7 | kafka: 8 | # 是否启用kafka消息转发 9 | broker-enabled: false 10 | bootstrap: 11 | # kafka地址 127.0.0.1:9092,127.0.0.1:9093 12 | servers: 127.0.0.1:9092 13 | # acks回令 如果必须等待回令,那么设置acks为all,否则,设置为-1,等待回令会有性能损耗 14 | acks: -1 15 | # 重试次数 16 | retries: 3 17 | batch: 18 | # 批量提交大小 19 | size: 16384 20 | linger: 21 | # 提交延迟等待时间(等待时间内可以追加提交) 22 | ms: 1 23 | buffer: 24 | # 缓存大小 25 | memory: 33554432 26 | key: 27 | # 序列化方式 28 | serializer: org.apache.kafka.common.serialization.StringSerializer 29 | value: 30 | # 序列化方式 31 | serializer: org.apache.kafka.common.serialization.StringSerializer 32 | partitioner: 33 | class: cn.wizzer.iot.mqtt.server.store.kafka.SimplePartitioner 34 | producer: 35 | # kafka转发的主题 36 | topic: mqtt_publish 37 | consumer: 38 | topic: mqtt_subscribe 39 | group: 40 | id: mqttwk 41 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | :: MqttWk by https://wizzer.cn 2 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-kafka/src/main/resources/logback.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | %highlight(%date{yyyy-MM-dd HH:mm:ss.SSS}) %cyan([%thread]) %yellow(%-5level) %green(%logger{36}).%gray(%M)-%boldMagenta(%line) - %blue(%msg%n) 7 | 8 | 9 | 10 | 11 | 12 | 13 | logs/test-%d{yyyy-MM-dd}.%i.txt 14 | 15 | 50MB 16 | 30 17 | 1GB 18 | 19 | 20 | [%-5level] %d{HH:mm:ss.SSS} %logger - %msg%n 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 30 | 256 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /mqtt-zoo/mqtt-test-websocket/src/mqtt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Wizzer.cn 4 | 5 | 6 | 7 | 8 | 9 | F12打开控制台 10 |
11 |
12 | 13 |
14 |
15 | 16 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /mqtt-zoo/test.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Wizzercn/MqttWk/addfd0ad452a1917e8a95e3cf06096696ce3fc38/mqtt-zoo/test.png -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | cn.wizzer 7 | mqtt-wk 8 | pom 9 | 1.4.0-netty 10 | MqttWk 11 | 12 | mqtt-common 13 | mqtt-auth 14 | mqtt-broker 15 | mqtt-store 16 | 17 | 18 | 1.4.0-netty 19 | 2.4.3-SNAPSHOT 20 | 4.1.77.Final 21 | 2.0.4 22 | 5.8.1 23 | 1.7.25 24 | 1.2.9 25 | 2.0.0 26 | UTF-8 27 | UTF-8 28 | 29 | https://budwk.com 30 | java MQTT broker, base on nutzboot and netty 31 | 32 | Github Issue 33 | https://github.com/Wizzercn/MqttWk/issues 34 | 35 | 36 | 37 | The Apache Software License, Version 2.0 38 | http://apache.org/licenses/LICENSE-2.0.txt 39 | 40 | 41 | 42 | scm:git:git://github.com/Wizzercn/MqttWk.git 43 | scm:git:git://github.com/Wizzercn/MqttWk.git 44 | git://github.com/Wizzercn/MqttWk.git 45 | 46 | 47 | 48 | wizzercn 49 | Wizzer 50 | wizzer.cn@gmail.com 51 | https://budwk.com 52 | 53 | 54 | wendal 55 | Wendal Chen 56 | wendal1985@gmail.com 57 | http://wendal.net/ 58 | 59 | 60 | lithium429 61 | Cao Shi 62 | 63 | 64 | 65 | 66 | 67 | org.nutz 68 | nutzboot-parent 69 | ${nutzboot.version} 70 | pom 71 | import 72 | 73 | 74 | 75 | 76 | 77 | nutz 78 | https://jfrog.nutz.cn/artifactory/libs-release 79 | 80 | 81 | nutz-snapshots 82 | https://jfrog.nutz.cn/artifactory/snapshots 83 | 84 | true 85 | 86 | 87 | false 88 | 89 | 90 | 91 | 92 | 93 | nutz-snapshots 94 | https://jfrog.nutz.cn/artifactory/snapshots 95 | 96 | true 97 | 98 | 99 | false 100 | 101 | 102 | 103 | 104 | 105 | 106 | org.apache.maven.plugins 107 | maven-compiler-plugin 108 | 3.7.0 109 | 110 | 1.8 111 | 1.8 112 | 113 | -parameters 114 | 115 | false 116 | 117 | 118 | 119 | org.apache.maven.plugins 120 | maven-javadoc-plugin 121 | 3.0.1 122 | 123 | UTF-8 124 | UTF-8 125 | UTF-8 126 | 127 | 128 | 129 | attach-javadocs 130 | 131 | jar 132 | 133 | 134 | 135 | 136 | 137 | org.apache.maven.plugins 138 | maven-source-plugin 139 | 3.0.1 140 | 141 | true 142 | 143 | 144 | 145 | compile 146 | 147 | jar 148 | 149 | 150 | 151 | 152 | 153 | org.apache.maven.plugins 154 | maven-surefire-plugin 155 | 2.22.0 156 | 157 | true 158 | 159 | 160 | 161 | 162 | 163 | 164 | nutzcn-snapshots 165 | https://jfrog.nutz.cn/artifactory/snapshots/ 166 | 167 | 168 | sonatype-release-staging 169 | Sonatype Nexus release repository 170 | https://oss.sonatype.org/service/local/staging/deploy/maven2 171 | 172 | 173 | --------------------------------------------------------------------------------