ROS2&MQTT

MQTT - 1, ๊ธฐ๋ณธํ†ต์‹ 

๋ฐ๋ถ€์žฅ 2025. 4. 6. 00:20

๐Ÿ”ท 1. MQTT ์‹คํ—˜ ํ™˜๊ฒฝ ์„ค์ •

๐Ÿ“ [1-1] Mosquitto ๋ธŒ๋กœ์ปค ์„ค์ • (mosquitto.conf)

  • Mosquitto๋Š” ์˜คํ”ˆ์†Œ์Šค MQTT ๋ธŒ๋กœ์ปค
  • ์„ค์ • ํŒŒ์ผ: config/mosquitto.conf
listener 1883             # ํฌํŠธ ์„ค์ •
allow_anonymous true      # ์ธ์ฆ ์—†์ด ์‚ฌ์šฉ
persistence true          # ์žฌ์‹œ์ž‘ ํ›„์—๋„ ๋ฉ”์‹œ์ง€ ์ €์žฅ
persistence_location /mosquitto/data/
log_dest stdout           # ๋กœ๊ทธ ์ถœ๋ ฅ

๐Ÿ“ [1-2] Docker๋กœ ๋ธŒ๋กœ์ปค ์‹คํ–‰ (์˜ต์…˜)

docker run -it -p 1883:1883 -v ./config:/mosquitto/config -v ./data:/mosquitto/data eclipse-mosquitto

๐Ÿ”ท 1.  mqttPub_from_csv.py ์ฝ”๋“œ ์„ค๋ช… (Publisher)

๐Ÿ“Œ ์ „์ฒด ๋ชฉ์ 

CSV ํŒŒ์ผ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด, ์ผ์ • ๊ฐ„๊ฒฉ์œผ๋กœ MQTT ๋ธŒ๋กœ์ปค๋ฅผ ํ†ตํ•ด ๋ฉ”์‹œ์ง€๋ฅผ ์ „์†กํ•˜๊ณ , ๊ทธ ๋‚ด์šฉ์„ ๋กœ์ปฌ ๋กœ๊ทธ ํŒŒ์ผ์—๋„ ์ €์žฅํ•จ.

๐Ÿ“„ ์ฝ”๋“œ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…


### 1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฐ ๊ฒฝ๋กœ ์„ค์ •

import time, json, csv
import paho.mqtt.client as mqtt
import os

 

  • paho.mqtt.client: MQTT ํ†ต์‹ ์„ ์œ„ํ•œ ํŒŒ์ด์ฌ ํด๋ผ์ด์–ธํŠธ
  • json, csv: ๋ฉ”์‹œ์ง€ ํฌ๋งท๊ณผ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ
  • os: ๊ฒฝ๋กœ ํ™•์ธ ๋ฐ ํด๋” ์ƒ์„ฑ

### 2. ๊ฒฝ๋กœ ๋ฐ ์„ค์ • ๋ณ€์ˆ˜

CSV_PATH = "/app/industrial_robot_control_6G_network.csv"
LOG_PATH = "/app/pub_log.csv"
MQTT_BROKER = "mqtt-broker"
MQTT_PORT = 1883
MQTT_TOPIC = "test/latency"
PUBLISH_INTERVAL = 0.1โ€‹
  • CSV_PATH: ๋ฐœํ–‰ํ•  ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ธด CSV ํŒŒ์ผ
  • LOG_PATH: ๋ฐœํ–‰ ๋กœ๊ทธ๋ฅผ ์ €์žฅํ•  ๊ฒฝ๋กœ
  • MQTT_BROKER: MQTT ๋ธŒ๋กœ์ปค ์ฃผ์†Œ (mqtt-broker๋Š” Docker ๋„คํŠธ์›Œํฌ alias๋กœ ์‚ฌ์šฉ๋  ์ˆ˜๋„ ์žˆ์Œ. ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ๋Š” 192.168.x.x ๋“ฑ์œผ๋กœ ๋ณ€๊ฒฝ ๊ฐ€๋Šฅ)
  • PUBLISH_INTERVAL: ๋ฉ”์‹œ์ง€ ๋ฐœํ–‰ ์ฃผ๊ธฐ (์ดˆ ๋‹จ์œ„, ์˜ˆ: 0.1์ดˆ  10Hz)

### 3. MQTT ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ๋ฐ ์—ฐ๊ฒฐ

client = mqtt.Client()
client.connect(MQTT_BROKER, MQTT_PORT)
client.loop_start()
msg_id = 0

 

  • ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๋ธŒ๋กœ์ปค์— ์—ฐ๊ฒฐํ•จ.
  • loop_start()๋Š” ๋น„๋™๊ธฐ๋กœ ๋ฉ”์‹œ์ง€ ์†ก์ˆ˜์‹  ์ฒ˜๋ฆฌ๋ฅผ ์‹œ์ž‘ํ•จ.
  • msg_id๋Š” ๋ฉ”์‹œ์ง€ ๋ฒˆํ˜ธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์นด์šดํ„ฐ (์†์‹ค๋ฅ  ๋ถ„์„์— ์“ฐ์ž„)


### 4. MQTT ํด๋ผ์ด์–ธํŠธ ์ดˆ๊ธฐํ™” ๋ฐ ์—ฐ๊ฒฐ

os.makedirs(os.path.dirname(LOG_PATH), exist_ok=True)
  • ๋กœ๊ทธ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์—†์œผ๋ฉด ์ƒ์„ฑ (/app ๋‚ด๋ถ€๋Š” Docker ์ปจํ…Œ์ด๋„ˆ ๊ธฐ์ค€)

### 5. ๋กœ๊ทธ ํŒŒ์ผ ์—ด๊ธฐ & ํ—ค๋” ์“ฐ๊ธฐ

with open(LOG_PATH, "w", newline='') as logfile:
    log_writer = csv.writer(logfile)
    log_writer.writerow(["id", "timestamp", "payload"])
  • pub_log.csv ์ƒ์„ฑ ๋ฐ ๋กœ๊ทธ์šฉ CSV writer ์ค€๋น„

### 6. CSV ํŒŒ์ผ์„ ํ•œ ์ค„์”ฉ ์ฝ์–ด์„œ MQTT๋กœ ์ „์†ก

    with open(CSV_PATH, newline='') as csvfile:
        reader = csv.DictReader(csvfile)

        for row in reader:
            now = time.time()
            msg = {
                "id": msg_id,
                "timestamp": now,
                "data": row,
            }
            payload = json.dumps(msg)
            client.publish(MQTT_TOPIC, payload)
            ...
            log_writer.writerow([msg_id, now, payload]) 
            msg_id += 1
            time.sleep(PUBLISH_INTERVAL)

 

  • CSV์—์„œ ํ•œ ์ค„์”ฉ ์ฝ๊ณ 
  • ๊ฐ row๋ฅผ ํฌํ•จํ•œ JSON ๋ฉ”์‹œ์ง€๋ฅผ ์ƒ์„ฑ
  • ๋ฉ”์‹œ์ง€ ID์™€ timestamp๋ฅผ ํฌํ•จํ•˜์—ฌ test/latency ํ† ํ”ฝ์œผ๋กœ MQTT ๋ฐœํ–‰
  • ๋ฐœํ–‰ ํ›„ ํ•ด๋‹น ๋ฉ”์‹œ์ง€๋ฅผ ๋กœ๊ทธ๋กœ ๊ธฐ๋ก
  • ์ง€์ •๋œ ์ฃผ๊ธฐ(0.1์ดˆ)๋กœ ์ „์†ก ๋ฐ˜๋ณต

### 7. ์‹คํ–‰ ์ข…๋ฃŒ ๋กœ๊ทธ

print("[mqttPub_from_csv] Finished publishing.")

 

  • ์ „์ฒด ๋ฐœํ–‰์ด ๋๋‚ฌ์„ ๋•Œ ์ถœ๋ ฅ

๐ŸŒ ๋„คํŠธ์›Œํฌ ๊ธฐ๋ฐ˜ ์‹คํ—˜์—์„œ์˜ ์‹คํ–‰ ๋ฐฉ๋ฒ•

ํผ๋ธ”๋ฆฌ์…”์™€ ๋ธŒ๋กœ์ปค๋Š” ๊ฐ™์€ ๋„คํŠธ์›Œํฌ ๋‚ด์—์„œ ํ†ต์‹  ๊ฐ€๋Šฅํ•ด์•ผ ํ•จ.

๐Ÿ–ฅ๏ธ ๋กœ์ปฌ ๋จธ์‹  1 (๋ธŒ๋กœ์ปค ์‹คํ–‰):

mosquitto -c config/mosquitto.conf

 

๋˜๋Š” Docker ์‚ฌ์šฉ ์‹œ:

docker run -it -p 1883:1883 -v $PWD/config:/mosquitto/config eclipse-mosquitto

๐Ÿ–ฅ๏ธ ๋จธ์‹  2 (ํผ๋ธ”๋ฆฌ์…” ์‹คํ–‰):

๋ธŒ๋กœ์ปค๊ฐ€ ์‹คํ–‰ ์ค‘์ธ ์ปดํ“จํ„ฐ์˜ IP๊ฐ€ ์˜ˆ๋ฅผ ๋“ค์–ด 192.168.0.42๋ผ๋ฉด:

MQTT_BROKER = "192.168.0.42"

์œผ๋กœ ์ˆ˜์ • ํ›„ ์‹คํ–‰:

python3 mqttPub_from_csv.py

๐Ÿ“Œ ๋ฐœํ‘œ ์‹œ ๊ฐ•์กฐํ•  ํ‚คํฌ์ธํŠธ

ํฌ์ธํŠธ ์„ค๋ช…
๋„คํŠธ์›Œํฌ ๊ธฐ๋ฐ˜ ํ†ต์‹  ๋ธŒ๋กœ์ปค์™€ ํผ๋ธ”๋ฆฌ์…”๋Š” ์„œ๋กœ ๋‹ค๋ฅธ ์žฅ๋น„์—์„œ ๋™์ž‘ ๊ฐ€๋Šฅ
๋ฉ”์‹œ์ง€ ๊ตฌ์กฐ id, timestamp, data๋กœ ๊ตฌ์„ฑ → ์ •๋Ÿ‰ ๋ถ„์„ ๊ฐ€๋Šฅ
์ „์†ก ๊ฐ„๊ฒฉ ์‹คํ—˜ ๋ชฉ์ ์— ๋”ฐ๋ผ ์ž์œ ๋กญ๊ฒŒ ์„ค์ • ๊ฐ€๋Šฅ
๋กœ๊ทธ ๊ธฐ๋ก ์‹ค์‹œ๊ฐ„ ์‹คํ—˜ ํ›„ latency, ์†์‹ค๋ฅ  ๋ถ„์„์— ํ™œ์šฉ
์œ ์—ฐํ•œ ๋ฐ์ดํ„ฐ ํฌ๊ธฐ ์กฐ์ ˆ payload ํฌ๊ธฐ ์‹คํ—˜์€ ์ด์ „ ๋ฒ„์ „ ์ฝ”๋“œ์— ํฌํ•จ ๊ฐ€๋Šฅ (generate_fixed_size_payload() ํ™œ์šฉ)

๐Ÿ”ท2. mqttSub.py ์ฝ”๋“œ ์„ค๋ช… (Subscriber)

๐Ÿ“Œ ์ „์ฒด ๋ชฉ์ 

  • MQTT ๋ธŒ๋กœ์ปค์—์„œ ๋ฉ”์‹œ์ง€๋ฅผ ์ˆ˜์‹ 
  • ๋ฉ”์‹œ์ง€๋งˆ๋‹ค latency ์ธก์ •
  • ๋กœ๊ทธ ๊ธฐ๋ก (sub_log.csv)
  • ์‹คํ—˜ ์ข…๋ฃŒ ์‹œ ์†์‹ค๋ฅ  ๊ณ„์‚ฐ (loss_result.csv)

๐Ÿ“„ ์ฝ”๋“œ ๋‹จ๊ณ„๋ณ„ ์„ค๋ช…


### 1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฐ ์„ค์ • ๊ฒฝ๋กœ

import time, json, csv
import paho.mqtt.client as mqtt
import os

### 2. ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฐ ๋ณ€์ˆ˜ ์ดˆ๊ธฐํ™”

PUB_LOG_PATH = "/app/pub_log.csv"
SUB_LOG_PATH = "/app/sub_log.csv"
LOSS_LOG_PATH = "/app/loss_result.csv"
  • ํผ๋ธ”๋ฆฌ์…”๊ฐ€ ๊ธฐ๋กํ•œ ๋ฉ”์‹œ์ง€ ๋กœ๊ทธ๋ฅผ ์ฝ์–ด ์†์‹ค๋ฅ  ๋ถ„์„์— ํ™œ์šฉ
count = 0
total_latency = 0
received = 0
received_ids = set()
  • ์ˆ˜์‹ ํ•œ ๋ฉ”์‹œ์ง€ ๊ฐœ์ˆ˜, ID ๋ชฉ๋ก ๋“ฑ์„ ์ถ”์ 

### 3. ์ˆ˜์‹  ๋กœ๊ทธ ํŒŒ์ผ ์ƒ์„ฑ ๋ฐ ํ—ค๋” ์ž‘์„ฑ

os.makedirs("/app", exist_ok=True)
csv_file = open(SUB_LOG_PATH, "w", newline="")
writer = csv.writer(csv_file)
writer.writerow(["id", "timestamp", "latency_ms"])
  • ์ˆ˜์‹  ๋กœ๊ทธ ํŒŒ์ผ์„ /app/sub_log.csv๋กœ ์ƒ์„ฑํ•˜๊ณ  ํ—ค๋”๋ฅผ ๊ธฐ๋ก

### 4. MQTT ์ˆ˜์‹  ์ฒ˜๋ฆฌ ํ•จ์ˆ˜ ์ •์˜

def on_message(client, userdata, msg):

 

  • ์ˆ˜์‹  ์‹œ ํ˜ธ์ถœ๋จ
  • payload ๋””์ฝ”๋”ฉ → latency ๊ณ„์‚ฐ
latency = (time.time() - raw["timestamp"]) * 1000
  • ํผ๋ธ”๋ฆฌ์…”๊ฐ€ ๊ธฐ๋กํ•œ timestamp์™€ ํ˜„์žฌ ์‹œ๊ฐ„์˜ ์ฐจ์ด๋กœ latency(ms) ๊ณ„์‚ฐ
writer.writerow([msg_id, time.time(), round(latency, 3)])

 

  • sub_log.csv์— ์ €์žฅ
received_ids.add(int(msg_id))
  • ๋ฉ”์‹œ์ง€ ID ์ €์žฅ → ์†์‹ค๋ฅ  ๊ณ„์‚ฐ์— ํ™œ์šฉ๋จ

### 5. ์†์‹ค๋ฅ  ๊ณ„์‚ฐ ํ•จ์ˆ˜

def calculate_loss():
  • ์ˆ˜์‹ ์ด ๋๋‚œ ๋’ค ํผ๋ธ”๋ฆฌ์…” ๋กœ๊ทธ์™€ ์ˆ˜์‹  ๋กœ๊ทธ๋ฅผ ๋น„๊ตํ•˜์—ฌ
  • ์ „์†ก ๊ฐœ์ˆ˜, ์ˆ˜์‹  ๊ฐœ์ˆ˜, ์†์‹ค ๊ฐœ์ˆ˜, ์†์‹ค๋ฅ ์„ ๊ณ„์‚ฐ
 
loss_rate = (loss_count / total_sent) * 100

### 6. ๊ตฌ๋… ์„ค์ • ๋ฐ ์‹คํ–‰

client = mqtt.Client()
client.connect("mqtt-broker", 1883)
client.subscribe("test/latency")
client.on_message = on_message
 
  • ํผ๋ธ”๋ฆฌ์…”์™€ ๋™์ผํ•œ ํ† ํ”ฝ test/latency์— ๊ตฌ๋…
client.loop_forever()
  • ๋ฌดํ•œ ๋ฃจํ”„ ํ˜•ํƒœ๋กœ ์‹คํ–‰ → ์‹คํ—˜ ์ข…๋ฃŒ ์‹œ Ctrl+C

### 7. ์ข…๋ฃŒ ํ›„ ๋กœ๊ทธ ๋ฐ ์†์‹ค๋ฅ  ์ถœ๋ ฅ

finally:
    csv_file.close()
    calculate_loss()

๐ŸŒ ๋„คํŠธ์›Œํฌ ๊ธฐ๋ฐ˜ ์‹คํ–‰๋ฒ• (Subscriber)

๐Ÿ–ฅ๏ธ ๋‹ค๋ฅธ ์žฅ๋น„์—์„œ ์‹คํ–‰ํ•  ๊ฒฝ์šฐ:

  1. mqttSub.py์—์„œ ๋ธŒ๋กœ์ปค IP ์ˆ˜์ •:
client.connect("192.168.0.42", 1883)  # ๋ธŒ๋กœ์ปค ์‹คํ–‰ ์ค‘์ธ ์žฅ๋น„์˜ IP

   2. ์‹คํ–‰:

python3 mqttSub.py

๐Ÿ“Œ ์ฃผ์˜: ํผ๋ธ”๋ฆฌ์…”๊ฐ€ ๋ณด๋‚ด๊ธฐ ์‹œ์ž‘ํ•œ ํ›„ ์‹คํ–‰ํ•ด๋„ ๋˜์ง€๋งŒ, ๋™์‹œ ์‹คํ–‰์ด ์ด์ƒ์ 
๋กœ๊ทธ ์ €์žฅ์€ /app/sub_log.csv, ์†์‹ค๋ฅ ์€ /app/loss_result.csv์— ์ €์žฅ๋จ


โœ… ์‹คํ—˜ ๊ฒฐ๊ณผ ๋ถ„์„ ํ๋ฆ„ ์ •๋ฆฌ

ํŒŒ์ผ ๋‚ด์šฉ
pub_log.csv ๋ฐœํ–‰๋œ ๋ฉ”์‹œ์ง€ ๋กœ๊ทธ (id, timestamp, payload)
sub_log.csv ์ˆ˜์‹ ๋œ ๋ฉ”์‹œ์ง€ ๋กœ๊ทธ (id, timestamp, latency_ms)
loss_result.csv ์ด ๋ฐœ์†ก, ์ˆ˜์‹ , ์†์‹ค๋ฅ  ์ •๋ณด
pub_cpu.csv, sub_cpu.csv ๊ฐ๊ฐ์˜ CPU ์‚ฌ์šฉ๋ฅ  (์‹คํ—˜ ๋ถ€ํ•˜ ์ธก์ • ๋ชฉ์ )

๐Ÿ”ฌ ๋ถ„์„ ์˜ˆ์‹œ (calculate_loss.py ๊ฒฐ๊ณผ ์˜ˆ์‹œ)

์ „์†ก ์ˆ˜: 1000, ์ˆ˜์‹  ์ˆ˜: 995, ์†์‹ค ์ˆ˜: 5, ์†์‹ค๋ฅ : 0.50%
 

loss_result.csv ์˜ˆ์‹œ:

total_sent total_received loss_count loss_rate(%)
1000 995 5 0.5

๐ŸŽฏ ๋ฐœํ‘œ ์‹œ ๊ฐ•์กฐ ํฌ์ธํŠธ ์ •๋ฆฌ

๊ตฌ๊ฐ„ ๊ฐ•์กฐ ๋‚ด์šฉ
Publisher payload ํฌ๊ธฐ, ์ „์†ก ์ฃผ๊ธฐ, ๋กœ๊ทธ ๊ธฐ๋ฐ˜ ์‹คํ—˜ ๋ถ„์„ ๊ฐ€๋Šฅ
Subscriber latency, ์‹ค์‹œ๊ฐ„ ์ˆ˜์‹ , ์†์‹ค๋ฅ  ์ž๋™ ๊ณ„์‚ฐ
๋ถ„์„ CSV ๊ธฐ๋ฐ˜์œผ๋กœ ๊ฐ๊ด€์ ์ธ ์ง€ํ‘œ ๋„์ถœ ๊ฐ€๋Šฅ
ํ™•์žฅ์„ฑ ๋„คํŠธ์›Œํฌ ๊ธฐ๋ฐ˜ ์‹คํ—˜, ๋‹ค์–‘ํ•œ ์žฅ๋น„/ํ™˜๊ฒฝ์—์„œ ์‹คํ—˜ ๊ฐ€๋Šฅ

pub_cpu_logger.py / sub_cpu_logger.py ์„ค๋ช… (CPU ๋กœ๊น…์šฉ)


๐Ÿ“Œ ๊ณตํ†ต ๋ชฉ์ 

  • psutil ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•ด CPU ์‚ฌ์šฉ๋ฅ ์„ 1์ดˆ ๋‹จ์œ„๋กœ ์ธก์ •
  • CSV๋กœ ์ €์žฅํ•˜์—ฌ ์‹œ๊ฐํ™”/๋น„๊ต ๋ถ„์„์— ํ™œ์šฉ

1๏ธโƒฃ pub_cpu_logger.py (ํผ๋ธ”๋ฆฌ์…” ์ธก CPU ๋กœ๊ฑฐ)

import time
import csv
import psutil
import os

print("[pub-cpu] Starting CPU logging...")

log_path = "/app/pub_cpu.csv"
with open(log_path, "w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["timestamp", "cpu_percent"])
    try:
        while True:
            cpu = psutil.cpu_percent(interval=1)  # 1์ดˆ๋งˆ๋‹ค CPU ์ธก์ •
            timestamp = time.time()
            writer.writerow([timestamp, cpu])
            print(f"[pub-cpu] {timestamp:.2f}, {cpu:.2f}%")
            f.flush()
    except KeyboardInterrupt:
        print("[pub-cpu] Logging stopped.")

2๏ธโƒฃ sub_cpu_logger.py (์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ์ธก CPU ๋กœ๊ฑฐ)

import psutil
import time
import csv

log_path = "/app/sub_cpu.csv"
with open(log_path, mode="w", newline="") as f:
    writer = csv.writer(f)
    writer.writerow(["timestamp", "cpu_percent"])

    while True:
        cpu = psutil.cpu_percent(interval=1)
        writer.writerow([time.time(), cpu])
        f.flush()

์ฐจ์ด์ : sub_cpu_logger.py๋Š” ๊ฐ„๋‹จํžˆ ๊ตฌ์„ฑ๋˜์–ด ์žˆ์ง€๋งŒ ๊ธฐ๋Šฅ์€ ๋™์ผ


๐Ÿ“ ๊ฒฐ๊ณผ ํŒŒ์ผ ๊ตฌ์กฐ

ํŒŒ์ผ๋ช… ๋‚ด์šฉ
pub_cpu.csv ํผ๋ธ”๋ฆฌ์…” ์ธก ์‹œ๊ฐ„๋Œ€๋ณ„ CPU ์‚ฌ์šฉ๋ฅ 
sub_cpu.csv ์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ์ธก ์‹œ๊ฐ„๋Œ€๋ณ„ CPU ์‚ฌ์šฉ๋ฅ 
timestamp,cpu_percent
1712320764.89,4.3
1712320765.89,5.1
...

๐Ÿ’ก ์‹คํ–‰ ๋ฐฉ๋ฒ• (์„œ๋กœ ๋‹ค๋ฅธ ํ„ฐ๋ฏธ๋„์—์„œ ๋ณ‘๋ ฌ ์‹คํ–‰)

ํผ๋ธ”๋ฆฌ์…” ์ธก:

python3 pub_cpu_logger.py

์„œ๋ธŒ์Šคํฌ๋ผ์ด๋ฒ„ ์ธก:

python3 sub_cpu_logger.py

์‹คํ—˜์ด ๋๋‚ฌ์œผ๋ฉด Ctrl+C๋กœ ์ข…๋ฃŒ ๊ฐ€๋Šฅ
(๋ฐ์ดํ„ฐ๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ์ €์žฅ๋˜๋ฉฐ, flush ์‚ฌ์šฉ๋จ)