MQTT 테스트 하면서 궁금했던 점 정리

H.GOO·2024년 5월 21일
0

MQTT

목록 보기
2/2

🪴 1. clientId MQTT 에서 clientId 와 userId/userPw 의 목적

  • clientId

    클라이언트를 브로커에 연결할 때 사용되는 고유 식별자, 클라이언트의 연결 유지/세션 상태 관리

  • userId/userPw

    클라이언트가 브로커에 연결할 때 브로커에 인증 및 권한 부여를 위함.




🪴 2. MQTT 계정 권한 사용자에게 특정 토픽에 대한 PUB/SUB 권한을 부여하는 방법

ACL(Access Control List)을 사용하여 제어 가능

GitHub-eclipse/mosquitto

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

계정 PUB/SUB 권한 테스트

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 를 수신하지 못한 모습 확인됨.




🪴 3. 계정 설정(pwfile)

  • 처음 설정은 이전 포스팅대로 진행
  • 이후 mosquitto 폴더 통째로 복붙하여 (./conf 폴더 내부의 파일들만 잘 가져온다면) 계정 정보 유지됨.



🪴 4. 포트(1883 vs 9001)

1883: default mqtt port
9001: default mqtt port for websocket


웹 브라우저는 보안상 이유로 TCP 소켓을 직접 열 수 없고, WebSocket을 사용하여 안전한 HTTP(S) 기반 연결을 통해 양방향 통신을 함.


일반 MQTT 클라이언트(ex. Python, Java, Node.js ...): 1883
웹 브라우저 MQTT 클라이언트: 9001




🪴 5. 이미지 발행

이미지 발행 테스트 진행 예정




🪴 6. qos 개념

메시지 전송의 신뢰성을 관리하기 위해 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) -> 구독자가 메시지 수신

  • 사용예: 금융 거래

0개의 댓글