clientId
클라이언트를 브로커에 연결할 때 사용되는 고유 식별자, 클라이언트의 연결 유지/세션 상태 관리
userId/userPw
클라이언트가 브로커에 연결할 때 브로커에 인증 및 권한 부여를 위함.
ACL(Access Control List)을 사용하여 제어 가능
acl.example
# This affects access control for clients with no username.
topic read $SYS/#
# This only affects clients with username "roger".
user roger
topic foo/bar
# This affects all clients.
pattern write $SYS/broker/connection/%c/state
mosquitto 서버 폴더 구조
mosquitto
├── config
│ ├── mosquitto.conf
│ ├── acl.conf
│ └── pwfile
│
├── data
│ └── mosquitto.db
│
├── log
└── docker-compose.yml
pwfile
admin:$7$101$dxSs5DOWacg2drF1$R1zwwZPf9qysg3IxG1kHdAHuOAOJFlODLngFRCfK8d084MhC2aSfy8CGCk4jsymwtGEy8H5IJwy9g/36kiegcw==
user1:$7$101$dxSs5DOWacg2drF1$R1zwwZPf9qysg3IxG1kHdAHuOAOJFlODLngFRCfK8d084MhC2aSfy8CGCk4jsymwtGEy8H5IJwy9g/36kiegcw==
user2:$7$101$dxSs5DOWacg2drF1$R1zwwZPf9qysg3IxG1kHdAHuOAOJFlODLngFRCfK8d084MhC2aSfy8CGCk4jsymwtGEy8H5IJwy9g/36kiegcw==
user3:$7$101$dxSs5DOWacg2drF1$R1zwwZPf9qysg3IxG1kHdAHuOAOJFlODLngFRCfK8d084MhC2aSfy8CGCk4jsymwtGEy8H5IJwy9g/36kiegcw==
acl.conf
user admin
topic readwrite #
user user1
topic read CAM/CAM0
user user2
topic write CAM/CAM0
user user3
topic readwrite CAM/CAM0
pattern write $SYS/broker/connection/%c/state
mosquitto.conf
allow_anonymous false
listener 1883
listener 9001
protocol websockets
persistence true
password_file /mosquitto/config/pwfile
persistence_file mosquitto.db
persistence_location /mosquitto/data/
acl_file /mosquitto/config/acl.conf
docker-compose.yml
version: "3.7"
services:
mosquitto:
image: eclipse-mosquitto
container_name: mosquitto_c
restart: always
ports:
- "1883:1883"
- "9001:9001"
volumes:
- ./config:/mosquitto/config:rw
- ./data:/mosquitto/data:rw
- ./log:/mosquitto/log:rw
MQTT.jsx
import ClientCard from "./ClientCard";
import styled from "styled-components";
export default function Mqtt({}) {
return (
<MqttBox>
<ClientCard id={0} userId="admin" userPw="1234" access="" />
<ClientCard id={1} userId="user1" userPw="1234" access="read" />
<ClientCard id={2} userId="user2" userPw="1234" access="write" />
<ClientCard id={3} userId="user3" userPw="1234" access="readwrite" />
</MqttBox>
);
}
const MqttBox = styled.div`
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
color: white;
.container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
margin: 30px;
p {
font-size: 20px;
padding: 10px 0;
}
}
button {
padding: 10px;
background: white;
}
`;
ClientCard.jsx
import { useEffect, useState } from "react";
import mqtt from "mqtt";
export default function ClientCard({ id, userId, userPw, access }) {
const [client, setClient] = useState(null);
const [payload, setPayload] = useState(null);
const [connectStatus, setConnectStatus] = useState(null);
const [isSub, setIsSub] = useState(false);
const mqttConnect = (host, mqttOption) => {
setConnectStatus("Connecting");
setClient(mqtt.connect(host, mqttOption));
};
const mqttSub = (subscription) => {
if (client) {
const { topic, qos } = subscription;
client.subscribe(topic, { qos }, (error) => {
if (error) {
console.log("Subscribe to topics error", error);
return;
}
setIsSub(true);
});
}
};
useEffect(() => {
if (client) {
console.log(client);
client.on("connect", () => {
setConnectStatus("Connected");
});
client.on("error", (err) => {
console.error("Connection error: ", err);
client.end();
});
client.on("reconnect", () => {
setConnectStatus("Reconnecting");
});
client.on("message", (topic, message) => {
const payload = { topic, message: message.toString() };
setPayload(payload);
console.log(payload);
console.log(message.toString());
});
}
}, [client]);
return (
<div className="container">
<p>
{userId} ({access})
</p>
<div>connectStatus: [{connectStatus}]</div>
<div>
payload: [topic: {payload?.topic}, message: {payload?.message}]
</div>
<div>isSub: [{isSub ? "Sub" : "noSUb"}]</div>
<button
onClick={() =>
mqttConnect("mqtt://192.168.0.200:9001", {
clientId: `web_${id}`,
username: userId,
password: userPw
})
}>
mqttConnect
</button>
<button onClick={() => mqttSub({ topic: "CAM/CAM0", qos: 1 })}>mqttSub</button>
</div>
);
}
admin 계정으로 발행했을 경우
읽기 권한이 없는 user2
만 message 를 수신하지 못한 모습 확인됨.
user1 계정으로 발행했을 경우
write 권한이 없으므로 발행 안됨.
user2 계정으로 발행했을 경우
읽기 권한이 없는 user2
만 message 를 수신하지 못한 모습 확인됨.
user3 계정으로 발행했을 경우
읽기 권한이 없는 user2
만 message 를 수신하지 못한 모습 확인됨.
1883: default mqtt port
9001: default mqtt port for websocket
웹 브라우저는 보안상 이유로 TCP 소켓을 직접 열 수 없고, WebSocket을 사용하여 안전한 HTTP(S) 기반 연결을 통해 양방향 통신을 함.
일반 MQTT 클라이언트(ex. Python, Java, Node.js ...): 1883
웹 브라우저 MQTT 클라이언트: 9001
이미지 발행 테스트 진행 예정
메시지 전송의 신뢰성을 관리하기 위해 QoS (Quality of Service) 수준을 제공
QoS 0: At most once (최대 한 번 전달)
메시지가 최대 한 번 전달됨. (메시지가 전달 안될 수도 있음.)
메시지가 전송 되지만, 전달 여부에 대한 확인(ACK)을 받지 않음.
발행자가 전송 -> 브로커가 구독자에게 즉시 전달
사용예: 센서 데이터 스트리밍
OoS 1: At least once (최소 한 번 전달)
메시지가 최소 한 번전달됨. (중복 메시지가 발생할 수 있음.)
메시지가 적어도 한번 전달되도록 보장, 발행자가 브로커로부터 PUBACK(확인 응답)을 받을 때까지 반복적으로 메시지 전송.
발행자가 전송 -> 브로커의 PUBACK 패킷 반환이 있을 때까지 전송 -> 브로커 수신 후 PUBACK 반환 -> 구독자는 중복 메시지를 받을 수 있음.
사용예: 재난 경보 같은 중요한 알림
QoS 2: Exactly once (정확히 한 번 전달)
메시지가 정확히 한 번 전달됨.
메시지가 중복되거나 손실되지 않도록 보장.
발행자가 전송 -> 브로커가 발행자에게 메시지 수신 확인(RUBREC) -> 발행자가 브로커에게 메시지 수신을 확인(PUBREL) -> 브로커가 발행자에게 최종 수신 확인(PUBCOMP) -> 구독자가 메시지 수신
사용예: 금융 거래