본문 바로가기
📁 [4] 개발자 정보 & 코드 노트/C#

C# Windows Forms 강의 74편: MQTT 프로토콜을 활용한 IoT 애플리케이션 제작

by wawManager 2025. 4. 18.

 

1. 강의 개요

이번 강의에서는 MQTT(Message Queuing Telemetry Transport) 프로토콜을 활용하여 IoT 애플리케이션을 제작합니다.
MQTT는 경량 메시징 프로토콜로, 제한된 네트워크 환경에서도 실시간 데이터 통신이 가능합니다.
IoT 장치 간 메시지를 송수신하며, Windows Forms 애플리케이션을 통해 데이터를 관리하고 실시간 업데이트하는 방법을 배웁니다.


2. 학습 목표

  • MQTT 클라이언트를 사용하여 브로커와 통신 구현
  • IoT 메시지를 발행(Publish) 및 구독(Subscribe)
  • 실시간으로 수신된 데이터를 UI에 표시
  • Mosquitto와 같은 MQTT 브로커를 활용

3. 기능 요구사항

필수 기능

1️⃣ 브로커 연결:

  • MQTT 브로커에 연결 및 연결 해제

2️⃣ 메시지 발행(Publish):

  • 특정 주제로 메시지를 발행

3️⃣ 메시지 구독(Subscribe):

  • 구독한 주제의 메시지를 실시간으로 수신

4️⃣ UI 표시:

  • 수신된 메시지를 ListBox에 표시

4. 실습: MQTT 클라이언트 애플리케이션 제작

1️⃣ 사전 준비

  1. MQTT 브로커 설치:
  2. NuGet 패키지 설치:
    • Visual Studio에서 MQTTnet 패키지를 설치합니다.
    • MQTTnet은 .NET용 MQTT 클라이언트 라이브러리입니다.

2️⃣ 폼 구성

  • 폼(Form) 이름: Form1
  • 컨트롤 배치

컨트롤 타입 이름 위치 크기

TextBox txtBrokerUrl 폼 상단 왼쪽 (300 x 30)
Button btnConnect 폼 상단 중앙 (100 x 30)
TextBox txtTopic 폼 중앙 왼쪽 (300 x 30)
Button btnSubscribe 폼 중앙 오른쪽 (100 x 30)
TextBox txtMessage 폼 하단 왼쪽 (300 x 30)
Button btnPublish 폼 하단 오른쪽 (100 x 30)
ListBox lstMessages 폼 하단 전체 (400 x 200)

📌 폼 디자인 예시:

--------------------------------------------------
| [Broker URL: TextBox]       [Connect 버튼]     |
--------------------------------------------------
| [Topic: TextBox]             [Subscribe 버튼]  |
--------------------------------------------------
| [Message: TextBox]           [Publish 버튼]    |
--------------------------------------------------
|               [ListBox - 메시지 표시]           |
--------------------------------------------------

3️⃣ 코드 작성

(1) MQTT 클라이언트 구현

using System;
using System.Text;
using System.Windows.Forms;
using MQTTnet;
using MQTTnet.Client;
using MQTTnet.Client.Options;

namespace WindowsFormsApp_MQTT
{
    public partial class Form1 : Form
    {
        private IMqttClient _mqttClient;

        public Form1()
        {
            InitializeComponent();
        }

        // MQTT 브로커 연결
        private async void btnConnect_Click(object sender, EventArgs e)
        {
            string brokerUrl = txtBrokerUrl.Text.Trim();

            if (string.IsNullOrEmpty(brokerUrl))
            {
                MessageBox.Show("브로커 URL을 입력하세요.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            try
            {
                var mqttFactory = new MqttFactory();
                _mqttClient = mqttFactory.CreateMqttClient();

                var mqttOptions = new MqttClientOptionsBuilder()
                    .WithTcpServer(brokerUrl) // 브로커 URL 설정
                    .WithCleanSession()
                    .Build();

                _mqttClient.UseConnectedHandler(e =>
                {
                    Invoke(new Action(() => lstMessages.Items.Add("브로커에 연결되었습니다.")));
                });

                _mqttClient.UseDisconnectedHandler(e =>
                {
                    Invoke(new Action(() => lstMessages.Items.Add("브로커 연결이 종료되었습니다.")));
                });

                _mqttClient.UseApplicationMessageReceivedHandler(e =>
                {
                    string topic = e.ApplicationMessage.Topic;
                    string message = Encoding.UTF8.GetString(e.ApplicationMessage.Payload);

                    Invoke(new Action(() => lstMessages.Items.Add($"[{topic}] {message}")));
                });

                await _mqttClient.ConnectAsync(mqttOptions);
            }
            catch (Exception ex)
            {
                MessageBox.Show($"연결 실패: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }
    }
}

(2) 메시지 구독 및 발행

        // MQTT 메시지 구독
        private async void btnSubscribe_Click(object sender, EventArgs e)
        {
            string topic = txtTopic.Text.Trim();

            if (string.IsNullOrEmpty(topic))
            {
                MessageBox.Show("구독할 주제를 입력하세요.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (_mqttClient == null || !_mqttClient.IsConnected)
            {
                MessageBox.Show("브로커와 연결되지 않았습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            try
            {
                await _mqttClient.SubscribeAsync(new MqttTopicFilterBuilder().WithTopic(topic).Build());
                lstMessages.Items.Add($"구독 시작: {topic}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"구독 실패: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

        // MQTT 메시지 발행
        private async void btnPublish_Click(object sender, EventArgs e)
        {
            string topic = txtTopic.Text.Trim();
            string message = txtMessage.Text.Trim();

            if (string.IsNullOrEmpty(topic) || string.IsNullOrEmpty(message))
            {
                MessageBox.Show("주제와 메시지를 입력하세요.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            if (_mqttClient == null || !_mqttClient.IsConnected)
            {
                MessageBox.Show("브로커와 연결되지 않았습니다.", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return;
            }

            try
            {
                var mqttMessage = new MqttApplicationMessageBuilder()
                    .WithTopic(topic)
                    .WithPayload(message)
                    .WithAtLeastOnceQoS()
                    .Build();

                await _mqttClient.PublishAsync(mqttMessage);
                lstMessages.Items.Add($"발행 완료: [{topic}] {message}");
            }
            catch (Exception ex)
            {
                MessageBox.Show($"발행 실패: {ex.Message}", "오류", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
        }

(3) Designer 코드

        private void InitializeComponent()
        {
            this.txtBrokerUrl = new TextBox();
            this.btnConnect = new Button();
            this.txtTopic = new TextBox();
            this.btnSubscribe = new Button();
            this.txtMessage = new TextBox();
            this.btnPublish = new Button();
            this.lstMessages = new ListBox();

            // Broker URL TextBox 설정
            this.txtBrokerUrl.Location = new System.Drawing.Point(10, 10);
            this.txtBrokerUrl.Size = new System.Drawing.Size(300, 30);

            // Connect Button 설정
            this.btnConnect.Location = new System.Drawing.Point(320, 10);
            this.btnConnect.Size = new System.Drawing.Size(100, 30);
            this.btnConnect.Text = "Connect";
            this.btnConnect.Click += new EventHandler(this.btnConnect_Click);

            // Topic TextBox 설정
            this.txtTopic.Location = new System.Drawing.Point(10, 50);
            this.txtTopic.Size = new System.Drawing.Size(300, 30);

            // Subscribe Button 설정
            this.btnSubscribe.Location = new System.Drawing.Point(320, 50);
            this.btnSubscribe.Size = new System.Drawing.Size(100, 30);
            this.btnSubscribe.Text = "Subscribe";
            this.btnSubscribe.Click += new EventHandler(this.btnSubscribe_Click);

            // Message TextBox 설정
            this.txtMessage.Location = new System.Drawing.Point(10, 90);
            this.txtMessage.Size = new System.Drawing.Size(300, 30);

            // Publish Button 설정
            this.btnPublish.Location = new System.Drawing.Point(320, 90);
            this.btnPublish.Size = new System.Drawing.Size(100, 30);
            this.btnPublish.Text = "Publish";
            this.btnPublish.Click += new EventHandler(this.btnPublish_Click);

            // ListBox 설정
            this.lstMessages.Location = new System.Drawing.Point(10, 130);
            this.lstMessages.Size = new System.Drawing.Size(400, 200);

            // Form 설정
            this.ClientSize = new System.Drawing.Size(450, 350);
            this.Controls.Add(this.txtBrokerUrl);
            this.Controls.Add(this.btnConnect);
            this.Controls.Add(this.txtTopic);
            this.Controls.Add(this.btnSubscribe);
            this.Controls.Add(this.txtMessage);
            this.Controls.Add(this.btnPublish);
            this.Controls.Add(this.lstMessages);
            this.Text = "MQTT 클라이언트 애플리케이션";
        }

4️⃣ 실행 결과

1️⃣ 브로커 연결

  • 브로커 URL 입력 후 "Connect" 버튼 클릭 → 브로커 연결 성공

2️⃣ 메시지 구독

  • 주제 입력 후 "Subscribe" 클릭 → 해당 주제의 메시지 수신

3️⃣ 메시지 발행

  • 주제와 메시지 입력 후 "Publish" 클릭 → 메시지가 브로커를 통해 발행

5. 주요 개념 요약

  • MQTTnet: .NET용 MQTT 클라이언트 라이브러리
  • Subscribe: 특정 주제의 메시지를 수신
  • Publish: 특정 주제에 메시지 전송
  • QoS: 메시지 전달 품질 보장 수준

 

📌 #CSharp #WindowsForms #MQTT #IoT #실시간통신 #MQTTnet