- 이번 포스팅에서는,
IoT 에 주로 사용되는 MQTT 프로토콜에 대해 알아보고,
MQTT 메시지 전달 서버(브로커)를 EMQX 기술을 사용하여 구축한 후,
이를 기반으로 연결되는 클라이언트를 구축하여 테스트까지 진행해보겠습니다.
서버 역할을 하는 브로커는 EMQX 를 Docker 를 사용하여 구축하고,
확장성을 위하여 클러스터링 설정 및 보안을 위해 기본 인증/인가 설정을 적용할 것입니다.
클라이언트 역할로는,
엔터프라이즈급 서비스 백엔드에서 일반적으로 사용되는 Java Springboot,
데이터 분석 분야에서 두각을 나타내는 Python,
웹 프론트엔드 분야에서 대체 불가능한 JavaScript,
그리고 IoT 단말기 부분에서 사용되는 C++
위에서 나열한 예시를 전부 다루면 좋겠지만,
본 게시글에서는 가시적인 예시를 위한 JavaScript 와 IoT 디바이스 핵심 코드가 되는 C++ 코드만 게시하겠습니다.
이 과정을 통해 IoT 구현에 필요한 네트워크 통신 응용 능력을 갖출 수 있을 것입니다.
[MQTT 설명]
(MQTT란?)
- MQTT(Message Queuing Telemetry Transport) 는 경량 메시지 통신 프로토콜입니다.
주로 사물인터넷(IoT) 환경에서 제한된 리소스(저전력, 저대역폭)에서도 빠르고 안정적으로 데이터를 주고받기 위해 사용됩니다.
(MQTT 의 기본 구조)
- MQTT는 Pub/Sub 구조를 사용합니다.
- Pub/Sub 구조란,
데이터를 보내는 쪽(Publisher)과 데이터를 받는 쪽(Subscriber)이 있고, 이 둘 사이에 Broker(중개자)가 있는 구조로,
예를 들어 쉽게 설명하겠습니다.
Subscriber 를 고객, Publisher 를 신문사, Broker 를 신문 제공 지역의 지점이라고 설명이 가능합니다.
고객이 지점에 스포츠 신문을 구독하겠다고 의뢰를 하면 지점은 그 의뢰를 받아들입니다.(Subscribe)
신문사가 스포츠 신문을 발행하여 지점으로 전달합니다.(Publish)
지점은 신문을 받으면 해당 종류(Topic)의 신문(Message)을 구독하고 있는 고객들에게 이를 전달합니다.
다만, 고객이 구독한 것은 스포츠 신문이기에, 신문사가 경제 신문이나 연예신문을 발행하면 이는 스포츠 신문을 구독하는 고객들에게는 전달되지 않습니다.
위와 같습니다.
이 구조의 좋은 점은,
신문사가 고객이 누군지 몰라도 지점에 발행만 하면 되고,
고객 역시 신문사가 어디있는지 몰라도 되기 때문에 소프트웨어의 결합성이 낮아져서 유연한 구조가 된다는 것이고,
지점은 단순히 전달하는 것으로 끝나는 것이 아니라 어느정도 이전 신문을 저장(보존 메시지, 큐잉)해두고 있기 때문에 지점 부분에 문제가 발생해도 복구 후 전달 못한 메시지를 재발행 하기도 수월하며, 지점을 여러개 두어 부하를 분산(클러스터링, 분산 처리)하는 분산 기능, 지점 단위에서의 보안(발행자, 수신자에 대한 인증, 권한 제어 등) 등의 장점이 있습니다.
참고로, MSA 를 구축할 때나 대용량 메시지 전달이 일어날 수 있는 소켓 서버에 분산 처리를 할 때 사용되는 Apache Kafka 와 같은 이벤트 브로커 역시 동일한 pub/sub 구조이므로 둘 중 하나를 먼저 익혀둔다면 나머지 기술을 이해하기 쉽습니다.
- QoS (Quality of Service)
MQTT는 전송 품질을 3단계로 설정할 수 있습니다.
0 (최소) : 한 번만 전송 (보장 없음)
1 (보통) : 적어도 한 번 전송 (중복 가능)
2 (최대) : 정확히 한 번 전송 (중복 없음, 가장 안전)
- MQTT의 장점은 아래와 같습니다.
1. 가볍고 빠름 (Header가 작음, 2바이트부터 시작)
2. 저전력/저대역폭 환경에 최적화
3. 연결 유지(keep-alive) 기능 포함
4. 다양한 플랫폼/언어에서 지원
- MQTT 메시지 구조
MQTT 의 실제 전달되는 메시지 구조는,
[ Fixed Header | Variable Header | Payload ]
위와 같은 형식이며,
Fixed Header :
메시지 타입 (예: PUBLISH, SUBSCRIBE 등)
DUP flag, QoS level, Retain flag
남은 길이 (Remaining Length)
Variable Header :
메시지 타입에 따라 다름
예: PUBLISH인 경우에는 Topic Name, Packet Identifier 포함
Payload :
실제 메시지 내용 (예: 23.5°C)
이렇습니다.
예시를 들자면,
Fixed Header:
- Type: PUBLISH (0x30 for QoS 0)
- Remaining Length: 20
Variable Header:
- Topic Name Length: 0x0017
- Topic Name: "sensor/room1/temperature"
Payload:
- "23.5°C" (바이너리 데이터로 전송됨)
위와 같이 전달되는데, 다른 통신 프로토콜에 비교해보자면 간단하고 짧은 편이라는 것을 확인할 수 있습니다.
(실제 전송 데이터는 바이너리로 인코딩 되어 전송됩니다. 와이어샤크와 같은 분석 툴로 확인 가능합니다.)
- MQTT 용도
IoT 전반에 사용될 수 있으며, 세부 예시는 아래와 같습니다.
1. 스마트 홈 (온도센서, 조명 제어 등)
2. 스마트 팩토리
3. 차량/드론 위치 정보 전송
4. 실시간 모니터링 시스템
[MQTT 구현]
(MQTT 브로커 실행)
- 앞서 신문사로 예를 들었던 Pub/sub 구조 예제에서 지점 역할을 수행하던 MQTT 브로커를 구축하는 방법을 정리하겠습니다.
MQTT 브로커 구축에 있어 자주 사용되는 기술로는 EMQX 와 Mosquitto 가 있습니다.
둘을 비교해보자면,
항목 | EMQX | Mosquitto |
성격 | 풀기능 MQTT 플랫폼 | 경량화된 MQTT 브로커 |
성능 | 고성능 (수십만~백만 클라이언트 처리 가능) | 가벼움, 수천~수만 클라이언트에 적합 |
기능 | TLS, 인증, 권한 제어, 웹 대시보드, REST API, 클러스터링 등 기본 내장 | 매우 기본적인 기능만 제공 (확장은 설정 또는 별도 도구 필요) |
확장성 | 클러스터링, 플러그인, 다양한 메시징 백엔드 연동 (Kafka, Redis 등) | 기본 단일 노드 중심 |
관리 UI | 웹 기반 대시보드 제공 (기본 포함) | 없음 (모니터링은 외부 도구 필요) |
설정 편의성 | 환경 변수로 대부분 설정 가능, Docker 친화적 | 설정 파일 기반, 다소 정적 |
커뮤니티/기업 지원 | 활발한 커뮤니티 + 기업(EMQ) 지원 | 오픈소스 커뮤니티 중심, 단순 유지관리 |
용량/리소스 | 상대적으로 무겁고 기능 많음 | 매우 가벼움 (IoT 디바이스용 최적화) |
라이선스 | Apache 2.0 (오픈소스) / Enterprise 에디션 있음 | EPL-2.0 (완전 오픈소스) |
위의 테이블과 같습니다.
요약하자면, EMQX 는 대규모 프로젝트(수만~수십만 디바이스)에 적합한 다기능 브로커이고,
Mosquitto 는 소규모 프로젝트(수십~수천 디바이스)에 적합한 완전 오픈소스의 경량 브로커입니다.
저의 경우는 고성능에 보안 등 다양한 기능을 제공하고, 커뮤니티도 활발한 EMQX 가 실습에 적합하다고 생각했기 때문에 본 게시글에서는 EMQX 를 사용할 것입니다.
구축에는 Docker 를 사용할 것이며,
확장성을 위해 클러스터링을 적용하고, 기본 보안을 적용하겠습니다.
(EMQX 실행 절차 설명)
1. docker-compose.yml 파일 준비
services:
emqx1:
image: emqx/emqx:5.8.6
container_name: emqx1
environment:
- "EMQX_NODE_NAME=emqx@node1.emqx.com"
- "EMQX_CLUSTER__DISCOVERY_STRATEGY=static"
- "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.com,emqx@node2.emqx.com]"
healthcheck:
test: ["CMD", "/opt/emqx/bin/emqx", "ctl", "status"]
interval: 5s
timeout: 25s
retries: 5
networks:
emqx-bridge:
aliases:
- node1.emqx.com
ports:
- 8082:1883
- 8083:8083
- 8084:8084
- 8883:8883
- 18083:18083
volumes:
- C:/Users/raill/Downloads/tmp/emqx/emqx1_data:/opt/emqx/data
emqx2:
image: emqx/emqx:5.8.6
container_name: emqx2
environment:
- "EMQX_NODE_NAME=emqx@node2.emqx.com"
- "EMQX_CLUSTER__DISCOVERY_STRATEGY=static"
- "EMQX_CLUSTER__STATIC__SEEDS=[emqx@node1.emqx.com,emqx@node2.emqx.com]"
healthcheck:
test: ["CMD", "/opt/emqx/bin/emqx", "ctl", "status"]
interval: 5s
timeout: 25s
retries: 5
networks:
emqx-bridge:
aliases:
- node2.emqx.com
volumes:
- C:/Users/raill/Downloads/tmp/emqx/emqx2_data:/opt/emqx/data
networks:
emqx-bridge:
driver: bridge
위와 같이 mqtt-emqx-compose.yml 파일을 준비합니다.
설정을 간단히 설명하겠습니다.
emqx/emqx:5.8.6
부분은 이미지와 버전 정보입니다. 만약 유료버전을 사용하려면 emqx-enterprise 이미지를 사용하면 됩니다.
나머지는 클러스터링을 적용하기 위한 정보이며,
각 포트를 설명하자면,
8082:1883 : (MQTT 기본 포트) → 외부에서 MQTT 연결 가능.
8083:8083 : WebSocket MQTT 포트.
8084:8084 : WebSocket MQTT 포트(SSL).
8883:8883 : MQTT over SSL.
18083:18083 : EMQX 대시보드 (웹 UI).
위와 같습니다.
기본적으로 1883 과 매핑되는 8082 로 연결되고, 모니터링을 위한 대시보드 웹 서비스에는 18083 포트로 접근하는 의미입니다.
2. docker 컨테이너 실행.
>> docker-compose -f mqtt-emqx-compose.yml up -d
위 명령어로 컨테이너를 실행시킵니다.
3. 18083 포트로 모니터링 대시보드 웹 서비스로 접근 가능
http://localhost:18083
초기 아이디/비번 : admin/public
4. 클라이언트 접속 테스트
위와 같이 정상 동작하는 상태이므로 클라이언트 프로그램으로 간단히 접근 테스트를 하겠습니다.
테스트 프로그램은
위 경로에서 다운받아 설치하고 실행시키면,
위와 같은 프로그램이 실행하며,
로컬에서 실행중인 mqtt 에 접근하겠습니다.
+ 아이콘을 누르고, New Connection 을 누릅니다.
접속 서버 설정은 위와 같이 로컬 경로의 해당 포트를 설정하고 Connect 버튼을 누릅니다.
정상적으로 연결되면 문제 없이 다음 화면으로 넘어갈 것입니다.
이제 통신 테스트를 위해 subscribe 를 할 것입니다.
New Subscribe 버튼을 누르면 토픽 구독 다이얼로그가 나올 것이며,
위와 같이 test 라는 토픽을 구독하도록 설정하여 confirm 을 눌러줍니다.
이 상태로는, 테스트용 프로그램이 로컬 서버의 mqtt 를 구독중이고, test 라는 토픽을 구독중인 것입니다.
이제 실제 메시지를 발행하고 받아오는 테스트를 한다면,
위와 같이 test 토픽에 메시지를 발행하면, 구독한 위치에서 해당 메시지를 잘 받아옴을 확인할 수 있습니다.
5. 기본 보안 설정
위와 같은 상태로는 어느 누구든 접속 주소만 안다면 메시지를 발행하고 탈취할 수 있습니다.
여기서는 기본적으로 Username 과 Password 정보를 알아야만 접근이 가능하도록 설정하여 최소한의 보안을 갖추도록 하겠습니다.
다시 대시보드에 접근해서,
왼쪽 메뉴 → Access Control > Authentication 클릭
우측 상단 → Create 클릭
인증 방식에서 Password Based 선택 -> Next 클릭
Built-in Database 선택 -> Next 클릭
기본 선택 -> Next 클릭
Authentication 항목이 생성되면, Users 버튼을 누르고, Add 버튼 클릭.
생성할 username, password 를 입력
클라이언트에서 기존 연결을 종료하고 username, password 를 입력하고 다시 연결
완료된다면 위와 같이 username 과 password 를 입력하여 정상적으로 접속 가능함을 확인할 수 있습니다.
이로써 MQTT 브로커에 대한 기본 설치, 설정 및 실행 방법을 이해하였습니다.
다음으로는 각 클라이언트에서 MQTT 브로커 서버를 사용하여 통신하는 방법을 정리하고 실행하도록 하겠습니다.
[MQTT 클라이언트 구현]
(Javascript 설정)
- Javascript 에서 MQTT 로 통신하는 방법을 아래와 같이 정리합니다.
- HTML/Javascript 기반으로, paho-mqtt 라이브러리를 사용하여 처리할 것입니다.
특이한 점으로는, javascript 에서는 MQTT 프로토콜을 직접 사용하는 것이 아니라 WebSocket 의 wss:// 주소를 사용합니다.
고로 클라이언트 연결시 wss 로 연결됨을 봅시다.
아래는 전체 HTML 코드입니다.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>MQTT Web Client</title>
<script src="https://unpkg.com/paho-mqtt@1.1.0/paho-mqtt.js"></script>
</head>
<body>
<h2>MQTT Web Client</h2>
<div>
<input type="text" id="messageInput" placeholder="메시지 입력..." />
<button onclick="publish()">Publish</button>
</div>
<div>
<h4>수신된 메시지:</h4>
<pre id="messages"></pre>
</div>
<script>
const clientId = "webclient_" + parseInt(Math.random() * 10000);
const client = new Paho.Client("127.0.0.1", 8083, "/mqtt", clientId);
// 연결 옵션
const options = {
userName: "testuser",
password: "testpass",
useSSL: false,
onSuccess: onConnect,
onFailure: function (err) {
console.error("접속 실패", err);
}
};
client.onConnectionLost = function (responseObject) {
if (responseObject.errorCode !== 0) {
console.error("연결 끊김:", responseObject.errorMessage);
}
};
client.onMessageArrived = function (message) {
const output = document.getElementById("messages");
output.textContent += `[${message.destinationName}] ${message.payloadString}\n`;
};
function onConnect() {
console.log("MQTT 브로커 연결 성공");
client.subscribe("test");
}
function publish() {
const msgInput = document.getElementById("messageInput");
const message = new Paho.Message(msgInput.value);
message.destinationName = "test";
client.send(message);
msgInput.value = "";
}
// 연결 시작
client.connect(options);
</script>
</body>
</html>
MQTTX 와 더불어 웹 화면 동작을 확인하면 아래와 같이 정상 동작함을 볼 수 있습니다.
(C++ 설정)
- C++ 에서 MQTT 로 통신하는 방법을 아래와 같이 정리합니다.
네트워크 사용을 위한 WIFI 라이브러리와 MQTT 클라이언트 라이브러리를 사용한 것입니다.
아래 환경은 테스트를 위해 아두이노가 필요합니다.
#include <WiFi.h> // WiFi 라이브러리 (ESP8266, ESP32용)
#include <PubSubClient.h> // MQTT 클라이언트 라이브러리
// WiFi 설정
const char* ssid = "your_wifi_ssid";
const char* password = "your_wifi_password";
// MQTT 브로커 설정
const char* mqtt_server = "127.0.0.1"; // 브로커 주소 (예: 로컬 서버 IP)
const int mqtt_port = 8083; // WebSocket 포트
WiFiClient espClient; // WiFi 클라이언트 객체
PubSubClient client(espClient); // MQTT 클라이언트 객체
// 연결 함수
void setup_wifi() {
delay(10);
// WiFi 연결 시작
Serial.println();
Serial.print("Connecting to WiFi...");
WiFi.begin(ssid, password);
// WiFi 연결 대기
while (WiFi.status() != WL_CONNECTED) {
delay(1000);
Serial.print(".");
}
Serial.println(" connected");
}
void reconnect() {
// MQTT 브로커에 연결될 때까지 시도
while (!client.connected()) {
Serial.print("Attempting MQTT connection...");
// 클라이언트 ID는 고유한 값으로 설정 (여기서는 "arduinoClient"로 설정)
if (client.connect("arduinoClient", "testuser", "testpass")) {
Serial.println("connected");
// 연결 후 토픽 구독
client.subscribe("test");
} else {
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
delay(5000);
}
}
}
void setup() {
// 시리얼 모니터 시작
Serial.begin(115200);
// WiFi 연결
setup_wifi();
// MQTT 브로커 주소 설정
client.setServer(mqtt_server, mqtt_port);
client.setCallback(callback);
}
void loop() {
if (!client.connected()) {
reconnect();
}
client.loop(); // MQTT 핸들링
}
// 메시지가 수신되었을 때 호출되는 콜백 함수
void callback(char* topic, byte* payload, unsigned int length) {
Serial.print("Message arrived [");
Serial.print(topic);
Serial.print("] ");
for (int i = 0; i < length; i++) {
Serial.print((char)payload[i]);
}
Serial.println();
}
[끝으로...]
- 이상입니다.
이로써 IoT 를 구성하는 구성 요소끼리의 데이터 통신을 구현할 수 있게 되었고,
HW, SW 각각의 처리를 진행하면 됩니다.
추가적으로, MQTT 는 메시지 기반의 작은 데이터를 전송하는데 유리한 프로토콜인데,
카메라 센서의 이미지 데이터와 같은 것을 통신하기 위해선 별도의 프로토콜 기술을 사용합니다.
아래와 같은 종류가 존재하며,
✅ 1. RTSP (Real-Time Streaming Protocol)
용도: IP 카메라, CCTV, 실시간 영상 전송
지연 : 낮음
구현 난이도 : 중
특징: 전통적이고 안정적인 영상 스트리밍 방식입니다.
실시간 재생에 최적화되었으며,
주로 H.264 또는 H.265 인코딩된 비디오를 RTP로 전송
✅ 2. WebRTC
용도: 브라우저 기반 실시간 화상 통신, 저지연 영상 스트리밍
지연 : 매우 낮음
구현 난이도 : 높음
특징: P2P 기반이며, 양방향 전송 가능 클라이언트간 영상 교환 기술입니다.
브라우저에서 별도 플러그인 없이 실시간 영상 전송 가능하고 성능도 좋지만,
Raspberry Pi나 Jetson 같은 고성능 디바이스에서만 지원하고,
아두이노 등의 저성능 디바이스에선 지원하지 않는 경우가 많습니다.
✅ 3. MJPEG (Motion JPEG) over HTTP
용도: 간단한 웹 스트리밍 카메라용 (ESP32-CAM 등에서 흔함)
지연 : 중간
구현 난이도 : 낮음
특징: 각 프레임을 JPEG으로 인코딩하고 연속 전송하는 방식입니다.
구현이 단순하고 브라우저에서 바로 보기가 가능하지만,
압축 효율이 낮고, 네트워크 부하가 큽니다.
✅ 4. RTMP (Real-Time Messaging Protocol)
용도: 유튜브, 트위치 같은 실시간 방송 플랫폼
지연 : 중간
구현 난이도 : 중
특징: 방송 시스템용 고화질 영상 스트리밍을 지원하며, 미디어 서버(FMS, Nginx RTMP 등)가 필요합니다.
IoT 보다는 방송 시스템에 적합합니다.
이에 대해서는 MQTT 브로커에 대한 SSL 적용 등 상세 보안 설정과 더불어 다음기회에 정리하겠습니다.
'Programming > ETC' 카테고리의 다른 글
Python 서버 실시간 웹캠 영상 처리 방법 (ReactJS, WebRTC, FastAPI, OpenCV) - 미디어 통신 실시간 영상 합성 (1) | 2025.04.16 |
---|---|
IoT(Internet of Things, 사물 인터넷) 설명 및 기술 로드맵 (0) | 2025.04.14 |
[Java] TLV(Tag-Length-Value) 설명 및 해석 함수 구현 (0) | 2024.10.26 |
[Java] JVM Garbage Collector 정리 (2) | 2024.10.13 |
[Java] 자바 Thread Dump 개인정리 (0) | 2024.10.13 |