java后端应用服务器mqtt框架源码及框架介绍

版权声明:本文为CSDN博主 无、涯 原创文章,可以转载但请注明出处。联系方式:[email protected] 博客地址 https://blog.csdn.net/a704397849 https://blog.csdn.net/a704397849/article/details/89475125

java后端应用服务器mqtt框架源码及框架介绍

应用服务器和客户端之间的发布、订阅设计

假设我们项目中 有2种客户端 一个是app客户端(Android、ios都是app客户端) ,另一个是设备客户端(比如:智能家居、机器人等) 。 当然客户端还可以有其他比如web等。发布订阅设计图 如下:
在这里插入图片描述
简述一下整个流程:
1 mqtt服务器 (EMQ) 启动 .
2 应用服务器启动,作为一个mqtt客户端 去连接 mqtt服务器。出于性能考虑,下面提供的应用服务器源码中与mqtt服务器建立了2个连接。一个专门用于发的mqttPublishClient,另一个专门用于收的mqttClient。(注:客户端就不要分2个连接了,mqtt服务器的资源有限!!!)

用于收的mqttClient 连接mqtt服务器成功后,订阅2个主题 ( 下面主题中的 ‘+’ 是单层通配符,比如app/+/client 可以匹配 app/123/client,app/abc/client ,不能匹配 app/123/abc/client)

  • 用于接收 app客户端 消息 的主题 app/+/client
  • 用于接收device客户端 消息 的主题 device/+/client

3 客户端启动,作为一个客户端与mqtt服务器建一个连接,假设客户端是app且用户的id 是110,那么连接mqtt服务器成功后,订阅用于接收服务器消息的主题 app/110/server

4 客户端发布一个消息: 主题 app/110/client , 内容是 {“cmd”:"/test"} ,
由于应用服务器订阅了主题 app/+/client , 所以mqtt服务器会将客户端发布的那个消息推给应用服务器,
应用服务器收到消息: 主题 app/110/client , 内容是 {“cmd”:"/test"} , 处理并返回(发布)消息: 主题 app/110/server , 内容是 {“cmd”:"/test/response" , “ret”:“0” , “msg”:“测试消息已经处成功处理”} ,同理由于app客户端110已经订阅了 app/110/server , 客户端110能收到这个消息。

源码

下面源码是一个完整的demo源码,业务处理模块需要根据你们自己的项目框架完善。
用到的jar包:
org.eclipse.paho.client.mqttv3-1.2.0.jar ,链接:https://pan.baidu.com/s/1SJL0A7zCsG9vPhPntttFSA
提取码:jbsp
json jar包,链接:https://pan.baidu.com/s/1rHyj05NXZpZxT4hYUI146A
提取码:8eee

在最后,会将整个完整代码分享,如有需要,自行下载

源码结构图:
在这里插入图片描述
MqttClientUtil

package frame.mqtt;

import org.eclipse.paho.client.mqttv3.*;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * @author zx
 */
public class MqttClientUtil {

	public static MqttAsyncClient mqttClient;
	public static MqttAsyncClient mqttPublishClient;
	public static String TOPIC_PREFIX_DEVICE = "device";
	public static String TOPIC_PREFIX_APP = "app";
	private static MqttClientCallback mqttClientCallback = new MqttClientCallback();

	/**
	 * 要发布的消息队列
	 */
	private static LinkedBlockingQueue<String[]> queue = new LinkedBlockingQueue<String[]>();
	public static ExecutorService executorService = Executors.newFixedThreadPool(2 * Runtime.getRuntime().availableProcessors() + 1);

	/**
	 * mqtt broker 连接配置,填自己的mqtt地址,及账号密码
	 */
	private static String broker = "tcp://xxx.xx.xxx.xxx:1883";
	private static String username = "xxx";
	private static String password = "xxx";

	static {
		
	}

	public static void createClient() {
		System.out.println("mqtt createClient, " + broker);
		String clientId = "mqttserver" + String.valueOf(System.currentTimeMillis());
		System.out.println("mqtt sub_clientId="+clientId+",pub_clientId="+clientId+"-publish");
		try {
			//subscribe client
			mqttClient = new MqttAsyncClient(broker, clientId, new MemoryPersistence());
			mqttClient.setCallback(mqttClientCallback);

			//publish client
			mqttPublishClient = new MqttAsyncClient(broker, clientId + "-publish", new MemoryPersistence());
			mqttPublishClient.setCallback(new MqttCallback() {
				@Override
				public void connectionLost(Throwable arg0) {
					MqttClientUtil.publishReconnect();
				}

				@Override
				public void deliveryComplete(IMqttDeliveryToken arg0) {
				}

				@Override
				public void messageArrived(String arg0, MqttMessage arg1) throws Exception {
				}
			});
			subscribeReconnect();
			publishReconnect();
		} catch (MqttException me) {
			System.out.println("reason " + me.getReasonCode());
			System.out.println("msg " + me.getMessage());
			System.out.println("loc " + me.getLocalizedMessage());
			System.out.println("cause " + me.getCause());
			System.out.println("excep " + me);
			me.printStackTrace();
		}

		//启动发布消息的线程, 循环从队列queue 获取要发布的消息 发布。也就是说要发消息,只要将消息写入队列queue
		new Thread(new PublishThread()).start();
	}

	/**
	 * 发布消息
	 * @param topic
	 * @param content
	 */
	public static void publish_common(String topic, String content) {
		String[] array = { topic, content };
		//要发布的消息,入队列。
		//PublishThread 会循环从这个队列获取消息发布.
		queue.offer(array);
	}

	public static String[] poll() {
		return (String[]) queue.poll();
	}

	public static String getDeviceIdByTopic(String topic) {
		String[] array = topic.split("/");
		if (array.length >= 2) {
			return array[1];
		}
		return "";
	}

	public static String getDevicePublishTopic(String deviceId) {
		return TOPIC_PREFIX_DEVICE + "/" + deviceId + "/server";
	}

	public static String getAppPublishTopic(String deviceId) {
		return TOPIC_PREFIX_APP + "/" + deviceId + "/server";
	}

	/**
	 * 订阅连接 重连
	 */
	public static void subscribeReconnect() {
		if (mqttClient != null) {
			System.out.println("mqtt subscribeReconnect");
            try {
                MqttConnectOptions connOpts = new MqttConnectOptions();
                connOpts.setCleanSession(true);
                connOpts.setMaxInflight(100000);
                if (username != null && !"".equals(username)) {
                    connOpts.setUserName(username);
                    connOpts.setPassword(password.toCharArray());
                }

                mqttClient.connect(connOpts, null, new IMqttActionListener() {
                    @Override
					public void onSuccess(IMqttToken asyncActionToken) {
                        try {
                        	System.out.println("mqtt 已连接!");
                            MqttClientUtil.mqttClient.subscribe(MqttClientUtil.TOPIC_PREFIX_DEVICE + "/+/client", 0);
                            MqttClientUtil.mqttClient.subscribe(MqttClientUtil.TOPIC_PREFIX_APP + "/+/client", 0);

                            System.out.println("订阅主题:"+MqttClientUtil.TOPIC_PREFIX_DEVICE + "/+/client");
							System.out.println("订阅主题:"+MqttClientUtil.TOPIC_PREFIX_APP + "/+/client");
                        } catch (MqttException me) {
							System.out.println("reason " + me.getReasonCode());
							System.out.println("msg " + me.getMessage());
							System.out.println("loc " + me.getLocalizedMessage());
							System.out.println("cause " + me.getCause());
							System.out.println("excep " + me);
                            me.printStackTrace();
                        }
                    }

                    @Override
					public void onFailure(IMqttToken asyncActionToken, Throwable exception) {
						System.out.println("mqtt 没有连接上:" + exception.getMessage());
                        try {
                            Thread.sleep(60000L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        MqttClientUtil.subscribeReconnect();
                    }
                });
            } catch (MqttException me) {
                System.out.println("reason " + me.getReasonCode());
                System.out.println("msg " + me.getMessage());
                System.out.println("loc " + me.getLocalizedMessage());
                System.out.println("cause " + me.getCause());
                System.out.println("excep " + me);
                me.printStackTrace();
            }
        }
	}

	/**
	 * 发布连接 重连
	 */
	public static void publishReconnect() {
		if (mqttPublishClient != null) {
			System.out.println("mqtt publishReconnect");
            try {
                MqttConnectOptions connOpts = new MqttConnectOptions();
                connOpts.setCleanSession(true);
                connOpts.setMaxInflight(100000);
                if (username != null && !"".equals(username)) {
                    connOpts.setUserName(username);
                    connOpts.setPassword(password.toCharArray());
                }
                mqttPublishClient.connect(connOpts, null, new IMqttActionListener() {
                    @Override
					public void onFailure(IMqttToken arg0, Throwable arg1) {
                        System.out.println("mqtt publish client connect is failed" + arg1.getMessage());
                        try {
                            Thread.sleep(30000L);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        MqttClientUtil.publishReconnect();
                    }

                    @Override
					public void onSuccess(IMqttToken arg0) {
						System.out.println("mqtt publish client is connected");
                    }
                });
            } catch (MqttException me) {
                System.out.println("reason " + me.getReasonCode());
                System.out.println("msg " + me.getMessage());
                System.out.println("loc " + me.getLocalizedMessage());
                System.out.println("cause " + me.getCause());
                System.out.println("excep " + me);
                me.printStackTrace();
            }
        }
	}
}

MqttClientCallback

package frame.mqtt;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttMessage;

/**
 * @author
 */
public class MqttClientCallback implements MqttCallback {

    @Override
    public void connectionLost(Throwable arg0) {
        System.out.println("mqtt 失去了连接");
        MqttClientUtil.subscribeReconnect();
    }

    @Override
    public void deliveryComplete(IMqttDeliveryToken arg0) {
        System.out.println("mqtt 发送完成!");
    }

    @Override
    public void messageArrived(String topic, MqttMessage message)
            throws Exception {
        String content = new String(message.getPayload(), "utf-8");
        System.out.println("收到mqtt消息,topic: " + topic + " ,content: " + content);
        if (MqttClientUtil.executorService != null) {
            MqttClientUtil.executorService.execute(new HandlerThread(topic, content));
        }
    }
}

PublishThread

package frame.mqtt;

import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;

import java.io.UnsupportedEncodingException;

/**
 * @author
 */
public class PublishThread implements Runnable {
  @Override
  public void run()
  {
      String[] obj = null;
      System.out.println("mqtt publish thread start");
      while (true)
      {
          obj = MqttClientUtil.poll();
          if (obj != null) {
              String topic = obj[0];
              String content = obj[1];
              System.out.println("mqtt从队列取出topic:"+topic+",content:"+content);
              byte[] array = null;
              try {
                array = content.getBytes("utf-8");
              }
              catch (UnsupportedEncodingException e1) {
                e1.printStackTrace();
              }

              MqttMessage message2 = new MqttMessage(array);
              message2.setQos(1);
              try
              {
                MqttClientUtil.mqttPublishClient.publish(topic, message2);
                System.out.println("发送mqtt消息,topic: "+topic+" ,content: "+content);
              } catch (MqttPersistenceException e) {
                System.out.println("发消息给设备,topic:"+topic+",content:"+content);
                e.printStackTrace();
              } catch (MqttException e) {
                System.out.println("发消息给设备,topic:"+topic+",content:"+content);
                e.printStackTrace();
              } catch (Exception e) {
                System.out.println("发消息给设备,topic:"+topic+",content:"+content);
                e.printStackTrace();
              }
          }
          else{
              try {
                Thread.sleep(1000L);
              }
              catch (InterruptedException e) {
                e.printStackTrace();
              }
          }
      }
  }

}

HandlerThread

package frame.mqtt;

import org.json.JSONObject;

/**
 * @author
 */
public class HandlerThread implements Runnable {

	private String topic;
	private String content;

	public HandlerThread(String topic, String content) {
		this.topic = topic;
		this.content = content;
	}

	@Override
	public void run() {

		if (this.content.length() > 0) {
			//注: 下面逻辑处理,仅仅是我提供的简单案例,需要你们根据自己实际项目框架处理
			//最好是用工厂模式创建每个接口对应的处理对象,或调用每个接口对应的处理方法.
			try {
				JSONObject json = new JSONObject(content);
				String cmd_id = json.optString("cmd");

				if(topic.startsWith(MqttClientUtil.TOPIC_PREFIX_DEVICE)){
					//来自device的消息
					//...
				}else if(topic.startsWith(MqttClientUtil.TOPIC_PREFIX_APP)){
					//来自app的消息
					if("/test".equals(cmd_id)){
						//从top 获取app 用户的 id
						String id = topic.substring( topic.indexOf("app/") + 4 , topic.lastIndexOf("/client") );

						//获取 发送给 指定id 的app用户的topic
						String topic = MqttClientUtil.getAppPublishTopic(id);

						//要发送消息的内容
						JSONObject content = new JSONObject();
						content.put("cmd",cmd_id+"/response");
						content.put("ret","0");
						content.put("msg","测试消息已经处成功处理");

						//发布消息
						MqttClientUtil.publish_common(topic,content.toString());
					}
				}
			} catch (Throwable e) {
				System.out.println("mqtt HandlerThread run");
				e.printStackTrace();
			}
		}
	}

}

All

package main;

import frame.mqtt.MqttClientUtil;

/**
 * @author zx
 */
public class All {
    public static void main(String[] args){
        //mqtt服务启动
        MqttClientUtil.createClient();

        Runtime.getRuntime().addShutdownHook(new Thread() {
            @Override
            public void run() {
                try {
                    //释放工作
                    //...

                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        });
    }
}

运行以及结果:

注: mqtt服务器ip地址 ,屏蔽了,更换你们自己的mqtt服务器ip .

在这里插入图片描述
1 启动应用服务器程序
2 打开mqtt图形客户端(如果还没有mqtt图形客户端,看下这篇博文https://blog.csdn.net/a704397849/article/details/88533875
3 mqtt图形客户端,连接mqtt服务器,假设这个app客户端用户id 是 110 ,那么这个app客户端需要订阅
app/110/server 用来接收服务器发布给自己的消息。
4 mqtt图形客户端,发布消息: 主题 app/110/client ,内容 {“cmd”:"/test"}
5 可以看到结果是应用服务器收到了 app客户端发布的消息 并且处理 返回(发布) 消息 {“ret”:“0”,“msg”:“测试消息已经处成功处理”,“cmd”:"/test/response"}

demo完整代码

链接:https://pan.baidu.com/s/1mUWMaMXSm4vTumQziOGIlA
提取码:yvrv

连接mqtt的ip和账号密码,我屏蔽了,填写你们自己的mqtt信息,如果还没有mqtt服务器,那么看下我之前mqtt相关文章 。

关于app、设备等客户端建议

1 app、设备 客户端连接mqtt服务器的clientId 不要给随机的,这样的话用不了mqtt的离线消息。(应用服务器不打算保存离线消息,所以上面应用服务器代码中clientId是不固定的,是启动程序时当前时间搓。)

我们app客户端clientId 是用户id + mac 地址 ,连接mqtt服务器时 不要设置清除session,这样的话客户端就可以收到离线消息了。当然clientId这样创建,如果app用户下线期间有离线消息,然后在别的手机登录会收不到这期间的离线消息。

注: 上面提到的用户id 是app客户端用http请求应用服务器 登录接口获取的id

2 我现在用到mqtt的项目中, app 客户端 有2个登录 首先要 http登录获取用户id和token ,然后再连接mqtt服务器,发布一个mqtt 登录请求给服务器。
问题来了!为什么要有2个登录?首先http登录是为了获取后续http请求的令牌token ,那么为什么还要mqtt登录呢?mqtt登录是要让应用服务器知道app客户端与mqtt建立了长连接。通过mqtt, 应用服务器可以推消息给指定的客户端。
同样的,app客户端有2个下线操作

设备客户端 不需要token验证,所以只有一个mqtt登录。

3 app、设备 客户端连接mqtt时,记得设置遗愿,例如上述 app客户端id是110的用户 应该设置遗愿,主题 app/110/client ,内容是下线信息,如 {“cmd”:"/user/logout"}

最后

本文只是介绍了 java应用服务器 mqtt框架设计及源码 ,没有使用ssl/tls安全协议。如果需要mqtt服务器的安装及安全连接 可以看看我之前关于mqtt的文章.
mqtt服务器搭建以及mqtt客户端图形调试测试工具安装使用:https://blog.csdn.net/a704397849/article/details/88533875
emqtt安全连接 单向认证连接 加密 ssl/tls mosquitto客户端ssl单向认证连接测试
https://blog.csdn.net/a704397849/article/details/88885198

如果本文对你有所帮助,点赞关注支持一波,谢谢(〃‘▽’〃)

猜你喜欢

转载自blog.csdn.net/a704397849/article/details/89475125