EMQ实例01-EMQ的搭建及java环境本地客户端的使用

EMQ实例01-EMQ的搭建及java环境本地客户端的使用

EMQ的安装

环境:

CentOS 7 64位

emqttd-centos7-v2.3.6.zip

Erlang //由于emqttd是用Erlang语言编写的,所以,在Linux下安装时,需要先安装Erlang

安装Erlang命令 #sudo yum -y install make gcc gcc-c++ kernel-devel m4 ncurses-devel openssl-devel

EMQ的安装

安装命令:

mkdir emq
cd emq
wget http://emqtt.com/static/brokers/emqttd-centos7-v2.3.6.zip
unzip emqttd-centos7-v2.3.6.zip

EMQ启动

方式一:
cd emqttd
./bin/emqttd console //会在控制台打印日志 Ctrl+c结束
方式二:
./bin/emqttd start //不会在控制台打印日志
./bin/emqttd status
./bin/emqttd stop
EMQ实例01-EMQ的搭建及java环境本地客户端的使用_第1张图片
注意:EMQ中有两个端口18083和1883需要打开。

java客户端使用

包结构
EMQ实例01-EMQ的搭建及java环境本地客户端的使用_第2张图片
client

@Component // 不加这个注解的话, 使用@Autowired 就不能注入进去了
@PropertySource("classpath:application.yml") //指定要读取的配置文件
@ConfigurationProperties(prefix = "mqtt") // 配置文件中的前缀
public class MqttConfiguration {
    private String host;
    private String username;
    private String password;
    private Integer qos;
    private String[] hosts;
    private Integer connectionTimeout;
    private Integer keepAliveInterval;
    private String publishClientId;
    private String subscribeClientId;
    private boolean retained;
/**
 * 发布消息的回调类
 * 

* 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。 * 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。 * 在回调中,将它用来标识已经启动了该回调的哪个实例。 * 必须在回调类中实现三个方法: *

* public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。 *

* public void connectionLost(Throwable cause)在断开连接时调用。 *

* public void deliveryComplete(MqttDeliveryToken token)) * 接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。 * 由 MqttClient.connect 激活此回调。 */ public class PushCallback implements MqttCallback { @Override public void connectionLost(Throwable throwable) { /** * 连接丢失后,一般在这里面进行重连 */ System.out.println("连接断开,可以做重连"); } @Override public void messageArrived(String topic, MqttMessage message) throws Exception { // subscribe后得到的消息会执行到这里面 System.out.println("Server 接收消息主题 : " + topic); System.out.println("Server 接收消息Qos : " + message.getQos()); System.out.println("Server 接收消息内容 : " + new String(message.getPayload())); } @Override public void deliveryComplete(IMqttDeliveryToken token) { System.out.println("deliveryComplete---------" + token.isComplete()); } }

@RestController
public class MQTTServerController {

    @Autowired
    private IEmqService iEmqService;

    String TOPIC = "MQTT_PRODUCER_TOPIC";

    @RequestMapping("/")
    public String sayHello() {
        return "Hello !";
    }

    @RequestMapping("/send")
    public void send(String msg) throws MqttException {
        System.out.println(Thread.currentThread().getStackTrace()[1].getMethodName() + "==========:" + msg);
        iEmqService.publish(TOPIC, msg);
    }

}

@Service
@Slf4j
@EnableConfigurationProperties({MqttConfiguration.class})
public class IEmqServiceImpl implements IEmqService {

    @Autowired
    private MqttConfiguration mqttConfiguration;

    @Override
    public Boolean publish(String topic, String content) {
        log.info("MQ===public=== 入参:topic:{};content:{}", topic, content);
        MqttMessage message = new MqttMessage(content.getBytes());
        message.setQos(mqttConfiguration.getQos());
        /**
         * Retained为true时MQ会保留最后一条发送的数据,当断开再次订阅即会接收到这最后一次的数据
         */
        message.setRetained(true);
        try {
            MqttClient mqttClient = this.connect(mqttConfiguration.getPublishClientId(), mqttConfiguration.getUsername(),
                    mqttConfiguration.getPassword());
            // 判定是否需要重新连接
            /*String clientId =  UUID.randomUUID().toString() +
                    "[" + InetAddress.getLocalHost().getHostAddress() + "]";*/
            if (mqttClient == null || !mqttClient.isConnected() || !mqttClient.getClientId().equals(mqttConfiguration.getPublishClientId())) {
                mqttClient = this.connect(mqttConfiguration.getPublishClientId(), mqttConfiguration.getUsername(),
                        mqttConfiguration.getPassword());
            }
            mqttClient.publish(topic, message);
            log.info("emq已发topic: {} - message: {}", topic, message);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }

    @Override
    public Boolean subscribe(String topic) {
        return null;
    }

    public MqttClient connect(String clientId, String userName, String password) throws MqttException {
        MemoryPersistence persistence = new MemoryPersistence();
        MqttConnectOptions options = new MqttConnectOptions();
        options.setCleanSession(false);
        options.setUserName(userName);
        options.setPassword(password.toCharArray());
        options.setConnectionTimeout(mqttConfiguration.getConnectionTimeout());
        options.setKeepAliveInterval(mqttConfiguration.getKeepAliveInterval());

        MqttClient client = new MqttClient(mqttConfiguration.getHost(), clientId, persistence);
        client.setCallback(new PushCallback());
        client.connect(options);
        return client;
    }
}
/**
 * 
 * EMQ服务管理接口。
 * 
 *
 * @author zhangs
 * @createDate 2018/04/09
 */
public interface IEmqService {
    /**
     * 发布消息
     *
     * @param topic
     * @param content
     * @return
     */
    Boolean publish(String topic, String content);

    /**
     * 订阅消息
     *
     * @param topic
     * @return
     */
    Boolean subscribe(String topic);
}

receive

@Service
@Slf4j
@EnableConfigurationProperties({MqttConfiguration.class})
public class IMqttWrapperServiceImpl implements IMqttWrapperService {

    @Autowired
    private MqttConfiguration mqttConfiguration;

    @Autowired
    private SubscribeConn subscribeConn;

    @Override
    public Boolean publish(String topic, String content) {
        return true;
    }

    @Override
    public Boolean subscribe(String topic) {
        log.info("MQ===subscribe=== 入参:topic:{}", topic);
        MqttClient mqttClient = subscribeConn.getMqttClient();
        // 判定是否需要重新连接
        if (mqttClient==null || !mqttClient.isConnected() || !mqttClient.getClientId().equals(mqttConfiguration.getSubscribeClientId())) {
            mqttClient = subscribeConn.getConn();
        }
        try {
            // 订阅消息
            int[] qos = {mqttConfiguration.getQos()};
            mqttClient.subscribe(new String[]{topic},qos);
        } catch (MqttException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}
/**
 * 
 * EMQ服务管理接口。
 * 
 *
 * @author zhangs
 * @createDate 2018/04/09
 */
public interface IMqttWrapperService {
    /**
     * 发布消息
     *
     * @param topic
     * @param content
     * @return
     */
    Boolean publish(String topic, String content);

    /**
     * 订阅消息
     *
     * @param topic
     * @return
     */
    Boolean subscribe(String topic);
}
/**
 * @author zhangs
 */
@Component // 不加这个注解的话, 使用@Autowired 就不能注入进去了
@PropertySource("classpath:application.yml")
@ConfigurationProperties(prefix = "mqtt") // 配置文件中的前缀
@Data
public class MqttConfiguration {
    private String host;
    private String username;
    private String password;
    private Integer qos;
    private String[] hosts;
    private Integer connectionTimeout;
    private Integer keepAliveInterval;
    private String publishClientId;
    private String subscribeClientId;
    private boolean retained;
}
/**
 * @author zhangs
 * @createDate 2018/5/21
 *
 * 发布消息的回调类
 *
 * 必须实现MqttCallback的接口并实现对应的相关接口方法CallBack 类将实现 MqttCallBack。
 * 每个客户机标识都需要一个回调实例。在此示例中,构造函数传递客户机标识以另存为实例数据。
 * 在回调中,将它用来标识已经启动了该回调的哪个实例。
 * 必须在回调类中实现三个方法:
 *
 *  public void messageArrived(MqttTopic topic, MqttMessage message)接收已经预订的发布。
 *
 *  public void connectionLost(Throwable cause)在断开连接时调用。
 *
 *  public void deliveryComplete(MqttDeliveryToken token))
 *  接收到已经发布的 QoS 1 或 QoS 2 消息的传递令牌时调用。
 *  由 MqttClient.connect 激活此回调。
 *
 */
@Slf4j
@Component
public class PushCallback implements MqttCallback {
    /**
     * 消息订阅者配置类
     */
    @Autowired
    private SubscribeConn subscribeConn;

    @Autowired
    private QueueHandleUtil queueHandleUtil;

    @Override
    public void connectionLost(Throwable cause) {
        log.info("连接断开,进行重连");
        subscribeConn.getConn();
    }

    @Override
    public void messageArrived(String topic, MqttMessage mqttMessage) throws Exception {
        log.info("接收消息主题:{},接收消息Qos:{},接收消息内容:{}",topic,mqttMessage.getQos(),new String(mqttMessage.getPayload()));
        String content = new String(mqttMessage.getPayload());
        log.info("MQ消费者接收消息:" + content);
//        Thread.sleep(10000);
        queueHandleUtil.advanceDisruptor(content);
//        queueHandleUtil.advanceDisruptor2(content);
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
        log.info("deliveryComplete:{}",iMqttDeliveryToken.isComplete());
    }
}
/**
 * 
 * 消息订阅者配置类
 * 
 *
 * @author zhangs
 * @createDate 2018/8/30
 */
@Data
@Slf4j
@Component
@EnableConfigurationProperties({MqttConfiguration.class})
public class SubscribeConn {
    private MqttClient mqttClient;
    private MqttConnectOptions mqttConnectOptions;

    @Autowired
    private ServiceInfoUtil serviceInfoUtil;
    @Autowired
    private MqttConfiguration mqttConfiguration;
    @Autowired
    private PushCallback pushCallback;

    /**
     * 连接emq服务器
     */
    public MqttClient getConn() {
        try {
            // host为主机名,clientId即连接MQTT的客户端ID,一般以客户端唯一标识符表示,MemoryPersistence设置clientId的保存形式,默认为以内存保存
            String clientId = UUID.randomUUID().toString() + "[" + InetAddress.getLocalHost().getHostAddress() +
                    "-" + serviceInfoUtil.getPort() + "]";
            mqttClient = new MqttClient(mqttConfiguration.getHost(), clientId, new MemoryPersistence());
            // MQTT的连接设置
            mqttConnectOptions = new MqttConnectOptions();
            // 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
            mqttConnectOptions.setCleanSession(true);
            // 设置连接的用户名
            mqttConnectOptions.setUserName(mqttConfiguration.getUsername());
            // 设置连接的密码
            mqttConnectOptions.setPassword(mqttConfiguration.getPassword().toCharArray());
            // 设置超时时间 单位为秒
            mqttConnectOptions.setConnectionTimeout(mqttConfiguration.getConnectionTimeout());
            // 设置会话心跳时间 单位为秒 服务器会每隔1.5*keepAliveInterval秒的时间向客户端发送个消息判断客户端是否在线,
            // 但这个方法并没有重连的机制
            mqttConnectOptions.setKeepAliveInterval(mqttConfiguration.getKeepAliveInterval());
            //配置多个服务器列表,一个挂掉会自动切换到其他正常的服务器,挂掉的服务器正常后,客户端会再次切换回来
            if (mqttConfiguration.getHosts() != null && mqttConfiguration.getHosts().length > 0) {
                mqttConnectOptions.setServerURIs(mqttConfiguration.getHosts());
            }
            // 设置回调
            mqttClient.setCallback(pushCallback);
            /* if(topic.length>0){
                MqttTopic mqttTopic = mqttClient.getTopic(topic[0]);
                //setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
                mqttConnectOptions.setWill(mqttTopic, "close".getBytes(), mqttConfiguration.getQos(), true);
            }*/
//            mqttClient.connect(mqttConnectOptions);
            IMqttToken iMqttToken = mqttClient.connectWithResult(mqttConnectOptions);
            log.info("连接服务器成功...");

        } catch (Exception e) {
            System.out.println(e);
            log.info("连接服务器失败...");
        }
        return mqttClient;
    }
}
/**
 * 
 *      启动MQTT订阅
 * 
 * @author zhangs
 * @createDate 2018/08/30
 */
@Slf4j
@Component
public class MyApplicationRunner implements ApplicationRunner {
    @Autowired
    private IMqttSubscribe iMqttSubscribe;

    String TOPIC = "MQTT_PRODUCER_TOPIC";

    @Override
    public void run(ApplicationArguments applicationArguments) throws Exception {
        log.info(" MQTT Subscribe Server 开始...");
        iMqttSubscribe.subscribe(TOPIC);
    }
}

public interface IMqttSubscribe {
    Boolean subscribe(String topic);
}

`@Service
@Slf4j
public class MqttSubscribeImpl implements IMqttSubscribe {
    @Autowired
    private IMqttWrapperService iMqttWrapperService;

    @Override
    public Boolean subscribe(String topic) {
        return iMqttWrapperService.subscribe(topic);
    }
}

import lombok.Data;

/**
 * 1.建Event类(数据对象)
 * 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
 * 3.监听事件类(处理Event数据)
 * 4.实例化Disruptor,配置参数,绑定事件;
 * 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
 */
@Data
public class ObjectEvent {
    private Object event;
}

/**
 * 1.建Event类(数据对象)
 * 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
 * 3.监听事件类(处理Event数据)
 * 4.实例化Disruptor,配置参数,绑定事件;
 * 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
 */
@Slf4j
public class ObjectEventConsumer implements EventHandler {
    @Override
    public void onEvent(ObjectEvent objectEvent, long l, boolean b) throws Exception {
        log.info("Disruptor===事件消费者:" + JSON.toJSONString(objectEvent.getEvent()));
        Thread.sleep(10000);
    }
}
/**
 * 1.建Event类(数据对象)
 * 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
 * 3.监听事件类(处理Event数据)
 * 4.实例化Disruptor,配置参数,绑定事件;
 * 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
 */
@Slf4j
public class ObjectEventConsumer2 implements EventHandler {
    @Override
    public void onEvent(ObjectEvent objectEvent, long sequence, boolean endOfBatch) throws Exception {
        log.info("Disruptor2===事件消费者:" + JSON.toJSONString(objectEvent.getEvent()));
        Thread.sleep(10000);
    }
}
/**
 * 1.建Event类(数据对象)
 * 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
 * 3.监听事件类(处理Event数据)
 * 4.实例化Disruptor,配置参数,绑定事件;
 * 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
 */
public class ObjectEventFactory implements EventFactory {

    @Override
    public Object newInstance() {
        return new ObjectEvent();
    }
}

public class ObjectEventMain {
public static void main(String[] args) throws Exception{
/**
* 创建缓冲池
/
ExecutorService executor = Executors.newCachedThreadPool();
/
*
* 创建工厂
/
ObjectEventFactory factory = new ObjectEventFactory();
/
*
* 创建bufferSize,即RingBuffer大小(必须是2的N次方)
*/
int ringBufferSize = 1024 * 1024;

    /**
     * BlockingWaitStrategy 是最低效的策略,但其对CPU的消耗最小并且在各种不同部署环境中能提供更加一致的性能表现
     * WaitStrategy BLOCKING_WAIT = new BlockingWaitStrategy();
     * SleepingWaitStrategy 的性能表现跟BlockingWaitStrategy差不多,对CPU的消耗也类似,但其对生产者线程的影响最小,适合用于异步日志类似的场景
     * WaitStrategy SLEEPING_WAIT = new SleepingWaitStrategy();
     * YieldingWaitStrategy 的性能是最好的,适合用于低延迟的系统。在要求极高性能且事件处理线数小于CPU逻辑核心数的场景中,推荐使用此策略;例如,CPU开启超线程的特性
     * WaitStrategy YIELDING_WAIT = new YieldingWaitStrategy();
     */

    /**
     * 创建disruptor
     */
    Disruptor disruptor =
            new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
    /**
     * 连接消费事件方法
     */
    disruptor.handleEventsWith(new ObjectEventConsumer());

    /**
     * 启动
     */
    disruptor.start();

    RingBuffer ringBuffer = disruptor.getRingBuffer();
    /*ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
    ByteBuffer byteBuffer = ByteBuffer.allocate(20);
    for (long l = 0;l < 100;l++) {
        String content = "===Producer:" + l;
        byteBuffer.putLong(0, l);
        producer.onData(byteBuffer);
    }*/

    ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
    for (long l = 0;l < 100;l++) {
        String content = "===Producer:" + l;
        producer.onData(content);
    }
}
/**
 * 1.建Event类(数据对象)
 * 2.建立一个生产数据的工厂类,EventFactory,用于生产数据;
 * 3.监听事件类(处理Event数据)
 * 4.实例化Disruptor,配置参数,绑定事件;
 * 5.建存放数据的核心 RingBuffer,生产的数据放入 RungBuffer。
 *
 * 当用一个简单队列来发布事件的时候会牵涉更多的细节,这是因为事件对象还需要预先创建。
 * 发布事件最少需要两步:获取下一个事件槽并发布事件(发布事件的时候要使用try/finnally保证事件一定会被发布)。
 * 如果我们使用RingBuffer.next()获取一个事件槽,那么一定要发布对应的事件。
 * 如果不能发布事件,那么就会引起Disruptor状态的混乱。
 * 尤其是在多个事件生产者的情况下会导致事件消费者失速,从而不得不重启应用才能会恢复。
 */
@Slf4j
public class ObjectEventProducer {
    private final RingBuffer ringBuffer;

    public ObjectEventProducer(RingBuffer ringBuffer) {
        this.ringBuffer = ringBuffer;
    }

    /**
     * onData用来发布事件,每调用一次就发布一次事件
     */
    public void onData(ByteBuffer byteBuffer) {
        /**
         * 1.可以把ringBuffer看做一个事件队列,那么next就是得到下面一个事件槽
         */
        long sequence = ringBuffer.next();
        try {
            /**
             * 用上面的索引取出一个空的时间用于填充(获取该序号对应的事件对象)
             */
            ObjectEvent event = ringBuffer.get(sequence);
            /**
             * 获取要通过事件传递的业务数据
             */
            event.setEvent(byteBuffer.getLong(0));
        } finally {
            /**
             * 发布事件
             * 最后的 ringBuffer.publish 方法必须包含在 finally 中以确保必须得到调用;
             * 如果某个请求的 sequence 未被提交,将会堵塞后续的发布操作或者其它的 producer。
             */
            ringBuffer.publish(sequence);
        }
    }

    public void onData(String content) {
        long sequence = ringBuffer.next();
        try {
            ObjectEvent event = ringBuffer.get(sequence);
            event.setEvent(content);
        } finally {
            ringBuffer.publish(sequence);
            log.info("Disruptor===publish");
        }
    }
}
@Slf4j
@Component
public class QueueHandleUtil {

    /**
     * 创建缓冲池
     */
    ExecutorService executor = Executors.newCachedThreadPool();

    public void advanceDisruptor(String content) {
        /**
         * 创建工厂
         */
        ObjectEventFactory factory = new ObjectEventFactory();
        /**
         * 创建bufferSize,即RingBuffer大小(必须是2的N次方)
         */
        int ringBufferSize = 1024 * 1024;
        /**
         * 创建disruptor
         */
        Disruptor disruptor =
                new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
        RingBuffer ringBuffer = disruptor.getRingBuffer();
        disruptor.handleEventsWith(new ObjectEventConsumer());
        disruptor.start();
        ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
        log.info("MQ消费者将接收的消息放入Disruptor===content:" + content);
        producer.onData(content);
    }

    public void advanceDisruptor2(String content) {
        /**
         * 创建工厂
         */
        ObjectEventFactory factory = new ObjectEventFactory();
        /**
         * 创建bufferSize,即RingBuffer大小(必须是2的N次方)
         */
        int ringBufferSize = 1024 * 1024;
        /**
         * 创建disruptor
         */
        Disruptor disruptor =
                new Disruptor(factory, ringBufferSize, executor, ProducerType.SINGLE, new YieldingWaitStrategy());
        RingBuffer ringBuffer = disruptor.getRingBuffer();
        disruptor.handleEventsWith(new ObjectEventConsumer2());
        disruptor.start();
        ObjectEventProducer producer = new ObjectEventProducer(ringBuffer);
        log.info("MQ消费者将接收的消息放入Disruptor2===content:" + content);
        producer.onData(content);
    }
}
/**
 * 监听WebServerInitializedEvent事件,用来获取Servlet容器初始化的端口
 */
@Slf4j
@Component
public class ServiceInfoUtil implements ApplicationListener {

    private static WebServerInitializedEvent event;

    @Override
    public void onApplicationEvent(WebServerInitializedEvent event) {
        ServiceInfoUtil.event = event;
    }

    public int getPort() {
        int port = event.getWebServer().getPort();
        return port;
    }

}

你可能感兴趣的:(物联网,物联网,EMQ,java)