规范原则
服务质量
客户端
服务端
发布订阅
mqtt协议
协议
内置数据源
外部数据库
其他
匿名认证
认证链
管理-插件-emqx_auth_username
emqx/etc/plugins/emqx_auth_username.conf
api
api/v4/auth_username请求头
GET http://hostname:port/api/v4/auth_username HTTP/1.1
Content-Type: application/json
Authorizarion: Basic {{username}}:{{password}}
管理-插件-emqx_auth_clientId
emqx/etc/plugins/emqx_auth_clientid.conf
api
api/v4/auth_clientid请求头
POST http://hostname:port/api/v4/auth_clientid HTTP/1.1
Content-Type: application/json
Authorizarion: Basic {{username}}:{{password}}
@RestController
@RequestMapping("/mqtt")
public class AuthController{
@RequestMapping("/auth")
public ResponseEntity auth(String clientid,String username,String password){
// 查询数据库
// 首先通过username查询数据
String pass = xxxx.xx(username);
if(StringUtils.isEmpty(pass)){
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
if(!pass.equals(password)){
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
return new ResponseEntity(HttpStatus.OK);
}
}
<depenency>
<groupId>org.eclipse.pahogroupId>
<artifactId>org.eclipse.paho.client.mqttv3artifactId>
<version>1.2.2version>
depenency>
mqtt:
broker-url: tcp://xxx:xx
client-id: xx
username: xx
password: xx
@Configuration
@ConfigurationProperties(prefix="mqtt")
@Data
public class MqttProperties{
private String brokerUrl;
private String clientId;
private String username;
private String password;
}
@Component
public class EmqClient{
private IMqttClient mqttClient;
@AutoWired
private MqttProperties mqttProperties;
@AutoWired
private MqttCallback mqttCallback;
@PostConstruct
public void init(){
MqttClientPersistence mempersitence = new MemoryPersistence();
try{
mqttClient = new MqttClient(mqttProperties.getBorkerUrl(),mqttProperties.getClientId,mempersitence);
}catch(MqttException ex){
}
}
// 连接broker
public void connect(String username,String password){
MqttConnectOptions options = new MqttConnectOptions();
options.setAutomaticReconnect(true);
options.setUserName(username);
options.setPassword(password);
options.setCleanSession(true);
mqttClient.setCallback(mqttCallback);
try{
mqttClient.connect(options);
}catch(MqttException ex){
}
}
// 断开连接
@PreDestroy
public void disconnect(){
try{
mqttClient.disconnect();
}catch(MqttException ex){
}
}
// 重连
public void reConnect(){
try{
mqttClient.reconnect();
}catch(MqttException ex){
}
}
// 发布
public void publish(String topic,String msg,QosEnum qos,boolean retain){
MqttMessage mqttMessage = new MqttMessage();
mqttMessage.setPayload(msg.getBytes());
mqttMessage.setQos(qos.value());
mqttMessage.setRetained(retain);
try{
mqttClient.publish(topic,mqttMessage);
}catch(MqttException ex){
}
}
// 订阅
public void subscribe(String topicFilter,QosEnum qos){
try{
mqttClient.subscribe(topicFilter,qos.value());
}catch(MqttException ex){
}
}
// 取消订阅
public void unSubscribe(String topicFilter){
try{
mqttClient.unsubscribe(topicFilter);
}catch(MqttException ex){
}
}
}
public enum QosEnum{
Qos0(0),Qos1(1),Qos2(2);
private final int value;
QosEnum(int value){
this.value = value;
}
public int value(){
return this.value;
}
}
public class MessageCallback implements MqttCallback{
@Override
// 丢失连接后触发回调
public void connectionLost(Throwable cause){
}
@Override
// 应用收到消息后触发回调
public void messageArrived(String topic,MqttMessage message)throws Exception{
}
@Override
// 消息发布完成触发回调
public void deliveryComplete(IMqttDeliveryToken token){
}
}
@SpringbootApplication
public class EmqApplication{
public static void main(String[] args){
SpringApplication.run(EmqApplication.class,agrs);
}
@Autowired
private EmqClient emqClient;
@Autowired
private MqttProperties properties;
@PostConstruct
public void init(){
emqClient.connect(properties.getUsername,properties.getPassword);
emqClient.subscribe("xxx/#",QosEnum.Qos2);
// 开启线程,每五秒发布一次
new Thread(()->{
while(ture){
emqClient.public("xxx/123",
"public msg :" + LocalDateTime.now().format(DateTimeFormatter.ISO_DATE_TIME),
QosEnum.Qos2,
false);
TimeUnit.SECONDS.sleep(5);
}
}).start();
}
}
$(function(){
// 定义连接
const options = {
clean:true,//不保留会话
connectTimeout:4000,//超时时间
clientId:'xxx',
username:'xx',
password:'xxx'
}
const connectUrl = 'ws://xxx:8083/mqtt
const client = mqtt.connect(connectUrl,options);
client.on('reconnect',()=>{
// 重连
})
client.on('close',()=>{
// 关闭
})
client.on('disconnect',(packet)=>{
// 接收断开连接的数据包
})
client.on('offline',()=>{
// 客户端下线
})
client.on('error',(error)=>{
// 客户端错误
})
client.on('packetsend',(packet)=>{
// 客户端任何消息发出
})
client.on('packetreceive',(packet)=>{
// 客户端任何消息接收
})
client.on('connect',(connack)=>{
// 客户端成功连接
// 订阅
client.subscribe('xxx/#',{qos:2})
// 每隔2秒发布一次
setInterval(publish,2000)
})
function publish(){
// 发布数据
const message = 'h5 message '+ Math.random()+new Date()
client.publish('xxx/123',message,{qos:2})
}
client.on('message',(topic,message,packet)=>{
// 收到发布的消息事件
})
})
emqx.conf
log handler
修改日志级别
发布订阅权限管理
超级用户不受acl管理
ACL 规则
etc/emqx.conf
清除acl缓存
ACL鉴权链
@PostMapping("/superuser")
public ResponseEntity superuser(String clientid,String username){
// 查询当前用户是否时超级用户
if(clientid.contains("admin")|| username.contains("admin")){
return new ResponseEntity(HttpStatus.OK);
}else{
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
}
@PostMapping("/acl")
// access 1 sub订阅 2 pub发布
public ResponseEntity acl(int access,
String clientid,
String username,
String ipaddr,
String topic,
String mountpoint){
// 查询当前用户是否有topic的操作权限
if(username.equals("xxx")&& topic.equals("xxx/#")&access == 1){
return new ResponseEntity(HttpStatus.OK);
}
if(clientid.equals("xxx")&& topic.equals("xxx/#")&access == 2){
return new ResponseEntity(HttpStatus.OK);
}
return new ResponseEntity(HttpStatus.UNAUTHORIZED);
}
// web.hook.rule.client.connected.1={"action":"on_client_connected"}
// web.hook.rule.client.disconnected.1={"action":"on_client_disconnected"}
@RestController
@RequestMapping("/mqtt")
public class WebHookController{
private Map<String,boolean> clientStatus = new HashMap<>;
@PostMapping("/webhook")
public void hook(@RequestBody Map<String,Object> params){
// 获取事件名称
String action = (String)params.get("action");
String clientId = (String)params.get("clientid");
if(action.equals("client_connected")){
// 客户端接入
clientStatus.put(clientId,true);
}
if(action.equals("client_disconnected")){
// 客户端下线
clientStatus.put(clientId,false);
}
}
@GetMapping("/status")
public Map getStatus(){
return clientStatus;
}
}
node
cluster集群
集群发现策列
客户端自动订阅
etc/emqx.conf
webhook&httpapi实现动态代理订阅
// web.hook.rule.client.connected.1={"action":"on_client_connected"}
// web.hook.rule.client.disconnected.1={"action":"on_client_disconnected"}
@RestController
@RequestMapping("/mqtt")
public class WebHookController{
private Map<String,boolean> clientStatus = new HashMap<>;
@PostMapping("/webhook")
public void hook(@RequestBody Map<String,Object> params){
// 获取事件名称
String action = (String)params.get("action");
String clientId = (String)params.get("clientid");
if(action.equals("client_connected")){
// 客户端接入
clientStatus.put(clientId,true);
autoSub(clientId,"/xx/#",QosEnum.Qos2,true);
}
if(action.equals("client_disconnected")){
// 客户端下线
clientStatus.put(clientId,false);
autoSub(clientId,"/xx/#",QosEnum.Qos2,false);
}
}
@GetMapping("/status")
public Map getStatus(){
return clientStatus;
}
// 自动订阅和取消
public void autoSub(String clientId,String topicFilter,QosEnum qos,boolean sub){
RestTemplate restTemplate = new RestTemplateBuilder().basicAuthentican("admin","public")
.defaultHeader(HttpHeaders.CONTENT_TYPE,MediaType.APPLICATION_JSON_VALUE)
.build();
Map<String,Object> params = new HashMap<>();
params.put("clientid",clientId);
params.put("topic",topicFilter);
params.put("qos",qos.value());
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity entity = new HttpEntity(params,headers);
if(sub){
// 订阅
new Thread(()->{
ResponseEntit<String> responseEntity = restTemplate.postForEntity("xx/api/v4/mqtt/subscribe",entity,String.class);
}).start();
return;
}
// 取消
ResponseEntit<String> responseEntity = restTemplate.postForEntity("xx/api/v4/mqtt/unsubscribe",entity,String.class);
}
}
少量客户端时使用
etc/emqx.conf
api
飞行窗口
消息队列
max_infight
max_mqueue_len
mqueue_store_qos0
emqx消息与事件等响应规则
应用场景
规则引擎组成
sql语句
# 所有进程最大文件数
sysctl -w fs.file-max=2097152
# 单个进程可分割最大文件数
sysctl -w fs.nr_open=2097152
# 允许当前会话/进程打开文件句柄数
ulimit -n 1048576
# backlog
sysctl -w net.core.somaxconn=32768
sysctl -w net.ipv4.tcp_max_syn_backlog=16384
sysctl -w net.core.netdev_max_backlog=16384