Ver3.0 ๐ MQTT ๋ธ๋ก์ปค์ ํด๋ผ์ด์ธํธ ์์ ์์ค์ฝ๋

๐ MQTT ๋ธ๋ก์ปค์ ํด๋ผ์ด์ธํธ ์์ ์์ค์ฝ๋
IoT ์ธ์์ ๋ฉ์ ์ , MQTT๋ฅผ ์ค์ ์ฝ๋๋ก ๋ง์คํฐํ๊ธฐ
์๋ , ์น๊ตฌ! ๐ ์ค๋์ IoT ๊ฐ๋ฐ์๋ผ๋ฉด ๋ฐ๋์ ์์์ผ ํ MQTT(Message Queuing Telemetry Transport)์ ๋ํด ์ด์ผ๊ธฐํด๋ณผ๊น ํด. ์์ฆ ์ค๋งํธํ, ์์จ์ฃผํ์ฐจ, ์ฐ์ ์ฉ ์ผ์ ๋ฑ ๊ฑฐ์ ๋ชจ๋ IoT ๊ธฐ๊ธฐ๋ค์ด MQTT๋ฅผ ์ฌ์ฉํ๊ณ ์๊ฑฐ๋ . ๊ทธ๋ฐ๋ฐ ๋ง์ ์์ํ๋ ค๋ฉด "์ด๋์๋ถํฐ ์๋์ผ ํ์ง?" ํ๋ ๋ง๋งํจ์ด ์์์? ๊ทธ๋์ ์ค๋์ ์ค์ ๋ก ๋์ํ๋ ์์ ์์ค์ฝ๋์ ํจ๊ป MQTT ๋ธ๋ก์ปค์ ํด๋ผ์ด์ธํธ๋ฅผ ์๋ฒฝํ๊ฒ ์ ๋ณตํด๋ณด๋ ค๊ณ ํด! ๐ฏ
๐ MQTT๊ฐ ๋ญ๊ธธ๋ ์ด๋ ๊ฒ ์ธ๊ธฐ๊ฐ ๋ง์๊น?
MQTT๋ 1999๋ IBM์์ ๊ฐ๋ฐํ ๊ฒฝ๋ ๋ฉ์์ง ํ๋กํ ์ฝ์ด์ผ. "๊ฒฝ๋"์ด๋ผ๋ ๊ฒ ํต์ฌ์ธ๋ฐ, HTTP๋ณด๋ค ํจ์ฌ ์ ์ ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ๋ฉด์๋ ์์ ์ ์ผ๋ก ๋ฉ์์ง๋ฅผ ์ ๋ฌํ ์ ์๊ฑฐ๋ . ๐ชถ ํนํ ๋คํธ์ํฌ ๋์ญํญ์ด ์ ํ์ ์ด๊ฑฐ๋ ๋ฐฐํฐ๋ฆฌ ์๋ช ์ด ์ค์ํ IoT ํ๊ฒฝ์์ ๋น์ ๋ฐํ์ง!
2. QoS(Quality of Service): ๋ฉ์์ง ์ ๋ฌ ๋ณด์ฅ ์์ค์ 0, 1, 2๋ก ์ ํ ๊ฐ๋ฅ
3. ๊ฒฝ๋ ํ๋กํ ์ฝ: ์ต์ 2๋ฐ์ดํธ์ ํค๋๋ก ํต์ ๊ฐ๋ฅ
4. ์๋ฐฉํฅ ํต์ : ํด๋ผ์ด์ธํธ ๊ฐ ์ค์๊ฐ ๋ฐ์ดํฐ ๊ตํ
5. Last Will & Testament: ํด๋ผ์ด์ธํธ ๋น์ ์ ์ข ๋ฃ ์ ์๋ ์๋ฆผ
์ค์ ๋ก ์ฌ๋ฅ๋ท ๊ฐ์ ํ๋ซํผ์์๋ ์ค์๊ฐ ์๋ฆผ ์์คํ ์ ๊ตฌ์ถํ ๋ MQTT๋ฅผ ํ์ฉํ๋ฉด ํจ์จ์ ์ด์ผ. ์ฌ์ฉ์๊ฐ ์๋ก์ด ์ฌ๋ฅ ๊ฑฐ๋ ์์ฒญ์ ๋ฐ์์ ๋ ์ฆ์ ํธ์ ์๋ฆผ์ ๋ณด๋ผ ์ ์๊ฑฐ๋ ! ๐ฒ
๐๏ธ MQTT ๋ธ๋ก์ปค ์ค์นํ๊ธฐ - Mosquitto
์, ์ด์ ๋ณธ๊ฒฉ์ ์ผ๋ก ์ค์ต์ ์์ํด๋ณผ๊น? ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ์คํ์์ค MQTT ๋ธ๋ก์ปค์ธ Mosquitto๋ฅผ ์ค์นํด๋ณด์. ๋ฌด๋ฃ์ด๋ฉด์๋ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ์ ๊ณตํ๊ฑฐ๋ ! ๐ช
๐ง Linux/Ubuntu ์ค์น
# Mosquitto ๋ธ๋ก์ปค ์ค์น
sudo apt-get update
sudo apt-get install mosquitto mosquitto-clients
# ์๋น์ค ์์
sudo systemctl start mosquitto
# ๋ถํ
์ ์๋ ์์ ์ค์
sudo systemctl enable mosquitto
# ์ํ ํ์ธ
sudo systemctl status mosquitto
๐ช Windows ์ค์น
Windows์์๋ ๊ณต์ ์น์ฌ์ดํธ(mosquitto.org)์์ ์ธ์คํจ๋ฌ๋ฅผ ๋ค์ด๋ก๋ํด์ ์ค์นํ๋ฉด ๋ผ. ์ค์น ํ ๋ช ๋ น ํ๋กฌํํธ์์ ๋ค์๊ณผ ๊ฐ์ด ์คํํ ์ ์์ด:
# Mosquitto ๋ธ๋ก์ปค ์คํ
mosquitto -v
# ์ค์ ํ์ผ๊ณผ ํจ๊ป ์คํ
mosquitto -c mosquitto.conf -v
๐ macOS ์ค์น
# Homebrew๋ฅผ ์ฌ์ฉํ ์ค์น
brew install mosquitto
# ์๋น์ค ์์
brew services start mosquitto
# ๋๋ ์ง์ ์คํ
/usr/local/sbin/mosquitto -c /usr/local/etc/mosquitto/mosquitto.conf
โ๏ธ Mosquitto ๋ธ๋ก์ปค ์ค์ ํ๊ธฐ
๊ธฐ๋ณธ ์ค์ ์ผ๋ก๋ ๋์ํ์ง๋ง, ์ค์ ํ๋ก๋์
ํ๊ฒฝ์์๋ ๋ณด์๊ณผ ์ฑ๋ฅ์ ์ํด ์ค์ ์ ์ปค์คํฐ๋ง์ด์งํด์ผ ํด. ์ค์ ํ์ผ์ ๋ณดํต /etc/mosquitto/mosquitto.conf์ ์์นํด ์์ด. ๐
# mosquitto.conf ๊ธฐ๋ณธ ์ค์ ์์
# ๋ฆฌ์ค๋ ํฌํธ ์ค์ (๊ธฐ๋ณธ๊ฐ: 1883)
listener 1883
listener 9001
protocol websockets
# ์ต๋ช
์ ์ ํ์ฉ ์ฌ๋ถ (๊ฐ๋ฐ ํ๊ฒฝ์์๋ง true ๊ถ์ฅ)
allow_anonymous false
# ํจ์ค์๋ ํ์ผ ๊ฒฝ๋ก
password_file /etc/mosquitto/passwd
# ๋ก๊ทธ ์ค์
log_dest file /var/log/mosquitto/mosquitto.log
log_type all
# ์ต๋ ์ฐ๊ฒฐ ์
max_connections 1000
# ๋ฉ์์ง ํ ํฌ๊ธฐ
max_queued_messages 1000
# ์ง์ ์ฐ๊ฒฐ ์ค์
persistence true
persistence_location /var/lib/mosquitto/
# ์๋ ์ ์ฅ ๊ฐ๊ฒฉ (์ด)
autosave_interval 1800
allow_anonymous false๋ก ์ค์ ํ๊ณ , ์ฌ์ฉ์ ์ธ์ฆ์ ํ์ฑํํด์ผ ํด. ๊ทธ๋ ์ง ์์ผ๋ฉด ๋๊ตฌ๋ ๋ธ๋ก์ปค์ ์ ์ํ ์ ์์ด์ ๋ณด์ ์ํ์ด ์ปค์ ธ!
๐ ์ฌ์ฉ์ ์ธ์ฆ ์ค์
# ํจ์ค์๋ ํ์ผ ์์ฑ ๋ฐ ์ฌ์ฉ์ ์ถ๊ฐ
sudo mosquitto_passwd -c /etc/mosquitto/passwd username1
# ์ถ๊ฐ ์ฌ์ฉ์ ๋ฑ๋ก (-c ์ต์
์ ์ธ)
sudo mosquitto_passwd /etc/mosquitto/passwd username2
# ์ฌ์ฉ์ ์ญ์
sudo mosquitto_passwd -D /etc/mosquitto/passwd username1
# ๋ธ๋ก์ปค ์ฌ์์
sudo systemctl restart mosquitto
๐ Python์ผ๋ก MQTT ํด๋ผ์ด์ธํธ ๋ง๋ค๊ธฐ
์ด์ ์ค์ ๋ก ๋์ํ๋ ํด๋ผ์ด์ธํธ๋ฅผ ๋ง๋ค์ด๋ณผ ์๊ฐ์ด์ผ! Python์ paho-mqtt ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ ๋ง ๊ฐ๋จํ๊ฒ ๊ตฌํํ ์ ์์ด. ๐จ
๐ฆ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ค์น
# pip๋ฅผ ์ฌ์ฉํ ์ค์น
pip install paho-mqtt
# ๋๋ ํน์ ๋ฒ์ ์ค์น
pip install paho-mqtt==1.6.1
๐ค Publisher ํด๋ผ์ด์ธํธ ์์
๋จผ์ ๋ฉ์์ง๋ฅผ ๋ฐํํ๋ Publisher๋ฅผ ๋ง๋ค์ด๋ณด์. ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ์ ์กํ๋ ์๋๋ฆฌ์ค๋ฅผ ๊ฐ์ ํ ๊ฒ:
import paho.mqtt.client as mqtt
import time
import json
import random
# MQTT ๋ธ๋ก์ปค ์ค์
BROKER_ADDRESS = "localhost" # ๋๋ ๋ธ๋ก์ปค IP ์ฃผ์
BROKER_PORT = 1883
TOPIC = "sensors/temperature"
CLIENT_ID = "temperature_sensor_01"
# ์ฌ์ฉ์ ์ธ์ฆ ์ ๋ณด (ํ์ํ ๊ฒฝ์ฐ)
USERNAME = "your_username"
PASSWORD = "your_password"
# ์ฝ๋ฐฑ ํจ์: ์ฐ๊ฒฐ ์ฑ๊ณต ์
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("โ
๋ธ๋ก์ปค์ ์ฑ๊ณต์ ์ผ๋ก ์ฐ๊ฒฐ๋์์ต๋๋ค!")
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ. ๋ฐํ ์ฝ๋: {rc}")
# ์ฝ๋ฐฑ ํจ์: ๋ฉ์์ง ๋ฐํ ์๋ฃ ์
def on_publish(client, userdata, mid):
print(f"๐ค ๋ฉ์์ง ๋ฐํ ์๋ฃ (Message ID: {mid})")
# ํด๋ผ์ด์ธํธ ์์ฑ
client = mqtt.Client(CLIENT_ID)
# ์ฝ๋ฐฑ ํจ์ ๋ฑ๋ก
client.on_connect = on_connect
client.on_publish = on_publish
# ์ฌ์ฉ์ ์ธ์ฆ ์ค์ (ํ์ํ ๊ฒฝ์ฐ)
client.username_pw_set(USERNAME, PASSWORD)
# ๋ธ๋ก์ปค ์ฐ๊ฒฐ
try:
client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
client.loop_start() # ๋ฐฑ๊ทธ๋ผ์ด๋ ์ค๋ ๋ ์์
# ์จ๋ ๋ฐ์ดํฐ ์ฃผ๊ธฐ์ ๋ฐํ
for i in range(10):
# ์ผ์ ๋ฐ์ดํฐ ์์ฑ (์๋ฎฌ๋ ์ด์
)
temperature = round(20 + random.uniform(-5, 5), 2)
humidity = round(50 + random.uniform(-10, 10), 2)
# JSON ํ์์ผ๋ก ๋ฐ์ดํฐ ๊ตฌ์ฑ
payload = {
"sensor_id": CLIENT_ID,
"timestamp": time.time(),
"temperature": temperature,
"humidity": humidity,
"unit": "celsius"
}
# ๋ฉ์์ง ๋ฐํ (QoS 1)
result = client.publish(
TOPIC,
json.dumps(payload),
qos=1,
retain=False
)
# ๋ฐํ ๊ฒฐ๊ณผ ํ์ธ
if result.rc == mqtt.MQTT_ERR_SUCCESS:
print(f"๐ก๏ธ ์จ๋: {temperature}ยฐC, ์ต๋: {humidity}% ๋ฐํ")
else:
print(f"โ ๏ธ ๋ฐํ ์คํจ: {result.rc}")
time.sleep(2) # 2์ด ๋๊ธฐ
# ์ ๋ฆฌ
client.loop_stop()
client.disconnect()
print("๐ ์ฐ๊ฒฐ ์ข
๋ฃ")
except Exception as e:
print(f"โ ์ค๋ฅ ๋ฐ์: {e}")
QoS 1: ์ต์ 1๋ฒ ์ ๋ฌ - ๋ฉ์์ง ์ค๋ณต ๊ฐ๋ฅํ์ง๋ง ์์ค ์์
QoS 2: ์ ํํ 1๋ฒ ์ ๋ฌ - ๊ฐ์ฅ ๋๋ฆฌ์ง๋ง ์ค๋ณต๊ณผ ์์ค ๋ชจ๋ ๋ฐฉ์ง
๐ฅ Subscriber ํด๋ผ์ด์ธํธ ์์
์ด๋ฒ์ ๋ฉ์์ง๋ฅผ ๊ตฌ๋ ํ๋ Subscriber๋ฅผ ๋ง๋ค์ด๋ณผ๊ฒ. ์ค์๊ฐ์ผ๋ก ์ผ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ์ฒ๋ฆฌํ๋ ์ฝ๋์ผ:
import paho.mqtt.client as mqtt
import json
from datetime import datetime
# MQTT ๋ธ๋ก์ปค ์ค์
BROKER_ADDRESS = "localhost"
BROKER_PORT = 1883
TOPIC = "sensors/#" # ์์ผ๋์นด๋๋ก ๋ชจ๋ ์ผ์ ๊ตฌ๋
CLIENT_ID = "data_receiver_01"
USERNAME = "your_username"
PASSWORD = "your_password"
# ์ฝ๋ฐฑ ํจ์: ์ฐ๊ฒฐ ์ฑ๊ณต ์
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("โ
๋ธ๋ก์ปค์ ์ฐ๊ฒฐ๋์์ต๋๋ค!")
# ํ ํฝ ๊ตฌ๋
client.subscribe(TOPIC, qos=1)
print(f"๐ก ํ ํฝ ๊ตฌ๋
์์: {TOPIC}")
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ. ์ฝ๋: {rc}")
# ์ฝ๋ฐฑ ํจ์: ๋ฉ์์ง ์์ ์
def on_message(client, userdata, message):
try:
# ๋ฉ์์ง ๋์ฝ๋ฉ
payload = message.payload.decode('utf-8')
topic = message.topic
print(f"\n{'='*50}")
print(f"๐ฌ ์ ๋ฉ์์ง ์์ !")
print(f"๐ ํ ํฝ: {topic}")
print(f"๐ข QoS: {message.qos}")
# JSON ํ์ฑ
data = json.loads(payload)
# ํ์์คํฌํ ๋ณํ
timestamp = datetime.fromtimestamp(data['timestamp'])
# ๋ฐ์ดํฐ ์ถ๋ ฅ
print(f"๐ ์ผ์ ID: {data['sensor_id']}")
print(f"โฐ ์๊ฐ: {timestamp.strftime('%Y-%m-%d %H:%M:%S')}")
print(f"๐ก๏ธ ์จ๋: {data['temperature']}ยฐC")
print(f"๐ง ์ต๋: {data['humidity']}%")
print(f"{'='*50}\n")
# ์ฌ๊ธฐ์ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ์ฅ, ์๋ฆผ ์ ์ก ๋ฑ ์ถ๊ฐ ์ฒ๋ฆฌ ๊ฐ๋ฅ
process_sensor_data(data)
except json.JSONDecodeError:
print(f"โ ๏ธ JSON ํ์ฑ ์คํจ: {payload}")
except Exception as e:
print(f"โ ๋ฉ์์ง ์ฒ๋ฆฌ ์ค๋ฅ: {e}")
# ์ฝ๋ฐฑ ํจ์: ๊ตฌ๋
์๋ฃ ์
def on_subscribe(client, userdata, mid, granted_qos):
print(f"โ
๊ตฌ๋
์๋ฃ! (QoS: {granted_qos[0]})")
# ์ผ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํจ์
def process_sensor_data(data):
"""
์์ ํ ์ผ์ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ํจ์
์ค์ ์ ํ๋ฆฌ์ผ์ด์
์์๋ ์ฌ๊ธฐ์ DB ์ ์ฅ, ์๋ฆผ ๋ฑ์ ์ํ
"""
# ์จ๋ ์๊ณ๊ฐ ์ฒดํฌ
if data['temperature'] > 30:
print("๐ฅ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!")
elif data['temperature'] < 10:
print("โ๏ธ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!")
# ์ต๋ ์๊ณ๊ฐ ์ฒดํฌ
if data['humidity'] > 70:
print("๐ฆ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!")
elif data['humidity'] < 30:
print("๐๏ธ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!")
# ํด๋ผ์ด์ธํธ ์์ฑ
client = mqtt.Client(CLIENT_ID)
# ์ฝ๋ฐฑ ํจ์ ๋ฑ๋ก
client.on_connect = on_connect
client.on_message = on_message
client.on_subscribe = on_subscribe
# ์ฌ์ฉ์ ์ธ์ฆ
client.username_pw_set(USERNAME, PASSWORD)
# ๋ธ๋ก์ปค ์ฐ๊ฒฐ ๋ฐ ๋ฃจํ ์์
try:
client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
print("๐ ๋ฉ์์ง ์์ ๋๊ธฐ ์ค... (Ctrl+C๋ก ์ข
๋ฃ)")
client.loop_forever() # ๋ฌดํ ๋ฃจํ๋ก ๋ฉ์์ง ์์
except KeyboardInterrupt:
print("\nโน๏ธ ์ฌ์ฉ์์ ์ํด ์ข
๋ฃ๋์์ต๋๋ค.")
client.disconnect()
except Exception as e:
print(f"โ ์ค๋ฅ ๋ฐ์: {e}")
โ Java๋ก MQTT ํด๋ผ์ด์ธํธ ๊ตฌํํ๊ธฐ
Java ๊ฐ๋ฐ์๋ผ๋ฉด? Eclipse Paho Java ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ผ! ์๋๋ก์ด๋ ์ฑ ๊ฐ๋ฐ์๋ ํ์ฉํ ์ ์์ด์ ์ ๋ง ์ ์ฉํ์ง. ๐ฑ
๐ฆ Maven ์์กด์ฑ ์ถ๊ฐ
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>1.2.5</version>
</dependency>
๐ค Java Publisher ์์
import org.eclipse.paho.client.mqttv3.*;
import org.json.JSONObject;
import java.util.Random;
public class MqttPublisher {
private static final String BROKER = "tcp://localhost:1883";
private static final String CLIENT_ID = "JavaPublisher";
private static final String TOPIC = "sensors/temperature";
private static final String USERNAME = "your_username";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
try {
// MQTT ํด๋ผ์ด์ธํธ ์์ฑ
MqttClient client = new MqttClient(BROKER, CLIENT_ID);
// ์ฐ๊ฒฐ ์ต์
์ค์
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
options.setCleanSession(true);
options.setKeepAliveInterval(60);
options.setAutomaticReconnect(true);
// ์ฝ๋ฐฑ ์ค์
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
System.out.println("โ ์ฐ๊ฒฐ ๋๊น: " + cause.getMessage());
}
@Override
public void messageArrived(String topic, MqttMessage message) {
// Publisher๋ ๋ฉ์์ง๋ฅผ ๋ฐ์ง ์์
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
System.out.println("โ
๋ฉ์์ง ์ ๋ฌ ์๋ฃ");
}
});
// ๋ธ๋ก์ปค ์ฐ๊ฒฐ
System.out.println("๐ ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ค...");
client.connect(options);
System.out.println("โ
์ฐ๊ฒฐ ์ฑ๊ณต!");
Random random = new Random();
// 10๋ฒ ๋ฉ์์ง ๋ฐํ
for (int i = 0; i < 10; i++) {
// ์ผ์ ๋ฐ์ดํฐ ์์ฑ
double temperature = 20 + (random.nextDouble() * 10 - 5);
double humidity = 50 + (random.nextDouble() * 20 - 10);
// JSON ๊ฐ์ฒด ์์ฑ
JSONObject payload = new JSONObject();
payload.put("sensor_id", CLIENT_ID);
payload.put("timestamp", System.currentTimeMillis() / 1000.0);
payload.put("temperature", Math.round(temperature * 100.0) / 100.0);
payload.put("humidity", Math.round(humidity * 100.0) / 100.0);
payload.put("unit", "celsius");
// MQTT ๋ฉ์์ง ์์ฑ
MqttMessage message = new MqttMessage(payload.toString().getBytes());
message.setQos(1);
message.setRetained(false);
// ๋ฉ์์ง ๋ฐํ
client.publish(TOPIC, message);
System.out.println("๐ค ๋ฐํ: " + payload.toString());
Thread.sleep(2000); // 2์ด ๋๊ธฐ
}
// ์ฐ๊ฒฐ ์ข
๋ฃ
client.disconnect();
System.out.println("๐ ์ฐ๊ฒฐ ์ข
๋ฃ");
} catch (MqttException e) {
System.err.println("โ MQTT ์ค๋ฅ: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.err.println("โ ์ธํฐ๋ฝํธ ์ค๋ฅ: " + e.getMessage());
}
}
}
๐ฅ Java Subscriber ์์
import org.eclipse.paho.client.mqttv3.*;
import org.json.JSONObject;
import java.text.SimpleDateFormat;
import java.util.Date;
public class MqttSubscriber {
private static final String BROKER = "tcp://localhost:1883";
private static final String CLIENT_ID = "JavaSubscriber";
private static final String TOPIC = "sensors/#";
private static final String USERNAME = "your_username";
private static final String PASSWORD = "your_password";
public static void main(String[] args) {
try {
// MQTT ํด๋ผ์ด์ธํธ ์์ฑ
MqttClient client = new MqttClient(BROKER, CLIENT_ID);
// ์ฐ๊ฒฐ ์ต์
์ค์
MqttConnectOptions options = new MqttConnectOptions();
options.setUserName(USERNAME);
options.setPassword(PASSWORD.toCharArray());
options.setCleanSession(true);
options.setKeepAliveInterval(60);
options.setAutomaticReconnect(true);
// ์ฝ๋ฐฑ ์ค์
client.setCallback(new MqttCallback() {
@Override
public void connectionLost(Throwable cause) {
System.out.println("โ ์ฐ๊ฒฐ ๋๊น: " + cause.getMessage());
System.out.println("๐ ์๋ ์ฌ์ฐ๊ฒฐ ์๋ ์ค...");
}
@Override
public void messageArrived(String topic, MqttMessage message) {
try {
String payload = new String(message.getPayload());
System.out.println("\n" + "=".repeat(50));
System.out.println("๐ฌ ์ ๋ฉ์์ง ์์ !");
System.out.println("๐ ํ ํฝ: " + topic);
System.out.println("๐ข QoS: " + message.getQos());
// JSON ํ์ฑ
JSONObject data = new JSONObject(payload);
// ํ์์คํฌํ ๋ณํ
long timestamp = (long)(data.getDouble("timestamp") * 1000);
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String dateTime = sdf.format(new Date(timestamp));
// ๋ฐ์ดํฐ ์ถ๋ ฅ
System.out.println("๐ ์ผ์ ID: " + data.getString("sensor_id"));
System.out.println("โฐ ์๊ฐ: " + dateTime);
System.out.println("๐ก๏ธ ์จ๋: " + data.getDouble("temperature") + "ยฐC");
System.out.println("๐ง ์ต๋: " + data.getDouble("humidity") + "%");
System.out.println("=".repeat(50) + "\n");
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ
processSensorData(data);
} catch (Exception e) {
System.err.println("โ ๏ธ ๋ฉ์์ง ์ฒ๋ฆฌ ์ค๋ฅ: " + e.getMessage());
}
}
@Override
public void deliveryComplete(IMqttDeliveryToken token) {
// Subscriber๋ ๋ฉ์์ง๋ฅผ ๋ณด๋ด์ง ์์
}
});
// ๋ธ๋ก์ปค ์ฐ๊ฒฐ
System.out.println("๐ ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ค...");
client.connect(options);
System.out.println("โ
์ฐ๊ฒฐ ์ฑ๊ณต!");
// ํ ํฝ ๊ตฌ๋
client.subscribe(TOPIC, 1);
System.out.println("๐ก ํ ํฝ ๊ตฌ๋
: " + TOPIC);
System.out.println("๐ ๋ฉ์์ง ์์ ๋๊ธฐ ์ค...\n");
// ํ๋ก๊ทธ๋จ ๊ณ์ ์คํ
while (true) {
Thread.sleep(1000);
}
} catch (MqttException e) {
System.err.println("โ MQTT ์ค๋ฅ: " + e.getMessage());
e.printStackTrace();
} catch (InterruptedException e) {
System.err.println("โ ์ธํฐ๋ฝํธ ์ค๋ฅ: " + e.getMessage());
}
}
private static void processSensorData(JSONObject data) {
double temperature = data.getDouble("temperature");
double humidity = data.getDouble("humidity");
// ์จ๋ ์๊ณ๊ฐ ์ฒดํฌ
if (temperature > 30) {
System.out.println("๐ฅ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!");
} else if (temperature < 10) {
System.out.println("โ๏ธ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!");
}
// ์ต๋ ์๊ณ๊ฐ ์ฒดํฌ
if (humidity > 70) {
System.out.println("๐ฆ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!");
} else if (humidity < 30) {
System.out.println("๐๏ธ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!");
}
}
}
๐ Node.js๋ก MQTT ํด๋ผ์ด์ธํธ ๋ง๋ค๊ธฐ
์น ๊ฐ๋ฐ์๋ผ๋ฉด Node.js๊ฐ ์ต์ํ๊ฒ ์ง? JavaScript๋ก๋ MQTT ํด๋ผ์ด์ธํธ๋ฅผ ์ฝ๊ฒ ๋ง๋ค ์ ์์ด. ํนํ ์น์์ผ์ ํตํ ๋ธ๋ผ์ฐ์ ์ฐ๋๋ ๊ฐ๋ฅํด์ ์ค์๊ฐ ๋์๋ณด๋ ๊ตฌ์ถ์ ์ต์ ์ด์ผ! ๐
๐ฆ ํจํค์ง ์ค์น
# npm์ ์ฌ์ฉํ ์ค์น
npm install mqtt
# ๋๋ yarn ์ฌ์ฉ
yarn add mqtt
๐ค Node.js Publisher ์์
const mqtt = require('mqtt');
// MQTT ๋ธ๋ก์ปค ์ค์
const BROKER_URL = 'mqtt://localhost:1883';
const CLIENT_ID = 'NodePublisher_' + Math.random().toString(16).substr(2, 8);
const TOPIC = 'sensors/temperature';
// ์ฐ๊ฒฐ ์ต์
const options = {
clientId: CLIENT_ID,
username: 'your_username',
password: 'your_password',
clean: true,
keepalive: 60,
reconnectPeriod: 1000
};
// ๋ธ๋ก์ปค ์ฐ๊ฒฐ
console.log('๐ ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ค...');
const client = mqtt.connect(BROKER_URL, options);
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฐ๊ฒฐ ์ฑ๊ณต
client.on('connect', () => {
console.log('โ
๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ฑ๊ณต!');
console.log(`๐ ํด๋ผ์ด์ธํธ ID: ${CLIENT_ID}`);
let count = 0;
const maxCount = 10;
// 2์ด๋ง๋ค ์ผ์ ๋ฐ์ดํฐ ๋ฐํ
const interval = setInterval(() => {
// ์ผ์ ๋ฐ์ดํฐ ์์ฑ
const temperature = (20 + (Math.random() * 10 - 5)).toFixed(2);
const humidity = (50 + (Math.random() * 20 - 10)).toFixed(2);
// JSON ํ์ด๋ก๋ ์์ฑ
const payload = JSON.stringify({
sensor_id: CLIENT_ID,
timestamp: Date.now() / 1000,
temperature: parseFloat(temperature),
humidity: parseFloat(humidity),
unit: 'celsius'
});
// ๋ฉ์์ง ๋ฐํ
client.publish(TOPIC, payload, { qos: 1, retain: false }, (error) => {
if (error) {
console.error('โ ๋ฐํ ์คํจ:', error);
} else {
console.log(`๐ค ๋ฐํ [${count + 1}/${maxCount}]: ์จ๋ ${temperature}ยฐC, ์ต๋ ${humidity}%`);
}
});
count++;
// 10๋ฒ ๋ฐํ ํ ์ข
๋ฃ
if (count >= maxCount) {
clearInterval(interval);
setTimeout(() => {
client.end();
console.log('๐ ์ฐ๊ฒฐ ์ข
๋ฃ');
}, 1000);
}
}, 2000);
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ค๋ฅ ๋ฐ์
client.on('error', (error) => {
console.error('โ ์ค๋ฅ ๋ฐ์:', error);
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฐ๊ฒฐ ๋๊น
client.on('close', () => {
console.log('๐ ์ฐ๊ฒฐ์ด ์ข
๋ฃ๋์์ต๋๋ค.');
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฌ์ฐ๊ฒฐ ์๋
client.on('reconnect', () => {
console.log('๐ ์ฌ์ฐ๊ฒฐ ์๋ ์ค...');
});
๐ฅ Node.js Subscriber ์์
const mqtt = require('mqtt');
// MQTT ๋ธ๋ก์ปค ์ค์
const BROKER_URL = 'mqtt://localhost:1883';
const CLIENT_ID = 'NodeSubscriber_' + Math.random().toString(16).substr(2, 8);
const TOPIC = 'sensors/#';
// ์ฐ๊ฒฐ ์ต์
const options = {
clientId: CLIENT_ID,
username: 'your_username',
password: 'your_password',
clean: true,
keepalive: 60,
reconnectPeriod: 1000
};
// ๋ธ๋ก์ปค ์ฐ๊ฒฐ
console.log('๐ ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ค...');
const client = mqtt.connect(BROKER_URL, options);
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฐ๊ฒฐ ์ฑ๊ณต
client.on('connect', () => {
console.log('โ
๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ฑ๊ณต!');
console.log(`๐ ํด๋ผ์ด์ธํธ ID: ${CLIENT_ID}`);
// ํ ํฝ ๊ตฌ๋
client.subscribe(TOPIC, { qos: 1 }, (error) => {
if (error) {
console.error('โ ๊ตฌ๋
์คํจ:', error);
} else {
console.log(`๐ก ํ ํฝ ๊ตฌ๋
: ${TOPIC}`);
console.log('๐ ๋ฉ์์ง ์์ ๋๊ธฐ ์ค...\n');
}
});
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ๋ฉ์์ง ์์
client.on('message', (topic, message) => {
try {
const payload = message.toString();
console.log('\n' + '='.repeat(50));
console.log('๐ฌ ์ ๋ฉ์์ง ์์ !');
console.log(`๐ ํ ํฝ: ${topic}`);
// JSON ํ์ฑ
const data = JSON.parse(payload);
// ํ์์คํฌํ ๋ณํ
const date = new Date(data.timestamp * 1000);
const dateTime = date.toLocaleString('ko-KR', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
hour: '2-digit',
minute: '2-digit',
second: '2-digit'
});
// ๋ฐ์ดํฐ ์ถ๋ ฅ
console.log(`๐ ์ผ์ ID: ${data.sensor_id}`);
console.log(`โฐ ์๊ฐ: ${dateTime}`);
console.log(`๐ก๏ธ ์จ๋: ${data.temperature}ยฐC`);
console.log(`๐ง ์ต๋: ${data.humidity}%`);
console.log('='.repeat(50) + '\n');
// ๋ฐ์ดํฐ ์ฒ๋ฆฌ
processSensorData(data);
} catch (error) {
console.error('โ ๏ธ ๋ฉ์์ง ์ฒ๋ฆฌ ์ค๋ฅ:', error.message);
}
});
// ์ผ์ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ํจ์
function processSensorData(data) {
const { temperature, humidity } = data;
// ์จ๋ ์๊ณ๊ฐ ์ฒดํฌ
if (temperature > 30) {
console.log('๐ฅ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!');
// ์ฌ๊ธฐ์ ์๋ฆผ ์ ์ก, ๋ก๊ทธ ๊ธฐ๋ก ๋ฑ ์ํ
} else if (temperature < 10) {
console.log('โ๏ธ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!');
}
// ์ต๋ ์๊ณ๊ฐ ์ฒดํฌ
if (humidity > 70) {
console.log('๐ฆ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!');
} else if (humidity < 30) {
console.log('๐๏ธ ๊ฒฝ๊ณ : ์ต๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!');
}
}
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ค๋ฅ ๋ฐ์
client.on('error', (error) => {
console.error('โ ์ค๋ฅ ๋ฐ์:', error);
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฐ๊ฒฐ ๋๊น
client.on('close', () => {
console.log('๐ ์ฐ๊ฒฐ์ด ์ข
๋ฃ๋์์ต๋๋ค.');
});
// ์ด๋ฒคํธ ํธ๋ค๋ฌ: ์ฌ์ฐ๊ฒฐ ์๋
client.on('reconnect', () => {
console.log('๐ ์ฌ์ฐ๊ฒฐ ์๋ ์ค...');
});
// ํ๋ก์ธ์ค ์ข
๋ฃ ์ฒ๋ฆฌ
process.on('SIGINT', () => {
console.log('\nโน๏ธ ํ๋ก๊ทธ๋จ ์ข
๋ฃ ์ค...');
client.end();
process.exit();
});
๐จ ์น ๋ธ๋ผ์ฐ์ ์์ MQTT ์ฌ์ฉํ๊ธฐ
์น ๋ธ๋ผ์ฐ์ ์์ ์ง์ MQTT๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด WebSocket ํ๋กํ ์ฝ์ ํ์ฉํด์ผ ํด. ์ค์๊ฐ ๋์๋ณด๋๋ ๋ชจ๋ํฐ๋ง ์์คํ ์ ๋ง๋ค ๋ ์ ๋ง ์ ์ฉํ์ง! ๐ฅ๏ธ
listener 9001๊ณผ protocol websockets๋ฅผ ์ถ๊ฐํด์ผ ํด!
๐ HTML + JavaScript ์์
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MQTT ์ค์๊ฐ ๋ชจ๋ํฐ๋ง</title>
<script src="https://unpkg.com/mqtt/dist/mqtt.min.js"></script>
<style>
body {
font-family: 'Segoe UI', sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
background: #f5f6fa;
}
.container {
background: white;
padding: 30px;
border-radius: 15px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
}
.status {
padding: 15px;
border-radius: 8px;
margin: 15px 0;
font-weight: bold;
}
.connected {
background: #d4edda;
color: #155724;
}
.disconnected {
background: #f8d7da;
color: #721c24;
}
.data-box {
background: #e3f2fd;
padding: 20px;
border-radius: 10px;
margin: 15px 0;
}
.temperature {
font-size: 48px;
color: #ff6b6b;
font-weight: bold;
}
.humidity {
font-size: 48px;
color: #4ecdc4;
font-weight: bold;
}
button {
background: #6c5ce7;
color: white;
border: none;
padding: 12px 24px;
border-radius: 8px;
cursor: pointer;
font-size: 16px;
margin: 5px;
}
button:hover {
background: #5f4dd1;
}
#log {
background: #2d3436;
color: #dfe6e9;
padding: 15px;
border-radius: 8px;
max-height: 300px;
overflow-y: auto;
font-family: 'Courier New', monospace;
font-size: 13px;
}
</style>
</head>
<body>
<div class="container">
<h1 style="text-align:center; color:#6c5ce7;">๐ก๏ธ MQTT ์ค์๊ฐ ์ผ์ ๋ชจ๋ํฐ๋ง</h1>
<div id="status" class="status disconnected">
โ ์ฐ๊ฒฐ ์๋จ
</div>
<div style="text-align:center;">
<button onclick="connect()">๐ ์ฐ๊ฒฐ</button>
<button onclick="disconnect()">๐ ์ฐ๊ฒฐ ํด์ </button>
</div>
<div class="data-box">
<h3>๐ ์ต์ ์ผ์ ๋ฐ์ดํฐ</h3>
<div style="display:flex; justify-content:space-around;">
<div style="text-align:center;">
<div>๐ก๏ธ ์จ๋</div>
<div class="temperature" id="temperature">--</div>
<div>ยฐC</div>
</div>
<div style="text-align:center;">
<div>๐ง ์ต๋</div>
<div class="humidity" id="humidity">--</div>
<div>%</div>
</div>
</div>
<div style="margin-top:15px; font-size:14px; color:#666;">
<div>๐ ์ผ์: <span id="sensorId">-</span></div>
<div>โฐ ์๊ฐ: <span id="timestamp">-</span></div>
</div>
</div>
<h3>๐ ๋ก๊ทธ</h3>
<div id="log"></div>
</div>
<script>
let client = null;
const BROKER_URL = 'ws://localhost:9001'; // WebSocket ํฌํธ
const TOPIC = 'sensors/#';
function addLog(message) {
const log = document.getElementById('log');
const time = new Date().toLocaleTimeString();
log.innerHTML += `[${time}] ${message}<br>`;
log.scrollTop = log.scrollHeight;
}
function connect() {
if (client && client.connected) {
addLog('โ ๏ธ ์ด๋ฏธ ์ฐ๊ฒฐ๋์ด ์์ต๋๋ค.');
return;
}
const clientId = 'WebClient_' + Math.random().toString(16).substr(2, 8);
addLog('๐ ๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ค...');
client = mqtt.connect(BROKER_URL, {
clientId: clientId,
username: 'your_username',
password: 'your_password',
clean: true,
keepalive: 60
});
client.on('connect', () => {
addLog('โ
๋ธ๋ก์ปค ์ฐ๊ฒฐ ์ฑ๊ณต!');
document.getElementById('status').className = 'status connected';
document.getElementById('status').textContent = 'โ
์ฐ๊ฒฐ๋จ';
client.subscribe(TOPIC, { qos: 1 }, (error) => {
if (error) {
addLog('โ ๊ตฌ๋
์คํจ: ' + error);
} else {
addLog('๐ก ํ ํฝ ๊ตฌ๋
: ' + TOPIC);
}
});
});
client.on('message', (topic, message) => {
try {
const data = JSON.parse(message.toString());
// UI ์
๋ฐ์ดํธ
document.getElementById('temperature').textContent =
data.temperature.toFixed(1);
document.getElementById('humidity').textContent =
data.humidity.toFixed(1);
document.getElementById('sensorId').textContent =
data.sensor_id;
const date = new Date(data.timestamp * 1000);
document.getElementById('timestamp').textContent =
date.toLocaleString('ko-KR');
addLog(`๐ฌ ๋ฉ์์ง ์์ : ์จ๋ ${data.temperature.toFixed(1)}ยฐC, ` +
`์ต๋ ${data.humidity.toFixed(1)}%`);
// ๊ฒฝ๊ณ ์ฒดํฌ
if (data.temperature > 30) {
addLog('๐ฅ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋์ต๋๋ค!');
} else if (data.temperature < 10) {
addLog('โ๏ธ ๊ฒฝ๊ณ : ์จ๋๊ฐ ๋๋ฌด ๋ฎ์ต๋๋ค!');
}
} catch (error) {
addLog('โ ๏ธ ๋ฉ์์ง ํ์ฑ ์ค๋ฅ: ' + error.message);
}
});
client.on('error', (error) => {
addLog('โ ์ค๋ฅ: ' + error.message);
});
client.on('close', () => {
addLog('๐ ์ฐ๊ฒฐ ์ข
๋ฃ');
document.getElementById('status').className = 'status disconnected';
document.getElementById('status').textContent = 'โ ์ฐ๊ฒฐ ์๋จ';
});
}
function disconnect() {
if (client && client.connected) {
client.end();
addLog('๐ ์ฐ๊ฒฐ ํด์ ์์ฒญ');
} else {
addLog('โ ๏ธ ์ฐ๊ฒฐ๋์ด ์์ง ์์ต๋๋ค.');
}
}
// ํ์ด์ง ๋ก๋ ์ ์๋ ์ฐ๊ฒฐ
window.addEventListener('load', () => {
addLog('๐ ํ์ด์ง ๋ก๋ ์๋ฃ');
});
</script>
</body>
</html>
๐ MQTT ๋ณด์ ๊ฐํํ๊ธฐ
ํ๋ก๋์ ํ๊ฒฝ์์๋ ๋ณด์์ด ์ ๋ง ์ค์ํด! ๊ธฐ๋ณธ ์ค์ ์ผ๋ก๋ ๋๊ตฌ๋ ์ ๊ทผํ ์ ์์ด์ ์ํํ๊ฑฐ๋ . TLS/SSL ์ํธํ์ ์ธ์ฆ์ ๋ฐ๋์ ์ ์ฉํด์ผ ํด. ๐ก๏ธ
๐ TLS/SSL ์ธ์ฆ์ ์์ฑ
# OpenSSL์ ์ฌ์ฉํ ์์ฒด ์๋ช
์ธ์ฆ์ ์์ฑ
# 1. CA(Certificate Authority) ํค ์์ฑ
openssl genrsa -out ca.key 2048
# 2. CA ์ธ์ฆ์ ์์ฑ (10๋
์ ํจ)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt
# 3. ์๋ฒ ํค ์์ฑ
openssl genrsa -out server.key 2048
# 4. ์๋ฒ ์ธ์ฆ์ ์๋ช
์์ฒญ(CSR) ์์ฑ
openssl req -new -key server.key -out server.csr
# 5. CA๋ก ์๋ฒ ์ธ์ฆ์ ์๋ช
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt -days 3650
# ํ์ผ ๊ถํ ์ค์
chmod 600 *.key
chmod 644 *.crt
โ๏ธ TLS ํ์ฑํ ์ค์
# mosquitto.conf์ TLS ์ค์ ์ถ๊ฐ
# ์ผ๋ฐ ํฌํธ ๋นํ์ฑํ
#listener 1883
# TLS ํฌํธ ํ์ฑํ
listener 8883
protocol mqtt
# ์ธ์ฆ์ ๊ฒฝ๋ก ์ค์
cafile /etc/mosquitto/certs/ca.crt
certfile /etc/mosquitto/certs/server.crt
keyfile /etc/mosquitto/certs/server.key
# TLS ๋ฒ์ ์ค์
tls_version tlsv1.2
# ํด๋ผ์ด์ธํธ ์ธ์ฆ ์๊ตฌ (์ ํ์ฌํญ)
require_certificate false
# ์ฌ์ฉ์ ์ธ์ฆ ํ์
allow_anonymous false
password_file /etc/mosquitto/passwd
๐ Python์์ TLS ์ฐ๊ฒฐ
import paho.mqtt.client as mqtt
import ssl
BROKER_ADDRESS = "your-broker.com"
BROKER_PORT = 8883
CLIENT_ID = "SecureClient"
# TLS ์ค์
def on_connect(client, userdata, flags, rc):
if rc == 0:
print("โ
๋ณด์ ์ฐ๊ฒฐ ์ฑ๊ณต!")
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ: {rc}")
client = mqtt.Client(CLIENT_ID)
client.on_connect = on_connect
# ์ฌ์ฉ์ ์ธ์ฆ
client.username_pw_set("username", "password")
# TLS/SSL ์ค์
client.tls_set(
ca_certs="/path/to/ca.crt",
certfile="/path/to/client.crt", # ํด๋ผ์ด์ธํธ ์ธ์ฆ์ (์ ํ)
keyfile="/path/to/client.key", # ํด๋ผ์ด์ธํธ ํค (์ ํ)
cert_reqs=ssl.CERT_REQUIRED,
tls_version=ssl.PROTOCOL_TLSv1_2,
ciphers=None
)
# ์ธ์ฆ์ ๊ฒ์ฆ ๋นํ์ฑํ (๊ฐ๋ฐ ํ๊ฒฝ์์๋ง!)
# client.tls_insecure_set(True)
# ์ฐ๊ฒฐ
client.connect(BROKER_ADDRESS, BROKER_PORT, keepalive=60)
client.loop_forever()
๐ฏ MQTT ํ ํฝ ์ค๊ณ ๋ฒ ์คํธ ํ๋ํฐ์ค
ํ ํฝ ๊ตฌ์กฐ๋ฅผ ์ ์ค๊ณํ๋ ๊ฒ ์ ๋ง ์ค์ํด! ๋์ค์ ์์คํ ์ด ์ปค์ก์ ๋ ๊ด๋ฆฌํ๊ธฐ ์ฝ๋๋ก ์ฒ์๋ถํฐ ์ฒด๊ณ์ ์ผ๋ก ๋ง๋ค์ด์ผ ํ๊ฑฐ๋ . ๐
company/location/building/floor/room/device/sensorsmart-home/living-room/temperaturefactory/line-1/machine-5/statusvehicle/car-123/gps/locationโ ๋์ ํ ํฝ ๊ตฌ์กฐ ์์
temp (๋๋ฌด ์ผ๋ฐ์ )sensor_data_123_temp_living_room (์ธ๋์ค์ฝ์ด ๋จ์ฉ)home/room/device/sensor/type/unit/value (๋๋ฌด ๊น์)๐ ํ ํฝ ์ค๊ณ ์์น
1. ๊ณ์ธต ๊ตฌ์กฐ ์ฌ์ฉ: ์ฌ๋์(/)๋ก ๋
ผ๋ฆฌ์ ๊ณ์ธต ๊ตฌ๋ถ
2. ๋ช
ํํ ๋ค์ด๋ฐ: ๋๊ฐ ๋ด๋ ์ดํดํ ์ ์๋ ์ด๋ฆ ์ฌ์ฉ
3. ์ ์ ํ ๊น์ด: 3~5๋จ๊ณ๊ฐ ์ ๋น, ๋๋ฌด ๊น์ผ๋ฉด ๋ณต์ก
4. ์๋ฌธ์ ์ฌ์ฉ: ๋์๋ฌธ์ ํผ์ฉ ํผํ๊ธฐ
5. ์์ผ๋์นด๋ ๊ณ ๋ ค: ๊ตฌ๋
ํจํด์ ์๊ฐํ๋ฉฐ ์ค๊ณ
6. ํ์ฅ์ฑ: ๋ฏธ๋์ ์ถ๊ฐ ์๊ตฌ์ฌํญ ๊ณ ๋ ค
๐ ์์ผ๋์นด๋ ์ฌ์ฉ๋ฒ
| ์์ผ๋์นด๋ | ์๋ฏธ | ์์ | ๋งค์นญ๋๋ ํ ํฝ |
|---|---|---|---|
+ |
๋จ์ผ ๋ ๋ฒจ | home/+/temperature |
home/living-room/temperaturehome/bedroom/temperature |
# |
๋ค์ค ๋ ๋ฒจ | home/# |
home/living-room/temperaturehome/bedroom/light/status |
| ์กฐํฉ | ํผํฉ ์ฌ์ฉ | factory/+/machine/# |
factory/line-1/machine/statusfactory/line-2/machine/temp/sensor-1 |
# ์์ผ๋์นด๋๋ ํ ํฝ์ ๋ง์ง๋ง์๋ง ์ฌ์ฉํ ์ ์์ด. home/#/temperature๋ ์๋ชป๋ ์ฌ์ฉ๋ฒ์ด์ผ!
โก ์ฑ๋ฅ ์ต์ ํ ํ
MQTT๋ ๊ฒฝ๋ ํ๋กํ ์ฝ์ด์ง๋ง, ๋๊ท๋ชจ ์์คํ ์์๋ ์ต์ ํ๊ฐ ํ์ํด. ๋ช ๊ฐ์ง ์ค์ ํ์ ๊ณต์ ํ ๊ฒ! ๐
โข ์ค์๊ฐ ์ผ์ ๋ฐ์ดํฐ: QoS 0 (๋น ๋ฅธ ์ ์ก)
โข ์ค์ํ ๋ช ๋ น: QoS 1 (์ต์ 1๋ฒ ์ ๋ฌ)
โข ๊ธ์ต ๊ฑฐ๋: QoS 2 (์ ํํ 1๋ฒ)
2. Keep Alive ์ค์
โข ๋ชจ๋ฐ์ผ: 60~120์ด (๋ฐฐํฐ๋ฆฌ ์ ์ฝ)
โข ์๋ฒ: 30~60์ด (๋น ๋ฅธ ์ฐ๊ฒฐ ๊ฐ์ง)
โข IoT ๋๋ฐ์ด์ค: 120~300์ด (๋คํธ์ํฌ ์ ์ฝ)
3. Clean Session
โข true: ๋ฉ๋ชจ๋ฆฌ ์ ์ฝ, ์ผ์์ ์ฐ๊ฒฐ
โข false: ์คํ๋ผ์ธ ๋ฉ์์ง ์์ , ์ง์ ์ฐ๊ฒฐ
4. ๋ฉ์์ง ํฌ๊ธฐ
โข ์ต๋ํ ์๊ฒ ์ ์ง (JSON ์์ถ, ๋ถํ์ํ ํ๋ ์ ๊ฑฐ)
โข ๋ฐ์ด๋๋ฆฌ ํ๋กํ ์ฝ ๊ณ ๋ ค (Protocol Buffers, MessagePack)
5. ์ฐ๊ฒฐ ํ๋ง
โข ํด๋ผ์ด์ธํธ ์ฌ์ฌ์ฉ
โข ๋ถํ์ํ ์ฌ์ฐ๊ฒฐ ๋ฐฉ์ง
6. ํ ํฝ ๊ตฌ๋ ์ต์ ํ
โข ํ์ํ ํ ํฝ๋ง ๊ตฌ๋
โข ์์ผ๋์นด๋ ๋จ์ฉ ์์
๐ ๋ฉ์์ง ์์ถ ์์
import paho.mqtt.client as mqtt
import json
import gzip
def compress_payload(data):
"""JSON ๋ฐ์ดํฐ๋ฅผ ์์ถ"""
json_str = json.dumps(data)
compressed = gzip.compress(json_str.encode('utf-8'))
return compressed
def decompress_payload(compressed_data):
"""์์ถ๋ ๋ฐ์ดํฐ๋ฅผ ํด์ """
decompressed = gzip.decompress(compressed_data)
json_str = decompressed.decode('utf-8')
return json.loads(json_str)
# Publisher
client = mqtt.Client("CompressedPublisher")
client.connect("localhost", 1883)
data = {
"sensor_id": "sensor_001",
"timestamp": 1234567890,
"readings": [
{"temp": 25.5, "humidity": 60.2},
{"temp": 25.6, "humidity": 60.1},
# ... ๋ง์ ๋ฐ์ดํฐ
]
}
# ์์ถ ์ ํ ํฌ๊ธฐ ๋น๊ต
original = json.dumps(data).encode('utf-8')
compressed = compress_payload(data)
print(f"์๋ณธ ํฌ๊ธฐ: {len(original)} bytes")
print(f"์์ถ ํฌ๊ธฐ: {len(compressed)} bytes")
print(f"์์ถ๋ฅ : {(1 - len(compressed)/len(original)) * 100:.1f}%")
# ์์ถ๋ ๋ฐ์ดํฐ ๋ฐํ
client.publish("sensors/compressed", compressed)
# Subscriber
def on_message(client, userdata, message):
compressed_data = message.payload
data = decompress_payload(compressed_data)
print(f"์์ ๋ฐ์ดํฐ: {data}")
๐ ๋๋ฒ๊น ๊ณผ ๋ชจ๋ํฐ๋ง
๊ฐ๋ฐํ๋ค ๋ณด๋ฉด ๋ฌธ์ ๊ฐ ์๊ธฐ๊ธฐ ๋ง๋ จ์ด์ผ. MQTT ํต์ ์ ํจ๊ณผ์ ์ผ๋ก ๋๋ฒ๊น ํ๋ ๋ฐฉ๋ฒ์ ์์๋ณด์! ๐
๐ ๏ธ Mosquitto ํด๋ผ์ด์ธํธ ๋๊ตฌ
# ๋ฉ์์ง ๊ตฌ๋
(๋ชจ๋ ํ ํฝ)
mosquitto_sub -h localhost -t '#' -v
# ํน์ ํ ํฝ ๊ตฌ๋
mosquitto_sub -h localhost -t 'sensors/temperature' -v
# ์ฌ์ฉ์ ์ธ์ฆ๊ณผ ํจ๊ป ๊ตฌ๋
mosquitto_sub -h localhost -t '#' -u username -P password -v
# ๋ฉ์์ง ๋ฐํ
mosquitto_pub -h localhost -t 'test/topic' -m 'Hello MQTT!'
# JSON ๋ฉ์์ง ๋ฐํ
mosquitto_pub -h localhost -t 'sensors/temp' \
-m '{"temperature": 25.5, "unit": "celsius"}'
# QoS ์ค์ ๊ณผ ํจ๊ป ๋ฐํ
mosquitto_pub -h localhost -t 'important/data' -m 'Critical' -q 2
# Retain ํ๋๊ทธ์ ํจ๊ป ๋ฐํ
mosquitto_pub -h localhost -t 'status/online' -m 'true' -r
๐ฑ MQTT ํด๋ผ์ด์ธํธ ์ฑ
GUI ๋๊ตฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๋ ํธํ๊ฒ ํ
์คํธํ ์ ์์ด:
โข MQTT Explorer: Windows/Mac/Linux์ฉ ๋ฌด๋ฃ ๋๊ตฌ, ํ ํฝ ํธ๋ฆฌ ์๊ฐํ
โข MQTT.fx: Java ๊ธฐ๋ฐ ํฌ๋ก์ค ํ๋ซํผ ํด๋ผ์ด์ธํธ
โข MyMQTT (Android): ๋ชจ๋ฐ์ผ์์ ํ
์คํธํ๊ธฐ ์ข์
โข MQTTBox: Chrome ํ์ฅ ํ๋ก๊ทธ๋จ
๐ ๋ก๊น ์ค์
# mosquitto.conf ๋ก๊น
์ค์
# ๋ก๊ทธ ์ถ๋ ฅ ์์น
log_dest file /var/log/mosquitto/mosquitto.log
log_dest stdout
# ๋ก๊ทธ ํ์
(์ฌ๋ฌ ๊ฐ ์ง์ ๊ฐ๋ฅ)
log_type error
log_type warning
log_type notice
log_type information
log_type all
# ์ฐ๊ฒฐ ๋ฉ์์ง ๋ก๊น
connection_messages true
# ํ์์คํฌํ ํฌํจ
log_timestamp true
# ๋ก๊ทธ ํ์ผ ํฌ๊ธฐ ์ ํ (bytes)
# log_file_size 10485760 # 10MB
๐ ์ค์ ํ์ฉ ์ฌ๋ก
์ด๋ก ๋ง ์์์๋ ์ฌ๋ฏธ์์ง! ์ค์ ๋ก MQTT๊ฐ ์ด๋ป๊ฒ ํ์ฉ๋๋์ง ๋ช ๊ฐ์ง ์ฌ๋ก๋ฅผ ์ดํด๋ณด์. ๐ช
๐ ์ค๋งํธํ ์์คํ ์์
์ค์ ์ค๋งํธํ ์์คํ ์ ๊ฐ๋จํ๊ฒ ๊ตฌํํด๋ณด์. ์กฐ๋ช , ์จ๋, ๋ณด์ ์ผ์๋ฅผ ํตํฉ ๊ด๋ฆฌํ๋ ์์คํ ์ด์ผ:
import paho.mqtt.client as mqtt
import json
import time
from datetime import datetime
class SmartHomeController:
def __init__(self, broker_address, username, password):
self.broker = broker_address
self.client = mqtt.Client("SmartHomeController")
self.client.username_pw_set(username, password)
self.client.on_connect = self.on_connect
self.client.on_message = self.on_message
# ๋๋ฐ์ด์ค ์ํ ์ ์ฅ
self.devices = {
"lights": {},
"temperature": {},
"security": {}
}
def on_connect(self, client, userdata, flags, rc):
if rc == 0:
print("โ
์ค๋งํธํ ์ปจํธ๋กค๋ฌ ์ฐ๊ฒฐ ์ฑ๊ณต!")
# ๋ชจ๋ ๋๋ฐ์ด์ค ํ ํฝ ๊ตฌ๋
self.client.subscribe("home/#", qos=1)
else:
print(f"โ ์ฐ๊ฒฐ ์คํจ: {rc}")
def on_message(self, client, userdata, message):
topic = message.topic
payload = json.loads(message.payload.decode())
# ํ ํฝ ํ์ฑ
parts = topic.split('/')
if len(parts) >= 3:
category = parts[1] # lights, temperature, security
room = parts[2] # living-room, bedroom, etc.
# ์ํ ์
๋ฐ์ดํธ
if category in self.devices:
self.devices[category][room] = payload
self.process_device_update(category, room, payload)
def process_device_update(self, category, room, data):
"""๋๋ฐ์ด์ค ์
๋ฐ์ดํธ ์ฒ๋ฆฌ"""
timestamp = datetime.now().strftime("%H:%M:%S")
if category == "temperature":
temp = data.get("temperature")
print(f"[{timestamp}] ๐ก๏ธ {room}: {temp}ยฐC")
# ์๋ ์จ๋ ์กฐ์
if temp > 28:
self.control_ac(room, "on", 24)
elif temp < 18:
self.control_heater(room, "on", 22)
elif category == "lights":
status = data.get("status")
brightness = data.get("brightness", 0)
print(f"[{timestamp}] ๐ก {room}: {status} ({brightness}%)")
elif category == "security":
motion = data.get("motion_detected")
if motion:
print(f"[{timestamp}] ๐จ {room}: ์์ง์ ๊ฐ์ง!")
self.send_alert(room)
def control_light(self, room, status, brightness=100):
"""์กฐ๋ช
์ ์ด"""
payload = {
"command": "set",
"status": status,
"brightness": brightness,
"timestamp": time.time()
}
topic = f"home/lights/{room}/control"
self.client.publish(topic, json.dumps(payload), qos=1)
print(f"๐ก {room} ์กฐ๋ช
{status} (๋ฐ๊ธฐ: {brightness}%)")
def control_ac(self, room, status, temperature):
"""์์ด์ปจ ์ ์ด"""
payload = {
"command": "set",
"status": status,
"temperature": temperature,
"mode": "cool",
"timestamp": time.time()
}
topic = f"home/climate/{room}/control"
self.client.publish(topic, json.dumps(payload), qos=1)
print(f"โ๏ธ {room} ์์ด์ปจ {status} (์ค์ : {temperature}ยฐC)")
def control_heater(self, room, status, temperature):
"""ํํฐ ์ ์ด"""
payload = {
"command": "set",
"status": status,
"temperature": temperature,
"mode": "heat",
"timestamp": time.time()
}
topic = f"home/climate/{room}/control"
self.client.publish(topic, json.dumps(payload), qos=1)
print(f"๐ฅ {room} ํํฐ {status} (์ค์ : {temperature}ยฐC)")
def send_alert(self, room):
"""๋ณด์ ์๋ฆผ ์ ์ก"""
payload = {
"type": "security_alert",
"room": room,
"message": f"{room}์์ ์์ง์์ด ๊ฐ์ง๋์์ต๋๋ค.",
"timestamp": time.time()
}
topic = "home/alerts"
self.client.publish(topic, json.dumps(payload), qos=2)
print(f"๐ฑ ๋ณด์ ์๋ฆผ ์ ์ก: {room}")
def get_status_report(self):
"""์ ์ฒด ์ํ ๋ฆฌํฌํธ"""
print("\n" + "="*60)
print("๐ ์ค๋งํธํ ์ํ ๋ฆฌํฌํธ")
print("="*60)
for category, rooms in self.devices.items():
if rooms:
print(f"\n๐ {category.upper()}")
for room, data in rooms.items():
print(f" โข {room}: {data}")
print("="*60 + "\n")
def start(self):
"""์ปจํธ๋กค๋ฌ ์์"""
self.client.connect(self.broker, 1883, keepalive=60)
self.client.loop_start()
def stop(self):
"""์ปจํธ๋กค๋ฌ ์ข
๋ฃ"""
self.client.loop_stop()
self.client.disconnect()
# ์ฌ์ฉ ์์
if __name__ == "__main__":
controller = SmartHomeController(
broker_address="localhost",
username="smarthome",
password="password123"
)
controller.start()
try:
# ์ด๊ธฐ ์ค์
time.sleep(2)
# ๊ฑฐ์ค ์กฐ๋ช
์ผ๊ธฐ
controller.control_light("living-room", "on", 80)
# ์นจ์ค ์กฐ๋ช
๋๊ธฐ
controller.control_light("bedroom", "off", 0)
# ์ํ ๋ฆฌํฌํธ ์ฃผ๊ธฐ์ ์ถ๋ ฅ
while True:
time.sleep(30)
controller.get_status_report()
except KeyboardInterrupt:
print("\nโน๏ธ ์ปจํธ๋กค๋ฌ ์ข
๋ฃ ์ค...")
controller.stop()
๐ ํ์ต ๋ฆฌ์์ค์ ์ปค๋ฎค๋ํฐ
MQTT๋ฅผ ๋ ๊น์ด ๊ณต๋ถํ๊ณ ์ถ๋ค๋ฉด? ์ด๋ฐ ๋ฆฌ์์ค๋ค์ ํ์ฉํด๋ด! ๐ ๊ทธ๋ฆฌ๊ณ ์ฌ๋ฅ๋ท์์๋ MQTT ์ ๋ฌธ๊ฐ๋ค์ด ์ค์๊ฐ ๋ฉํ ๋ง๋ ์ ๊ณตํ๊ณ ์์ผ๋, ๋งํ๋ ๋ถ๋ถ์ด ์๋ค๋ฉด ๋์์ ๋ฐ์ ์ ์์ด!
docs.oasis-open.org/mqttโข MQTT 5.0 ์คํ: ์ต์ ๊ธฐ๋ฅ ํฌํจ
โข Eclipse Paho:
eclipse.org/pahoโข Mosquitto ๋ฌธ์:
mosquitto.org/documentation๐ฅ ์จ๋ผ์ธ ๊ฐ์
โข Udemy: "MQTT Essentials" ์ฝ์ค
โข YouTube: HiveMQ ์ฑ๋
โข Coursera: IoT ์ ๋ฌธํ ๊ณผ์
๐ฌ ์ปค๋ฎค๋ํฐ
โข Reddit: r/mqtt
โข Stack Overflow: mqtt ํ๊ทธ
โข HiveMQ Community Forum
โข Eclipse Paho ๋ฉ์ผ๋ง ๋ฆฌ์คํธ
๐ ๋ค์ ๋จ๊ณ๋ก
์ฌ๊ธฐ๊น์ง MQTT ๋ธ๋ก์ปค์ ํด๋ผ์ด์ธํธ์ ๊ธฐ๋ณธ๋ถํฐ ์ค์ ํ์ฉ๊น์ง ๋ชจ๋ ๋ค๋ค๋ดค์ด! ์ด์ ์ง์ ํ๋ก์ ํธ๋ฅผ ๋ง๋ค์ด๋ณด๋ฉด์ ๊ฒฝํ์ ์์๊ฐ๋ ๊ฒ ์ค์ํด. ๐ช
2. ์ฑํ ์ ํ๋ฆฌ์ผ์ด์ : MQTT๋ฅผ ๋ฉ์์ง ๋ฐฑ์๋๋ก ํ์ฉ
3. ์๊ฒฉ ์ ์ด ๋ก๋ด: ๋ผ์ฆ๋ฒ ๋ฆฌํ์ด์ MQTT๋ก IoT ๋ก๋ด ์ ์ด
4. ์ค์๊ฐ ์๋ฆผ ์์คํ : ๋ชจ๋ฐ์ผ ์ฑ์ ํธ์ ์๋ฆผ ์ ์ก
5. ์๋์ง ๋ชจ๋ํฐ๋ง: ๊ฐ์ ์ ์ ๋ ฅ ์ฌ์ฉ๋ ์ค์๊ฐ ์ถ์
MQTT๋ ์ ๋ง ๊ฐ๋ ฅํ๊ณ ์ ์ฐํ ํ๋กํ ์ฝ์ด์ผ. ํ๋ฒ ์ต์ํด์ง๋ฉด ๋ค์ํ IoT ํ๋ก์ ํธ์ ํ์ฉํ ์ ์์ด์ ๊ฐ๋ฐ์๋ก์ ํฐ ๋ฌด๊ธฐ๊ฐ ๋ ๊ฑฐ์ผ! ๐ฏ
ํน์ ๊ตฌํํ๋ค๊ฐ ๋งํ๋ ๋ถ๋ถ์ด ์๊ฑฐ๋, ๋ ๊น์ด ์๋ ๋ด์ฉ์ด ๊ถ๊ธํ๋ค๋ฉด ์ธ์ ๋ ์ง ์ง๋ฌธํด์ค. ํจ๊ป ์ฑ์ฅํ๋ ๊ฐ๋ฐ์ ์ปค๋ฎค๋ํฐ๊ฐ ๋์์ผ๋ฉด ์ข๊ฒ ์ด! ๐
๐ Happy Coding with MQTT! ๐
๊ด๋ จ ํค์๋
๋๊ธ ์์ฑ
์ด ๊ธ์ ๋ํ ์ฌ๋ฌ๋ถ์ ์๊ฐ์ ๋ค๋ ค์ฃผ์ธ์
๋ก๊ทธ์ธ์ด ํ์ํฉ๋๋ค
๋๊ธ์ ์์ฑํ๋ ค๋ฉด ๋จผ์ ๋ก๊ทธ์ธํด์ฃผ์ธ์.
๋๊ธ 0
์ง์์ธ์ ์ฒ - ์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
์ง์ ์ฌ์ฐ๊ถ ๋ณดํธ ๊ณ ์ง
- ์ ์๊ถ ๋ฐ ์์ ๊ถ: ๋ณธ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ๋ ์ AI ๊ธฐ์ ๋ก ์์ฑ๋์์ผ๋ฉฐ, ๋ํ๋ฏผ๊ตญ ์ ์๊ถ๋ฒ ๋ฐ ๊ตญ์ ์ ์๊ถ ํ์ฝ์ ์ํด ๋ณดํธ๋ฉ๋๋ค.
- AI ์์ฑ ์ปจํ ์ธ ์ ๋ฒ์ ์ง์: ๋ณธ AI ์์ฑ ์ปจํ ์ธ ๋ ์ฌ๋ฅ๋ท์ ์ง์ ์ฐฝ์๋ฌผ๋ก ์ธ์ ๋๋ฉฐ, ๊ด๋ จ ๋ฒ๊ท์ ๋ฐ๋ผ ์ ์๊ถ ๋ณดํธ๋ฅผ ๋ฐ์ต๋๋ค.
- ์ฌ์ฉ ์ ํ: ์ฌ๋ฅ๋ท์ ๋ช ์์ ์๋ฉด ๋์ ์์ด ๋ณธ ์ปจํ ์ธ ๋ฅผ ๋ณต์ , ์์ , ๋ฐฐํฌ, ๋๋ ์์ ์ ์ผ๋ก ํ์ฉํ๋ ํ์๋ ์๊ฒฉํ ๊ธ์ง๋ฉ๋๋ค.
- ๋ฐ์ดํฐ ์์ง ๊ธ์ง: ๋ณธ ์ปจํ ์ธ ์ ๋ํ ๋ฌด๋จ ์คํฌ๋ํ, ํฌ๋กค๋ง, ๋ฐ ์๋ํ๋ ๋ฐ์ดํฐ ์์ง์ ๋ฒ์ ์ ์ฌ์ ๋์์ด ๋ฉ๋๋ค.
- AI ํ์ต ์ ํ: ์ฌ๋ฅ๋ท์ AI ์์ฑ ์ปจํ ์ธ ๋ฅผ ํ AI ๋ชจ๋ธ ํ์ต์ ๋ฌด๋จ ์ฌ์ฉํ๋ ํ์๋ ๊ธ์ง๋๋ฉฐ, ์ด๋ ์ง์ ์ฌ์ฐ๊ถ ์นจํด๋ก ๊ฐ์ฃผ๋ฉ๋๋ค.
์์ง ๋๊ธ์ด ์์ต๋๋ค.