├── .gitignore ├── README.md ├── deploy ├── prod │ ├── start.sh │ └── stop.sh └── test │ ├── start.sh │ └── stop.sh ├── img └── readme │ ├── author.png │ └── redBag.png ├── mqtt-broker ├── .gitignore ├── pom.xml └── src │ ├── main │ └── java │ │ └── joey │ │ └── mqtt │ │ └── broker │ │ ├── MqttServer.java │ │ ├── MqttServerMain.java │ │ ├── auth │ │ ├── AuthUser.java │ │ ├── IAuth.java │ │ └── impl │ │ │ └── DefaultAuthImpl.java │ │ ├── codec │ │ └── MqttWebSocketCodec.java │ │ ├── config │ │ ├── Config.java │ │ ├── CustomConfig.java │ │ ├── NettyConfig.java │ │ ├── RedisConfig.java │ │ ├── ServerConfig.java │ │ ├── SslContextConfig.java │ │ └── YamlWrapperConfig.java │ │ ├── constant │ │ ├── BusinessConstants.java │ │ └── NumConstants.java │ │ ├── core │ │ ├── MqttMaster.java │ │ ├── client │ │ │ └── ClientSession.java │ │ ├── dispatcher │ │ │ ├── DispatcherCommand.java │ │ │ ├── DispatcherCommandCenter.java │ │ │ ├── DispatcherResult.java │ │ │ └── DispatcherWorker.java │ │ ├── message │ │ │ └── CommonPublishMessage.java │ │ └── subscription │ │ │ ├── SubWildcardTree.java │ │ │ └── Subscription.java │ │ ├── enums │ │ ├── AuthTopicOperationEnum.java │ │ └── ServerProtocolTypeEnum.java │ │ ├── event │ │ ├── listener │ │ │ ├── EventListenerExecutor.java │ │ │ ├── IEventListener.java │ │ │ ├── adapter │ │ │ │ └── EventListenerAdapter.java │ │ │ └── impl │ │ │ │ └── HttpCallbackEventListener.java │ │ ├── message │ │ │ ├── AbstractEventMessage.java │ │ │ ├── ConnectEventMessage.java │ │ │ ├── ConnectionLostEventMessage.java │ │ │ ├── DisconnectEventMessage.java │ │ │ ├── EventMessage.java │ │ │ ├── PingEventMessage.java │ │ │ ├── PubAckEventMessage.java │ │ │ ├── PubCompEventMessage.java │ │ │ ├── PubRecEventMessage.java │ │ │ ├── PubRelEventMessage.java │ │ │ ├── PublishEventMessage.java │ │ │ ├── SubscribeEventMessage.java │ │ │ └── UnsubscribeEventMessage.java │ │ └── processor │ │ │ ├── ConnectEventProcessor.java │ │ │ ├── ConnectionLostEventProcessor.java │ │ │ ├── DisconnectEventProcessor.java │ │ │ ├── IEventProcessor.java │ │ │ ├── PingReqEventProcessor.java │ │ │ ├── PubAckEventProcessor.java │ │ │ ├── PubCompEventProcessor.java │ │ │ ├── PubRecEventProcessor.java │ │ │ ├── PubRelEventProcessor.java │ │ │ ├── PublishEventProcessor.java │ │ │ ├── SubscribeEventProcessor.java │ │ │ └── UnsubscribeEventProcessor.java │ │ ├── exception │ │ └── MqttException.java │ │ ├── handler │ │ └── MqttMainHandler.java │ │ ├── hazelcast │ │ └── HazelcastFactory.java │ │ ├── innertraffic │ │ ├── BaseInnerTraffic.java │ │ ├── EmptyInnerTraffic.java │ │ ├── HazelcastInnerTraffic.java │ │ ├── IInnerTraffic.java │ │ ├── InnerPublishEventProcessor.java │ │ └── RedisInnerTraffic.java │ │ ├── provider │ │ ├── HazelcastExtendProvider.java │ │ ├── IExtendProvider.java │ │ ├── MemoryExtendProvider.java │ │ ├── RedisExtendProvider.java │ │ └── RedisWithHzInnerTrafficExtendProvider.java │ │ ├── redis │ │ ├── RedisClient.java │ │ └── RedisFactory.java │ │ ├── store │ │ ├── BaseSubscriptionStore.java │ │ ├── IDupPubMessageStore.java │ │ ├── IDupPubRelMessageStore.java │ │ ├── IMessageIdStore.java │ │ ├── IRetainMessageStore.java │ │ ├── ISessionStore.java │ │ ├── IStore.java │ │ ├── ISubscriptionStore.java │ │ ├── hazelcast │ │ │ ├── HazelcastBaseStore.java │ │ │ ├── HazelcastDupBaseMessageStore.java │ │ │ ├── HazelcastDupPubMessageStore.java │ │ │ ├── HazelcastDupPubRelMessageStore.java │ │ │ ├── HazelcastMessageIdStore.java │ │ │ ├── HazelcastRetainMessageStore.java │ │ │ └── HazelcastSubscriptionStore.java │ │ ├── memory │ │ │ ├── MemoryDupBaseMessageStore.java │ │ │ ├── MemoryDupPubMessageStore.java │ │ │ ├── MemoryDupPubRelMessageStore.java │ │ │ ├── MemoryMessageIdStore.java │ │ │ ├── MemoryRetainMessageStore.java │ │ │ ├── MemorySessionStore.java │ │ │ └── MemorySubscriptionStore.java │ │ └── redis │ │ │ ├── RedisDupBaseMessageStore.java │ │ │ ├── RedisDupPubMessageStore.java │ │ │ ├── RedisDupPubRelMessageStore.java │ │ │ ├── RedisMessageIdStore.java │ │ │ ├── RedisRetainMessageStore.java │ │ │ └── RedisSubscriptionStore.java │ │ └── util │ │ ├── ConfigUtils.java │ │ ├── MessageUtils.java │ │ ├── NettyUtils.java │ │ ├── SslContextUtils.java │ │ ├── Stopwatch.java │ │ └── TopicUtils.java │ └── test │ ├── java │ └── joey │ │ └── mqtt │ │ └── broker │ │ ├── ServerWithPropertiesTest.java │ │ └── ServerWithYmlTest.java │ └── resources │ ├── config.properties │ └── config.yml ├── mqtt-springboot ├── .gitignore ├── mvnw ├── mvnw.cmd ├── pom.xml └── src │ ├── main │ ├── java │ │ └── joey │ │ │ └── mqtt │ │ │ └── springboot │ │ │ ├── MqttApplication.java │ │ │ ├── config │ │ │ ├── MqttConfig.java │ │ │ └── MqttServerBeanConfig.java │ │ │ ├── controller │ │ │ ├── ClientController.java │ │ │ └── StatsController.java │ │ │ ├── runner │ │ │ └── MqttServerRunner.java │ │ │ └── task │ │ │ └── StatsTask.java │ └── resources │ │ ├── application-local.yml │ │ ├── application-prod.yml │ │ ├── application-test.yml │ │ ├── application.yml │ │ ├── banner.txt │ │ ├── hazelcast │ │ ├── hazelcast-local.xml │ │ ├── hazelcast-prod.xml │ │ └── hazelcast-test.xml │ │ ├── log4j2-local.xml │ │ ├── log4j2-spring.xml │ │ └── ssl │ │ ├── ca.jks │ │ ├── ca.key │ │ ├── ca.p12 │ │ ├── ca.pem │ │ ├── ca.srl │ │ ├── client.csr │ │ ├── client.jks │ │ ├── client.key │ │ ├── client.p12 │ │ ├── client.pem │ │ ├── server.csr │ │ ├── server.jks │ │ ├── server.key │ │ ├── server.p12 │ │ ├── server.pem │ │ └── 生成ssl文件说明.md │ └── test │ └── java │ └── joey │ └── mqtt │ └── springboot │ └── MqttSpringbootApplicationTests.java ├── mqtt-test ├── .gitignore ├── mqtt-mock ├── mqtt-mock-linux ├── mqtt-websocket-ssl.html ├── mqtt-websocket.html ├── pom.xml └── src │ ├── main │ ├── java │ │ └── joey │ │ │ └── mqtt │ │ │ └── broker │ │ │ └── pubsub │ │ │ └── performance │ │ │ └── MqttCounter.java │ └── resources │ │ └── ssl │ │ └── jomqtt-server.pfx │ └── test │ ├── java │ └── joey │ │ └── mqtt │ │ └── test │ │ ├── BaseTest.java │ │ ├── TestSuite.java │ │ ├── pubSub │ │ ├── RetainMessageTest.java │ │ └── WillMessageTest.java │ │ └── topic │ │ ├── TopicUtilsTest.java │ │ └── WildcardTreeTest.java │ └── resources │ ├── hazelcast-conf.xml │ └── mqtt-conf.properties └── pom.xml /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .settings/ 4 | .classpath 5 | *.iml 6 | *.DS_Store 7 | target/ 8 | /mqtt-springboot/.mvn/ 9 | -------------------------------------------------------------------------------- /deploy/prod/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JAVA_MEM_OPTS="-Xms4g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m -XX:NewRatio=1 -XX:SurvivorRatio=8 " 4 | 5 | JAVA_GC_OPTS="-XX:+ExplicitGCInvokesConcurrent -XX:MaxTenuringThreshold=6 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly " 6 | 7 | JAVA_DUMP_OPTS="-XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/logs/jo-mqtt/heap-dump.hprof " 8 | 9 | JAVA_LOG_OPTS="-XX:+PrintCommandLineFlags -Xloggc:/home/logs/jo-mqtt/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:ErrorFile=/home/logs/jo-mqtt/hs_err_pid_%p.log " 10 | 11 | JAVA_JMX_OPTS="-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=1899 -Dcom.sun.management.jmxremote.ssl=false " 12 | 13 | JAVA_OTHER_OPTS="-Dfile.encoding=UTF-8 -Duser.timezone=GMT+08 -Djava.security.egd=file:/dev/./urandom -XX:+UseCompressedOops -XX:+ParallelRefProcEnabled " 14 | 15 | JAVA_OPTS="$JAVA_MEM_OPTS $JAVA_GC_OPTS $JAVA_DUMP_OPTS $JAVA_LOG_OPTS $JAVA_JMX_OPTS $JAVA_OTHER_OPTS " 16 | 17 | nohup java -server -jar $JAVA_OPTS -Dserver.port=7788 -Dmqtt.serverConfig.tcpPort=1883 -Dspring.profiles.active=prod -Dmqtt.nettyConfig.epoll=true mqtt-broker.jar>console.log 2>&1 & 18 | echo "$!" > run.pid 19 | echo "mqtt启动完成!" 20 | -------------------------------------------------------------------------------- /deploy/prod/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kill `cat run.pid` -------------------------------------------------------------------------------- /deploy/test/start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | JAVA_MEM_OPTS="-Xms4g -Xmx4g -XX:MetaspaceSize=512m -XX:MaxMetaspaceSize=1024m -XX:NewRatio=1 -XX:SurvivorRatio=8 " 4 | 5 | JAVA_GC_OPTS="-XX:+ExplicitGCInvokesConcurrent -XX:MaxTenuringThreshold=6 -XX:+UseConcMarkSweepGC -XX:+UseParNewGC -XX:CMSInitiatingOccupancyFraction=70 -XX:+UseCMSInitiatingOccupancyOnly " 6 | 7 | JAVA_DUMP_OPTS="-XX:+CrashOnOutOfMemoryError -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/home/logs/jo-mqtt/heap-dump.hprof " 8 | 9 | JAVA_LOG_OPTS="-XX:+PrintCommandLineFlags -Xloggc:/home/logs/jo-mqtt/gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCApplicationStoppedTime -XX:ErrorFile=/home/logs/jo-mqtt/hs_err_pid_%p.log " 10 | 11 | JAVA_JMX_OPTS="-Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.port=1899 -Dcom.sun.management.jmxremote.ssl=false " 12 | 13 | JAVA_OTHER_OPTS="-Dfile.encoding=UTF-8 -Duser.timezone=GMT+08 -Djava.security.egd=file:/dev/./urandom -XX:+UseCompressedOops -XX:+ParallelRefProcEnabled " 14 | 15 | JAVA_OPTS="$JAVA_MEM_OPTS $JAVA_GC_OPTS $JAVA_DUMP_OPTS $JAVA_LOG_OPTS $JAVA_JMX_OPTS $JAVA_OTHER_OPTS " 16 | 17 | nohup java -server -jar $JAVA_OPTS -Dserver.port=7788 -Dmqtt.serverConfig.tcpPort=1883 -Dspring.profiles.active=test -Dmqtt.nettyConfig.epoll=false mqtt-broker.jar>console.log 2>&1 & 18 | echo "$!" > run.pid 19 | echo "mqtt启动完成!" 20 | -------------------------------------------------------------------------------- /deploy/test/stop.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | kill `cat run.pid` -------------------------------------------------------------------------------- /img/readme/author.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/img/readme/author.png -------------------------------------------------------------------------------- /img/readme/redBag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/img/readme/redBag.png -------------------------------------------------------------------------------- /mqtt-broker/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .settings/ 4 | .classpath 5 | *.iml 6 | *.DS_Store 7 | target/ -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/MqttServerMain.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker; 2 | 3 | import joey.mqtt.broker.config.Config; 4 | import joey.mqtt.broker.constant.BusinessConstants; 5 | import joey.mqtt.broker.util.ConfigUtils; 6 | import joey.mqtt.broker.util.Stopwatch; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | /** 10 | * mqtt server 启动主方法 11 | * 12 | * @author Joey 13 | * @date 2019/7/18 14 | */ 15 | @Slf4j 16 | public class MqttServerMain { 17 | public static void main(String[] args) throws Exception { 18 | Stopwatch start = Stopwatch.start(); 19 | 20 | MqttServer server = new MqttServer(ConfigUtils.loadFromSystemProps(BusinessConstants.MQTT_CONFIG, new Config())); 21 | server.start(); 22 | 23 | log.info("MqttServer start. timeCost={}ms", start.elapsedMills()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/auth/AuthUser.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.auth; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * 授权用户 8 | * 9 | * @author Joey 10 | * @date 2019/9/7 11 | */ 12 | @Setter 13 | @Getter 14 | public class AuthUser { 15 | private String userName; 16 | 17 | private String password; 18 | } 19 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/auth/IAuth.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.auth; 2 | 3 | import joey.mqtt.broker.enums.AuthTopicOperationEnum; 4 | 5 | /** 6 | * 授权 7 | * 8 | * @author Joey 9 | * @date 2022/7/22 10 | */ 11 | public interface IAuth { 12 | /** 13 | * 检查用户名和密码 14 | * 15 | * @param userName 16 | * @param password 17 | * @return 18 | */ 19 | boolean checkUserAuth(String userName, byte[] password); 20 | 21 | /** 22 | * 检查topic权限 23 | * 24 | * @param clientId 25 | * @param topic 26 | * @param topicOperationEnum 27 | * @return 28 | */ 29 | boolean checkTopicAuth(String clientId, String topic, AuthTopicOperationEnum topicOperationEnum); 30 | } 31 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/auth/impl/DefaultAuthImpl.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.auth.impl; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.util.ObjectUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import cn.hutool.crypto.SecureUtil; 7 | import joey.mqtt.broker.auth.AuthUser; 8 | import joey.mqtt.broker.auth.IAuth; 9 | import joey.mqtt.broker.config.CustomConfig; 10 | import joey.mqtt.broker.enums.AuthTopicOperationEnum; 11 | 12 | import java.util.HashMap; 13 | import java.util.List; 14 | import java.util.Map; 15 | 16 | /** 17 | * 默认授权实现 18 | * @author Joey 19 | * @date 2019/7/22 20 | */ 21 | public class DefaultAuthImpl implements IAuth { 22 | private final Map userPassMap = new HashMap<>(); 23 | 24 | private final CustomConfig customConfig; 25 | 26 | public DefaultAuthImpl(List userList, CustomConfig customConfig) { 27 | if (CollUtil.isNotEmpty(userList)) { 28 | userList.forEach(user -> { 29 | userPassMap.put(user.getUserName(), user.getPassword()); 30 | }); 31 | } 32 | 33 | this.customConfig = customConfig; 34 | } 35 | 36 | @Override 37 | public boolean checkUserAuth(String userName, byte[] password) { 38 | if (StrUtil.isBlank(userName) || null == password) { 39 | return false; 40 | } 41 | 42 | String authPass = userPassMap.get(userName); 43 | if (StrUtil.isBlank(authPass)) { 44 | return false; 45 | } 46 | 47 | String encodedPass = SecureUtil.sha256(new String(password)); 48 | if (ObjectUtil.notEqual(authPass, encodedPass)) { 49 | return false; 50 | } 51 | 52 | return true; 53 | } 54 | 55 | @Override 56 | public boolean checkTopicAuth(String clientId, String topic, AuthTopicOperationEnum topicOperationEnum) { 57 | // todo 需要业务方自己完善 58 | switch (topicOperationEnum) { 59 | case READ: 60 | break; 61 | 62 | case WRITE: 63 | break; 64 | 65 | case READ_WRITE: 66 | break; 67 | 68 | default: 69 | // ignore 70 | } 71 | 72 | return true; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/codec/MqttWebSocketCodec.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.codec; 2 | 3 | import io.netty.buffer.ByteBuf; 4 | import io.netty.channel.ChannelHandlerContext; 5 | import io.netty.handler.codec.MessageToMessageCodec; 6 | import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; 7 | 8 | import java.util.List; 9 | 10 | /** 11 | * WebSocket Mqtt消息编解码器 12 | * 13 | * @author Joey 14 | * @date 2021-03-13 15 | * 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/joey/mqtt/broker/config/Config.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * mqtt-broker配置 9 | * 10 | * @author Joey 11 | * @date 2019/7/18 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class Config { 17 | private ServerConfig serverConfig = new ServerConfig(); 18 | 19 | private NettyConfig nettyConfig = new NettyConfig(); 20 | 21 | private CustomConfig customConfig = new CustomConfig(); 22 | } 23 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/CustomConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.alibaba.fastjson.JSONObject; 6 | import lombok.Data; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | /** 12 | * 自定义配置 13 | * 14 | * 用户可以根据自身需求配置extConf 15 | * 参考:test/resource/config.properties 16 | * 17 | * @author Joey 18 | * @date 2019/7/18 19 | */ 20 | @Data 21 | public class CustomConfig { 22 | /** 23 | * 若使用hazelcastExtendProvider集群间通信 24 | * 可配置hazelcast配置文件路径(不填将使用默认配置) 25 | */ 26 | private String hazelcastConfigFile; 27 | 28 | /** 29 | * redis配置 30 | */ 31 | private RedisConfig redisConfig = new RedisConfig(); 32 | 33 | /** 34 | * sslContext配置 35 | */ 36 | private SslContextConfig sslContextConfig = new SslContextConfig(); 37 | 38 | /** 39 | * 节点名称 用于区分不同的服务实例 40 | */ 41 | private String nodeName = IdUtil.fastSimpleUUID(); 42 | 43 | /** 44 | * 用户自定义扩展配置map 45 | */ 46 | private Map extConfig = new HashMap<>(); 47 | 48 | /** 49 | * 将自定义扩展配置 转换成用户定义的对象 50 | * 51 | * @param clazz 52 | * @param 53 | * @return 54 | */ 55 | public T convertExtConfig(Class clazz) { 56 | return JSONObject.parseObject(JSON.toJSONString(extConfig), clazz); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/NettyConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import joey.mqtt.broker.constant.NumConstants; 4 | import lombok.Data; 5 | 6 | /** 7 | * netty 配置 8 | * @author Joey 9 | * @date 2019/7/18 10 | */ 11 | @Data 12 | public class NettyConfig { 13 | /** 14 | * 0 = current_processors_amount * 2 15 | */ 16 | private int bossThreads = NumConstants.INT_0; 17 | 18 | /** 19 | * 0 = current_processors_amount * 2 20 | */ 21 | private int workerThreads = NumConstants.INT_0; 22 | 23 | private boolean epoll = false; 24 | 25 | private int soBacklog = NumConstants.INT_1024; 26 | 27 | private boolean soReuseAddress = true; 28 | 29 | private boolean tcpNoDelay = true; 30 | 31 | private int soSndBuf = NumConstants.INT_65536; 32 | 33 | private int soRcvBuf = NumConstants.INT_65536; 34 | 35 | private boolean soKeepAlive = true; 36 | 37 | private int channelTimeoutSeconds = NumConstants.INT_100; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/RedisConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import lombok.Data; 4 | import redis.clients.jedis.Protocol; 5 | 6 | /** 7 | * redis配置 8 | * 9 | * @author Joey 10 | * @date 2019/9/7 11 | */ 12 | @Data 13 | public class RedisConfig { 14 | private String host; 15 | 16 | private String password; 17 | 18 | private int port; 19 | 20 | private int database; 21 | 22 | private int timeout = Protocol.DEFAULT_TIMEOUT; 23 | 24 | private Pool pool; 25 | 26 | @Data 27 | public static class Pool { 28 | /** 29 | * Max number of "idle" connections in the pool. Use a negative value to indicate 30 | * an unlimited number of idle connections. 31 | */ 32 | private int maxIdle = 50; 33 | 34 | /** 35 | * Target for the minimum number of idle connections to maintain in the pool. This 36 | * setting only has an effect if it is positive. 37 | */ 38 | private int minIdle = 10; 39 | 40 | /** 41 | * Max number of connections that can be allocated by the pool at a given time. 42 | * Use a negative value for no limit. 43 | */ 44 | private int maxActive = 200; 45 | 46 | /** 47 | * Maximum amount of time (in milliseconds) a connection allocation should block 48 | * before throwing an exception when the pool is exhausted. Use a negative value 49 | * to block indefinitely. 50 | */ 51 | private int maxWait = 1000; 52 | 53 | private long minEvictableIdleTimeMillis = 5 * 60 * 1000L; 54 | 55 | /** 56 | * 有两个含义: 57 | *
58 | * 1) 检查连接的间隔时间,如果连接空闲时间大于等于minEvictableIdleTimeMillis则关闭物理连接。 59 | *
60 | * 2) testWhileIdle的判断依据,详细看testWhileIdle属性的说明 61 | */ 62 | private long timeBetweenEvictionRunsMillis = 60 * 1000L; 63 | 64 | /** 65 | * 申请连接的时候,如果空闲时间大于timeBetweenEvictionRunsMillis,则检查 66 | */ 67 | private boolean testWhileIdle = true; 68 | 69 | /** 70 | * 向资源池借用连接时是否做连接有效性检测(ping),无效连接会被移除 71 | */ 72 | private boolean testOnBorrow = false; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/ServerConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import joey.mqtt.broker.auth.AuthUser; 5 | import joey.mqtt.broker.constant.BusinessConstants; 6 | import joey.mqtt.broker.constant.NumConstants; 7 | import joey.mqtt.broker.provider.MemoryExtendProvider; 8 | import lombok.Data; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | /** 14 | * 服务配置 15 | * @author Joey 16 | * @date 2019/7/18 17 | */ 18 | @Data 19 | public class ServerConfig { 20 | /** 21 | * tcp端口 -1表示不启动 22 | */ 23 | private int tcpPort = BusinessConstants.MQTT_TCP_PORT; 24 | 25 | /** 26 | * tcp-ssl端口 -1表示不启动 1888 27 | */ 28 | private int tcpSslPort = NumConstants.INT_NEGATIVE_1; 29 | 30 | /** 31 | * webSocket访问路径 32 | */ 33 | private String webSocketPath = "/joMqtt"; 34 | 35 | /** 36 | * webSocket端口 -1表示不启动 2883 37 | */ 38 | private int webSocketPort = NumConstants.INT_NEGATIVE_1; 39 | 40 | /** 41 | * websocket-ssl端口 -1表示不启动 2888 42 | */ 43 | private int webSocketSslPort = NumConstants.INT_NEGATIVE_1; 44 | 45 | /** 46 | * 开启用户CA认证 47 | */ 48 | private boolean enableClientCA = false; 49 | 50 | private String hostname = StrUtil.EMPTY; 51 | 52 | /** 53 | * extendProvider接口实现的类全路径名称 54 | */ 55 | private String extendProviderClass = MemoryExtendProvider.class.getName(); 56 | 57 | /** 58 | * 是否开启用户名密码认证 59 | */ 60 | private boolean enableUserAuth = false; 61 | 62 | /** 63 | * 授权用户名和密码list 64 | */ 65 | private List authUsers = new ArrayList<>(); 66 | 67 | /** 68 | * 分发器数量 69 | * 70 | * 压力测试结果表现 此处数量与cpu核数相同为最佳 71 | */ 72 | private Integer dispatcherCount = Runtime.getRuntime().availableProcessors(); 73 | 74 | /** 75 | * 每个分发器处理队列大小 76 | */ 77 | private Integer dispatcherQueueSize = NumConstants.INT_1024; 78 | } 79 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/SslContextConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import lombok.Data; 4 | 5 | /** 6 | * sslContext配置 7 | * @author Joey 8 | * @date 2020/4/3 9 | */ 10 | @Data 11 | public class SslContextConfig { 12 | /** 13 | * ssl支持 14 | * @see io.netty.handler.ssl.SslProvider 15 | */ 16 | private String sslProvider; 17 | 18 | /** 19 | * jks文件绝对路径或者classPath路径 20 | */ 21 | private String jksFilePath; 22 | 23 | /** 24 | * keyStore类型 25 | */ 26 | private String keyStoreType; 27 | 28 | /** 29 | * 打开keyStore密码 30 | */ 31 | private String keyStorePassword; 32 | 33 | /** 34 | * 管理keyStore中别名密码 35 | */ 36 | private String keyManagerPassword; 37 | } 38 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/config/YamlWrapperConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.config; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Data; 5 | import lombok.NoArgsConstructor; 6 | 7 | /** 8 | * mqtt-broker-yaml文件配置 9 | * 10 | * @author Joey 11 | * @date 2019/7/18 12 | */ 13 | @Data 14 | @NoArgsConstructor 15 | @AllArgsConstructor 16 | public class YamlWrapperConfig { 17 | private Config mqtt = new Config(); 18 | } 19 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/constant/BusinessConstants.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.constant; 2 | 3 | /** 4 | * 业务常量类 5 | * 6 | * @author Joey 7 | * @date 2019/7/18 8 | */ 9 | public class BusinessConstants { 10 | public static final Integer MQTT_TCP_PORT = 1883; 11 | 12 | public static final String MQTT_CONFIG = "mqtt.conf"; 13 | 14 | public static final String MQTT_CONFIG_PROPS_PRE = "mqtt"; 15 | 16 | public static final String MQTT_SUB_PROTOCOL_CSV_LIST = "mqtt, mqttv3.1, mqttv3.1.1"; 17 | 18 | public static final String MQTT_DISPATCHER_THREAD_NAME_PRE = "mqtt-dispatcher-executor-"; 19 | 20 | /** 21 | * netty handler名称常量 22 | */ 23 | public static final String HANDLER_IDLE_STATE = "idleStateHandler"; 24 | 25 | public static final String HANDLER_SSL = "sslHandler"; 26 | 27 | public static final String HANDLER_HTTP_CODEC = "httpCodecHandler"; 28 | 29 | public static final String HANDLER_HTTP_AGGREGATOR = "httpAggregatorHandler"; 30 | 31 | public static final String HANDLER_HTTP_COMPRESSOR = "httpCompressorHandler"; 32 | 33 | public static final String HANDLER_WEB_SOCKET_SERVER_PROTOCOL = "webSocketServerProtocolHandler"; 34 | 35 | public static final String HANDLER_MQTT_WEB_SOCKET_CODEC = "mqttWebSocketCodecHandler"; 36 | 37 | public static final String HANDLER_MQTT_ENCODER = "mqttEncoderHandler"; 38 | 39 | public static final String HANDLER_MQTT_DECODER = "mqttDecoderHandler"; 40 | 41 | public static final String HANDLER_MQTT_MAIN = "mqttMainHandler"; 42 | 43 | /** 44 | * topic token常量 45 | */ 46 | public static final String TOKEN_ROOT = "joRootTopic"; 47 | 48 | public static final String TOKEN_MULTI = "#"; 49 | 50 | public static final String TOKEN_SINGLE = "+"; 51 | 52 | /** 53 | * hazelcast先关基本设置 54 | */ 55 | public static final String HAZELCAST_KEY_PRE = "joHz:"; 56 | 57 | /** 58 | * 使用hazelcast作为集群通信时的topic 59 | */ 60 | public static final String HAZELCAST_INNER_TRAFFIC_TOPIC = HAZELCAST_KEY_PRE + "innerTraffic"; 61 | 62 | public static final String HAZELCAST_MSG_ID = HAZELCAST_KEY_PRE + "msgId"; 63 | 64 | public static final String HAZELCAST_SUB_STORE = HAZELCAST_KEY_PRE + "subStore"; 65 | 66 | public static final String HAZELCAST_MSG_RETAIN = HAZELCAST_KEY_PRE + "msgRetain"; 67 | 68 | public static final String HAZELCAST_MSG_DUP_PUB = HAZELCAST_KEY_PRE + "msgDupPub"; 69 | 70 | public static final String HAZELCAST_MSG_DUP_PUB_REL = HAZELCAST_KEY_PRE + "msgDupPubRel"; 71 | 72 | /** 73 | * redis相关基本设置 74 | */ 75 | public static final Integer REDIS_EACH_SCAN_COUNT = NumConstants.INT_500; 76 | 77 | public static final String REDIS_KEY_PRE = "joMqtt:"; 78 | 79 | public static final String REDIS_MSG_ID_KEY_PRE = REDIS_KEY_PRE + "msg:"; 80 | 81 | public static final String REDIS_MSG_ID_FIELD = "id"; 82 | 83 | public static final String REDIS_SUB_STORE_KEY = REDIS_KEY_PRE + "subStore:"; 84 | 85 | public static final String REDIS_MSG_RETAIN_KEY = REDIS_KEY_PRE + "msgRetain"; 86 | 87 | public static final String REDIS_MSG_DUP_PUB_KEY_PRE = REDIS_KEY_PRE + "msgDupPub:"; 88 | 89 | public static final String REDIS_MSG_DUP_PUB_REL_KEY_PRE = REDIS_KEY_PRE + "msgDupPubRel:"; 90 | 91 | public static final String REDIS_INNER_TRAFFIC_PUB_CHANNEL = REDIS_KEY_PRE + "innerTrafficChannel"; 92 | } 93 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/constant/NumConstants.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.constant; 2 | 3 | /** 4 | * @Author:Joey 5 | * @Date: 2022/02/21 6 | * @Desc: 数字常量 7 | */ 8 | public final class NumConstants { 9 | public static final Integer INT_0 = 0; 10 | 11 | public static final Integer INT_1 = 1; 12 | 13 | public static final Integer INT_NEGATIVE_1 = -1; 14 | 15 | public static final Integer INT_2 = 2; 16 | 17 | public static final Integer INT_3 = 3; 18 | 19 | public static final Integer INT_10 = 10; 20 | 21 | public static final Integer INT_30 = 30; 22 | 23 | public static final Integer INT_100 = 100; 24 | 25 | public static final Integer INT_200 = 200; 26 | 27 | public static final Integer INT_500 = 500; 28 | 29 | public static final Integer INT_1000 = 1000; 30 | 31 | public static final Integer INT_1024 = 1024; 32 | 33 | public static final Integer INT_5000 = 5000; 34 | 35 | public static final Integer INT_65535 = 65535; 36 | 37 | public static final Integer INT_65536 = 65536; 38 | 39 | public static final Integer INT_1048576 = 1048576; 40 | 41 | public static final Long LONG_0 = 0L; 42 | 43 | public static final Long LONG_1 = 1L; 44 | 45 | public static final Long LONG_10 = 10L; 46 | 47 | public static final Float FLOAT_1_5 = 1.5F; 48 | } 49 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/client/ClientSession.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.client; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import cn.hutool.core.date.DateUtil; 5 | import cn.hutool.core.util.StrUtil; 6 | import io.netty.channel.Channel; 7 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 8 | import joey.mqtt.broker.core.message.CommonPublishMessage; 9 | import lombok.AccessLevel; 10 | import lombok.Getter; 11 | 12 | import java.io.Serializable; 13 | import java.util.Date; 14 | import java.util.Optional; 15 | 16 | /** 17 | * 用户session对象 18 | * @author Joey 19 | * @date 2019/7/22 20 | */ 21 | @Getter 22 | public class ClientSession implements Serializable { 23 | private final String clientId; 24 | 25 | private final String userName; 26 | 27 | @Getter(AccessLevel.NONE) 28 | private final Channel channel; 29 | 30 | private final boolean cleanSession; 31 | 32 | @Getter(AccessLevel.NONE) 33 | private final MqttPublishMessage willMessage; 34 | 35 | private final int keepAliveTimeSeconds; 36 | 37 | private final String createTimeStr; 38 | 39 | public ClientSession(Channel channel, String clientId, String userName, boolean cleanSession, MqttPublishMessage willMessage, int keepAliveTimeSeconds) { 40 | this(channel, clientId, userName, cleanSession, willMessage, keepAliveTimeSeconds, DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN)); 41 | } 42 | 43 | public ClientSession(Channel channel, String clientId, String userName, boolean cleanSession, MqttPublishMessage willMessage, int keepAliveTimeSeconds, String createTimeStr) { 44 | this.clientId = clientId; 45 | this.userName = userName; 46 | this.channel = channel; 47 | this.cleanSession = cleanSession; 48 | this.willMessage = willMessage; 49 | this.keepAliveTimeSeconds = keepAliveTimeSeconds; 50 | 51 | this.createTimeStr = createTimeStr; 52 | } 53 | 54 | public boolean isSameChannel(Channel comparedChannel) { 55 | return this.channel == comparedChannel; 56 | } 57 | 58 | /** 59 | * 关闭连接 60 | */ 61 | public void closeChannel() { 62 | channel.close(); 63 | } 64 | 65 | /** 66 | * 发送消息 67 | * 68 | * @param msg 69 | */ 70 | public void sendMsg(Object msg) { 71 | channel.writeAndFlush(msg); 72 | } 73 | 74 | /** 75 | * 获取遗言 76 | * 77 | * @return 78 | */ 79 | public CommonPublishMessage getPubMsgForWillMessage() { 80 | return Optional.ofNullable(willMessage) 81 | .map(msg -> CommonPublishMessage.convert(this.clientId, msg, true, StrUtil.EMPTY) 82 | .setCreateTimeStr(createTimeStr) 83 | ) 84 | .orElse(null); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/dispatcher/DispatcherCommand.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.dispatcher; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.concurrent.CompletableFuture; 5 | 6 | /** 7 | * @Author:Joey 8 | * @Date: 2022/7/25 9 | * @Desc: 分发命令 10 | **/ 11 | public class DispatcherCommand { 12 | private final String clientId; 13 | 14 | private final Callable action; 15 | 16 | private final CompletableFuture task; 17 | 18 | public DispatcherCommand(String clientId, Callable action) { 19 | this.clientId = clientId; 20 | this.action = action; 21 | this.task = new CompletableFuture<>(); 22 | } 23 | 24 | public void execute() throws Exception { 25 | action.call(); 26 | 27 | task.complete(clientId); 28 | } 29 | 30 | public CompletableFuture completableFuture() { 31 | return task; 32 | } 33 | 34 | public String getClientId() { 35 | return this.clientId; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/dispatcher/DispatcherResult.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.dispatcher; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.concurrent.CompletableFuture; 6 | 7 | /** 8 | * @Author:Joey 9 | * @Date: 2022/7/25 10 | * @Desc: 分发结果 11 | * 12 | * 参考: moquette PostOffice.RouteResult 13 | **/ 14 | @Slf4j 15 | public class DispatcherResult { 16 | private final String clientId; 17 | 18 | private final Status status; 19 | 20 | private CompletableFuture queuedFuture; 21 | 22 | enum Status {SUCCESS, FAIL} 23 | 24 | public static DispatcherResult success(String clientId, CompletableFuture queuedFuture) { 25 | return new DispatcherResult(clientId, Status.SUCCESS, queuedFuture); 26 | } 27 | 28 | public static DispatcherResult failed(String clientId) { 29 | return failed(clientId, null); 30 | } 31 | 32 | public static DispatcherResult failed(String clientId, String error) { 33 | final CompletableFuture failed = new CompletableFuture<>(); 34 | failed.completeExceptionally(new Error(error)); 35 | return new DispatcherResult(clientId, Status.FAIL, failed); 36 | } 37 | 38 | private DispatcherResult(String clientId, Status status, CompletableFuture queuedFuture) { 39 | this.clientId = clientId; 40 | this.status = status; 41 | this.queuedFuture = queuedFuture; 42 | } 43 | 44 | public CompletableFuture completableFuture() { 45 | if (status == Status.FAIL) { 46 | throw new IllegalArgumentException("Accessing completable future on a failed result"); 47 | } 48 | 49 | return queuedFuture; 50 | } 51 | 52 | public boolean isSuccess() { 53 | return status == Status.SUCCESS; 54 | } 55 | 56 | public DispatcherResult ifFailed(Runnable action) { 57 | if (!isSuccess()) { 58 | action.run(); 59 | } 60 | 61 | return this; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/dispatcher/DispatcherWorker.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.dispatcher; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | 5 | import java.util.concurrent.BlockingQueue; 6 | import java.util.concurrent.FutureTask; 7 | 8 | /** 9 | * @Author:Joey 10 | * @Date: 2022/7/25 11 | * @Desc: 分发任务 12 | **/ 13 | @Slf4j 14 | public class DispatcherWorker implements Runnable { 15 | private final BlockingQueue> dispatcherQueue; 16 | 17 | public DispatcherWorker(BlockingQueue> dispatcherQueue) { 18 | this.dispatcherQueue = dispatcherQueue; 19 | } 20 | 21 | @Override 22 | public void run() { 23 | while (!Thread.currentThread().isInterrupted()) { 24 | try { 25 | final FutureTask task = this.dispatcherQueue.take(); 26 | executeTask(task); 27 | 28 | } catch (InterruptedException e) { 29 | log.warn("Dispatcher task interrupted. threadName={}", Thread.currentThread().getName()); 30 | break; 31 | } 32 | } 33 | 34 | log.warn("Dispatcher task exit. threadName={}", Thread.currentThread().getName()); 35 | } 36 | 37 | public static void executeTask(final FutureTask task) { 38 | if (!task.isCancelled()) { 39 | try { 40 | task.run(); 41 | 42 | //阻塞 等待结果 43 | task.get(); 44 | } catch (Throwable ex) { 45 | log.error("Dispatcher task executed error. threadName={}", Thread.currentThread().getName(), ex); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/message/CommonPublishMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.message; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import cn.hutool.core.date.DateUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 7 | import joey.mqtt.broker.util.MessageUtils; 8 | import lombok.Getter; 9 | import lombok.NoArgsConstructor; 10 | import lombok.Setter; 11 | import lombok.experimental.Accessors; 12 | 13 | import java.io.Serializable; 14 | import java.util.Date; 15 | 16 | /** 17 | * 通用发布消息 18 | * 19 | * @author Joey 20 | * @date 2019/9/16 21 | */ 22 | @Getter 23 | @Setter 24 | @NoArgsConstructor 25 | @Accessors(chain = true) 26 | public class CommonPublishMessage implements Serializable { 27 | private String publishClientId; 28 | 29 | private String targetClientId; 30 | 31 | private String topic; 32 | 33 | private int messageId; 34 | 35 | private String messageBody; 36 | 37 | private int mqttQoS; 38 | 39 | private boolean isRetain; 40 | 41 | private boolean isDup; 42 | 43 | private boolean isWill; 44 | 45 | private String createTimeStr; 46 | 47 | private String sourceNodeName; 48 | 49 | /** 50 | * 转换消息 51 | * 52 | * @param publishClientId 53 | * @param msg 54 | * @param isWill 55 | * @param sourceNodeName 56 | * @return 57 | */ 58 | public static CommonPublishMessage convert(String publishClientId, MqttPublishMessage msg, boolean isWill, String sourceNodeName) { 59 | CommonPublishMessage convert = new CommonPublishMessage(); 60 | 61 | convert.publishClientId = publishClientId; 62 | 63 | convert.topic = msg.variableHeader().topicName(); 64 | convert.messageId = msg.variableHeader().packetId(); 65 | convert.messageBody = new String(MessageUtils.readBytesAndRewind(msg.payload())); 66 | convert.mqttQoS = msg.fixedHeader().qosLevel().value(); 67 | convert.isDup = msg.fixedHeader().isDup(); 68 | convert.isRetain = msg.fixedHeader().isRetain(); 69 | convert.isWill = isWill; 70 | 71 | convert.createTimeStr = DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN); 72 | convert.sourceNodeName = sourceNodeName; 73 | 74 | return convert; 75 | } 76 | 77 | /** 78 | * 拷贝消息 79 | * 80 | * @return 81 | */ 82 | public CommonPublishMessage copy() { 83 | CommonPublishMessage copy = new CommonPublishMessage(); 84 | 85 | copy.publishClientId = this.publishClientId; 86 | 87 | copy.topic = this.topic; 88 | copy.messageId = this.messageId; 89 | copy.messageBody = this.messageBody; 90 | copy.mqttQoS = this.mqttQoS; 91 | 92 | copy.isDup = this.isDup; 93 | copy.isRetain = this.isRetain; 94 | copy.isWill = this.isWill; 95 | 96 | copy.createTimeStr = this.createTimeStr; 97 | copy.sourceNodeName = this.sourceNodeName; 98 | 99 | return copy; 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return JSON.toJSONString(this); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/core/subscription/Subscription.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.core.subscription; 2 | 3 | import cn.hutool.core.date.DatePattern; 4 | import cn.hutool.core.date.DateUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import io.netty.handler.codec.mqtt.MqttQoS; 7 | import lombok.Getter; 8 | 9 | import java.io.Serializable; 10 | import java.util.Date; 11 | import java.util.Objects; 12 | 13 | /** 14 | * topic订阅对象 15 | * 16 | * @author Joey 17 | * @date 2019/7/22 18 | */ 19 | @Getter 20 | public class Subscription implements Serializable { 21 | private final String clientId; 22 | 23 | private final String topic; 24 | 25 | private final MqttQoS qos; 26 | 27 | private final String createTimeStr; 28 | 29 | public Subscription(String clientId, String topic, MqttQoS qos) { 30 | this(clientId, topic, qos, DateUtil.format(new Date(), DatePattern.PURE_DATETIME_PATTERN)); 31 | } 32 | 33 | public Subscription(String clientId, String topic, MqttQoS qos, String createTimeStr) { 34 | this.clientId = clientId; 35 | this.topic = topic; 36 | this.qos = qos; 37 | this.createTimeStr = createTimeStr; 38 | } 39 | 40 | @Override 41 | public boolean equals(Object o) { 42 | if (this == o) { 43 | return true; 44 | } 45 | if (o == null || getClass() != o.getClass()) { 46 | return false; 47 | } 48 | 49 | Subscription that = (Subscription) o; 50 | return Objects.equals(clientId, that.clientId) && Objects.equals(topic, that.topic); 51 | } 52 | 53 | @Override 54 | public int hashCode() { 55 | return Objects.hash(clientId, topic); 56 | } 57 | 58 | @Override 59 | public String toString() { 60 | return JSON.toJSONString(this); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/enums/AuthTopicOperationEnum.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @Author:Joey 8 | * @Date: 2022/7/23 9 | * @Desc: 服务协议类型枚举 10 | **/ 11 | @AllArgsConstructor 12 | @Getter 13 | public enum AuthTopicOperationEnum { 14 | READ, WRITE, READ_WRITE 15 | } 16 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/enums/ServerProtocolTypeEnum.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.enums; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | /** 7 | * @Author:Joey 8 | * @Date: 2022/7/23 9 | * @Desc: 服务协议类型枚举 10 | **/ 11 | @AllArgsConstructor 12 | @Getter 13 | public enum ServerProtocolTypeEnum { 14 | TCP("tcp"), 15 | 16 | WEB_SOCKET("webSocket"), 17 | ; 18 | 19 | public final String name; 20 | } 21 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/listener/IEventListener.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.listener; 2 | 3 | import joey.mqtt.broker.event.message.*; 4 | 5 | /** 6 | * 事件监听器 7 | * 8 | * @author Joey 9 | * @date 2019/9/8 10 | */ 11 | public interface IEventListener { 12 | enum Type { 13 | CONNECT, 14 | DISCONNECT, 15 | CONNECTION_LOST, 16 | PUBLISH, 17 | PUB_ACK, 18 | PUB_REC, 19 | PUB_REL, 20 | PUB_COMP, 21 | SUBSCRIBE, 22 | UNSUBSCRIBE, 23 | PING; 24 | } 25 | 26 | /** 27 | * 连接事件 28 | * @param connectMessage 29 | */ 30 | void onConnect(ConnectEventMessage connectMessage); 31 | 32 | /** 33 | * 断开连接事件 34 | * @param disconnectMessage 35 | */ 36 | void onDisconnect(DisconnectEventMessage disconnectMessage); 37 | 38 | /** 39 | * 连接丢失事件 40 | * @param connectionLostMessage 41 | */ 42 | void onConnectionLost(ConnectionLostEventMessage connectionLostMessage); 43 | 44 | /** 45 | * 发布事件 46 | * @param publishMessage 47 | */ 48 | void onPublish(PublishEventMessage publishMessage); 49 | 50 | /** 51 | * 发布ack事件 52 | * @param pubAckEventMessage 53 | */ 54 | void onPubAck(PubAckEventMessage pubAckEventMessage); 55 | 56 | /** 57 | * 发布rec事件 58 | * @param pubRecEventMessage 59 | */ 60 | void onPubRec(PubRecEventMessage pubRecEventMessage); 61 | 62 | /** 63 | * 发布rel事件 64 | * @param pubRelEventMessage 65 | */ 66 | void onPubRel(PubRelEventMessage pubRelEventMessage); 67 | 68 | /** 69 | * 发布comp事件 70 | * @param pubCompEventMessage 71 | */ 72 | void onPubComp(PubCompEventMessage pubCompEventMessage); 73 | 74 | /** 75 | * 订阅事件 76 | * @param subscribeMessage 77 | */ 78 | void onSubscribe(SubscribeEventMessage subscribeMessage); 79 | 80 | /** 81 | * 取消订阅事件 82 | * @param unsubscribeMessage 83 | */ 84 | void onUnsubscribe(UnsubscribeEventMessage unsubscribeMessage); 85 | 86 | /** 87 | * ping事件 88 | * @param pingEventMessage 89 | */ 90 | void onPing(PingEventMessage pingEventMessage); 91 | } 92 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/listener/adapter/EventListenerAdapter.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.listener.adapter; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.event.listener.IEventListener; 5 | import joey.mqtt.broker.event.message.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * 事件监听适配器默认实现 10 | * 11 | * @author Joey 12 | * @date 2019/9/8 13 | */ 14 | @Slf4j 15 | public class EventListenerAdapter implements IEventListener { 16 | protected final CustomConfig customConfig; 17 | 18 | public EventListenerAdapter(CustomConfig customConfig) { 19 | this.customConfig = customConfig; 20 | } 21 | 22 | @Override 23 | public void onConnect(ConnectEventMessage connectMessage) { 24 | log.debug("Event-connect trigger. clientId={},userName={}", connectMessage.getClientId(), connectMessage.getUserName()); 25 | } 26 | 27 | @Override 28 | public void onDisconnect(DisconnectEventMessage disconnectMessage) { 29 | log.debug("Event-disconnect trigger. clientId={},userName={}", disconnectMessage.getClientId(), disconnectMessage.getUserName()); 30 | } 31 | 32 | @Override 33 | public void onConnectionLost(ConnectionLostEventMessage connectionLostMessage) { 34 | log.debug("Event-connectionLost trigger. clientId={},userName={}", connectionLostMessage.getClientId(), connectionLostMessage.getUserName()); 35 | } 36 | 37 | @Override 38 | public void onPublish(PublishEventMessage publishMessage) { 39 | log.debug("Event-publish trigger. clientId={},userName={},topic={}", publishMessage.getClientId(), publishMessage.getUserName(), publishMessage.getTopic()); 40 | } 41 | 42 | @Override 43 | public void onPubAck(PubAckEventMessage pubAckEventMessage) { 44 | log.debug("Event-pubAck trigger. clientId={},userName={},messageId={}", pubAckEventMessage.getClientId(), pubAckEventMessage.getUserName(), pubAckEventMessage.getMessageId()); 45 | } 46 | 47 | @Override 48 | public void onPubRec(PubRecEventMessage pubRecEventMessage) { 49 | log.debug("Event-pubRec trigger. clientId={},userName={},messageId={}", pubRecEventMessage.getClientId(), pubRecEventMessage.getUserName(), pubRecEventMessage.getMessageId()); 50 | } 51 | 52 | @Override 53 | public void onPubRel(PubRelEventMessage pubRelEventMessage) { 54 | log.debug("Event-pubRel trigger. clientId={},userName={},messageId={}", pubRelEventMessage.getClientId(), pubRelEventMessage.getUserName(), pubRelEventMessage.getMessageId()); 55 | } 56 | 57 | @Override 58 | public void onPubComp(PubCompEventMessage pubCompEventMessage) { 59 | log.debug("Event-pubComp trigger. clientId={},userName={},messageId={}", pubCompEventMessage.getClientId(), pubCompEventMessage.getUserName(), pubCompEventMessage.getMessageId()); 60 | } 61 | 62 | @Override 63 | public void onSubscribe(SubscribeEventMessage subscribeMessage) { 64 | log.debug("Event-subscribe trigger. clientId={},userName={},topic={}", subscribeMessage.getClientId(), subscribeMessage.getUserName(), subscribeMessage.getTopic()); 65 | } 66 | 67 | @Override 68 | public void onUnsubscribe(UnsubscribeEventMessage unsubscribeMessage) { 69 | log.debug("Event-unsubscribe trigger. clientId={},userName={},topic={}", unsubscribeMessage.getClientId(), unsubscribeMessage.getUserName(), unsubscribeMessage.getTopic()); 70 | } 71 | 72 | @Override 73 | public void onPing(PingEventMessage pingEventMessage) { 74 | log.debug("Event-ping trigger. clientId={},userName={}", pingEventMessage.getClientId(), pingEventMessage.getUserName()); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/listener/impl/HttpCallbackEventListener.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.listener.impl; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.event.listener.adapter.EventListenerAdapter; 5 | import joey.mqtt.broker.event.message.*; 6 | import lombok.extern.slf4j.Slf4j; 7 | 8 | /** 9 | * http回调事件监听器 10 | * 11 | * @author Joey 12 | * @date 2021/05/27 13 | */ 14 | @Slf4j 15 | public class HttpCallbackEventListener extends EventListenerAdapter { 16 | public HttpCallbackEventListener(CustomConfig customConfig) { 17 | super(customConfig); 18 | } 19 | 20 | @Override 21 | public void onConnect(ConnectEventMessage connectMessage) { 22 | log.debug("Event-connect trigger. clientId={},userName={}", connectMessage.getClientId(), connectMessage.getUserName()); 23 | } 24 | 25 | @Override 26 | public void onDisconnect(DisconnectEventMessage disconnectMessage) { 27 | log.debug("Event-disconnect trigger. clientId={},userName={}", disconnectMessage.getClientId(), disconnectMessage.getUserName()); 28 | } 29 | 30 | @Override 31 | public void onConnectionLost(ConnectionLostEventMessage connectionLostMessage) { 32 | log.debug("Event-connectionLost trigger. clientId={},userName={}", connectionLostMessage.getClientId(), connectionLostMessage.getUserName()); 33 | } 34 | 35 | @Override 36 | public void onPublish(PublishEventMessage publishMessage) { 37 | log.debug("Event-publish trigger. clientId={},userName={},topic={}", publishMessage.getClientId(), publishMessage.getUserName(), publishMessage.getTopic()); 38 | } 39 | 40 | @Override 41 | public void onPubAck(PubAckEventMessage pubAckEventMessage) { 42 | log.debug("Event-pubAck trigger. clientId={},userName={},messageId={}", pubAckEventMessage.getClientId(), pubAckEventMessage.getUserName(), pubAckEventMessage.getMessageId()); 43 | } 44 | 45 | @Override 46 | public void onPubRec(PubRecEventMessage pubRecEventMessage) { 47 | log.debug("Event-pubRec trigger. clientId={},userName={},messageId={}", pubRecEventMessage.getClientId(), pubRecEventMessage.getUserName(), pubRecEventMessage.getMessageId()); 48 | } 49 | 50 | @Override 51 | public void onPubRel(PubRelEventMessage pubRelEventMessage) { 52 | log.debug("Event-pubRel trigger. clientId={},userName={},messageId={}", pubRelEventMessage.getClientId(), pubRelEventMessage.getUserName(), pubRelEventMessage.getMessageId()); 53 | } 54 | 55 | @Override 56 | public void onPubComp(PubCompEventMessage pubCompEventMessage) { 57 | log.debug("Event-pubComp trigger. clientId={},userName={},messageId={}", pubCompEventMessage.getClientId(), pubCompEventMessage.getUserName(), pubCompEventMessage.getMessageId()); 58 | } 59 | 60 | @Override 61 | public void onSubscribe(SubscribeEventMessage subscribeMessage) { 62 | log.debug("Event-subscribe trigger. clientId={},userName={},topic={}", subscribeMessage.getClientId(), subscribeMessage.getUserName(), subscribeMessage.getTopic()); 63 | } 64 | 65 | @Override 66 | public void onUnsubscribe(UnsubscribeEventMessage unsubscribeMessage) { 67 | log.debug("Event-unsubscribe trigger. clientId={},userName={},topic={}", unsubscribeMessage.getClientId(), unsubscribeMessage.getUserName(), unsubscribeMessage.getTopic()); 68 | } 69 | 70 | @Override 71 | public void onPing(PingEventMessage pingEventMessage) { 72 | log.debug("Event-ping trigger. clientId={},userName={}", pingEventMessage.getClientId(), pingEventMessage.getUserName()); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/AbstractEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import io.netty.handler.codec.mqtt.MqttFixedHeader; 4 | import io.netty.handler.codec.mqtt.MqttMessage; 5 | import io.netty.handler.codec.mqtt.MqttQoS; 6 | import lombok.Getter; 7 | 8 | /** 9 | * 事件抽象消息 10 | * 11 | * @author Joey 12 | * @date 2019/9/8 13 | */ 14 | @Getter 15 | public abstract class AbstractEventMessage implements EventMessage { 16 | private final boolean isRetain; 17 | 18 | private final boolean isDup; 19 | 20 | private final MqttQoS mqttQoS; 21 | 22 | protected AbstractEventMessage(MqttMessage msg) { 23 | MqttFixedHeader fixedHeader = msg.fixedHeader(); 24 | this.isRetain = fixedHeader.isRetain(); 25 | this.isDup = fixedHeader.isDup(); 26 | this.mqttQoS = fixedHeader.qosLevel(); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/ConnectEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.netty.handler.codec.mqtt.MqttConnectMessage; 5 | 6 | /** 7 | * 连接事件消息 8 | * 9 | * @author Joey 10 | * @date 2019/9/8 11 | */ 12 | public class ConnectEventMessage extends AbstractEventMessage { 13 | private final MqttConnectMessage msg; 14 | 15 | public ConnectEventMessage(MqttConnectMessage msg) { 16 | super(msg); 17 | this.msg = msg; 18 | } 19 | 20 | public String getClientId() { 21 | return msg.payload().clientIdentifier(); 22 | } 23 | 24 | public boolean isCleanSession() { 25 | return msg.variableHeader().isCleanSession(); 26 | } 27 | 28 | public int getKeepAlive() { 29 | return msg.variableHeader().keepAliveTimeSeconds(); 30 | } 31 | 32 | public boolean isPasswordFlag() { 33 | return msg.variableHeader().hasPassword(); 34 | } 35 | 36 | public byte getProtocolVersion() { 37 | return (byte) msg.variableHeader().version(); 38 | } 39 | 40 | public String getProtocolName() { 41 | return msg.variableHeader().name(); 42 | } 43 | 44 | public boolean isUserFlag() { 45 | return msg.variableHeader().hasUserName(); 46 | } 47 | 48 | public boolean isWillFlag() { 49 | return msg.variableHeader().isWillFlag(); 50 | } 51 | 52 | public byte getWillQos() { 53 | return (byte) msg.variableHeader().willQos(); 54 | } 55 | 56 | public boolean isWillRetain() { 57 | return msg.variableHeader().isWillRetain(); 58 | } 59 | 60 | public String getUserName() { 61 | return msg.payload().userName(); 62 | } 63 | 64 | public byte[] getPassword() { 65 | return msg.payload().password().getBytes(); 66 | } 67 | 68 | public String getWillTopic() { 69 | return msg.payload().willTopic(); 70 | } 71 | 72 | public byte[] getWillMessage() { 73 | return msg.payload().willMessage().getBytes(); 74 | } 75 | 76 | @Override 77 | public String info() { 78 | JSONObject obj = new JSONObject(); 79 | obj.put("clientId", getClientId()); 80 | obj.put("userName", getUserName()); 81 | return obj.toJSONString(); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/ConnectionLostEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import com.hazelcast.internal.json.Json; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | 8 | /** 9 | * 连接丢失事件消息 10 | * 11 | * @author Joey 12 | * @date 2019/9/8 13 | */ 14 | @Getter 15 | @AllArgsConstructor 16 | public class ConnectionLostEventMessage implements EventMessage { 17 | private final String clientId; 18 | 19 | private final String userName; 20 | 21 | @Override 22 | public String info() { 23 | return JSONObject.toJSONString(this); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/DisconnectEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * 连接断开事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/8 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class DisconnectEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | @Override 21 | public String info() { 22 | return JSONObject.toJSONString(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/EventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | /** 4 | * 事件消息 5 | * 6 | * @author Joey 7 | * @date 2019/9/8 8 | */ 9 | public interface EventMessage { 10 | /** 11 | * 消息信息 12 | * 13 | * @return 14 | */ 15 | String info(); 16 | } 17 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PingEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * ping事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/8 12 | */ 13 | @AllArgsConstructor 14 | @Getter 15 | public class PingEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | @Override 21 | public String info() { 22 | return JSONObject.toJSONString(this); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PubAckEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * pubAck事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/17 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class PubAckEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | private final Integer messageId; 21 | 22 | @Override 23 | public String info() { 24 | return JSONObject.toJSONString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PubCompEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * pubComp事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/17 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class PubCompEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | private final Integer messageId; 21 | 22 | @Override 23 | public String info() { 24 | return JSONObject.toJSONString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PubRecEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * pubRec事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/17 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class PubRecEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | private final Integer messageId; 21 | 22 | @Override 23 | public String info() { 24 | return JSONObject.toJSONString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PubRelEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * pubRel事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/17 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class PubRelEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | private final Integer messageId; 21 | 22 | @Override 23 | public String info() { 24 | return JSONObject.toJSONString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/PublishEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.netty.buffer.ByteBuf; 5 | import io.netty.handler.codec.mqtt.MqttPublishMessage; 6 | import joey.mqtt.broker.util.MessageUtils; 7 | import lombok.Getter; 8 | 9 | /** 10 | * 发布事件消息 11 | * 12 | * @author Joey 13 | * @date 2019/9/8 14 | */ 15 | public class PublishEventMessage extends AbstractEventMessage { 16 | private final MqttPublishMessage msg; 17 | 18 | @Getter 19 | private final String clientId; 20 | 21 | @Getter 22 | private final String userName; 23 | 24 | public PublishEventMessage(MqttPublishMessage msg, String clientId, String userName) { 25 | super(msg); 26 | this.msg = msg; 27 | this.clientId = clientId; 28 | this.userName = userName; 29 | } 30 | 31 | public String getTopic() { 32 | return msg.variableHeader().topicName(); 33 | } 34 | 35 | public ByteBuf getPayload() { 36 | return msg.payload(); 37 | } 38 | 39 | @Override 40 | public String info() { 41 | JSONObject obj = new JSONObject(); 42 | obj.put("clientId", getClientId()); 43 | obj.put("userName", getUserName()); 44 | obj.put("topic", getTopic()); 45 | obj.put("payload", new String(MessageUtils.readBytesAndRewind(getPayload()))); 46 | 47 | return obj.toJSONString(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/SubscribeEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import io.netty.handler.codec.mqtt.MqttQoS; 5 | import joey.mqtt.broker.core.subscription.Subscription; 6 | import lombok.Getter; 7 | 8 | /** 9 | * 订阅事件消息 10 | * 11 | * @author Joey 12 | * @date 2019/9/8 13 | */ 14 | @Getter 15 | public class SubscribeEventMessage implements EventMessage { 16 | private final String clientId; 17 | 18 | private final String userName; 19 | 20 | private final String topic; 21 | 22 | private final MqttQoS mqttQos; 23 | 24 | public SubscribeEventMessage(Subscription subscription, String userName) { 25 | this.clientId = subscription.getClientId(); 26 | this.mqttQos = subscription.getQos(); 27 | this.topic = subscription.getTopic(); 28 | this.userName = userName; 29 | } 30 | 31 | @Override 32 | public String info() { 33 | return JSONObject.toJSONString(this); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/message/UnsubscribeEventMessage.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.message; 2 | 3 | import com.alibaba.fastjson.JSONObject; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | 7 | /** 8 | * 取消订阅事件消息 9 | * 10 | * @author Joey 11 | * @date 2019/9/8 12 | */ 13 | @Getter 14 | @AllArgsConstructor 15 | public class UnsubscribeEventMessage implements EventMessage { 16 | private final String topic; 17 | 18 | private final String clientId; 19 | 20 | private final String userName; 21 | 22 | @Override 23 | public String info() { 24 | return JSONObject.toJSONString(this); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/DisconnectEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessage; 7 | import io.netty.handler.codec.mqtt.MqttMessageType; 8 | import joey.mqtt.broker.core.client.ClientSession; 9 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 10 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 11 | import joey.mqtt.broker.event.listener.IEventListener; 12 | import joey.mqtt.broker.event.message.DisconnectEventMessage; 13 | import joey.mqtt.broker.store.IDupPubMessageStore; 14 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 15 | import joey.mqtt.broker.store.ISessionStore; 16 | import joey.mqtt.broker.store.ISubscriptionStore; 17 | import joey.mqtt.broker.util.NettyUtils; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | /** 21 | * 断开连接事件处理 22 | * 23 | * @author Joey 24 | * @date 2019/7/22 25 | */ 26 | @Slf4j 27 | public class DisconnectEventProcessor implements IEventProcessor { 28 | private final DispatcherCommandCenter dispatcherCommandCenter; 29 | 30 | private final ISessionStore sessionStore; 31 | 32 | private final ISubscriptionStore subStore; 33 | 34 | private final IDupPubMessageStore dupPubMessageStore; 35 | 36 | private final IDupPubRelMessageStore dupPubRelMessageStore; 37 | 38 | private final EventListenerExecutor eventListenerExecutor; 39 | 40 | public DisconnectEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, ISessionStore sessionStore, ISubscriptionStore subStore, IDupPubMessageStore dupPubMessageStore, IDupPubRelMessageStore dupPubRelMessageStore, EventListenerExecutor eventListenerExecutor) { 41 | this.dispatcherCommandCenter = dispatcherCommandCenter; 42 | this.sessionStore = sessionStore; 43 | this.subStore = subStore; 44 | this.dupPubMessageStore = dupPubMessageStore; 45 | this.dupPubRelMessageStore = dupPubRelMessageStore; 46 | this.eventListenerExecutor = eventListenerExecutor; 47 | } 48 | 49 | @Override 50 | public void process(ChannelHandlerContext ctx, MqttMessage message) { 51 | Channel channel = ctx.channel(); 52 | channel.flush(); 53 | 54 | String clientId = NettyUtils.clientId(channel); 55 | String userName = NettyUtils.userName(channel); 56 | log.info("Process-disconnect. clientId={},userName={}", clientId, userName); 57 | 58 | ClientSession clientSession = sessionStore.get(clientId); 59 | if (ObjectUtil.isNull(clientSession)) { 60 | channel.close(); 61 | return; 62 | } 63 | 64 | if (!clientSession.isSameChannel(channel)) { 65 | log.warn("Process-disconnect. Another client is using the session. Closing connection. clientId={},userName={}", clientId, userName); 66 | clientSession.closeChannel(); 67 | return; 68 | } 69 | 70 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.DISCONNECT, () -> { 71 | doDisconnect(clientSession); 72 | return null; 73 | }); 74 | } 75 | 76 | /** 77 | * 断开连接 78 | * 79 | * @param clientSession 80 | */ 81 | private void doDisconnect(ClientSession clientSession) { 82 | String clientId = clientSession.getClientId(); 83 | if (clientSession.isCleanSession()) { 84 | subStore.removeAllBy(clientId); 85 | dupPubMessageStore.removeAllFor(clientId); 86 | dupPubRelMessageStore.removeAllFor(clientId); 87 | } 88 | 89 | clientSession.closeChannel(); 90 | sessionStore.remove(clientId); 91 | 92 | eventListenerExecutor.execute(new DisconnectEventMessage(clientId, clientSession.getUserName()), IEventListener.Type.DISCONNECT); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/IEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.mqtt.MqttMessage; 5 | 6 | /** 7 | * 处理事件接口定义 8 | * 9 | * @author Joey 10 | * @date 2019/7/22 11 | */ 12 | public interface IEventProcessor { 13 | /** 14 | * 处理事件 15 | * 16 | * @param ctx 17 | * @param message 18 | */ 19 | void process(ChannelHandlerContext ctx, T message); 20 | } 21 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/PingReqEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import io.netty.channel.ChannelHandlerContext; 4 | import io.netty.handler.codec.mqtt.MqttMessage; 5 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 6 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 7 | import joey.mqtt.broker.event.listener.IEventListener; 8 | import joey.mqtt.broker.event.message.PingEventMessage; 9 | import joey.mqtt.broker.util.MessageUtils; 10 | import joey.mqtt.broker.util.NettyUtils; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * ping响应事件处理 15 | * 16 | * @author Joey 17 | * @date 2019/7/22 18 | */ 19 | @Slf4j 20 | public class PingReqEventProcessor implements IEventProcessor { 21 | private final DispatcherCommandCenter dispatcherCommandCenter; 22 | 23 | private final EventListenerExecutor eventListenerExecutor; 24 | 25 | public PingReqEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, EventListenerExecutor eventListenerExecutor) { 26 | this.dispatcherCommandCenter = dispatcherCommandCenter; 27 | this.eventListenerExecutor = eventListenerExecutor; 28 | } 29 | 30 | @Override 31 | public void process(ChannelHandlerContext ctx, MqttMessage message) { 32 | ctx.channel().writeAndFlush(MessageUtils.buildPingRespMessage()); 33 | 34 | eventListenerExecutor.execute(new PingEventMessage(NettyUtils.clientId(ctx.channel()), NettyUtils.userName(ctx.channel())), IEventListener.Type.PING); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/PubAckEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessageType; 7 | import io.netty.handler.codec.mqtt.MqttPubAckMessage; 8 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 9 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 10 | import joey.mqtt.broker.event.listener.IEventListener; 11 | import joey.mqtt.broker.event.message.PubAckEventMessage; 12 | import joey.mqtt.broker.store.IDupPubMessageStore; 13 | import joey.mqtt.broker.util.NettyUtils; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * pubAck事件处理 18 | * 19 | * @author Joey 20 | * @date 2019/7/22 21 | */ 22 | @Slf4j 23 | public class PubAckEventProcessor implements IEventProcessor { 24 | private final DispatcherCommandCenter dispatcherCommandCenter; 25 | 26 | private final IDupPubMessageStore dupPubMessageStore; 27 | 28 | private final EventListenerExecutor eventListenerExecutor; 29 | 30 | public PubAckEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, IDupPubMessageStore dupPubMessageStore, EventListenerExecutor eventListenerExecutor) { 31 | this.dispatcherCommandCenter = dispatcherCommandCenter; 32 | this.dupPubMessageStore = dupPubMessageStore; 33 | this.eventListenerExecutor = eventListenerExecutor; 34 | } 35 | 36 | @Override 37 | public void process(ChannelHandlerContext ctx, MqttPubAckMessage message) { 38 | Channel channel = ctx.channel(); 39 | String clientId = NettyUtils.clientId(channel); 40 | 41 | if (StrUtil.isNotBlank(clientId)) { 42 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.PUBACK, () -> { 43 | doPubAck(clientId, channel, message); 44 | return null; 45 | }); 46 | } 47 | } 48 | 49 | /** 50 | * pub ack 51 | * 52 | * @param clientId 53 | * @param channel 54 | * @param message 55 | */ 56 | private void doPubAck(String clientId, Channel channel, MqttPubAckMessage message) { 57 | int messageId = message.variableHeader().messageId(); 58 | dupPubMessageStore.remove(clientId, messageId); 59 | 60 | String userName = NettyUtils.userName(channel); 61 | eventListenerExecutor.execute(new PubAckEventMessage(clientId, userName, messageId), IEventListener.Type.PUB_ACK); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/PubCompEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessage; 7 | import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; 8 | import io.netty.handler.codec.mqtt.MqttMessageType; 9 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 10 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 11 | import joey.mqtt.broker.event.listener.IEventListener; 12 | import joey.mqtt.broker.event.message.PubCompEventMessage; 13 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 14 | import joey.mqtt.broker.util.NettyUtils; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | * pubComp事件处理 19 | * 20 | * @author Joey 21 | * @date 2019/7/22 22 | */ 23 | @Slf4j 24 | public class PubCompEventProcessor implements IEventProcessor { 25 | private final DispatcherCommandCenter dispatcherCommandCenter; 26 | 27 | private final IDupPubRelMessageStore pubRelMessageStore; 28 | 29 | private final EventListenerExecutor eventListenerExecutor; 30 | 31 | public PubCompEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, IDupPubRelMessageStore pubRelMessageStore, EventListenerExecutor eventListenerExecutor) { 32 | this.dispatcherCommandCenter = dispatcherCommandCenter; 33 | this.pubRelMessageStore = pubRelMessageStore; 34 | this.eventListenerExecutor = eventListenerExecutor; 35 | } 36 | 37 | @Override 38 | public void process(ChannelHandlerContext ctx, MqttMessage message) { 39 | Channel channel = ctx.channel(); 40 | String clientId = NettyUtils.clientId(channel); 41 | 42 | if (StrUtil.isNotBlank(clientId)) { 43 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.PUBCOMP, () -> { 44 | doPubComp(clientId, channel, message); 45 | return null; 46 | }); 47 | } 48 | } 49 | 50 | /** 51 | * pub comp 52 | * 53 | * @param clientId 54 | * @param channel 55 | * @param message 56 | */ 57 | private void doPubComp(String clientId, Channel channel, MqttMessage message) { 58 | MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader)message.variableHeader(); 59 | int messageId = variableHeader.messageId(); 60 | pubRelMessageStore.remove(clientId, messageId); 61 | 62 | String userName = NettyUtils.userName(channel); 63 | eventListenerExecutor.execute(new PubCompEventMessage(clientId, userName, messageId), IEventListener.Type.PUB_COMP); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/PubRecEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessage; 7 | import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; 8 | import io.netty.handler.codec.mqtt.MqttMessageType; 9 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 10 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 11 | import joey.mqtt.broker.event.listener.IEventListener; 12 | import joey.mqtt.broker.event.message.PubRecEventMessage; 13 | import joey.mqtt.broker.store.IDupPubMessageStore; 14 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 15 | import joey.mqtt.broker.util.MessageUtils; 16 | import joey.mqtt.broker.util.NettyUtils; 17 | import lombok.extern.slf4j.Slf4j; 18 | 19 | import java.util.Optional; 20 | 21 | /** 22 | * pubRec事件处理 23 | * 24 | * @author Joey 25 | * @date 2019/7/22 26 | */ 27 | @Slf4j 28 | public class PubRecEventProcessor implements IEventProcessor { 29 | private final DispatcherCommandCenter dispatcherCommandCenter; 30 | 31 | private final IDupPubMessageStore dupPubMessageStore; 32 | 33 | private final IDupPubRelMessageStore dupPubRelMessageStore; 34 | 35 | private final EventListenerExecutor eventListenerExecutor; 36 | 37 | public PubRecEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, IDupPubMessageStore dupPubMessageStore, IDupPubRelMessageStore dupPubRelMessageStore, EventListenerExecutor eventListenerExecutor) { 38 | this.dispatcherCommandCenter = dispatcherCommandCenter; 39 | this.dupPubMessageStore = dupPubMessageStore; 40 | this.dupPubRelMessageStore = dupPubRelMessageStore; 41 | this.eventListenerExecutor = eventListenerExecutor; 42 | } 43 | 44 | @Override 45 | public void process(ChannelHandlerContext ctx, MqttMessage message) { 46 | Channel channel = ctx.channel(); 47 | String clientId = NettyUtils.clientId(channel); 48 | 49 | if (StrUtil.isNotBlank(clientId)) { 50 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.PUBREC, () -> { 51 | doPubRec(clientId, channel, message); 52 | return null; 53 | }); 54 | } 55 | } 56 | 57 | /** 58 | * pub rec 59 | * 60 | * @param clientId 61 | * @param channel 62 | * @param message 63 | */ 64 | private void doPubRec(String clientId, Channel channel, MqttMessage message) { 65 | MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader)message.variableHeader(); 66 | int messageId = variableHeader.messageId(); 67 | Optional.ofNullable(dupPubMessageStore.get(clientId, messageId)) 68 | .ifPresent(pubMsg -> { 69 | dupPubMessageStore.remove(clientId, messageId); 70 | dupPubRelMessageStore.add(pubMsg.copy()); 71 | }); 72 | 73 | MqttMessage pubRelResp = MessageUtils.buildPubRelMessage(messageId, false); 74 | channel.writeAndFlush(pubRelResp); 75 | 76 | String userName = NettyUtils.userName(channel); 77 | eventListenerExecutor.execute(new PubRecEventMessage(clientId, userName, messageId), IEventListener.Type.PUB_REC); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/PubRelEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessage; 7 | import io.netty.handler.codec.mqtt.MqttMessageIdVariableHeader; 8 | import io.netty.handler.codec.mqtt.MqttMessageType; 9 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 10 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 11 | import joey.mqtt.broker.event.listener.IEventListener; 12 | import joey.mqtt.broker.event.message.PubRelEventMessage; 13 | import joey.mqtt.broker.util.MessageUtils; 14 | import joey.mqtt.broker.util.NettyUtils; 15 | import lombok.extern.slf4j.Slf4j; 16 | 17 | /** 18 | * pubRel事件处理 19 | * 20 | * @author Joey 21 | * @date 2019/7/22 22 | */ 23 | @Slf4j 24 | public class PubRelEventProcessor implements IEventProcessor { 25 | private final DispatcherCommandCenter dispatcherCommandCenter; 26 | 27 | private final EventListenerExecutor eventListenerExecutor; 28 | 29 | public PubRelEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, EventListenerExecutor eventListenerExecutor) { 30 | this.dispatcherCommandCenter = dispatcherCommandCenter; 31 | this.eventListenerExecutor = eventListenerExecutor; 32 | } 33 | 34 | @Override 35 | public void process(ChannelHandlerContext ctx, MqttMessage message) { 36 | Channel channel = ctx.channel(); 37 | String clientId = NettyUtils.clientId(channel); 38 | 39 | if (StrUtil.isNotBlank(clientId)) { 40 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.PUBREL, () -> { 41 | doPubRel(clientId, channel, message); 42 | return null; 43 | }); 44 | } 45 | } 46 | 47 | /** 48 | * pub rel 49 | * 50 | * @param clientId 51 | * @param channel 52 | * @param message 53 | */ 54 | private void doPubRel(String clientId, Channel channel, MqttMessage message) { 55 | MqttMessageIdVariableHeader variableHeader = (MqttMessageIdVariableHeader)message.variableHeader(); 56 | int messageId = variableHeader.messageId(); 57 | channel.writeAndFlush(MessageUtils.buildPubCompMessage(messageId)); 58 | 59 | String userName = NettyUtils.userName(channel); 60 | eventListenerExecutor.execute(new PubRelEventMessage(clientId, userName, messageId), IEventListener.Type.PUB_REL); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/event/processor/UnsubscribeEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.event.processor; 2 | 3 | import cn.hutool.core.collection.CollectionUtil; 4 | import io.netty.channel.Channel; 5 | import io.netty.channel.ChannelHandlerContext; 6 | import io.netty.handler.codec.mqtt.MqttMessageType; 7 | import io.netty.handler.codec.mqtt.MqttUnsubAckMessage; 8 | import io.netty.handler.codec.mqtt.MqttUnsubscribeMessage; 9 | import joey.mqtt.broker.core.dispatcher.DispatcherCommandCenter; 10 | import joey.mqtt.broker.core.subscription.Subscription; 11 | import joey.mqtt.broker.event.listener.EventListenerExecutor; 12 | import joey.mqtt.broker.event.listener.IEventListener; 13 | import joey.mqtt.broker.event.message.UnsubscribeEventMessage; 14 | import joey.mqtt.broker.store.ISessionStore; 15 | import joey.mqtt.broker.store.ISubscriptionStore; 16 | import joey.mqtt.broker.util.MessageUtils; 17 | import joey.mqtt.broker.util.NettyUtils; 18 | import lombok.extern.slf4j.Slf4j; 19 | 20 | import java.util.List; 21 | import java.util.Optional; 22 | 23 | /** 24 | * 取消订阅事件处理 25 | * 26 | * @author Joey 27 | * @date 2019/7/22 28 | */ 29 | @Slf4j 30 | public class UnsubscribeEventProcessor implements IEventProcessor { 31 | private final DispatcherCommandCenter dispatcherCommandCenter; 32 | 33 | private final ISessionStore sessionStore; 34 | 35 | private final ISubscriptionStore subStore; 36 | 37 | private final EventListenerExecutor eventListenerExecutor; 38 | 39 | public UnsubscribeEventProcessor(DispatcherCommandCenter dispatcherCommandCenter, ISessionStore sessionStore, ISubscriptionStore subStore, EventListenerExecutor eventListenerExecutor) { 40 | this.dispatcherCommandCenter = dispatcherCommandCenter; 41 | this.sessionStore = sessionStore; 42 | this.subStore = subStore; 43 | this.eventListenerExecutor = eventListenerExecutor; 44 | } 45 | 46 | @Override 47 | public void process(ChannelHandlerContext ctx, MqttUnsubscribeMessage message) { 48 | Channel channel = ctx.channel(); 49 | String clientId = NettyUtils.clientId(channel); 50 | 51 | dispatcherCommandCenter.dispatch(clientId, MqttMessageType.UNSUBSCRIBE, () -> { 52 | doUnsubscribe(clientId, channel, message); 53 | return null; 54 | }); 55 | } 56 | 57 | /** 58 | * 取消订阅 59 | * 60 | * @param clientId 61 | * @param channel 62 | * @param message 63 | */ 64 | private void doUnsubscribe(String clientId, Channel channel, MqttUnsubscribeMessage message) { 65 | String userName = NettyUtils.userName(channel); 66 | List topicList = message.payload().topics(); 67 | log.info("Process-unsubscribe. clientId={},userName={},topicList={}", clientId, userName, topicList); 68 | 69 | if (CollectionUtil.isNotEmpty(topicList)) { 70 | Optional.ofNullable(sessionStore.get(clientId)) 71 | .ifPresent(clientSession -> { 72 | topicList.forEach(topic -> { 73 | Subscription sub = new Subscription(clientId, topic, null); 74 | subStore.remove(sub); 75 | eventListenerExecutor.execute(new UnsubscribeEventMessage(topic, clientId, userName), IEventListener.Type.UNSUBSCRIBE); 76 | }); 77 | 78 | MqttUnsubAckMessage unsubAckResp = MessageUtils.buildUnsubAckMessage(message.variableHeader().messageId()); 79 | channel.writeAndFlush(unsubAckResp); 80 | }); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/exception/MqttException.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.exception; 2 | 3 | /** 4 | * 异常 5 | * @author Joey 6 | * @date 2019/7/23 7 | */ 8 | public class MqttException extends RuntimeException { 9 | public MqttException() { 10 | super(); 11 | } 12 | 13 | public MqttException(String message) { 14 | super(message); 15 | } 16 | 17 | public MqttException(String message, Throwable cause) { 18 | super(message, cause); 19 | } 20 | 21 | public MqttException(Throwable cause) { 22 | super(cause); 23 | } 24 | 25 | protected MqttException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { 26 | super(message, cause, enableSuppression, writableStackTrace); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/hazelcast/HazelcastFactory.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.hazelcast; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.hazelcast.config.ClasspathXmlConfig; 5 | import com.hazelcast.config.Config; 6 | import com.hazelcast.config.FileSystemXmlConfig; 7 | import com.hazelcast.core.Hazelcast; 8 | import com.hazelcast.core.HazelcastInstance; 9 | import joey.mqtt.broker.exception.MqttException; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.io.FileNotFoundException; 13 | 14 | import static cn.hutool.core.util.URLUtil.CLASSPATH_URL_PREFIX; 15 | import static cn.hutool.core.util.URLUtil.FILE_URL_PREFIX; 16 | 17 | /** 18 | * hazelcast工厂 19 | * 20 | * @author Joey 21 | * @date 2021-03-19 22 | */ 23 | @Slf4j 24 | public class HazelcastFactory { 25 | private HazelcastFactory() { 26 | 27 | } 28 | 29 | /** 30 | * 创建hazelcast实例 31 | */ 32 | public static HazelcastInstance createInstance() { 33 | return createInstance(StrUtil.EMPTY); 34 | } 35 | 36 | /** 37 | * 创建hazelcast实例 38 | */ 39 | public static HazelcastInstance createInstance(String configFile) { 40 | if (StrUtil.isBlank(configFile)) { 41 | log.info("Hazelcast:use empty config."); 42 | return Hazelcast.newHazelcastInstance(); 43 | } else { 44 | try { 45 | Config hzConfig = null; 46 | 47 | if (configFile.startsWith(CLASSPATH_URL_PREFIX)) { 48 | hzConfig = new ClasspathXmlConfig(configFile.substring(CLASSPATH_URL_PREFIX.length())); 49 | 50 | } else if (configFile.startsWith(FILE_URL_PREFIX)) { 51 | hzConfig = new FileSystemXmlConfig(configFile.substring(FILE_URL_PREFIX.length())); 52 | } 53 | 54 | if (null == hzConfig) { 55 | throw new MqttException("Hazelcast:config file path error. configFilePath=" + configFile); 56 | } 57 | 58 | log.info("Hazelcast:config file path={},config={}.", configFile, hzConfig); 59 | return Hazelcast.newHazelcastInstance(hzConfig); 60 | 61 | } catch (FileNotFoundException e) { 62 | throw new MqttException("Hazelcast:could not find hazelcast config file. configFilePath=" + configFile); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/BaseInnerTraffic.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | import lombok.extern.slf4j.Slf4j; 5 | 6 | import java.util.concurrent.*; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * 集群间通信基类 11 | * 12 | * @author Joey 13 | * @date 2021/03/13 14 | */ 15 | @Slf4j 16 | public abstract class BaseInnerTraffic implements IInnerTraffic { 17 | protected final String nodeName; 18 | 19 | protected final InnerPublishEventProcessor innerPublishEventProcessor; 20 | 21 | private static final String THREAD_NAME_PRE = "joMqtt-innerTrafficExecutor-pool-"; 22 | 23 | private static final int THREAD_CORE_SIZE = 20; 24 | 25 | private static final int THREAD_MAX_POOL_SIZE = 100; 26 | 27 | private static final long THREAD_KEEP_ALIVE_TIME = 1L; 28 | 29 | private static final AtomicLong THREAD_IDX = new AtomicLong(); 30 | 31 | private static final ThreadFactory THREAD_FACTORY = new ThreadFactory() { 32 | @Override 33 | public Thread newThread(Runnable r) { 34 | Thread thread = new Thread(r); 35 | thread.setName(getThreadName()); 36 | return thread; 37 | } 38 | }; 39 | 40 | private static final RejectedExecutionHandler REJECTED_EXECUTION_HANDLER = new ThreadPoolExecutor.DiscardPolicy() { 41 | @Override 42 | public void rejectedExecution(Runnable runnable, ThreadPoolExecutor e) { 43 | // 继续执行任务 44 | Thread thread = new Thread(runnable); 45 | thread.setName(getThreadName()); 46 | thread.start(); 47 | } 48 | }; 49 | 50 | private static String getThreadName() { 51 | return THREAD_NAME_PRE + THREAD_IDX.incrementAndGet(); 52 | } 53 | 54 | private final ThreadPoolExecutor executor; 55 | 56 | public BaseInnerTraffic(String nodeName, InnerPublishEventProcessor innerPublishEventProcessor) { 57 | this.nodeName = nodeName; 58 | this.innerPublishEventProcessor = innerPublishEventProcessor; 59 | 60 | executor = new ThreadPoolExecutor(THREAD_CORE_SIZE, 61 | THREAD_MAX_POOL_SIZE, 62 | THREAD_KEEP_ALIVE_TIME, 63 | TimeUnit.HOURS, 64 | new SynchronousQueue<>(), 65 | THREAD_FACTORY, 66 | REJECTED_EXECUTION_HANDLER); 67 | } 68 | 69 | protected void publish2Subscribers(CommonPublishMessage commonPubMsg) { 70 | executor.execute( () -> { 71 | try { 72 | innerPublishEventProcessor.publish2Subscribers(commonPubMsg); 73 | } catch (Throwable t) { 74 | log.error("RedisInnerTraffic-onMessage error. nodeName={},message={}", nodeName, commonPubMsg, t); 75 | } 76 | }); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/EmptyInnerTraffic.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import joey.mqtt.broker.core.message.CommonPublishMessage; 5 | import lombok.extern.slf4j.Slf4j; 6 | 7 | /** 8 | * 空集群间通信实现 9 | * 10 | * @author Joey 11 | * @date 2019/12/3 12 | */ 13 | @Slf4j 14 | public class EmptyInnerTraffic extends BaseInnerTraffic { 15 | 16 | public EmptyInnerTraffic(String nodeName, InnerPublishEventProcessor innerPublishEventProcessor) { 17 | super(nodeName, innerPublishEventProcessor); 18 | } 19 | 20 | @Override 21 | public void publish(CommonPublishMessage message) { 22 | log.debug("EmptyInnerTraffic-publish message={}", JSON.toJSONString(message)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/HazelcastInnerTraffic.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import com.hazelcast.core.HazelcastInstance; 6 | import com.hazelcast.topic.ITopic; 7 | import com.hazelcast.topic.Message; 8 | import com.hazelcast.topic.MessageListener; 9 | import joey.mqtt.broker.config.CustomConfig; 10 | import joey.mqtt.broker.constant.BusinessConstants; 11 | import joey.mqtt.broker.core.message.CommonPublishMessage; 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * hazelcast 实现集群间内部通信 16 | * 17 | * @author Joey 18 | * @date 2019/7/25 19 | */ 20 | @Slf4j 21 | public class HazelcastInnerTraffic extends BaseInnerTraffic implements MessageListener { 22 | private final HazelcastInstance hzInstance; 23 | 24 | public HazelcastInnerTraffic(HazelcastInstance hzInstance, InnerPublishEventProcessor innerPublishEventProcessor, CustomConfig customConfig, String nodeName) { 25 | super(nodeName, innerPublishEventProcessor); 26 | 27 | this.hzInstance = hzInstance; 28 | 29 | //添加集群间topic监听 30 | ITopic topic = hzInstance.getTopic(BusinessConstants.HAZELCAST_INNER_TRAFFIC_TOPIC); 31 | topic.addMessageListener(this); 32 | } 33 | 34 | /** 35 | * 发布消息 36 | * @param message 37 | */ 38 | @Override 39 | public void publish(CommonPublishMessage message) { 40 | log.info("HazelcastInnerTraffic-publish message={}", JSON.toJSONString(message)); 41 | 42 | ITopic topic = hzInstance.getTopic(BusinessConstants.HAZELCAST_INNER_TRAFFIC_TOPIC); 43 | topic.publish(message); 44 | } 45 | 46 | /** 47 | * 监听集群发送的消息 48 | * @param msg 49 | */ 50 | @Override 51 | public void onMessage(Message msg) { 52 | try { 53 | if (ObjectUtil.notEqual(hzInstance.getCluster().getLocalMember(), msg.getPublishingMember())) { 54 | CommonPublishMessage commonPubMsg = msg.getMessageObject(); 55 | //集群间接收到消息 retain设置为false 56 | commonPubMsg.setRetain(false); 57 | 58 | log.info("Hazelcast:receive cluster message. nodeName={},message={}", nodeName, commonPubMsg.toString()); 59 | super.publish2Subscribers(commonPubMsg); 60 | } 61 | } catch (Exception ex) { 62 | log.error("Hazelcast:onMessage error. msg={}", msg, ex); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/IInnerTraffic.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | 5 | /** 6 | * 集群间通信 7 | * 8 | * @author Joey 9 | * @date 2019/7/25 10 | */ 11 | public interface IInnerTraffic { 12 | /** 13 | * 发布消息 14 | * 15 | * @param message 16 | */ 17 | void publish(CommonPublishMessage message); 18 | } 19 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/InnerPublishEventProcessor.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | import joey.mqtt.broker.event.processor.PublishEventProcessor; 5 | 6 | /** 7 | * 内部通信pub事件处理 8 | * 9 | * @author Joey 10 | * @date 2019/9/7 11 | */ 12 | public class InnerPublishEventProcessor { 13 | private final PublishEventProcessor publishEventProcessor; 14 | 15 | public InnerPublishEventProcessor(PublishEventProcessor publishEventProcessor) { 16 | this.publishEventProcessor = publishEventProcessor; 17 | } 18 | 19 | /** 20 | * 发布消息到所有订阅者 21 | * 22 | * @param pubMsg 23 | */ 24 | public void publish2Subscribers(CommonPublishMessage pubMsg) { 25 | publishEventProcessor.publish2Subscribers(pubMsg); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/innertraffic/RedisInnerTraffic.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.innertraffic; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.alibaba.fastjson.JSON; 6 | import com.alibaba.fastjson.JSONObject; 7 | import joey.mqtt.broker.constant.BusinessConstants; 8 | import joey.mqtt.broker.core.message.CommonPublishMessage; 9 | import joey.mqtt.broker.redis.RedisClient; 10 | import lombok.extern.slf4j.Slf4j; 11 | import redis.clients.jedis.JedisPubSub; 12 | 13 | /** 14 | * redis pub sub 实现集群间内部通信 15 | * 16 | * @author Joey 17 | * @date 2019/7/25 18 | */ 19 | @Slf4j 20 | public class RedisInnerTraffic extends BaseInnerTraffic { 21 | private final RedisClient redisClient; 22 | 23 | public RedisInnerTraffic(RedisClient redisClient, InnerPublishEventProcessor innerPublishEventProcessor, String nodeName) { 24 | super(nodeName, innerPublishEventProcessor); 25 | this.redisClient = redisClient; 26 | 27 | subTopic(); 28 | } 29 | 30 | private void subTopic() { 31 | new Thread(() -> { 32 | //防止redis连接意外断开不能重新订阅 33 | for (;;) { 34 | try { 35 | redisClient.subscribe(new JedisPubSub() { 36 | @Override 37 | public void onMessage(String channel, String message) { 38 | try { 39 | if (StrUtil.isNotBlank(message)) { 40 | log.info("RedisInnerTraffic-onMessage. nodeName={},channel={},message={}", nodeName, channel, message); 41 | CommonPublishMessage pubMsg = JSONObject.parseObject(message, CommonPublishMessage.class); 42 | 43 | //消息来源不是同一个node时候才会继续发布 44 | if (ObjectUtil.isNotNull(pubMsg) && ObjectUtil.notEqual(nodeName, pubMsg.getSourceNodeName())) { 45 | publish2Subscribers(pubMsg); 46 | } 47 | } 48 | } catch (Throwable t) { 49 | log.error("RedisInnerTraffic-onMessage error. nodeName={},channel={},message={}", nodeName, channel, message, t); 50 | } 51 | } 52 | 53 | @Override 54 | public void onSubscribe(String channel, int subscribedChannels) { 55 | log.info("RedisInnerTraffic-onSubscribe. nodeName={},channel={},subscribedChannels={}", nodeName, channel, subscribedChannels); 56 | } 57 | 58 | @Override 59 | public void onUnsubscribe(String channel, int subscribedChannels) { 60 | log.info("RedisInnerTraffic-onUnsubscribe. nodeName={},channel={},subscribedChannels={}", nodeName, channel, subscribedChannels); 61 | } 62 | }, BusinessConstants.REDIS_INNER_TRAFFIC_PUB_CHANNEL); 63 | } catch (Exception ex) { 64 | log.error("RedisInnerTraffic-subTopic error. nodeName={}", nodeName, ex); 65 | } 66 | } 67 | }).start(); 68 | } 69 | 70 | /** 71 | * 发布消息 72 | * @param message 73 | */ 74 | @Override 75 | public void publish(CommonPublishMessage message) { 76 | String jsonMsg = JSON.toJSONString(message); 77 | log.debug("RedisInnerTraffic-publish message={}", jsonMsg); 78 | 79 | redisClient.publish(BusinessConstants.REDIS_INNER_TRAFFIC_PUB_CHANNEL, jsonMsg); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/provider/HazelcastExtendProvider.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.provider; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.hazelcast.HazelcastFactory; 6 | import joey.mqtt.broker.innertraffic.HazelcastInnerTraffic; 7 | import joey.mqtt.broker.innertraffic.IInnerTraffic; 8 | import joey.mqtt.broker.innertraffic.InnerPublishEventProcessor; 9 | import joey.mqtt.broker.store.*; 10 | import joey.mqtt.broker.store.hazelcast.*; 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * hazelcast扩展实现 15 | * 16 | * @author Joey 17 | * @date 2020/04/09 18 | */ 19 | @Slf4j 20 | public class HazelcastExtendProvider extends MemoryExtendProvider { 21 | private final String configFile; 22 | 23 | private final HazelcastInstance hzInstance; 24 | 25 | /** 26 | * 默认适配器 反射调用此构造方法 27 | * 28 | * @param customConfig 29 | */ 30 | public HazelcastExtendProvider(CustomConfig customConfig) { 31 | super(customConfig); 32 | 33 | this.configFile = customConfig.getHazelcastConfigFile(); 34 | this.hzInstance = HazelcastFactory.createInstance(this.configFile); 35 | } 36 | 37 | @Override 38 | public IMessageIdStore initMessageIdStore() { 39 | return new HazelcastMessageIdStore(hzInstance, customConfig); 40 | } 41 | 42 | @Override 43 | public ISubscriptionStore initSubscriptionStore(ISessionStore sessionStore) { 44 | return new HazelcastSubscriptionStore(hzInstance, customConfig); 45 | } 46 | 47 | @Override 48 | public IRetainMessageStore initRetainMessageStore() { 49 | return new HazelcastRetainMessageStore(hzInstance, customConfig); 50 | } 51 | 52 | @Override 53 | public IDupPubMessageStore initDupPubMessageStore() { 54 | return new HazelcastDupPubMessageStore(hzInstance, customConfig); 55 | } 56 | 57 | @Override 58 | public IDupPubRelMessageStore initDupPubRelMessageStore() { 59 | return new HazelcastDupPubRelMessageStore(hzInstance, customConfig); 60 | } 61 | 62 | @Override 63 | public IInnerTraffic initInnerTraffic(InnerPublishEventProcessor innerPublishEventProcessor, String nodeName) { 64 | return new HazelcastInnerTraffic(hzInstance, innerPublishEventProcessor, customConfig, nodeName); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/provider/IExtendProvider.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.provider; 2 | 3 | import io.netty.handler.ssl.SslContext; 4 | import joey.mqtt.broker.auth.AuthUser; 5 | import joey.mqtt.broker.auth.IAuth; 6 | import joey.mqtt.broker.event.listener.IEventListener; 7 | import joey.mqtt.broker.innertraffic.IInnerTraffic; 8 | import joey.mqtt.broker.innertraffic.InnerPublishEventProcessor; 9 | import joey.mqtt.broker.store.*; 10 | 11 | import java.util.List; 12 | 13 | /** 14 | * 实现扩展接口定义 15 | * 16 | * @author Joey 17 | * @date 2019/7/23 18 | */ 19 | public interface IExtendProvider { 20 | /** 21 | * 初始化sslContext 22 | * 23 | * @param enableClientCA 24 | * @return 25 | * @throws Exception 26 | */ 27 | SslContext initSslContext(boolean enableClientCA) throws Exception; 28 | 29 | /** 30 | * 获取messageId存储实现 31 | * @return 32 | */ 33 | IMessageIdStore initMessageIdStore(); 34 | 35 | /** 36 | * 获取session存储实现 37 | * @return 38 | */ 39 | ISessionStore initSessionStore(); 40 | 41 | /** 42 | * 获取主题订阅存储实现 43 | * 44 | * @param sessionStore 45 | * @return 46 | */ 47 | ISubscriptionStore initSubscriptionStore(ISessionStore sessionStore); 48 | 49 | /** 50 | * 获取retain消息存储实现 51 | * @return 52 | */ 53 | IRetainMessageStore initRetainMessageStore(); 54 | 55 | /** 56 | * 获取pubMessage消息存储实现 57 | * @return 58 | */ 59 | IDupPubMessageStore initDupPubMessageStore(); 60 | 61 | /** 62 | * 获取pubRelMessage消息存储实现 63 | * @return 64 | */ 65 | IDupPubRelMessageStore initDupPubRelMessageStore(); 66 | 67 | /** 68 | * 获取授权管理实现 69 | * @param userList 70 | * @return 71 | */ 72 | IAuth initAuthManager(List userList); 73 | 74 | /** 75 | * 获取集群间通信实现 76 | * @param innerPublishEventProcessor 77 | * @param nodeName 部署实例唯一标识 78 | * @return 79 | */ 80 | IInnerTraffic initInnerTraffic(InnerPublishEventProcessor innerPublishEventProcessor, String nodeName); 81 | 82 | /** 83 | * 获取事件监听器列表 84 | * @return 85 | */ 86 | List initEventListeners(); 87 | 88 | /** 89 | * 获取节点名称 90 | * @return 91 | */ 92 | String getNodeName(); 93 | } 94 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/provider/MemoryExtendProvider.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.provider; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import io.netty.handler.ssl.SslContext; 5 | import joey.mqtt.broker.auth.AuthUser; 6 | import joey.mqtt.broker.auth.IAuth; 7 | import joey.mqtt.broker.auth.impl.DefaultAuthImpl; 8 | import joey.mqtt.broker.config.CustomConfig; 9 | import joey.mqtt.broker.config.SslContextConfig; 10 | import joey.mqtt.broker.event.listener.IEventListener; 11 | import joey.mqtt.broker.event.listener.adapter.EventListenerAdapter; 12 | import joey.mqtt.broker.innertraffic.EmptyInnerTraffic; 13 | import joey.mqtt.broker.innertraffic.IInnerTraffic; 14 | import joey.mqtt.broker.innertraffic.InnerPublishEventProcessor; 15 | import joey.mqtt.broker.store.*; 16 | import joey.mqtt.broker.store.memory.*; 17 | import joey.mqtt.broker.util.SslContextUtils; 18 | 19 | import java.util.List; 20 | 21 | /** 22 | * 默认扩展实现适配器 23 | * 24 | * @author Joey 25 | * @date 2019/7/23 26 | */ 27 | public class MemoryExtendProvider implements IExtendProvider { 28 | protected final CustomConfig customConfig; 29 | 30 | /** 31 | * 默认适配器 反射调用此构造方法 32 | * 33 | * @param customConfig 34 | */ 35 | public MemoryExtendProvider(CustomConfig customConfig) { 36 | this.customConfig = customConfig; 37 | } 38 | 39 | @Override 40 | public SslContext initSslContext(boolean enableClientCA) throws Exception { 41 | SslContextConfig cfg = customConfig.getSslContextConfig(); 42 | 43 | return SslContextUtils.build(enableClientCA, cfg); 44 | } 45 | 46 | @Override 47 | public IMessageIdStore initMessageIdStore() { 48 | return new MemoryMessageIdStore(); 49 | } 50 | 51 | @Override 52 | public ISessionStore initSessionStore() { 53 | return new MemorySessionStore(customConfig); 54 | } 55 | 56 | @Override 57 | public ISubscriptionStore initSubscriptionStore(ISessionStore sessionStore) { 58 | return new MemorySubscriptionStore(customConfig); 59 | } 60 | 61 | @Override 62 | public IRetainMessageStore initRetainMessageStore() { 63 | return new MemoryRetainMessageStore(customConfig); 64 | } 65 | 66 | @Override 67 | public IDupPubMessageStore initDupPubMessageStore() { 68 | return new MemoryDupPubMessageStore(customConfig); 69 | } 70 | 71 | @Override 72 | public IDupPubRelMessageStore initDupPubRelMessageStore() { 73 | return new MemoryDupPubRelMessageStore(customConfig); 74 | } 75 | 76 | @Override 77 | public IAuth initAuthManager(List userList) { 78 | return new DefaultAuthImpl(userList, customConfig); 79 | } 80 | 81 | @Override 82 | public IInnerTraffic initInnerTraffic(InnerPublishEventProcessor innerPublishEventProcessor, String nodeName) { 83 | return new EmptyInnerTraffic(nodeName, innerPublishEventProcessor); 84 | } 85 | 86 | @Override 87 | public List initEventListeners() { 88 | return CollUtil.newArrayList(new EventListenerAdapter(customConfig)); 89 | } 90 | 91 | @Override 92 | public String getNodeName() { 93 | return customConfig.getNodeName(); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/provider/RedisExtendProvider.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.provider; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.config.RedisConfig; 5 | import joey.mqtt.broker.innertraffic.IInnerTraffic; 6 | import joey.mqtt.broker.innertraffic.InnerPublishEventProcessor; 7 | import joey.mqtt.broker.innertraffic.RedisInnerTraffic; 8 | import joey.mqtt.broker.redis.RedisClient; 9 | import joey.mqtt.broker.redis.RedisFactory; 10 | import joey.mqtt.broker.store.*; 11 | import joey.mqtt.broker.store.redis.*; 12 | 13 | /** 14 | * redis 扩展实现 15 | * 16 | * @author Joey 17 | * @date 2019/9/7 18 | */ 19 | public class RedisExtendProvider extends MemoryExtendProvider { 20 | private final RedisClient redisClient; 21 | 22 | private final RedisConfig redisConfig; 23 | 24 | /** 25 | * 反射调用此构造方法 26 | * 27 | * @param customConfig 28 | */ 29 | public RedisExtendProvider(CustomConfig customConfig) { 30 | super(customConfig); 31 | 32 | this.redisConfig = customConfig.getRedisConfig(); 33 | this.redisClient = RedisFactory.createRedisClient(this.redisConfig); 34 | 35 | testConn(); 36 | } 37 | 38 | private void testConn() { 39 | redisClient.ping(); 40 | } 41 | 42 | @Override 43 | public IMessageIdStore initMessageIdStore() { 44 | return new RedisMessageIdStore(redisClient); 45 | } 46 | 47 | @Override 48 | public ISubscriptionStore initSubscriptionStore(ISessionStore sessionStore) { 49 | return new RedisSubscriptionStore(redisClient, customConfig); 50 | } 51 | 52 | @Override 53 | public IRetainMessageStore initRetainMessageStore() { 54 | return new RedisRetainMessageStore(redisClient); 55 | } 56 | 57 | @Override 58 | public IDupPubMessageStore initDupPubMessageStore() { 59 | return new RedisDupPubMessageStore(redisClient); 60 | } 61 | 62 | @Override 63 | public IDupPubRelMessageStore initDupPubRelMessageStore() { 64 | return new RedisDupPubRelMessageStore(redisClient); 65 | } 66 | 67 | @Override 68 | public IInnerTraffic initInnerTraffic(InnerPublishEventProcessor innerPublishEventProcessor, String nodeName) { 69 | return new RedisInnerTraffic(redisClient, innerPublishEventProcessor, nodeName); 70 | } 71 | 72 | @Override 73 | public String getNodeName() { 74 | return super.getNodeName(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/provider/RedisWithHzInnerTrafficExtendProvider.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.provider; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.config.RedisConfig; 6 | import joey.mqtt.broker.hazelcast.HazelcastFactory; 7 | import joey.mqtt.broker.innertraffic.HazelcastInnerTraffic; 8 | import joey.mqtt.broker.innertraffic.IInnerTraffic; 9 | import joey.mqtt.broker.innertraffic.InnerPublishEventProcessor; 10 | import joey.mqtt.broker.redis.RedisClient; 11 | import joey.mqtt.broker.redis.RedisFactory; 12 | import joey.mqtt.broker.store.*; 13 | import joey.mqtt.broker.store.redis.*; 14 | 15 | /** 16 | * redis与hazelcast混合实现 17 | * 18 | * redis实现qos所有等级要求 (有些redis集群不支持pub功能 例如:codis) 19 | * hazelcast实现集群间通信功能 20 | * 21 | * @author Joey 22 | * @date 2019/9/7 23 | */ 24 | public class RedisWithHzInnerTrafficExtendProvider extends MemoryExtendProvider { 25 | private RedisClient redisClient; 26 | 27 | private final RedisConfig redisConfig; 28 | 29 | private final String configFile; 30 | 31 | private final HazelcastInstance hzInstance; 32 | 33 | /** 34 | * 反射调用此构造方法 35 | * 36 | * @param customConfig 37 | */ 38 | public RedisWithHzInnerTrafficExtendProvider(CustomConfig customConfig) { 39 | super(customConfig); 40 | 41 | this.redisConfig = customConfig.getRedisConfig(); 42 | this.redisClient = RedisFactory.createRedisClient(this.redisConfig); 43 | 44 | this.configFile = customConfig.getHazelcastConfigFile(); 45 | this.hzInstance = HazelcastFactory.createInstance(this.configFile); 46 | } 47 | 48 | @Override 49 | public IMessageIdStore initMessageIdStore() { 50 | return new RedisMessageIdStore(redisClient); 51 | } 52 | 53 | @Override 54 | public ISubscriptionStore initSubscriptionStore(ISessionStore sessionStore) { 55 | return new RedisSubscriptionStore(redisClient, customConfig); 56 | } 57 | 58 | @Override 59 | public IRetainMessageStore initRetainMessageStore() { 60 | return new RedisRetainMessageStore(redisClient); 61 | } 62 | 63 | @Override 64 | public IDupPubMessageStore initDupPubMessageStore() { 65 | return new RedisDupPubMessageStore(redisClient); 66 | } 67 | 68 | @Override 69 | public IDupPubRelMessageStore initDupPubRelMessageStore() { 70 | return new RedisDupPubRelMessageStore(redisClient); 71 | } 72 | 73 | @Override 74 | public IInnerTraffic initInnerTraffic(InnerPublishEventProcessor innerPublishEventProcessor, String nodeName) { 75 | return new HazelcastInnerTraffic(hzInstance, innerPublishEventProcessor, customConfig, nodeName); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/redis/RedisFactory.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.redis; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import joey.mqtt.broker.config.RedisConfig; 5 | import redis.clients.jedis.JedisPool; 6 | import redis.clients.jedis.JedisPoolConfig; 7 | 8 | import java.time.Duration; 9 | 10 | /** 11 | * redis工厂 12 | * @author Joey 13 | * @date 2021-03-19 14 | */ 15 | public class RedisFactory { 16 | private RedisFactory() { 17 | 18 | } 19 | 20 | /** 21 | * 创建redis client 22 | * 23 | * @param redisConfig 24 | * @return 25 | */ 26 | public static RedisClient createRedisClient(RedisConfig redisConfig) { 27 | JedisPoolConfig config = new JedisPoolConfig(); 28 | 29 | // 最大空闲连接数, 默认8个 30 | RedisConfig.Pool pool = redisConfig.getPool(); 31 | config.setMaxIdle(pool.getMaxIdle()); 32 | 33 | // 最大连接数, 默认8个 34 | config.setMaxTotal(pool.getMaxActive()); 35 | 36 | // 获取连接时的最大等待毫秒数(如果设置为阻塞时BlockWhenExhausted),如果超时就抛异常, 小于零:阻塞不确定的时间, 默认-1 37 | config.setMaxWait(Duration.ofMillis(pool.getMaxWait())); 38 | 39 | // 逐出连接的最小空闲时间 默认1800000毫秒(30分钟) 40 | config.setMinEvictableIdleTime(Duration.ofMillis(pool.getMinEvictableIdleTimeMillis())); 41 | 42 | // 最小空闲连接数, 默认0 43 | config.setMinIdle(pool.getMinIdle()); 44 | 45 | // 在获取连接的时候检查有效性, 默认false 46 | config.setTestOnBorrow(pool.isTestOnBorrow()); 47 | 48 | // 在空闲时检查有效性, 默认false 49 | config.setTestWhileIdle(pool.isTestWhileIdle()); 50 | 51 | // 逐出扫描的时间间隔(毫秒) 如果为负数,则不运行逐出线程, 默认-1 52 | config.setTimeBetweenEvictionRuns(Duration.ofMillis(pool.getTimeBetweenEvictionRunsMillis())); 53 | 54 | String password = StrUtil.trimToNull(redisConfig.getPassword()); 55 | JedisPool jedisPool = new JedisPool(config, redisConfig.getHost(), 56 | redisConfig.getPort(), 57 | redisConfig.getTimeout(), 58 | password, redisConfig.getDatabase()); 59 | 60 | return new RedisClient(jedisPool); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/IDupPubMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * PUBLISH重发消息存储, 当QoS=1和QoS=2时存在该重发机制 9 | * 10 | * @author Joey 11 | * @date 2019/09/03 12 | */ 13 | public interface IDupPubMessageStore extends IStore { 14 | /** 15 | * 存储消息 16 | * 17 | * @param message 18 | */ 19 | void add(CommonPublishMessage message); 20 | 21 | /** 22 | * 获取消息 23 | * 24 | * @param clientId 25 | * @return 26 | */ 27 | List get(String clientId); 28 | 29 | /** 30 | * 获取消息 31 | * 32 | * @param clientId 33 | * @param messageId 34 | * @return 35 | */ 36 | CommonPublishMessage get(String clientId, int messageId); 37 | 38 | /** 39 | * 删除指定消息 40 | * 41 | * @param clientId 42 | * @param messageId 43 | */ 44 | void remove(String clientId, int messageId); 45 | 46 | /** 47 | * 删除用户所有消息 48 | * 49 | * @param clientId 50 | */ 51 | void removeAllFor(String clientId); 52 | } 53 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/IDupPubRelMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * PUBREL重发消息存储, 当QoS=2时存在该重发机制 9 | * 10 | * @author Joey 11 | * @date 2019/09/03 12 | */ 13 | public interface IDupPubRelMessageStore extends IStore { 14 | /** 15 | * 存储消息 16 | * 17 | * @param message 18 | */ 19 | void add(CommonPublishMessage message); 20 | 21 | /** 22 | * 获取消息 23 | * 24 | * @param clientId 25 | * @return 26 | */ 27 | List get(String clientId); 28 | 29 | /** 30 | * 获取消息 31 | * 32 | * @param clientId 33 | * @param messageId 34 | * @return 35 | */ 36 | CommonPublishMessage get(String clientId, int messageId); 37 | 38 | /** 39 | * 删除指定消息 40 | * 41 | * @param clientId 42 | * @param messageId 43 | */ 44 | void remove(String clientId, int messageId); 45 | 46 | /** 47 | * 删除用户所有消息 48 | * 49 | * @param clientId 50 | */ 51 | void removeAllFor(String clientId); 52 | } 53 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/IMessageIdStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | /** 4 | * 消息id存储 5 | * 6 | * @author Joey 7 | * @date 2019/9/3 8 | */ 9 | public interface IMessageIdStore { 10 | /** 11 | * 获取messageId 12 | * 13 | * @param clientId 14 | * @return 15 | */ 16 | int getNextMessageId(String clientId); 17 | } 18 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/IRetainMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | import joey.mqtt.broker.core.message.CommonPublishMessage; 4 | 5 | import java.util.List; 6 | 7 | /** 8 | * retain消息存储 9 | * 10 | * @author Joey 11 | * @date 2019/09/03 12 | */ 13 | public interface IRetainMessageStore extends IStore { 14 | /** 15 | * 存储消息 16 | * 17 | * @param message 18 | */ 19 | void add(CommonPublishMessage message); 20 | 21 | /** 22 | * 删除消息 23 | * 24 | * @param topic 25 | */ 26 | void remove(String topic); 27 | 28 | /** 29 | * 匹配满足topic的所有消息 30 | * 31 | * @param topic 32 | * @return 33 | */ 34 | List match(String topic); 35 | } 36 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/ISessionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | import joey.mqtt.broker.core.client.ClientSession; 4 | 5 | /** 6 | * session存储 7 | * @author Joey 8 | * @date 2019/7/22 9 | */ 10 | public interface ISessionStore extends IStore { 11 | /** 12 | * 存储会话 13 | * 14 | * @param clientSession 15 | */ 16 | void add(ClientSession clientSession); 17 | 18 | /** 19 | * 获取会话 20 | * 21 | * @param clientId 22 | * @return 23 | */ 24 | ClientSession get(String clientId); 25 | 26 | /** 27 | * 删除会话 28 | * 29 | * @param clientId 30 | */ 31 | void remove(String clientId); 32 | 33 | /** 34 | * 当前连接session数量 35 | * @return 36 | */ 37 | int getSessionCount(); 38 | } 39 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/IStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | /** 4 | * 存储接口 5 | * 6 | * @author Joey 7 | * @date 2022/7/23 8 | */ 9 | public interface IStore { 10 | /** 11 | * 关闭 12 | */ 13 | void close(); 14 | } 15 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/ISubscriptionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store; 2 | 3 | import joey.mqtt.broker.core.subscription.Subscription; 4 | 5 | import java.util.List; 6 | import java.util.Set; 7 | 8 | /** 9 | * 订阅存储 10 | * @author Joey 11 | * @date 2019/7/22 12 | */ 13 | public interface ISubscriptionStore extends IStore { 14 | /** 15 | * 添加订阅 16 | * 17 | * @param subscription 18 | * @param onlyMemory 19 | * @return 20 | */ 21 | boolean add(Subscription subscription, boolean onlyMemory); 22 | 23 | /** 24 | * 删除订阅 25 | * 26 | * @param subscription 27 | */ 28 | boolean remove(Subscription subscription); 29 | 30 | /** 31 | * 匹配满足topic的所有订阅关系 32 | * 33 | * @param topic 34 | * @return 35 | */ 36 | List match(String topic); 37 | 38 | /** 39 | * 根据clientId查找所有订阅 40 | * 41 | * @param clientId 42 | * @return 43 | */ 44 | Set findAllBy(String clientId); 45 | 46 | /** 47 | * 根据clientId删除所有订阅 48 | * 49 | * @param clientId 50 | */ 51 | void removeAllBy(String clientId); 52 | } 53 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastBaseStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | 6 | /** 7 | * hazelcast 基础存储 8 | * 9 | * @author Joey 10 | * @date 2021/03/18 11 | */ 12 | public class HazelcastBaseStore { 13 | protected final HazelcastInstance hzInstance; 14 | 15 | protected final CustomConfig customConfig; 16 | 17 | protected HazelcastBaseStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 18 | this.hzInstance = hzInstance; 19 | this.customConfig = customConfig; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastDupBaseMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import com.hazelcast.core.HazelcastInstance; 5 | import com.hazelcast.map.IMap; 6 | import joey.mqtt.broker.config.CustomConfig; 7 | import joey.mqtt.broker.core.message.CommonPublishMessage; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Optional; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * hazelcast dup消息存储 16 | * 17 | * @author Joey 18 | * @date 2019/7/23 19 | */ 20 | public class HazelcastDupBaseMessageStore extends HazelcastBaseStore { 21 | private final IMap> clientMsgMap; 22 | 23 | public HazelcastDupBaseMessageStore(HazelcastInstance hzInstance, CustomConfig customConfig, IMap> clientMsgMap) { 24 | super(hzInstance, customConfig); 25 | 26 | this.clientMsgMap = clientMsgMap; 27 | } 28 | 29 | public void add(CommonPublishMessage message) { 30 | String clientId = message.getTargetClientId(); 31 | clientMsgMap.lock(clientId); 32 | 33 | try { 34 | ConcurrentHashMap msgIdMap = clientMsgMap.computeIfAbsent(clientId, m -> new ConcurrentHashMap<>()); 35 | msgIdMap.put(message.getMessageId(), message); 36 | clientMsgMap.put(clientId, msgIdMap); 37 | } finally { 38 | clientMsgMap.unlock(clientId); 39 | } 40 | } 41 | 42 | public List get(String clientId) { 43 | List msgList = new ArrayList<>(); 44 | 45 | ConcurrentHashMap msgMap = clientMsgMap.get(clientId); 46 | if (CollUtil.isNotEmpty(msgMap)) { 47 | msgList = CollUtil.newArrayList(msgMap.values()); 48 | } 49 | 50 | return msgList; 51 | } 52 | 53 | public CommonPublishMessage get(String clientId, int messageId) { 54 | return Optional.ofNullable(clientMsgMap.get(clientId)) 55 | .map(msgMap -> msgMap.get(messageId)) 56 | .orElse(null); 57 | } 58 | 59 | public void remove(String clientId, int messageId) { 60 | clientMsgMap.lock(clientId); 61 | 62 | try { 63 | Optional.ofNullable(clientMsgMap.get(clientId)) 64 | .ifPresent(msgIdMap -> { 65 | msgIdMap.remove(messageId); 66 | }); 67 | } finally { 68 | clientMsgMap.unlock(clientId); 69 | } 70 | } 71 | 72 | public void removeAllFor(String clientId) { 73 | clientMsgMap.lock(clientId); 74 | 75 | try { 76 | clientMsgMap.remove(clientId); 77 | } finally { 78 | clientMsgMap.unlock(clientId); 79 | } 80 | } 81 | 82 | public void close() { 83 | 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastDupPubMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.constant.BusinessConstants; 6 | import joey.mqtt.broker.store.IDupPubMessageStore; 7 | 8 | /** 9 | * hazelcast dup pub消息存储 10 | * 11 | * @author Joey 12 | * @date 2021/03/18 13 | */ 14 | public class HazelcastDupPubMessageStore extends HazelcastDupBaseMessageStore implements IDupPubMessageStore { 15 | public HazelcastDupPubMessageStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 16 | super(hzInstance, customConfig, hzInstance.getMap(BusinessConstants.HAZELCAST_MSG_DUP_PUB)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastDupPubRelMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.constant.BusinessConstants; 6 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 7 | 8 | /** 9 | * hazelcast dup pub rel消息存储 10 | * 11 | * @author Joey 12 | * @date 2019/7/23 13 | */ 14 | public class HazelcastDupPubRelMessageStore extends HazelcastDupBaseMessageStore implements IDupPubRelMessageStore { 15 | public HazelcastDupPubRelMessageStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 16 | super(hzInstance, customConfig, hzInstance.getMap(BusinessConstants.HAZELCAST_MSG_DUP_PUB_REL)); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastMessageIdStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import com.hazelcast.core.HazelcastInstance; 5 | import com.hazelcast.map.IMap; 6 | import joey.mqtt.broker.config.CustomConfig; 7 | import joey.mqtt.broker.constant.BusinessConstants; 8 | import joey.mqtt.broker.constant.NumConstants; 9 | import joey.mqtt.broker.store.IMessageIdStore; 10 | 11 | /** 12 | * hazelcast消息id存储 13 | * 14 | * 参考:https://www.jianshu.com/p/a015ffb2dd8f 15 | * 16 | * @author Joey 17 | * @date 2019/9/3 18 | */ 19 | public class HazelcastMessageIdStore extends HazelcastBaseStore implements IMessageIdStore { 20 | private final IMap clientMsgIdMap; 21 | 22 | public HazelcastMessageIdStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 23 | super(hzInstance, customConfig); 24 | 25 | clientMsgIdMap = hzInstance.getMap(BusinessConstants.HAZELCAST_MSG_ID); 26 | } 27 | 28 | @Override 29 | public int getNextMessageId(String clientId) { 30 | clientMsgIdMap.lock(clientId); 31 | 32 | try { 33 | Integer currentMsgId = clientMsgIdMap.get(clientId); 34 | if (null == currentMsgId) { 35 | currentMsgId = NumConstants.INT_0; 36 | } 37 | 38 | Integer nextMsgId = (currentMsgId + NumConstants.INT_1) % 0xFFFF; 39 | if (ObjectUtil.equal(NumConstants.INT_0, nextMsgId)) { 40 | nextMsgId = (nextMsgId + NumConstants.INT_1) % 0xFFFF; 41 | } 42 | 43 | clientMsgIdMap.put(clientId, nextMsgId); 44 | return nextMsgId; 45 | } finally { 46 | clientMsgIdMap.unlock(clientId); 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastRetainMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import com.hazelcast.core.HazelcastInstance; 5 | import com.hazelcast.map.IMap; 6 | import joey.mqtt.broker.config.CustomConfig; 7 | import joey.mqtt.broker.constant.BusinessConstants; 8 | import joey.mqtt.broker.core.message.CommonPublishMessage; 9 | import joey.mqtt.broker.store.IRetainMessageStore; 10 | import joey.mqtt.broker.util.TopicUtils; 11 | 12 | import java.util.List; 13 | import java.util.Optional; 14 | 15 | /** 16 | * hazelcast retain消息存储 17 | * 18 | * @author Joey 19 | * @date 2021/03/18 20 | */ 21 | public class HazelcastRetainMessageStore extends HazelcastBaseStore implements IRetainMessageStore { 22 | private final IMap retainMsgMap; 23 | 24 | public HazelcastRetainMessageStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 25 | super(hzInstance, customConfig); 26 | 27 | retainMsgMap = hzInstance.getMap(BusinessConstants.HAZELCAST_MSG_RETAIN); 28 | } 29 | 30 | @Override 31 | public void add(CommonPublishMessage message) { 32 | retainMsgMap.put(message.getTopic(), message); 33 | } 34 | 35 | @Override 36 | public void remove(String topic) { 37 | retainMsgMap.remove(topic); 38 | } 39 | 40 | @Override 41 | public List match(String topic) { 42 | List retainMessageList = CollUtil.newLinkedList(); 43 | 44 | List subTokenList = TopicUtils.getTopicTokenList(topic); 45 | if (CollUtil.isNotEmpty(subTokenList)) { 46 | Optional.ofNullable(retainMsgMap.values()) 47 | .ifPresent(msgCollection -> { 48 | msgCollection.forEach(retainMessage -> { 49 | if (TopicUtils.match(subTokenList, TopicUtils.getTopicTokenList(retainMessage.getTopic()))) { 50 | retainMessageList.add(retainMessage); 51 | } 52 | }); 53 | }); 54 | } 55 | 56 | return retainMessageList; 57 | } 58 | 59 | @Override 60 | public void close() { 61 | 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/hazelcast/HazelcastSubscriptionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.hazelcast; 2 | 3 | import com.hazelcast.core.HazelcastInstance; 4 | import com.hazelcast.multimap.MultiMap; 5 | import joey.mqtt.broker.config.CustomConfig; 6 | import joey.mqtt.broker.constant.BusinessConstants; 7 | import joey.mqtt.broker.core.subscription.Subscription; 8 | import joey.mqtt.broker.store.BaseSubscriptionStore; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import java.util.HashSet; 12 | import java.util.Optional; 13 | import java.util.Set; 14 | 15 | /** 16 | * hazelcast订阅存储 17 | * 18 | * @author Joey 19 | * @date 2021/03/18 20 | */ 21 | @Slf4j 22 | public class HazelcastSubscriptionStore extends BaseSubscriptionStore { 23 | private final HazelcastInstance hzInstance; 24 | 25 | private final CustomConfig customConfig; 26 | 27 | private final MultiMap clientSubMultiMap; 28 | 29 | public HazelcastSubscriptionStore(HazelcastInstance hzInstance, CustomConfig customConfig) { 30 | super(customConfig); 31 | 32 | this.hzInstance = hzInstance; 33 | this.customConfig = customConfig; 34 | 35 | this.clientSubMultiMap = hzInstance.getMultiMap(BusinessConstants.HAZELCAST_SUB_STORE); 36 | } 37 | 38 | @Override 39 | public boolean add(Subscription subscription, boolean onlyMemory) { 40 | boolean addResult = super.add(subscription); 41 | if (addResult && !onlyMemory) { 42 | clientSubMultiMap.put(subscription.getClientId(), subscription); 43 | return true; 44 | } 45 | 46 | return addResult; 47 | } 48 | 49 | @Override 50 | public boolean remove(Subscription subscription) { 51 | boolean removeResult = super.remove(subscription); 52 | if (removeResult) { 53 | clientSubMultiMap.remove(subscription.getClientId(), subscription); 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | @Override 61 | public Set findAllBy(String clientId) { 62 | return Optional.ofNullable(clientSubMultiMap.get(clientId)) 63 | .map(subCollection -> new HashSet<>(subCollection)) 64 | .orElse(new HashSet<>()); 65 | } 66 | 67 | @Override 68 | public void removeAllBy(String clientId) { 69 | Optional.ofNullable(clientSubMultiMap.get(clientId)) 70 | .ifPresent(subSet -> { 71 | subSet.forEach(sub -> { 72 | //删除订阅关系 73 | super.remove(sub); 74 | }); 75 | 76 | clientSubMultiMap.remove(clientId); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemoryDupBaseMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.map.MapUtil; 5 | import joey.mqtt.broker.config.CustomConfig; 6 | import joey.mqtt.broker.core.message.CommonPublishMessage; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | import java.util.concurrent.ConcurrentHashMap; 11 | import java.util.concurrent.atomic.AtomicBoolean; 12 | 13 | /** 14 | * 内存dup消息存储 15 | * 16 | * @author Joey 17 | * @date 2019/7/23 18 | */ 19 | public class MemoryDupBaseMessageStore { 20 | private ConcurrentHashMap> messageCache; 21 | 22 | /** 23 | * 并发操作锁map 24 | */ 25 | private ConcurrentHashMap lockMap; 26 | 27 | private CustomConfig customConfig; 28 | 29 | public MemoryDupBaseMessageStore(CustomConfig customConfig) { 30 | this.messageCache = new ConcurrentHashMap<>(); 31 | this.lockMap = new ConcurrentHashMap<>(); 32 | this.customConfig = customConfig; 33 | } 34 | 35 | public void add(CommonPublishMessage message) { 36 | //解决添加和删除并发操作问题 37 | for (;;) { 38 | String clientId = message.getTargetClientId(); 39 | AtomicBoolean lockFlag = lockMap.computeIfAbsent(clientId, b -> new AtomicBoolean(false)); 40 | if (lockFlag.compareAndSet(false, true)) { 41 | ConcurrentHashMap msgMap = messageCache.computeIfAbsent(clientId, m -> new ConcurrentHashMap<>()); 42 | msgMap.put(message.getMessageId(), message); 43 | lockMap.remove(clientId); 44 | break; 45 | } 46 | } 47 | } 48 | 49 | public List get(String clientId) { 50 | List msgList = new ArrayList<>(); 51 | 52 | ConcurrentHashMap msgMap = messageCache.get(clientId); 53 | if (CollUtil.isNotEmpty(msgMap)) { 54 | msgList = CollUtil.newArrayList(msgMap.values()); 55 | } 56 | 57 | return msgList; 58 | } 59 | 60 | public CommonPublishMessage get(String clientId, int messageId) { 61 | ConcurrentHashMap msgMap = messageCache.get(clientId); 62 | if (CollUtil.isNotEmpty(msgMap)) { 63 | return msgMap.get(messageId); 64 | } 65 | 66 | return null; 67 | } 68 | 69 | public void remove(String clientId, int messageId) { 70 | //解决添加和删除并发操作问题 71 | for (;;) { 72 | AtomicBoolean lockFlag = lockMap.computeIfAbsent(clientId, b -> new AtomicBoolean(false)); 73 | if (lockFlag.compareAndSet(false, true)) { 74 | ConcurrentHashMap msgMap = messageCache.get(clientId); 75 | if (MapUtil.isNotEmpty(msgMap)) { 76 | msgMap.remove(messageId); 77 | } 78 | 79 | lockMap.remove(clientId); 80 | break; 81 | } 82 | } 83 | } 84 | 85 | public void removeAllFor(String clientId) { 86 | //解决添加和删除并发操作问题 87 | for (;;) { 88 | AtomicBoolean lockFlag = lockMap.computeIfAbsent(clientId, b -> new AtomicBoolean(false)); 89 | if (lockFlag.compareAndSet(false, true)) { 90 | messageCache.remove(clientId); 91 | lockMap.remove(clientId); 92 | break; 93 | } 94 | } 95 | } 96 | 97 | public void close() { 98 | 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemoryDupPubMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.store.IDupPubMessageStore; 5 | 6 | /** 7 | * 内存pub消息存储 8 | * 9 | * @author Joey 10 | * @date 2019/7/23 11 | */ 12 | public class MemoryDupPubMessageStore extends MemoryDupBaseMessageStore implements IDupPubMessageStore { 13 | public MemoryDupPubMessageStore(CustomConfig customConfig) { 14 | super(customConfig); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemoryDupPubRelMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 5 | 6 | /** 7 | * 内存pubRel消息存储 8 | * 9 | * @author Joey 10 | * @date 2019/7/23 11 | */ 12 | public class MemoryDupPubRelMessageStore extends MemoryDupBaseMessageStore implements IDupPubRelMessageStore { 13 | public MemoryDupPubRelMessageStore(CustomConfig customConfig) { 14 | super(customConfig); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemoryMessageIdStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import cn.hutool.core.util.NumberUtil; 4 | import joey.mqtt.broker.constant.NumConstants; 5 | import joey.mqtt.broker.store.IMessageIdStore; 6 | 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.atomic.AtomicInteger; 9 | 10 | /** 11 | * 内存消息id存储 12 | * 13 | * @author Joey 14 | * @date 2019/9/3 15 | */ 16 | public class MemoryMessageIdStore implements IMessageIdStore { 17 | private final ConcurrentHashMap clientMsgIdMap = new ConcurrentHashMap<>(); 18 | 19 | @Override 20 | public int getNextMessageId(String clientId) { 21 | AtomicInteger msgIdHolder = clientMsgIdMap.computeIfAbsent(clientId, m -> new AtomicInteger(NumConstants.INT_0)); 22 | return msgIdHolder.updateAndGet(v -> NumberUtil.equals(v, NumConstants.INT_65535) ? NumConstants.INT_1 : (v + NumConstants.INT_1)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemoryRetainMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.core.message.CommonPublishMessage; 6 | import joey.mqtt.broker.store.IRetainMessageStore; 7 | import joey.mqtt.broker.util.TopicUtils; 8 | 9 | import java.util.List; 10 | import java.util.Optional; 11 | import java.util.concurrent.ConcurrentHashMap; 12 | 13 | /** 14 | * 内存retain消息存储 15 | * 16 | * @author Joey 17 | * @date 2019/7/23 18 | */ 19 | public class MemoryRetainMessageStore implements IRetainMessageStore { 20 | private ConcurrentHashMap retainMsgMap = new ConcurrentHashMap<>(); 21 | 22 | public MemoryRetainMessageStore(CustomConfig customConfig) { 23 | 24 | } 25 | 26 | @Override 27 | public void add(CommonPublishMessage message) { 28 | retainMsgMap.put(message.getTopic(), message); 29 | } 30 | 31 | @Override 32 | public void remove(String topic) { 33 | retainMsgMap.remove(topic); 34 | } 35 | 36 | @Override 37 | public List match(String topic) { 38 | List retainMessageList = CollUtil.newLinkedList(); 39 | 40 | List subTokenList = TopicUtils.getTopicTokenList(topic); 41 | if (CollUtil.isNotEmpty(subTokenList)) { 42 | Optional.ofNullable(retainMsgMap.values()) 43 | .ifPresent(msgCollection -> { 44 | msgCollection.forEach(retainMessage -> { 45 | if (TopicUtils.match(subTokenList, TopicUtils.getTopicTokenList(retainMessage.getTopic()))) { 46 | retainMessageList.add(retainMessage); 47 | } 48 | }); 49 | }); 50 | } 51 | 52 | return retainMessageList; 53 | } 54 | 55 | @Override 56 | public void close() { 57 | 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemorySessionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.core.client.ClientSession; 5 | import joey.mqtt.broker.store.ISessionStore; 6 | 7 | import java.util.concurrent.ConcurrentHashMap; 8 | import java.util.concurrent.atomic.LongAdder; 9 | 10 | /** 11 | * 内存session存储 12 | * 13 | * @author Joey 14 | * @date 2019/7/22 15 | */ 16 | public class MemorySessionStore implements ISessionStore { 17 | private ConcurrentHashMap sessionCache = new ConcurrentHashMap<>(); 18 | 19 | public MemorySessionStore(CustomConfig config) { 20 | 21 | } 22 | 23 | @Override 24 | public void add(ClientSession clientSession) { 25 | sessionCache.put(clientSession.getClientId(), clientSession); 26 | } 27 | 28 | @Override 29 | public ClientSession get(String clientId) { 30 | return sessionCache.get(clientId); 31 | } 32 | 33 | @Override 34 | public void remove(String clientId) { 35 | sessionCache.remove(clientId); 36 | } 37 | 38 | @Override 39 | public int getSessionCount() { 40 | return sessionCache.size(); 41 | } 42 | 43 | @Override 44 | public void close() { 45 | 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/memory/MemorySubscriptionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.memory; 2 | 3 | import cn.hutool.core.collection.ConcurrentHashSet; 4 | import joey.mqtt.broker.config.CustomConfig; 5 | import joey.mqtt.broker.core.subscription.Subscription; 6 | import joey.mqtt.broker.store.BaseSubscriptionStore; 7 | import lombok.extern.slf4j.Slf4j; 8 | 9 | import java.util.HashSet; 10 | import java.util.Optional; 11 | import java.util.Set; 12 | import java.util.concurrent.ConcurrentHashMap; 13 | 14 | /** 15 | * 内存订阅存储 16 | * 17 | * @author Joey 18 | * @date 2019/7/22 19 | */ 20 | @Slf4j 21 | public class MemorySubscriptionStore extends BaseSubscriptionStore { 22 | /** 23 | * clientId与其相关所有订阅map 24 | */ 25 | private final ConcurrentHashMap> clientSubMap = new ConcurrentHashMap<>(); 26 | 27 | public MemorySubscriptionStore(CustomConfig config) { 28 | super(config); 29 | } 30 | 31 | @Override 32 | public boolean add(Subscription subscription, boolean onlyMemory) { 33 | boolean addResult = super.add(subscription); 34 | if (addResult && !onlyMemory) { 35 | String clientId = subscription.getClientId(); 36 | Set subSet = clientSubMap.computeIfAbsent(clientId, s -> new ConcurrentHashSet<>()); 37 | subSet.add(subscription); 38 | return true; 39 | } 40 | 41 | return addResult; 42 | } 43 | 44 | @Override 45 | public boolean remove(Subscription subscription) { 46 | boolean removeResult = super.remove(subscription); 47 | if (removeResult) { 48 | String clientId = subscription.getClientId(); 49 | Optional.ofNullable(clientSubMap.get(clientId)) 50 | .ifPresent(subSet -> { 51 | subSet.remove(subscription); 52 | }); 53 | 54 | return true; 55 | } 56 | 57 | return false; 58 | } 59 | 60 | @Override 61 | public Set findAllBy(String clientId) { 62 | return Optional.ofNullable(clientSubMap.get(clientId)) 63 | .map(subCollection -> new HashSet<>(subCollection)) 64 | .orElse(new HashSet<>()); 65 | } 66 | 67 | @Override 68 | public void removeAllBy(String clientId) { 69 | Optional.ofNullable(clientSubMap.get(clientId)) 70 | .ifPresent(subSet -> { 71 | subSet.forEach(sub -> { 72 | //删除订阅关系 73 | super.remove(sub); 74 | }); 75 | 76 | subSet.clear(); 77 | }); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisDupBaseMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import cn.hutool.core.util.StrUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import joey.mqtt.broker.constant.BusinessConstants; 6 | import joey.mqtt.broker.core.message.CommonPublishMessage; 7 | import joey.mqtt.broker.redis.RedisClient; 8 | 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | import java.util.Optional; 12 | 13 | /** 14 | * redis dup消息存储 15 | * 16 | * @author Joey 17 | * @date 2019/9/7 18 | */ 19 | public abstract class RedisDupBaseMessageStore { 20 | private final RedisClient redisClient; 21 | 22 | public RedisDupBaseMessageStore(RedisClient redisClient) { 23 | this.redisClient = redisClient; 24 | } 25 | 26 | /** 27 | * 获取redis key 28 | * 29 | * @param clientId 30 | * @return 31 | */ 32 | public abstract String getRedisKey(String clientId); 33 | 34 | public void add(CommonPublishMessage message) { 35 | redisClient.hset(getRedisKey(message.getTargetClientId()), String.valueOf(message.getMessageId()), JSON.toJSONString(message)); 36 | } 37 | 38 | public List get(String clientId) { 39 | List msgList = new ArrayList<>(); 40 | 41 | Optional.ofNullable(redisClient.hgetAllWithScan(getRedisKey(clientId), BusinessConstants.REDIS_EACH_SCAN_COUNT)) 42 | .ifPresent(msgMap -> { 43 | msgMap.forEach((msgId, msgStr) -> { 44 | msgList.add(JSON.parseObject(msgStr, CommonPublishMessage.class)); 45 | }); 46 | }); 47 | 48 | return msgList; 49 | } 50 | 51 | public CommonPublishMessage get(String clientId, int messageId) { 52 | String msgJsonStr = redisClient.hget(getRedisKey(clientId), String.valueOf(messageId)); 53 | if (StrUtil.isNotBlank(msgJsonStr)) { 54 | return JSON.parseObject(msgJsonStr, CommonPublishMessage.class); 55 | } 56 | 57 | return null; 58 | } 59 | 60 | public void remove(String clientId, int messageId) { 61 | redisClient.hdel(getRedisKey(clientId), String.valueOf(messageId)); 62 | } 63 | 64 | public void removeAllFor(String clientId) { 65 | redisClient.del(getRedisKey(clientId)); 66 | } 67 | 68 | public void close() { 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisDupPubMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import joey.mqtt.broker.constant.BusinessConstants; 4 | import joey.mqtt.broker.redis.RedisClient; 5 | import joey.mqtt.broker.store.IDupPubMessageStore; 6 | 7 | /** 8 | * redis pub消息存储 9 | * 10 | * @author Joey 11 | * @date 2019/9/7 12 | */ 13 | public class RedisDupPubMessageStore extends RedisDupBaseMessageStore implements IDupPubMessageStore { 14 | public RedisDupPubMessageStore(RedisClient redisClient) { 15 | super(redisClient); 16 | } 17 | 18 | @Override 19 | public String getRedisKey(String clientId) { 20 | return BusinessConstants.REDIS_MSG_DUP_PUB_KEY_PRE + clientId; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisDupPubRelMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import joey.mqtt.broker.constant.BusinessConstants; 4 | import joey.mqtt.broker.redis.RedisClient; 5 | import joey.mqtt.broker.store.IDupPubRelMessageStore; 6 | 7 | /** 8 | * redis pubRel消息存储 9 | * 10 | * @author Joey 11 | * @date 2019/9/7 12 | */ 13 | public class RedisDupPubRelMessageStore extends RedisDupBaseMessageStore implements IDupPubRelMessageStore { 14 | public RedisDupPubRelMessageStore(RedisClient redisClient) { 15 | super(redisClient); 16 | } 17 | 18 | @Override 19 | public String getRedisKey(String clientId) { 20 | return BusinessConstants.REDIS_MSG_DUP_PUB_REL_KEY_PRE + clientId; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisMessageIdStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import joey.mqtt.broker.constant.NumConstants; 5 | import joey.mqtt.broker.redis.RedisClient; 6 | import joey.mqtt.broker.store.IMessageIdStore; 7 | 8 | import static joey.mqtt.broker.constant.BusinessConstants.REDIS_MSG_ID_FIELD; 9 | import static joey.mqtt.broker.constant.BusinessConstants.REDIS_MSG_ID_KEY_PRE; 10 | 11 | /** 12 | * redis 消息id存储 13 | * 14 | * @author Joey 15 | * @date 2019/9/7 16 | */ 17 | public class RedisMessageIdStore implements IMessageIdStore { 18 | private final RedisClient redisClient; 19 | 20 | public RedisMessageIdStore(RedisClient redisClient) { 21 | this.redisClient = redisClient; 22 | } 23 | 24 | @Override 25 | public int getNextMessageId(String clientId) { 26 | String redisKey = REDIS_MSG_ID_KEY_PRE + clientId; 27 | Long incrId = redisClient.hincrBy(redisKey, REDIS_MSG_ID_FIELD, NumConstants.LONG_1); 28 | 29 | if (incrId >= Integer.MAX_VALUE) { 30 | redisClient.hset(redisKey, REDIS_MSG_ID_FIELD, String.valueOf(NumConstants.LONG_0)); 31 | incrId = redisClient.hincrBy(redisKey, REDIS_MSG_ID_FIELD, NumConstants.LONG_1); 32 | } 33 | 34 | for (;;) { 35 | int nextMsgId = (incrId.intValue() + NumConstants.INT_1) % 0xFFFF; 36 | if (ObjectUtil.notEqual(NumConstants.INT_0, nextMsgId)) { 37 | return nextMsgId; 38 | } 39 | 40 | incrId = redisClient.hincrBy(redisKey, REDIS_MSG_ID_FIELD, NumConstants.LONG_1); 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisRetainMessageStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import com.alibaba.fastjson.JSONObject; 6 | import joey.mqtt.broker.core.message.CommonPublishMessage; 7 | import joey.mqtt.broker.redis.RedisClient; 8 | import joey.mqtt.broker.store.IRetainMessageStore; 9 | import joey.mqtt.broker.util.TopicUtils; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | import static joey.mqtt.broker.constant.BusinessConstants.REDIS_EACH_SCAN_COUNT; 15 | import static joey.mqtt.broker.constant.BusinessConstants.REDIS_MSG_RETAIN_KEY; 16 | 17 | /** 18 | * redis retain消息存储 19 | * 20 | * @author Joey 21 | * @date 2019/9/7 22 | */ 23 | public class RedisRetainMessageStore implements IRetainMessageStore { 24 | private final RedisClient redisClient; 25 | 26 | public RedisRetainMessageStore(RedisClient redisClient) { 27 | this.redisClient = redisClient; 28 | } 29 | 30 | @Override 31 | public void add(CommonPublishMessage message) { 32 | redisClient.hset(REDIS_MSG_RETAIN_KEY, message.getTopic(), JSONObject.toJSONString(message)); 33 | } 34 | 35 | @Override 36 | public void remove(String topic) { 37 | redisClient.hdel(REDIS_MSG_RETAIN_KEY, topic); 38 | } 39 | 40 | @Override 41 | public List match(String topic) { 42 | List retainMessageList = CollUtil.newLinkedList(); 43 | 44 | List subTokenList = TopicUtils.getTopicTokenList(topic); 45 | if (CollUtil.isNotEmpty(subTokenList)) { 46 | Optional.ofNullable(redisClient.hgetAllWithScan(REDIS_MSG_RETAIN_KEY, REDIS_EACH_SCAN_COUNT)) 47 | .ifPresent(retainMessageMap -> { 48 | retainMessageMap.forEach((matchTopic, retainMessageStr) -> { 49 | if (TopicUtils.match(subTokenList, TopicUtils.getTopicTokenList(matchTopic))) { 50 | if (StrUtil.isNotBlank(retainMessageStr)) { 51 | retainMessageList.add(JSONObject.parseObject(retainMessageStr, CommonPublishMessage.class)); 52 | } 53 | } 54 | }); 55 | }); 56 | } 57 | 58 | return retainMessageList; 59 | } 60 | 61 | @Override 62 | public void close() { 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/store/redis/RedisSubscriptionStore.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.store.redis; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import com.alibaba.fastjson.JSON; 5 | import joey.mqtt.broker.config.CustomConfig; 6 | import joey.mqtt.broker.constant.BusinessConstants; 7 | import joey.mqtt.broker.core.subscription.Subscription; 8 | import joey.mqtt.broker.redis.RedisClient; 9 | import joey.mqtt.broker.store.BaseSubscriptionStore; 10 | import lombok.extern.slf4j.Slf4j; 11 | 12 | import java.util.HashSet; 13 | import java.util.List; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | 17 | /** 18 | * redis订阅存储 19 | * 20 | * @author Joey 21 | * @date 2019/7/22 22 | */ 23 | @Slf4j 24 | public class RedisSubscriptionStore extends BaseSubscriptionStore { 25 | private final RedisClient redisClient; 26 | 27 | public RedisSubscriptionStore(RedisClient redisClient, CustomConfig customConfig) { 28 | super(customConfig); 29 | this.redisClient = redisClient; 30 | } 31 | 32 | private String getRedisKey(Subscription subscription) { 33 | String clientId = subscription.getClientId(); 34 | return getRedisKey(clientId); 35 | } 36 | 37 | private String getRedisKey(String clientId) { 38 | return BusinessConstants.REDIS_SUB_STORE_KEY + clientId; 39 | } 40 | 41 | @Override 42 | public boolean add(Subscription subscription, boolean onlyMemory) { 43 | boolean addResult = super.add(subscription); 44 | if (addResult && !onlyMemory) { 45 | redisClient.hset(getRedisKey(subscription), subscription.getTopic(), JSON.toJSONString(subscription)); 46 | return true; 47 | } 48 | 49 | return addResult; 50 | } 51 | 52 | @Override 53 | public boolean remove(Subscription subscription) { 54 | boolean removeResult = super.remove(subscription); 55 | if (removeResult) { 56 | redisClient.hdel(getRedisKey(subscription), subscription.getTopic()); 57 | return true; 58 | } 59 | 60 | return false; 61 | } 62 | 63 | @Override 64 | public Set findAllBy(String clientId) { 65 | Set subSet = new HashSet<>(); 66 | List subJsonStrList = redisClient.hvals(getRedisKey(clientId)); 67 | 68 | if (CollUtil.isNotEmpty(subJsonStrList)) { 69 | for (String subJson : subJsonStrList) { 70 | subSet.add(JSON.parseObject(subJson, Subscription.class)); 71 | } 72 | } 73 | 74 | return subSet; 75 | } 76 | 77 | @Override 78 | public void removeAllBy(String clientId) { 79 | Optional.ofNullable(findAllBy(clientId)) 80 | .ifPresent(subSet -> { 81 | subSet.forEach(sub -> { 82 | //删除订阅关系 83 | super.remove(sub); 84 | }); 85 | }); 86 | 87 | redisClient.del(getRedisKey(clientId)); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/util/ConfigUtils.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.util; 2 | 3 | import cn.hutool.core.io.FileUtil; 4 | import cn.hutool.core.lang.Console; 5 | import cn.hutool.core.util.CharsetUtil; 6 | import cn.hutool.core.util.ObjectUtil; 7 | import cn.hutool.core.util.StrUtil; 8 | import cn.hutool.setting.dialect.Props; 9 | import cn.hutool.setting.yaml.YamlUtil; 10 | import joey.mqtt.broker.constant.BusinessConstants; 11 | import joey.mqtt.broker.config.Config; 12 | import joey.mqtt.broker.config.YamlWrapperConfig; 13 | 14 | /** 15 | * 配置工具类 16 | * 17 | * @author Joey 18 | * @date 2022/7/22 19 | */ 20 | public class ConfigUtils { 21 | private static final String PROPS_SUFFIX = "properties"; 22 | 23 | private static final String YAML_SUFFIX = "yml"; 24 | 25 | private ConfigUtils() { 26 | 27 | } 28 | 29 | /** 30 | * 从启动参数中加载配置文件 没有指定 则使用默认配置文件 31 | * 例如:java -Dmqtt.conf=XXXXX 32 | * 33 | * @param propsKey 34 | * @param defaultConfig 35 | * @return 36 | */ 37 | public static Config loadFromSystemProps(String propsKey, Config defaultConfig) { 38 | String mqttConfigFile = System.getProperty(propsKey); 39 | 40 | if (StrUtil.isBlank(mqttConfigFile)) { 41 | return defaultConfig; 42 | } 43 | 44 | if (ObjectUtil.equals(PROPS_SUFFIX, FileUtil.getSuffix(mqttConfigFile))) { 45 | return loadFromPropertiesFile(mqttConfigFile); 46 | } 47 | 48 | if (ObjectUtil.equals(YAML_SUFFIX, FileUtil.getSuffix(mqttConfigFile))) { 49 | return loadFromYamlFile(mqttConfigFile); 50 | } 51 | 52 | return null; 53 | } 54 | 55 | /** 56 | * 从properties文件加载配置 57 | * 58 | * @param mqttConfigFile 59 | * @return 60 | */ 61 | public static Config loadFromPropertiesFile(String mqttConfigFile) { 62 | Props props = Props.getProp(mqttConfigFile, CharsetUtil.CHARSET_UTF_8); 63 | return props.toBean(Config.class, BusinessConstants.MQTT_CONFIG_PROPS_PRE); 64 | } 65 | 66 | /** 67 | * 从yaml文件加载配置 68 | * 69 | * @param mqttConfigFile 70 | * @return 71 | */ 72 | public static Config loadFromYamlFile(String mqttConfigFile) { 73 | YamlWrapperConfig yamlWrapperConfig = YamlUtil.loadByPath(mqttConfigFile, YamlWrapperConfig.class); 74 | return yamlWrapperConfig.getMqtt(); 75 | } 76 | 77 | public static void main(String[] args) { 78 | Config config = ConfigUtils.loadFromSystemProps(BusinessConstants.MQTT_CONFIG, new Config()); 79 | Console.log(config); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/util/NettyUtils.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.util; 2 | 3 | import cn.hutool.core.util.ObjectUtil; 4 | import cn.hutool.core.util.StrUtil; 5 | import io.netty.channel.Channel; 6 | import io.netty.handler.codec.DecoderResult; 7 | import io.netty.handler.codec.mqtt.MqttMessage; 8 | import io.netty.util.AttributeKey; 9 | import joey.mqtt.broker.exception.MqttException; 10 | 11 | import java.net.InetSocketAddress; 12 | 13 | /** 14 | * netty工具类 15 | * 16 | * @author Joey 17 | * @date 2019-09-01 18 | */ 19 | public final class NettyUtils { 20 | private static final String CLIENT_ID = "clientId"; 21 | 22 | private static final AttributeKey ATTR_CLIENT_ID = AttributeKey.valueOf(CLIENT_ID); 23 | 24 | private static final String USER_NAME = "userName"; 25 | 26 | public static final AttributeKey ATTR_USER_NAME = AttributeKey.valueOf(USER_NAME); 27 | 28 | private NettyUtils() { 29 | 30 | } 31 | 32 | /** 33 | * 设置clientId属性 34 | * 35 | * @param channel 36 | * @param clientId 37 | * @param userName 38 | */ 39 | public static void clientInfo(Channel channel, String clientId, String userName) { 40 | clientId(channel, clientId); 41 | 42 | userName(channel, userName); 43 | } 44 | 45 | /** 46 | * 设置clientId属性 47 | * 48 | * @param channel 49 | * @param clientId 50 | */ 51 | public static void clientId(Channel channel, String clientId) { 52 | channel.attr(NettyUtils.ATTR_CLIENT_ID).set(clientId); 53 | } 54 | 55 | /** 56 | * 获取clientId属性 57 | * 58 | * @param channel 59 | * @return 60 | */ 61 | public static String clientId(Channel channel) { 62 | return channel.attr(NettyUtils.ATTR_CLIENT_ID).get(); 63 | } 64 | 65 | /** 66 | * 设置userName属性 67 | * 68 | * @param channel 69 | * @param userName 70 | */ 71 | public static void userName(Channel channel, String userName) { 72 | channel.attr(ATTR_USER_NAME).set(userName); 73 | } 74 | 75 | /** 76 | * 获取userName属性 77 | * 78 | * @param channel 79 | * @return 80 | */ 81 | public static String userName(Channel channel) { 82 | return channel.attr(ATTR_USER_NAME).get(); 83 | } 84 | 85 | /** 86 | * 获取远程连接ip 87 | * 88 | * @param channel 89 | * @return 90 | */ 91 | public static String getRemoteIp(Channel channel) { 92 | try { 93 | InetSocketAddress socketAddress = (InetSocketAddress) channel.remoteAddress(); 94 | return socketAddress.getAddress().getHostAddress(); 95 | } catch (Throwable t) { 96 | //ignore 97 | } 98 | 99 | return StrUtil.EMPTY; 100 | } 101 | 102 | /** 103 | * 检查消息合法性 104 | * 105 | * @param msg 106 | * @return 107 | */ 108 | public static void checkMessage(MqttMessage msg) { 109 | DecoderResult decoderResult = msg.decoderResult(); 110 | if (ObjectUtil.isNotNull(decoderResult) && decoderResult.isFailure()) { 111 | throw new MqttException("Invalid massage", decoderResult.cause()); 112 | } 113 | 114 | if (ObjectUtil.isNull(msg.fixedHeader())) { 115 | throw new MqttException("Unknown packet, no fixedHeader present, no cause provided"); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /mqtt-broker/src/main/java/joey/mqtt/broker/util/Stopwatch.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.util; 2 | 3 | import joey.mqtt.broker.constant.NumConstants; 4 | 5 | /** 6 | * 简单计时功能实现 7 | * 8 | * @author Joey 9 | * @date 2019-09-17 10 | * 11 | */ 12 | public class Stopwatch { 13 | private Long startTime = NumConstants.LONG_0; 14 | 15 | private Long elapsedMills = NumConstants.LONG_0; 16 | 17 | private volatile Boolean isRunning; 18 | 19 | private Stopwatch() { 20 | } 21 | 22 | public static Stopwatch start() { 23 | Stopwatch stopWatch = new Stopwatch(); 24 | 25 | stopWatch.startTime = System.currentTimeMillis(); 26 | stopWatch.isRunning = true; 27 | 28 | return stopWatch; 29 | } 30 | 31 | public boolean isRunning() { 32 | return this.isRunning; 33 | } 34 | 35 | public Stopwatch stop() { 36 | this.isRunning = false; 37 | this.elapsedMills = System.currentTimeMillis() - startTime; 38 | 39 | return this; 40 | } 41 | 42 | public Stopwatch reset() { 43 | this.elapsedMills = NumConstants.LONG_0; 44 | this.isRunning = false; 45 | 46 | return this; 47 | } 48 | 49 | public long elapsedMills() { 50 | return this.isRunning ? System.currentTimeMillis() - startTime : this.elapsedMills; 51 | } 52 | 53 | public long elapsedSeconds() { 54 | return this.isRunning ? ((System.currentTimeMillis() - startTime) / NumConstants.INT_1000) : (this.elapsedMills / NumConstants.INT_1000); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /mqtt-broker/src/test/java/joey/mqtt/broker/ServerWithPropertiesTest.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker; 2 | 3 | import cn.hutool.core.lang.Console; 4 | import com.alibaba.fastjson.JSONObject; 5 | import joey.mqtt.broker.config.Config; 6 | import joey.mqtt.broker.util.ConfigUtils; 7 | 8 | /** 9 | * 服务properties配置文件-测试 10 | * 11 | * @author Joey 12 | * @date 2022/7/22 13 | */ 14 | public class ServerWithPropertiesTest { 15 | public static void main(String[] args) throws Exception { 16 | Config config = ConfigUtils.loadFromPropertiesFile("config.properties"); 17 | 18 | //用户自定义配置json 可以转换成自己的java对象 19 | JSONObject extConfJsonObj = config.getCustomConfig().convertExtConfig(JSONObject.class); 20 | Console.log(extConfJsonObj); 21 | 22 | MqttServer mqttServer = new MqttServer(config); 23 | mqttServer.start(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mqtt-broker/src/test/java/joey/mqtt/broker/ServerWithYmlTest.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker; 2 | 3 | import cn.hutool.core.lang.Console; 4 | import com.alibaba.fastjson.JSONObject; 5 | import joey.mqtt.broker.config.Config; 6 | import joey.mqtt.broker.util.ConfigUtils; 7 | 8 | /** 9 | * 服务yml配置文件-测试 10 | * 11 | * @author Joey 12 | * @date 2022/7/22 13 | */ 14 | public class ServerWithYmlTest { 15 | public static void main(String[] args) throws Exception { 16 | Config config = ConfigUtils.loadFromYamlFile("config.yml"); 17 | 18 | //用户自定义配置json 可以转换成自己的java对象 19 | JSONObject extConfJsonObj = config.getCustomConfig().convertExtConfig(JSONObject.class); 20 | Console.log(extConfJsonObj); 21 | 22 | MqttServer mqttServer = new MqttServer(config); 23 | mqttServer.start(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /mqtt-broker/src/test/resources/config.properties: -------------------------------------------------------------------------------- 1 | #server config 2 | #tcp端口配置 3 | #-1表示不开启 4 | mqtt.serverConfig.tcpPort=1883 5 | #-1表示不开启 1888 6 | mqtt.serverConfig.tcpSslPort=-1 7 | 8 | #webSocket配置 9 | mqtt.serverConfig.webSocketPath=/joMqtt 10 | #-1表示不开启 2883 11 | mqtt.serverConfig.webSocketPort=-1 12 | #-1表示不开启 2888 13 | mqtt.serverConfig.webSocketSslPort=-1 14 | 15 | mqtt.serverConfig.enableClientCA=false 16 | 17 | mqtt.serverConfig.hostname= 18 | 19 | #provider配置 默认有如下3中实现 20 | #支持集群间通信 支持消息持久化 21 | #mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.RedisExtendProvider 22 | 23 | #不支持集群间通信 不支持消息持久化 24 | mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.MemoryExtendProvider 25 | 26 | #hazelcastProvider相关配置 支持集群间通信 不支持消息持久化 27 | #mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.HazelcastExtendProvider 28 | #mqtt.customConfig.hazelcastConfigFile=classpath:hazelcast/hazelcast-local.xml 29 | #mqtt.customConfig.hazelcastConfigFile=file:/home/hazelcast-local.xml 30 | 31 | #password 采用sha256hex加密 例子中密码明文和用户名一致 32 | mqtt.serverConfig.enableUserAuth=true 33 | mqtt.serverConfig.authUsers[0].userName=local 34 | mqtt.serverConfig.authUsers[0].password=25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6 35 | mqtt.serverConfig.authUsers[1].userName=admin 36 | mqtt.serverConfig.authUsers[1].password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 37 | 38 | #netty config 39 | mqtt.nettyConfig.bossThreads=0 40 | mqtt.nettyConfig.workerThreads=0 41 | mqtt.nettyConfig.epoll=false 42 | mqtt.nettyConfig.soBacklog=1024 43 | mqtt.nettyConfig.soReuseAddress=true 44 | mqtt.nettyConfig.tcpNoDelay=true 45 | mqtt.nettyConfig.soSndBuf=65536 46 | mqtt.nettyConfig.soRcvBuf=65536 47 | mqtt.nettyConfig.soKeepAlive=true 48 | mqtt.nettyConfig.channelTimeoutSeconds=200 49 | 50 | #如果使用了RedisExtendProvider 则必须配置redisConfig 51 | mqtt.customConfig.redisConfig.host=localhost 52 | mqtt.customConfig.redisConfig.password= 53 | mqtt.customConfig.redisConfig.port=6379 54 | mqtt.customConfig.redisConfig.database=0 55 | mqtt.customConfig.redisConfig.timeout=3000 56 | mqtt.customConfig.redisConfig.pool.maxActive=50 57 | mqtt.customConfig.redisConfig.pool.maxWait=1000 58 | mqtt.customConfig.redisConfig.pool.maxIdle=50 59 | mqtt.customConfig.redisConfig.pool.minIdle=20 60 | 61 | # 如果开启ssl 则必须配置如下信息 62 | mqtt.customConfig.sslContextConfig.sslProvider=JDK 63 | mqtt.customConfig.sslContextConfig.jksFilePath: ssl/server.jks 64 | mqtt.customConfig.sslContextConfig.keyStoreType: JKS 65 | mqtt.customConfig.sslContextConfig.keyStorePassword: 123456 66 | mqtt.customConfig.sslContextConfig.keyManagerPassword: mqtt 67 | 68 | #自定义节点名称 可以不配置 默认是UUID 69 | #mqtt.customConfig.nodeName=jo_mqtt_1 70 | 71 | #用户自定义扩展配置 72 | mqtt.customConfig.extConfig.k1=v1 73 | mqtt.customConfig.extConfig.k2=v2 74 | mqtt.customConfig.extConfig.k3.k31=v31 75 | mqtt.customConfig.extConfig.k3.k32=v32 -------------------------------------------------------------------------------- /mqtt-broker/src/test/resources/config.yml: -------------------------------------------------------------------------------- 1 | mqtt: 2 | serverConfig: # server配置 3 | tcpPort: 1883 # tcp端口配置 -1表示不开启 4 | tcpSslPort: -1 # tcp-ssl端口配置 -1表示不开启 5 | webSocketPath: /joMqtt # webSocket连接路径 6 | webSocketPort: -1 # webSocket端口配置 -1表示不开启 7 | webSocketSslPort: -1 # webSocket-ssl端口配置 -1表示不开启 8 | enableClientCA: false # 是否开启客户端认证 9 | hostname: '' 10 | # provider配置 默认有如下3中实现 11 | extendProviderClass: joey.mqtt.broker.provider.RedisExtendProvider # 支持集群间通信 支持消息持久化 12 | # extendProviderClass: joey.mqtt.broker.provider.MemoryExtendProvider # 不支持集群间通信 不支持消息持久化 13 | # hazelcastProvider相关配置 支持集群间通信 不支持消息持久化 14 | # 需要配置mqtt.customConfig.hazelcastConfigFile和mqtt.customConfig.hazelcastConfigFile 15 | # extendProviderClass: joey.mqtt.broker.provider.HazelcastExtendProvider 16 | 17 | enableUserAuth: true # 是否开启用户授权 18 | authUsers: # 用户授权配置 password采用sha256hex加密 例子中密码明文和用户名一致 19 | - password: 25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6 20 | userName: local 21 | - password: 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 22 | userName: admin 23 | nettyConfig: # netty配置 24 | bossThreads: 0 25 | workerThreads: 0 26 | epoll: false 27 | soBacklog: 1024 28 | soReuseAddress: true 29 | tcpNoDelay: true 30 | soSndBuf: 65536 31 | soRcvBuf: 65536 32 | soKeepAlive: true 33 | channelTimeoutSeconds: 200 34 | customConfig: # 自定义配置 35 | redisConfig: # 如果使用了RedisExtendProvider 则必须配置redisConfig 36 | host: localhost 37 | password: '' 38 | port: 6379 39 | database: 0 40 | timeout: 3000 41 | pool: 42 | maxActive: 50 43 | maxWait: 1000 44 | maxIdle: 50 45 | minIdle: 20 46 | # 如果开启ssl 则必须配置如下信息 47 | sslContextConfig: 48 | sslProvider: JDK # 目前支持3种 JDK,OPENSSL,OPENSSL_REFCNT 49 | jksFilePath: ssl/server.jks 50 | keyStoreType: JKS 51 | keyStorePassword: 123456 52 | keyManagerPassword: mqtt 53 | # nodeName: jo_mqtt_1 # 自定义节点名称 可以不配置 默认是UUID 54 | extConfig: # 用户扩展配置 55 | k1: v1-测试 56 | k2: v2 57 | k3: 58 | k31: v31 59 | k32: v32-中文 60 | -------------------------------------------------------------------------------- /mqtt-springboot/.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/** 5 | !**/src/test/** 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | 30 | ### VS Code ### 31 | .vscode/ 32 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/MqttApplication.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableScheduling; 6 | 7 | /** 8 | * spring-boot启动类 9 | * 10 | * @author Joey 11 | * @date 2019/09/02 12 | */ 13 | @EnableScheduling 14 | @SpringBootApplication 15 | public class MqttApplication { 16 | public static void main(String[] args) { 17 | SpringApplication.run(MqttApplication.class, args); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/config/MqttConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.config; 2 | 3 | import joey.mqtt.broker.config.CustomConfig; 4 | import joey.mqtt.broker.config.NettyConfig; 5 | import joey.mqtt.broker.config.ServerConfig; 6 | import joey.mqtt.broker.constant.BusinessConstants; 7 | import lombok.Data; 8 | import org.springframework.boot.context.properties.ConfigurationProperties; 9 | import org.springframework.context.annotation.Configuration; 10 | 11 | /** 12 | * mqtt相关配置 13 | * 14 | * @author Joey 15 | * @date 2019/9/7 16 | */ 17 | @Data 18 | @Configuration 19 | @ConfigurationProperties(prefix = BusinessConstants.MQTT_CONFIG_PROPS_PRE) 20 | public class MqttConfig { 21 | private ServerConfig serverConfig; 22 | 23 | private NettyConfig nettyConfig; 24 | 25 | private CustomConfig customConfig; 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/config/MqttServerBeanConfig.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.config; 2 | 3 | import joey.mqtt.broker.constant.BusinessConstants; 4 | import joey.mqtt.broker.MqttServer; 5 | import joey.mqtt.broker.config.Config; 6 | import joey.mqtt.broker.util.ConfigUtils; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | import javax.annotation.Resource; 11 | 12 | /** 13 | * @author Joey 14 | * @date 2019/9/9 15 | */ 16 | @Configuration 17 | public class MqttServerBeanConfig { 18 | @Resource 19 | private MqttConfig mqttConfig; 20 | 21 | @Bean 22 | public MqttServer mqttServer() throws Exception { 23 | //读取配置文件 优先级:命令行启动配置>jar包配置文件 24 | Config config = ConfigUtils.loadFromSystemProps(BusinessConstants.MQTT_CONFIG, new Config(mqttConfig.getServerConfig(), mqttConfig.getNettyConfig(), mqttConfig.getCustomConfig())); 25 | return new MqttServer(config); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/controller/ClientController.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.controller; 2 | 3 | import com.alibaba.fastjson.JSON; 4 | import com.alibaba.fastjson.JSONObject; 5 | import joey.mqtt.broker.MqttServer; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.RestController; 10 | 11 | import javax.annotation.Resource; 12 | 13 | /** 14 | * 用户controller 15 | * 16 | * @author Joey 17 | * @date 2019/9/20 18 | */ 19 | @RestController() 20 | @RequestMapping("/api/client") 21 | public class ClientController { 22 | @Resource 23 | private MqttServer mqttServer; 24 | 25 | @GetMapping("/info") 26 | public Object getInfo(@RequestParam String clientId) { 27 | JSONObject info = new JSONObject(); 28 | 29 | info.put("clientSession", mqttServer.getClientInfoFor(clientId)); 30 | info.put("clientSubSet", mqttServer.getClientSubInfoFor(clientId)); 31 | 32 | return JSON.toJSONString(info); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/controller/StatsController.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.controller; 2 | 3 | import joey.mqtt.broker.MqttServer; 4 | import org.springframework.web.bind.annotation.GetMapping; 5 | import org.springframework.web.bind.annotation.RequestMapping; 6 | import org.springframework.web.bind.annotation.RestController; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | * 统计controller 12 | * 13 | * @author Joey 14 | * @date 2019/9/20 15 | */ 16 | @RestController() 17 | @RequestMapping("/api/stats") 18 | public class StatsController { 19 | @Resource 20 | private MqttServer mqttServer; 21 | 22 | @GetMapping("/sessionCount") 23 | public Object getSessionCount() { 24 | return mqttServer.getSessionCount(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/runner/MqttServerRunner.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.runner; 2 | 3 | import joey.mqtt.broker.MqttServer; 4 | import joey.mqtt.broker.util.Stopwatch; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.boot.CommandLineRunner; 7 | import org.springframework.stereotype.Component; 8 | 9 | import javax.annotation.Resource; 10 | 11 | /** 12 | * mqtt-server 启动 13 | * 14 | * @author Joey 15 | * @date 2019/8/28 16 | */ 17 | @Component 18 | @Slf4j 19 | public class MqttServerRunner implements CommandLineRunner { 20 | @Resource 21 | private MqttServer mqttServer; 22 | 23 | @Override 24 | public void run(String... args) throws Exception { 25 | Stopwatch start = Stopwatch.start(); 26 | 27 | try { 28 | mqttServer.start(); 29 | } catch (Throwable t) { 30 | log.error("MqttServerRunner start error.", t); 31 | } 32 | 33 | log.info("MqttServer-start. timeCost={}ms", start.elapsedMills()); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/java/joey/mqtt/springboot/task/StatsTask.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot.task; 2 | 3 | import joey.mqtt.broker.MqttServer; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.scheduling.annotation.Scheduled; 6 | import org.springframework.stereotype.Component; 7 | 8 | import javax.annotation.Resource; 9 | 10 | /** 11 | * 统计任务 12 | * @author Joey 13 | * @date 2021-04-08 14 | */ 15 | @Component 16 | @Slf4j 17 | public class StatsTask { 18 | @Resource 19 | private MqttServer mqttServer; 20 | 21 | /** 22 | * 打印当前连接session数量 23 | * TODO 暂时注释掉 24 | */ 25 | @Scheduled(cron = "0 0/1 * * * ?") 26 | public void statSessionCount() { 27 | int sessionCount = mqttServer.getSessionCount(); 28 | log.info("StatsTask-statSessionCount count={}.", sessionCount); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/application-local.yml: -------------------------------------------------------------------------------- 1 | logging: 2 | config: classpath:log4j2-local.xml 3 | 4 | mqtt: 5 | serverConfig: # server配置 6 | tcpPort: 1883 # tcp端口配置 -1表示不开启 7 | tcpSslPort: -1 # tcp-ssl端口配置 -1表示不开启 8 | webSocketPath: /joMqtt # webSocket连接路径 9 | webSocketPort: -1 # webSocket端口配置 -1表示不开启 10 | webSocketSslPort: -1 # webSocket-ssl端口配置 -1表示不开启 11 | enableClientCA: false # 是否开启客户端认证 12 | hostname: '' 13 | # provider配置 默认有如下3中实现 14 | extendProviderClass: joey.mqtt.broker.provider.MemoryExtendProvider # 不支持集群间通信 不支持消息持久化 15 | # extendProviderClass: joey.mqtt.broker.provider.RedisExtendProvider # 支持集群间通信 支持消息持久化 16 | # extendProviderClass: joey.mqtt.broker.provider.RedisWithHzInnerTrafficExtendProvider # 支持集群间通信(hazelcast广播实现) 支持消息持久化(redis实现) 17 | # hazelcastProvider相关配置 支持集群间通信 不支持消息持久化 18 | # 需要配置mqtt.customConfig.hazelcastConfigFile和mqtt.customConfig.hazelcastConfigFile 19 | # extendProviderClass: joey.mqtt.broker.provider.HazelcastExtendProvider 20 | 21 | enableUserAuth: true # 是否开启用户授权 22 | authUsers: # 用户授权配置 password采用sha256hex加密 例子中密码明文和用户名一致 23 | - password: 25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6 24 | userName: local 25 | - password: 8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 26 | userName: admin 27 | nettyConfig: # netty配置 28 | bossThreads: 1 29 | workerThreads: 0 30 | epoll: false 31 | soBacklog: 1024 32 | soReuseAddress: true 33 | tcpNoDelay: true 34 | soSndBuf: 65536 35 | soRcvBuf: 65536 36 | soKeepAlive: true 37 | channelTimeoutSeconds: 200 38 | customConfig: # 自定义配置 39 | redisConfig: # 如果使用了RedisExtendProvider 则必须配置redisConfig 40 | host: localhost 41 | password: '' 42 | port: 6379 43 | database: 0 44 | timeout: 3000 45 | pool: 46 | testOnBorrow: true 47 | maxActive: 300 48 | maxWait: 3000 49 | maxIdle: 80 50 | minIdle: 50 51 | # 如果开启ssl 则必须配置如下信息 52 | sslContextConfig: 53 | sslProvider: JDK # 目前支持3种 JDK,OPENSSL,OPENSSL_REFCNT 54 | jksFilePath: ssl/server.jks 55 | keyStoreType: JKS 56 | keyStorePassword: 123456 57 | keyManagerPassword: mqtt 58 | # nodeName: jo_mqtt_1 # 自定义节点名称 可以不配置 默认是UUID 59 | extConfig: # 用户扩展配置 60 | k1: v1-测试 61 | k2: v2 62 | k3: 63 | k31: v31 64 | k32: v32-中文 65 | 66 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/application-prod.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/application-prod.yml -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/application-test.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/application-test.yml -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 7788 3 | tomcat: 4 | threads: 5 | min-spare: 20 6 | 7 | spring: 8 | profiles: 9 | active: local 10 | application: 11 | name: mqtt-springboot 12 | main: 13 | allow-bean-definition-overriding: true 14 | mvc: 15 | throw-exception-if-no-handler-found: true 16 | web: 17 | resources: 18 | add-mappings: false 19 | 20 | management: 21 | endpoints: 22 | web: 23 | base-path: / 24 | exposure: 25 | include: info,health,scheduledtasks,prometheus 26 | path-mapping: 27 | prometheus: metrics 28 | metrics: 29 | # 参考文档:https://cloud.tencent.com/document/product/1416/56031 30 | # 下面选项建议打开,以监控 http 请求的 P99/P95 等,具体的时间分布可以根据实际情况设置 31 | distribution: 32 | sla: 33 | http: 34 | server: 35 | # requests: 1ms,5ms,10ms,50ms,100ms,200ms,500ms,1s,5s 36 | requests: 10ms,50ms,100ms,200ms,500ms,1s,5s 37 | # 在 Prometheus 中添加特别的 Labels 38 | tags: 39 | # 必须加上对应的应用名,因为需要以应用的维度来查看对应的监控 40 | application: ${spring.application.name} 41 | endpoint: 42 | health: 43 | show-details: always 44 | info: 45 | git: 46 | mode: full -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ## ####### ## ## ####### ######## ######## 2 | ## ## ## ### ### ## ## ## ## 3 | ## ## ## #### #### ## ## ## ## 4 | ## ## ## ####### ## ### ## ## ## ## ## 5 | ## ## ## ## ## ## ## ## ## ## ## 6 | ## ## ## ## ## ## ## ## ## ## 7 | ###### ####### ## ## ##### ## ## ## 8 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/hazelcast/hazelcast-local.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | slf4j 9 | 10 | 11 | 12 | local 13 | local!@#$ 14 | 15 | 16 | 17 | 18 | 19 | 224.2.2.4 20 | 48888 21 | 40 22 | 5 23 | 24 | 10.10.2.* 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/hazelcast/hazelcast-prod.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | slf4j 9 | 10 | 11 | 12 | prod 13 | prod!@#$ 14 | 15 | 16 | 17 | 18 | 19 | 224.2.2.4 20 | 48888 21 | 40 22 | 5 23 | 24 | 10.100.1.* 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/hazelcast/hazelcast-test.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | slf4j 9 | 10 | 11 | 12 | test 13 | test!@#$ 14 | 15 | 16 | 17 | 18 | 19 | 224.2.2.4 20 | 48888 21 | 40 22 | 5 23 | 24 | 172.16.32.* 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/log4j2-local.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/log4j2-spring.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/ca.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/ca.jks -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEAq8o0DpHqS9AbwvFlCYxo9pWgNwLocL0aNSY5wtJULdUQYABm 3 | OO0U/VboEdcl9evJgmkZxCdJGqhQeqRTUDDmDyhxzu3Atagv3ffTwTKfiKCpQQYR 4 | N4oQ7E/dLUUKYbqA3jE1NYqvcJ0VFf2j9oUUkvAODQ+mh9vjQruplS9TRoSt50iy 5 | rPb2/LN1nk9TilEzygYxo3GGgPvrxS9eqvPb8uIlQ74G0D1/u5KWZxM53KW1PdfJ 6 | I8TmicLpOydQVdTZGAYV7c6ErcfyF1QCn+TMaiMF0RBNT0GjPCDQ5kZe57G/CHlb 7 | R2iLXGlhLPYYFpDRy3iE2RQqExW8ysERdL1eswIDAQABAoIBAHEY3BDDi6aByeUb 8 | m1Mf93Jvq1zMM08VkA1xrxFN+1F5NiCd4q6Tlv/6mSo1pK40nyOWIfp7iqtcKC+1 9 | w4vFxZAxJkv/RqHRqkHOCMDmnRUMAKHoZqlT4jRRl+FX1K/mYzTk3Iz1Uu8m21zw 10 | 8WM41gSuufFrXk2PPB0RA2JOF9sSJX/FTJu22ULvu8/Aa1Bd/lBb8E8e1wGqm/Hs 11 | H7XwXGHPyHoRs7KLhi4L7Hhb/TuGoO/cHKmJHPKcbq2ESHFXGB+BthturEdJ9F/Q 12 | jmo1VF1xk4TLczkX6lWwQUzm5ogtvaBQf9b2kOwP2tfdIJJ1jcI6oujOYPE+MNHJ 13 | 3JKIwYECgYEA49W7X4+LlmSrUrP5sb1qWD+JACmHxBl7ZafNxSMo263ttXD22VMB 14 | 1fz6t76/mrqpXGpdr1kJ1oyizdH6xf3ePFkRcLaeDdjWIzEcWV3tcgadimAtOhne 15 | DQm78OiV43ZDlZcXzx7dGR5U65T83t1PUIlpz6oDTNuDx3Z0esE55eECgYEAwQbR 16 | 4FKMwKTjrXAweJtBdKebvvrrwJLmmnmPriR68k0ePc5f2xjqrjV8JWevo2hbIqPQ 17 | annA+B1Y3qFGIxgEOf/qNHmseQcCO9Mwii+oTkGkctLUMbDa6NzO2bfvkaCcUw8c 18 | vliE8anKCbomVox23jwcIlM3TUO6DjO1jyndLxMCgYEAwu8W3z9JNkcx0pQMWsfu 19 | 2kyaIlpmQCFxU9vMEhTwG21oCir5+Z2s33MQ7O+2rmNxDpIvUB0Fbt1rWmCDiK2a 20 | XNX0NxT4jG5vYTLex5O22i3Q0xlQ+Poy48LlW80UmcaRMsdQ5rTIhXpPPDWAWgrO 21 | luDLeJNFOZhD2wB9zp3OtOECgYBR4eu8JiiRPCFdHJ9jOjEtJEC8ZkMF4Qsz8mdR 22 | 7yW8jZxLibU+AzbiicvNLkvinM00R5uM33NNjV16q51OG80HMZmPgyH4AVIgQLlx 23 | lT/nWo9BKlBbd/OVHr4cu5tEAobt8RY3ZOOYhHcxYycKRfHS2lrw8K4f96AAkEzv 24 | m0m5wwKBgAHUsfS4FnaqoXlk4U5uRlERpWUJuN5WequUN4Ebfr5CFoVfVDzqg8tU 25 | bmBNyrRHaQsA/6sx/+a05KnOHODemea85CuwLZYR4D9XwMNYBp2+CwMGAwWHsKdM 26 | vIfaG0xPCzd7czBvVwLpeUH/7nkpRtDViNLEZ6Lflf7K9E71O4OJ 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/ca.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/ca.p12 -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/ca.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDKjCCAhICCQD0MBrzml8jWzANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJC 3 | SjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQswCQYDVQQKDAJCSjELMAkGA1UE 4 | CwwCQkoxFDASBgNVBAMMC2NhLW1xdHQuY29tMB4XDTIyMDgxMjA0MzgyNloXDTMy 5 | MDgwOTA0MzgyNlowVzELMAkGA1UEBhMCQkoxCzAJBgNVBAgMAkJKMQswCQYDVQQH 6 | DAJCSjELMAkGA1UECgwCQkoxCzAJBgNVBAsMAkJKMRQwEgYDVQQDDAtjYS1tcXR0 7 | LmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKvKNA6R6kvQG8Lx 8 | ZQmMaPaVoDcC6HC9GjUmOcLSVC3VEGAAZjjtFP1W6BHXJfXryYJpGcQnSRqoUHqk 9 | U1Aw5g8occ7twLWoL93308Eyn4igqUEGETeKEOxP3S1FCmG6gN4xNTWKr3CdFRX9 10 | o/aFFJLwDg0Ppofb40K7qZUvU0aEredIsqz29vyzdZ5PU4pRM8oGMaNxhoD768Uv 11 | Xqrz2/LiJUO+BtA9f7uSlmcTOdyltT3XySPE5onC6TsnUFXU2RgGFe3OhK3H8hdU 12 | Ap/kzGojBdEQTU9Bozwg0OZGXuexvwh5W0doi1xpYSz2GBaQ0ct4hNkUKhMVvMrB 13 | EXS9XrMCAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAIB2nv4u1/DD6zZKD2xb8plg/ 14 | E0NOGvIwiUIw88PMXqgpY12WVwEEE0vNLtzAPl6whtaMpY9I89MWyT/hiq3SJXGp 15 | tz1O8ey4HwTTGdpTFxN1wL8AC9v5cq/VHXUi195LFBNKbAtwbO8MtT5P1qQL1sKL 16 | Xep3E2H+x8dII52razF7Bjk16ethx+0C3cW2KDN3Fjn5NMYyC9Yv6aqzhR4BvC4O 17 | t6EdqM9vYMT/PU5GpynybQ/mSwgvWPEn3Xeou2DhmFUPIUG+ROO/xm3cHMSPNvMf 18 | 8xNB83Qj1QX29SEIYHXt1cy7d/sve7unQZ7+NS9MVa/QY3ilxzzEtxSZtERmaA== 19 | -----END CERTIFICATE----- 20 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/ca.srl: -------------------------------------------------------------------------------- 1 | 8338297DEFCD1D75 2 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/client.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICoDCCAYgCAQAwWzELMAkGA1UEBhMCQkoxCzAJBgNVBAgMAkJKMQswCQYDVQQH 3 | DAJCSjELMAkGA1UECgwCQkoxCzAJBgNVBAsMAkJKMRgwFgYDVQQDDA9jbGllbnQt 4 | bXF0dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDep8dtzyTU 5 | o4dmzvWcFDziJ5MnBtq4J8T6gI+smpi13RgK6pwctEEPpXzzyiRiqAE7/x7Avmmy 6 | ZtBWBcdKhARmGTPXtmovrDg/BB8NaLV/HCOehCdjm8oE/Ha+EbefQV6oK9xeZgFP 7 | 199ol1ZeFL2y2Psg2iSiWYWxFkxzAwKsTTQiJ6PSgJNIzwA23HcL70P+nzdogrAh 8 | gU6LRlmia717+PLY5o6MeSJJZur+yiXAuLmSrdGN9QLgWExRFlHOejLW/4jIiqfR 9 | OrllnXOVn5CBnpkLnL87maSwcIa5KVt86i5SVo8QUuxxV+SJCDGXNWMrjLgx+kJe 10 | N4K2iwEjlTQjAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAZY7M21Vzuc999kAV 11 | MplXtPDZl0LptnZh554peQ10pHnvlDwlvgRT6FClOPgT4lj/UiyW7jsb8LMYSjVa 12 | JwG9mCQ+sWc2rfBe3ui2ANfJdD1DEBIxyqosduPk4mktqV+JMgoHmnXMiHEAyGJ9 13 | i+2nwNJ2DTYz/bOYJTjNraO1qjuG46qVLHOt9tHt5oqra7DWd1VzhoIOMpvvLJV3 14 | 58/z9514XNQwnupUxGD8r9r7r82hsw5vwpe+z8TLGRBw3SMrLO326ilvKOjQHELt 15 | pnrJrAf0E8J2jTyML+Gb4qdbEo2TcKLwzJjS92l5Xu1PYBpRJYixHth/3/gqUdzi 16 | VdEjgA== 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/client.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/client.jks -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/client.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA3qfHbc8k1KOHZs71nBQ84ieTJwbauCfE+oCPrJqYtd0YCuqc 3 | HLRBD6V888okYqgBO/8ewL5psmbQVgXHSoQEZhkz17ZqL6w4PwQfDWi1fxwjnoQn 4 | Y5vKBPx2vhG3n0FeqCvcXmYBT9ffaJdWXhS9stj7INokolmFsRZMcwMCrE00Iiej 5 | 0oCTSM8ANtx3C+9D/p83aIKwIYFOi0ZZomu9e/jy2OaOjHkiSWbq/solwLi5kq3R 6 | jfUC4FhMURZRznoy1v+IyIqn0Tq5ZZ1zlZ+QgZ6ZC5y/O5mksHCGuSlbfOouUlaP 7 | EFLscVfkiQgxlzVjK4y4MfpCXjeCtosBI5U0IwIDAQABAoIBAQC9P65qz15mvngg 8 | ElkAyNLXBvw0d8cAMA1lPlDgTKbwLXR6Et+OJkgGnIqQTIiQsmwaKsZg3XfJg0oh 9 | U9gIph13CoTeHNY0nxVPupkKNK3P1IMSARHZwFj90/i5pSWF33ItTtqIAfurebkS 10 | lU97t/VlRrhtO/1XYPAZjPJ971keSQpPR+RfxSU8CmKPSsARhJ2tsGUh4irYmc8f 11 | 2syK/n621F6M8tzLoPCzKs+UPAh/SF8ZnNgn8uAHTvZDlv4fq0D5mclOjFCn4mXW 12 | +AEZW1RvyVNw8/2XkzXVd2h1+neM1WaH0IAIk+x5CAd5RTd1Cl4MSPDKjfsrrJNE 13 | RNOatuABAoGBAPvXxDL3mZ2qZ9/EKLvWYMVwb/CchGEsdyRtCCRhaYq8xmVrZ62S 14 | fyWj9Fbk6zxR8bs0vfB4yH054fTRZORlr+9i44FB6ILWdLiCucYO22y2jxGI/1Cp 15 | tIquY7sQoWEf2fyi0SN02s0Nq2jgcMFBogcB6Bvavn93AZELQWt2/jYjAoGBAOJU 16 | rDhiae7Px+/7qroDWY4rlncPUJGG+neAno0GIfS8PfY9qCijztc8k1L+R6LA8d6z 17 | 1yodP5qF7eDhd0GNzo/nblWm0HkUkajTdSjeit5wS9jcxGkJ5Qo7faOh5l33Fsvk 18 | 32xyrYUiW2mG7eiVr81xtWOrLOG0jHVvByZGqeoBAoGAeUgCg2z7/JLY7UkhOLxX 19 | 5B1uuxgVJFrukYs0dZi35AlfHe5ogo1fT/gyLjqWEnmACWCv7AOuwafDakwA44EZ 20 | BhbAazobSA8RLN4/quLyxtrv8Ujhc64WdQbgnnC21vgo2WJrh0C6Mi/YEWyswFTk 21 | O62uYuOoA+iCx5/9BEzMxbMCgYBFmtq4IHQJsLcBc/lmaX9SPM9yYDBcARHoTJL2 22 | Neudc4Luxrl/fhkvkn6QaIkpYYNBBTfnyMT1xbiJj214qJ7dHMSO7NVyV0QQkq+W 23 | WRKGJCz/Ta3Ny/A3fGJAP5s0TLgjDokztfTtW8qXIprqC5bcbNrnAw6zfy4vM20U 24 | dWjuAQKBgD2/3TNVZUElUk5kvaM49qti/anT8Evcih3jkZTufLrd+7QydjMt5q3m 25 | Uie1wC7Jlgi82wM0dNpDrchkmDo3coLtInUogSyLJfzaX0uux9OAWPAAbtgHREEi 26 | M+/r+UKeUF/vWyAZooo1+9ZIBkkGXDB3iaMTcAX4vb56JNoRMUea 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/client.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/client.p12 -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/client.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLjCCAhYCCQCDOCl9780ddTANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJC 3 | SjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQswCQYDVQQKDAJCSjELMAkGA1UE 4 | CwwCQkoxFDASBgNVBAMMC2NhLW1xdHQuY29tMB4XDTIyMDgxMjA0NDM0NFoXDTMy 5 | MDgwOTA0NDM0NFowWzELMAkGA1UEBhMCQkoxCzAJBgNVBAgMAkJKMQswCQYDVQQH 6 | DAJCSjELMAkGA1UECgwCQkoxCzAJBgNVBAsMAkJKMRgwFgYDVQQDDA9jbGllbnQt 7 | bXF0dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDep8dtzyTU 8 | o4dmzvWcFDziJ5MnBtq4J8T6gI+smpi13RgK6pwctEEPpXzzyiRiqAE7/x7Avmmy 9 | ZtBWBcdKhARmGTPXtmovrDg/BB8NaLV/HCOehCdjm8oE/Ha+EbefQV6oK9xeZgFP 10 | 199ol1ZeFL2y2Psg2iSiWYWxFkxzAwKsTTQiJ6PSgJNIzwA23HcL70P+nzdogrAh 11 | gU6LRlmia717+PLY5o6MeSJJZur+yiXAuLmSrdGN9QLgWExRFlHOejLW/4jIiqfR 12 | OrllnXOVn5CBnpkLnL87maSwcIa5KVt86i5SVo8QUuxxV+SJCDGXNWMrjLgx+kJe 13 | N4K2iwEjlTQjAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAFdynJFswq1w+KL6len6 14 | 6FAlSE6+DghdHfHhfuT7HuD9V/6/cSE4K+uhuwhM6+Qx9xlFV3B8fusx3YoPXM38 15 | 1G4JmDCPwHTw/Kw0Dpsj8PX6A8N7o7e+GUBqM69OmgLebmFp0a2t57e43aQpHfQM 16 | 2eVyw8UJ3oazTKGwp4r4t6jpwtTRZGTCd4882AzajtCQS+36nqsDSC/FmC0863kx 17 | vsuPQkJDZRAh7N/RJubtnHw+Mg2DD2whnJP1748PGiVUGLzcBnUmTwNDtx/fViJc 18 | 7X03j84IJHr9HAxj/j6iirK7FCzu/GWciNLWEficaUYEVMtmXe1oyYWKbHrGT8kv 19 | up4= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/server.csr: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE REQUEST----- 2 | MIICoDCCAYgCAQAwWzELMAkGA1UEBhMCQkoxCzAJBgNVBAgMAkJKMQswCQYDVQQH 3 | DAJCSjELMAkGA1UECgwCQkoxCzAJBgNVBAsMAkJKMRgwFgYDVQQDDA9zZXJ2ZXIt 4 | bXF0dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRBghiScYP 5 | qMBhLyz6JaOv3OFmQ+lX1JYVjIpV3WcY4KnfyVweqfKA7ktCSLiU8sQQ3JANCOEl 6 | G2QPCYyw90v7n45aE5uZsyg7Ro/YpDbuLPL33Fuw7gbu7w+0+t9spxe3qwE0eqgO 7 | dp315Pn2zlIbXHDA+sjDC5fuLykGAzIw/EYxVBI2f37pkmYQgRf6FMbQMY/tBZNn 8 | ePxyPOu3GUnQrXjw1/HY2oADoy3SbKbRyCZJMd2TNWG7DRe0PFpjwddXx7Ty4Cdk 9 | wlBatcEV6pPp4hv12pguHc4D/rIn4OeLvgW6ave99j/FB1so8W6HOHekCrNUpu08 10 | iIz0lBqRil4nAgMBAAGgADANBgkqhkiG9w0BAQsFAAOCAQEAVKzuj3/HaonWAAfO 11 | de8+5Et4lRdig9/tFq9OTt+PfTQTku92jtK/6gBkPWmfwRyxvCqx+tVyoWHG19nM 12 | MXIJfsi5Wl1KLRHk4xYisO71VekbQLOqF6IONpD/QO/r2k0m92MsKxAXb9O0AAUO 13 | AmAZchGo6bX+56TUPKfm53Kgj+qN03PawebjnJzhdIAB9sJbyYQDsY6F2S4P5l+K 14 | M+gYu6Jy0crTe3oosHhiw0DSxkL7KC+82fhFTn4gLdt6MB/1+4jcoNlQKu9XqGv2 15 | 6wKCJaJV1v4BMzAK3P3vOh5V2LeXlz/NLQBzKDD99xUzPWX82AdwujIGOAtT12ZF 16 | RDqbrQ== 17 | -----END CERTIFICATE REQUEST----- 18 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/server.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/server.jks -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/server.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA0QYIYknGD6jAYS8s+iWjr9zhZkPpV9SWFYyKVd1nGOCp38lc 3 | HqnygO5LQki4lPLEENyQDQjhJRtkDwmMsPdL+5+OWhObmbMoO0aP2KQ27izy99xb 4 | sO4G7u8PtPrfbKcXt6sBNHqoDnad9eT59s5SG1xwwPrIwwuX7i8pBgMyMPxGMVQS 5 | Nn9+6ZJmEIEX+hTG0DGP7QWTZ3j8cjzrtxlJ0K148Nfx2NqAA6Mt0mym0cgmSTHd 6 | kzVhuw0XtDxaY8HXV8e08uAnZMJQWrXBFeqT6eIb9dqYLh3OA/6yJ+Dni74Fumr3 7 | vfY/xQdbKPFuhzh3pAqzVKbtPIiM9JQakYpeJwIDAQABAoIBAGw95YWQkqJ1TvqE 8 | xZPcfgGKLqcYr/OtwPLzzodcmhEF9rJOwgeXFl+yBwMSSZPIi8P3lBlL6dufbZdj 9 | 6JmT1qM9Iyh+UuurHUBk8ATONSvt1vyH/muHN8vvvICKEb1gYiXYxkz436JtxqsZ 10 | omAuw9IrcrBcri5jCJBtl57ndMxsR8JFCZe20botuglOvdGVFGxBJU7JwBlNYrmM 11 | wZeKYdcsw1meihWjfu4/AYBgePULB/vAwt2Gpda4O8z0YxTabhfqQ8hudIbz5xRW 12 | BFlkML6NMjltHL6D50nap1aL4Kwp8HG0d/x9BSTtjCuIUX6YriMnb3Pb4Ty697hO 13 | qyAa4DECgYEA80iZoAP1IhfJmCtRmqzDbL6DGLdcbajClTuFKEgsZaNNtBNbfRNd 14 | lyVFUFqtoS+YRtMsyUrQltPg+R5Gg48J/dp/Lc2rT+AYZNCR/oyCBLzxazJ/2ghH 15 | fIHi0GK7VC8Z798SEptqhH3T+zlAQ9y7lZ92KFCCM4YLYfXoXzJXEWMCgYEA2/L+ 16 | /9o40z1apGpFcljOicbulfx5vU82m0y/bwnBsFBTF6+vVFl8735T2YURTtItPXkf 17 | O/kHN4OpjvR1rjWLwpnPEfMOwbkSB9b7MAXSRKiWlst51Zwfzi770ICV1sRuKYg2 18 | 9AC60Zs2FP6MZL3P5b7W34FGSMWedciCCZ5tXW0CgYEA006cuMKXORyGKhh+EaW7 19 | PAmhXmds7YZmNC51cs51WXZk+GLT1Shr4uspcCjCpLztO58SSGgmEstHnbkxL/We 20 | jRIp4sO/52nSZkZOeFTcXXcrDZvYNq14Qemi8rK+2NjsY09SqDy7YQbVh2BrtEXV 21 | 3JibjVqYLF5iJpzrTzwvKdcCgYBIen6FGQf4tBMWct6Hm7QTurYMPrJvK5c2/cdn 22 | c7Obwhxfhckk5ohA80P2Pd9CNggqZathO+Kg7IYHUY8l5Qc1DTIrHAkU1UOXycPk 23 | Q0bS0SsubcasGVJxwxG/11+I8hnLHdPd/A+T2q2rpWXmHJZRgt2TsSnT+SsKbRRM 24 | zyQC0QKBgDouNiSprsctnCLBsNt8NgBonR1Z43MGTXkUjjFf4MoG7twTqIIHxjFR 25 | Ngx6WHUmmdZdrkiQO+S7qehtOTrjorlqFuI6mPMJXw5K4P9WAFjp5AoaxTEcQCw7 26 | EgV0ly0QKC0Yhk9dBulSMwQTDKlLQWQWX6fsAlpRCAmrZOLOdwUC 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/server.p12: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-springboot/src/main/resources/ssl/server.p12 -------------------------------------------------------------------------------- /mqtt-springboot/src/main/resources/ssl/server.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDLjCCAhYCCQCDOCl9780ddDANBgkqhkiG9w0BAQsFADBXMQswCQYDVQQGEwJC 3 | SjELMAkGA1UECAwCQkoxCzAJBgNVBAcMAkJKMQswCQYDVQQKDAJCSjELMAkGA1UE 4 | CwwCQkoxFDASBgNVBAMMC2NhLW1xdHQuY29tMB4XDTIyMDgxMjA0NDExOFoXDTMy 5 | MDgwOTA0NDExOFowWzELMAkGA1UEBhMCQkoxCzAJBgNVBAgMAkJKMQswCQYDVQQH 6 | DAJCSjELMAkGA1UECgwCQkoxCzAJBgNVBAsMAkJKMRgwFgYDVQQDDA9zZXJ2ZXIt 7 | bXF0dC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDRBghiScYP 8 | qMBhLyz6JaOv3OFmQ+lX1JYVjIpV3WcY4KnfyVweqfKA7ktCSLiU8sQQ3JANCOEl 9 | G2QPCYyw90v7n45aE5uZsyg7Ro/YpDbuLPL33Fuw7gbu7w+0+t9spxe3qwE0eqgO 10 | dp315Pn2zlIbXHDA+sjDC5fuLykGAzIw/EYxVBI2f37pkmYQgRf6FMbQMY/tBZNn 11 | ePxyPOu3GUnQrXjw1/HY2oADoy3SbKbRyCZJMd2TNWG7DRe0PFpjwddXx7Ty4Cdk 12 | wlBatcEV6pPp4hv12pguHc4D/rIn4OeLvgW6ave99j/FB1so8W6HOHekCrNUpu08 13 | iIz0lBqRil4nAgMBAAEwDQYJKoZIhvcNAQELBQADggEBAJy0e7l859W2MXGJwKDG 14 | xk3Ujf6zZu3N3cI+hdcl5U7/7kF0dbup4eeFvJuPyGSvGqsAcqg0jLTWvvGrS/cH 15 | lSW6nsfyRdaJxcQfDwoNut/2oP/9dTEAVi/j2fasA2BgM94j67pG6qiup/xbovca 16 | 9Y3S6uaSmQuBVNbUTVTnz6YCAWDX/dQOzt6pO+1mDj4IBbHSMc3B0iBxhAkpOxTn 17 | IjPhuhinwMkr1iaBPdP+rsEyzpxFs9GWIKKagjztRMh6HooAEgugNwSeMHJ9jHsp 18 | hzJLDHrirw0BCfDzKEjOSWCBchBA0DGPLYkqJ3qCoR4+d3afaelihQ+GiefNetqL 19 | wow= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /mqtt-springboot/src/test/java/joey/mqtt/springboot/MqttSpringbootApplicationTests.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.springboot; 2 | 3 | import org.springframework.boot.test.context.SpringBootTest; 4 | 5 | @SpringBootTest 6 | public class MqttSpringbootApplicationTests { 7 | 8 | } 9 | -------------------------------------------------------------------------------- /mqtt-test/.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .project 3 | .settings/ 4 | .classpath 5 | *.iml 6 | *.DS_Store 7 | target/ -------------------------------------------------------------------------------- /mqtt-test/mqtt-mock: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-test/mqtt-mock -------------------------------------------------------------------------------- /mqtt-test/mqtt-mock-linux: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-test/mqtt-mock-linux -------------------------------------------------------------------------------- /mqtt-test/mqtt-websocket-ssl.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 49 | 50 | 51 | websocket-测试 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /mqtt-test/mqtt-websocket.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 49 | 50 | 51 | websocket-测试 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /mqtt-test/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | mqtt-parent 7 | joey.mqtt 8 | 1.0.0-SNAPSHOT 9 | 10 | 11 | 4.0.0 12 | mqtt-test 13 | mqtt-test 14 | 1.0.0-SNAPSHOT 15 | 16 | 17 | 18 | joey.mqtt 19 | mqtt-broker 20 | ${mqtt.broker.version} 21 | 22 | 23 | 24 | junit 25 | junit 26 | test 27 | 28 | 29 | 30 | org.slf4j 31 | slf4j-api 32 | 33 | 34 | 35 | org.slf4j 36 | slf4j-simple 37 | 38 | 39 | 40 | org.eclipse.paho 41 | org.eclipse.paho.client.mqttv3 42 | 43 | 44 | 45 | com.hazelcast 46 | hazelcast 47 | 48 | 49 | 50 | cn.hutool 51 | hutool-system 52 | 53 | 54 | 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-maven-plugin 60 | ${springboot.version} 61 | 62 | joey.mqtt.broker.pubsub.PubSubTest 63 | JAR 64 | 65 | 66 | 67 | 68 | repackage 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | org.apache.maven.plugins 77 | maven-compiler-plugin 78 | ${maven.compiler.version} 79 | 80 | ${java.version} 81 | ${java.version} 82 | ${project.reporting.outputEncoding} 83 | 84 | 85 | 86 | 87 | pl.project13.maven 88 | git-commit-id-plugin 89 | 2.1.5 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /mqtt-test/src/main/java/joey/mqtt/broker/pubsub/performance/MqttCounter.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.broker.pubsub.performance; 2 | 3 | import java.util.concurrent.atomic.LongAdder; 4 | 5 | /** 6 | * mqtt数据统计测试 7 | * 8 | * @author Joey 9 | * @date 2019/11/12 10 | */ 11 | public class MqttCounter { 12 | public static LongAdder receiveCount = new LongAdder(); 13 | 14 | public static LongAdder connectCount = new LongAdder(); 15 | 16 | public static LongAdder connectFailCount = new LongAdder(); 17 | 18 | public static LongAdder connectLostCount = new LongAdder(); 19 | 20 | public static void addReceiveCount() { 21 | receiveCount.increment(); 22 | } 23 | 24 | public static void addConnectCount() { 25 | connectCount.increment(); 26 | } 27 | 28 | public static void addConnectFailCount() { 29 | connectFailCount.increment(); 30 | } 31 | 32 | public static void addConnectLostCount() { 33 | connectLostCount.increment(); 34 | } 35 | 36 | public static void clear() { 37 | receiveCount.reset(); 38 | connectCount.reset(); 39 | connectFailCount.reset(); 40 | connectLostCount.reset(); 41 | } 42 | 43 | public static String print(){ 44 | StringBuilder sb = new StringBuilder(); 45 | sb.append(" receiveCount = ").append(receiveCount); 46 | sb.append(" connectCount = ").append(connectCount); 47 | sb.append(" connectFailCount = ").append(connectFailCount); 48 | sb.append(" connectLostCount = ").append(connectLostCount); 49 | return sb.toString(); 50 | } 51 | } -------------------------------------------------------------------------------- /mqtt-test/src/main/resources/ssl/jomqtt-server.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/joey-happy/jo-mqtt/70e28b56386e45a02179f220d6bc18ab0af3e20d/mqtt-test/src/main/resources/ssl/jomqtt-server.pfx -------------------------------------------------------------------------------- /mqtt-test/src/test/java/joey/mqtt/test/BaseTest.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.test; 2 | 3 | import cn.hutool.core.util.IdUtil; 4 | import org.eclipse.paho.client.mqttv3.MqttClient; 5 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 6 | import org.eclipse.paho.client.mqttv3.MqttException; 7 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 8 | 9 | /** 10 | * @author Joey 11 | * @date 2019/9/18 12 | * @desc 基础测试类 13 | */ 14 | public class BaseTest { 15 | protected String serviceUrl = "tcp://localhost:1883"; 16 | 17 | protected String userName = "local"; 18 | 19 | protected String password = "local"; 20 | 21 | protected int connectionTimeout = 30; 22 | 23 | /** 24 | * 构建mqtt client 25 | * 26 | * @param cleanSession 27 | * @return 28 | * @throws MqttException 29 | */ 30 | protected MqttClient buildMqttClient(boolean cleanSession) throws MqttException { 31 | MqttClient client = new MqttClient(serviceUrl, IdUtil.simpleUUID(), new MemoryPersistence()); 32 | 33 | MqttConnectOptions connOpts = new MqttConnectOptions(); 34 | connOpts.setConnectionTimeout(connectionTimeout); 35 | connOpts.setCleanSession(cleanSession); 36 | 37 | connOpts.setUserName(userName); 38 | connOpts.setPassword(password.toCharArray()); 39 | 40 | client.connect(connOpts); 41 | 42 | return client; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /mqtt-test/src/test/java/joey/mqtt/test/TestSuite.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.test; 2 | 3 | import joey.mqtt.test.pubSub.RetainMessageTest; 4 | import joey.mqtt.test.topic.TopicUtilsTest; 5 | import joey.mqtt.test.topic.WildcardTreeTest; 6 | import org.junit.runner.RunWith; 7 | import org.junit.runners.Suite; 8 | 9 | /** 10 | * 测试套件 11 | * 12 | * @author Joey 13 | * @date 2019/9/16 14 | */ 15 | @RunWith(Suite.class) 16 | @Suite.SuiteClasses({ 17 | TopicUtilsTest.class, 18 | WildcardTreeTest.class, 19 | RetainMessageTest.class 20 | }) 21 | public class TestSuite { 22 | } 23 | -------------------------------------------------------------------------------- /mqtt-test/src/test/java/joey/mqtt/test/pubSub/RetainMessageTest.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.test.pubSub; 2 | 3 | import cn.hutool.core.collection.CollUtil; 4 | import cn.hutool.core.util.IdUtil; 5 | import cn.hutool.core.util.ObjectUtil; 6 | import cn.hutool.core.util.StrUtil; 7 | import io.netty.handler.codec.mqtt.MqttQoS; 8 | import joey.mqtt.test.BaseTest; 9 | import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken; 10 | import org.eclipse.paho.client.mqttv3.MqttCallback; 11 | import org.eclipse.paho.client.mqttv3.MqttClient; 12 | import org.eclipse.paho.client.mqttv3.MqttMessage; 13 | import org.junit.Assert; 14 | import org.junit.Test; 15 | 16 | import java.nio.charset.StandardCharsets; 17 | import java.util.List; 18 | 19 | /** 20 | * 保留消息测试 21 | * 22 | * @author Joey 23 | * @date 2019/9/18 24 | */ 25 | public class RetainMessageTest extends BaseTest { 26 | private static final String RETAIN_TOPIC = "test/retain"; 27 | 28 | /** 29 | * 30 | * @throws Exception 31 | */ 32 | @Test 33 | public void testRetainMessage() throws Exception { 34 | final List invokeResult = CollUtil.newArrayList(); 35 | 36 | MqttClient client = buildMqttClient(true); 37 | 38 | MqttMessage message = new MqttMessage(); 39 | message.setQos(MqttQoS.AT_MOST_ONCE.value()); 40 | message.setRetained(true); 41 | message.setPayload(IdUtil.simpleUUID().getBytes(StandardCharsets.UTF_8)); 42 | client.publish(RETAIN_TOPIC, message); 43 | 44 | client.disconnect(); 45 | 46 | client = buildMqttClient(true); 47 | client.setCallback(new MqttCallback() { 48 | @Override 49 | public void connectionLost(Throwable throwable) { 50 | 51 | } 52 | 53 | @Override 54 | public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { 55 | invokeResult.add(ObjectUtil.equal(RETAIN_TOPIC, topic)); 56 | } 57 | 58 | @Override 59 | public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { 60 | 61 | } 62 | }); 63 | 64 | client.subscribe(RETAIN_TOPIC); 65 | 66 | message = new MqttMessage(); 67 | message.setQos(MqttQoS.AT_MOST_ONCE.value()); 68 | message.setRetained(true); 69 | message.setPayload(StrUtil.EMPTY.getBytes(StandardCharsets.UTF_8)); 70 | client.publish(RETAIN_TOPIC, message); 71 | 72 | MqttClient client1 = buildMqttClient(true); 73 | client1.setCallback(new MqttCallback() { 74 | @Override 75 | public void connectionLost(Throwable throwable) { 76 | 77 | } 78 | 79 | @Override 80 | public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception { 81 | invokeResult.add(false); 82 | } 83 | 84 | @Override 85 | public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) { 86 | 87 | } 88 | }); 89 | 90 | client1.subscribe(RETAIN_TOPIC); 91 | 92 | invokeResult.stream().forEach(result -> { 93 | Assert.assertTrue(result); 94 | }); 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /mqtt-test/src/test/java/joey/mqtt/test/pubSub/WillMessageTest.java: -------------------------------------------------------------------------------- 1 | package joey.mqtt.test.pubSub; 2 | 3 | import io.netty.handler.codec.mqtt.MqttQoS; 4 | import joey.mqtt.test.BaseTest; 5 | import org.eclipse.paho.client.mqttv3.MqttClient; 6 | import org.eclipse.paho.client.mqttv3.MqttConnectOptions; 7 | import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence; 8 | import org.junit.Test; 9 | 10 | import java.util.UUID; 11 | import java.util.concurrent.TimeUnit; 12 | 13 | /** 14 | * 遗言消息测试 15 | * 16 | * 参考:https://www.jianshu.com/p/65e1748a930c 17 | * 18 | * @author Joey 19 | * @date 2019/9/18 20 | */ 21 | public class WillMessageTest extends BaseTest { 22 | private static final String WILL_TOPIC = "test/will"; 23 | 24 | /** 25 | * 用客户端工具连接到服务器 26 | * 然后订阅遗言topic 在跑此用例即可 27 | * 28 | * @throws Exception 29 | */ 30 | @Test 31 | public void testWillMessage() throws Exception { 32 | MqttClient client = new MqttClient(serviceUrl, UUID.randomUUID().toString(), new MemoryPersistence()); 33 | 34 | MqttConnectOptions connOpts = new MqttConnectOptions(); 35 | connOpts.setConnectionTimeout(connectionTimeout); 36 | connOpts.setWill(WILL_TOPIC, "I am over!".getBytes(), MqttQoS.AT_MOST_ONCE.value(), false); 37 | connOpts.setCleanSession(false); 38 | 39 | connOpts.setUserName(userName); 40 | connOpts.setPassword(password.toCharArray()); 41 | 42 | client.connect(connOpts); 43 | 44 | TimeUnit.SECONDS.sleep(3); 45 | } 46 | 47 | /** 48 | * 用客户端工具连接到服务器 49 | * 然后订阅遗言topic 在跑此用例即可 50 | * 51 | * @throws Exception 52 | */ 53 | @Test 54 | public void testWillMessageRetain() throws Exception { 55 | MqttClient client = new MqttClient(serviceUrl, UUID.randomUUID().toString(), new MemoryPersistence()); 56 | 57 | MqttConnectOptions connOpts = new MqttConnectOptions(); 58 | connOpts.setConnectionTimeout(connectionTimeout); 59 | connOpts.setWill(WILL_TOPIC, "I am over!".getBytes(), MqttQoS.AT_MOST_ONCE.value(), true); 60 | connOpts.setCleanSession(false); 61 | 62 | connOpts.setUserName(userName); 63 | connOpts.setPassword(password.toCharArray()); 64 | 65 | client.connect(connOpts); 66 | 67 | TimeUnit.SECONDS.sleep(3); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /mqtt-test/src/test/resources/hazelcast-conf.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | slf4j 9 | 10 | 11 | 12 | local 13 | local!@#$ 14 | 15 | 16 | 17 | 18 | 19 | 224.2.2.4 20 | 48888 21 | 40 22 | 5 23 | 24 | 10.10.2.* 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /mqtt-test/src/test/resources/mqtt-conf.properties: -------------------------------------------------------------------------------- 1 | #server config 2 | #tcp端口配置 3 | #-1表示不开启 4 | mqtt.serverConfig.tcpPort=1883 5 | #-1表示不开启 6 | mqtt.serverConfig.tcpSslPort=1888 7 | 8 | #webSocket配置 9 | mqtt.serverConfig.webSocketPath=/joMqtt 10 | #-1表示不开启 11 | mqtt.serverConfig.webSocketPort=2883 12 | #-1表示不开启 13 | mqtt.serverConfig.webSocketSslPort=2888 14 | 15 | mqtt.serverConfig.enableClientCA=false 16 | 17 | mqtt.serverConfig.hostname= 18 | 19 | #provider配置 默认有如下3中实现 20 | #支持集群间通信 支持消息持久化 21 | mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.RedisExtendProvider 22 | 23 | #不支持集群间通信 不支持消息持久化 24 | #mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.MemoryExtendProvider 25 | 26 | #hazelcastProvider相关配置 支持集群间通信 不支持消息持久化 27 | #mqtt.serverConfig.extendProviderClass=joey.mqtt.broker.provider.HazelcastExtendProvider 28 | #mqtt.customConfig.hazelcastConfigFile=classpath:hazelcast/hazelcast-local.xml 29 | #mqtt.customConfig.hazelcastConfigFile=file:/home/hazelcast-local.xml 30 | 31 | #password 采用sha256hex加密 例子中密码明文和用户名一致 32 | mqtt.serverConfig.enableUserAuth=true 33 | mqtt.serverConfig.authUsers[0].userName=local 34 | mqtt.serverConfig.authUsers[0].password=25bf8e1a2393f1108d37029b3df5593236c755742ec93465bbafa9b290bddcf6 35 | mqtt.serverConfig.authUsers[1].userName=admin 36 | mqtt.serverConfig.authUsers[1].password=8c6976e5b5410415bde908bd4dee15dfb167a9c873fc4bb8a81f6f2ab448a918 37 | 38 | #netty config 39 | mqtt.nettyConfig.bossThreads=0 40 | mqtt.nettyConfig.workerThreads=0 41 | mqtt.nettyConfig.epoll=false 42 | mqtt.nettyConfig.soBacklog=1024 43 | mqtt.nettyConfig.soReuseAddress=true 44 | mqtt.nettyConfig.tcpNoDelay=true 45 | mqtt.nettyConfig.soSndBuf=65536 46 | mqtt.nettyConfig.soRcvBuf=65536 47 | mqtt.nettyConfig.soKeepAlive=true 48 | mqtt.nettyConfig.channelTimeoutSeconds=200 49 | 50 | #如果使用了RedisExtendProvider 则必须配置redisConfig 51 | mqtt.customConfig.redisConfig.host=localhost 52 | mqtt.customConfig.redisConfig.password= 53 | mqtt.customConfig.redisConfig.port=6379 54 | mqtt.customConfig.redisConfig.database=0 55 | mqtt.customConfig.redisConfig.timeout=3000 56 | mqtt.customConfig.redisConfig.pool.maxActive=50 57 | mqtt.customConfig.redisConfig.pool.maxWait=1000 58 | mqtt.customConfig.redisConfig.pool.maxIdle=50 59 | mqtt.customConfig.redisConfig.pool.minIdle=20 60 | 61 | # 如果开启ssl 则必须配置如下信息 62 | mqtt.customConfig.sslContextConfig.sslProvider=JDK 63 | mqtt.customConfig.sslContextConfig.jksFilePath: ssl/server.jks 64 | mqtt.customConfig.sslContextConfig.keyStoreType: JKS 65 | mqtt.customConfig.sslContextConfig.keyStorePassword: 123456 66 | mqtt.customConfig.sslContextConfig.keyManagerPassword: mqtt 67 | 68 | #自定义节点名称 可以不配置 默认是UUID 69 | #mqtt.customConfig.nodeName=jo_mqtt_1 --------------------------------------------------------------------------------