MQTT 통신에서 애플리케이션 메시지를 주고받는 핵심 요소는 PUBLISH 패킷입니다. 이 패킷은 메시지의 실제 내용, 전달될 토픽, 그리고 서비스 품질(QoS) 등급과 같은 중요한 정보를 담고 있습니다. PUBLISH 패킷 외에도, 메시지 전달의 신뢰성을 보장하는 QoS 1 및 QoS 2 메커니즘을 구현하기 위해 PUBACK, PUBREC, PUBREL, PUBCOMP와 같은 다양한 응답 패킷이 활용됩니다. 이 글에서는 이들 패킷의 구조와 역할을 심층적으로 살펴보겠습니다.
패킷 흐름 예시
MQTT 통신에서 패킷이 어떻게 주고받는지 이해하기 위해 MQTTX CLI 도구를 사용하여 공용 MQTT 브로커에 메시지를 발행하는 상황을 가정해 봅시다. Wireshark와 같은 네트워크 패킷 분석 도구를 활용하면 클라이언트와 서버 간에 오가는 MQTT 패킷을 직접 확인할 수 있습니다. 리눅스 환경에서는 tcpdump 명령으로 패킷을 캡처한 후 Wireshark로 불러와 분석할 수 있습니다.
다음은 QoS 0 메시지를 발행하는 MQTTX CLI 명령어의 예시입니다. 메시지 만료 간격(Message Expiry Interval)과 응답 토픽(Response Topic) 속성도 포함하여 PUBLISH 패킷의 다양한 속성을 보여줍니다.
mqttx pub --hostname public.mqtt.broker --mqtt-version 5.0 \
--topic telemetry/sensor --qos 0 --message "{\"sensorId\": \"temp01\", \"value\": 23.5}" \
--message-expiry-interval 120 --response-topic telemetry/response
위 명령으로 전송된 QoS 0 PUBLISH 패킷의 16진수 데이터 예시는 다음과 같습니다. message-expiry-interval과 response-topic과 같은 속성들이 포함되어 있어, 고정 헤더, 가변 헤더 (토픽 이름, 속성), 그리고 페이로드의 상세 구조를 보여줍니다.
304F000E74656C656D657472792F73656E736F721A020000007808001274656C656D657472792F726573706F6E73657B2273656E736F724964223A202274656D703031222C202276616C7565223A2032332E357D
이제 MQTTX CLI 명령에서 QoS 수준만 1로 변경하여 메시지를 발행하면, 서버는 PUBLISH 패킷을 받은 후 PUBACK 패킷으로 응답합니다.
mqttx pub --hostname public.mqtt.broker --mqtt-version 5.0 \
--topic alerts/system --qos 1 --message "High temperature alert"
이 경우 클라이언트와 서버 간의 통신 흐름은 다음과 같습니다 (패킷 식별자는 예시 값입니다):
클라이언트 -- PUBLISH (고정 헤더 0x32, 패킷 식별자 0x0001, ...) --> 서버
클라이언트 <-- PUBACK (고정 헤더 0x40, 패킷 식별자 0x0001, 이유 코드 0x00) -- 서버
PUBLISH 패킷의 첫 바이트는 0x32로, QoS 1 메시지임을 나타냅니다. PUBACK 패킷은 PUBLISH와 동일한 패킷 식별자 0x0001을 사용하여 어떤 발행 메시지에 대한 응답인지 명확히 합니다. PUBACK의 이유 코드 0x00은 메시지가 성공적으로 수신되었으며 매칭되는 구독자가 있다는 의미입니다.
QoS 2 메시지를 발행하면, 클라이언트와 서버 사이에 두 번의 왕복 통신이 발생합니다. Wireshark에서 보면 PUBLISH, PUBREC, PUBREL, PUBCOMP 패킷들이 동일한 패킷 식별자를 공유하며 전송됩니다.
mqttx pub --hostname public.mqtt.broker --mqtt-version 5.0 \
--topic commands/control --qos 2 --message "Activate security protocol"
QoS 2 통신 흐름 (패킷 식별자 0x0003 예시):
클라이언트 -- PUBLISH (고정 헤더 0x34, 패킷 식별자 0x0003, ...) --> 서버
클라이언트 <-- PUBREC (고정 헤더 0x50, 패킷 식별자 0x0003, 이유 코드 0x00) -- 서버
클라이언트 -- PUBREL (고정 헤더 0x62, 패킷 식별자 0x0003, 이유 코드 0x00) --> 서버
클라이언트 <-- PUBCOMP (고정 헤더 0x70, 패킷 식별자 0x0003, 이유 코드 0x00) -- 서버
이러한 16진수 바이트열에서 어떤 패킷이 PUBLISH이고, QoS 수준은 무엇이며, 응답 패킷의 이유 코드가 무엇인지 정확히 알아내는 방법은 이어지는 각 패킷 구조 설명에서 자세히 다룹니다.
PUBLISH 패킷 구조
고정 헤더 (Fixed Header)
PUBLISH 패킷의 고정 헤더 중 첫 바이트의 상위 4비트는 패킷 유형을 나타내는 3 (0b0011)으로 고정됩니다. 하위 4비트는 다음 세 가지 플래그로 구성됩니다:
- DUP (Bit 3): 클라이언트나 서버가
PUBLISH패킷을 재전송할 때 이 플래그를1로 설정합니다. 이는 재전송된 메시지임을 나타내며,DUP플래그가1인 패킷의 수와 빈도는 현재 통신 링크의 품질을 파악하는 데 유용한 지표가 될 수 있습니다. - QoS (Bit 2 - 1): 메시지의 서비스 품질(QoS) 등급을 지정합니다. 0, 1, 2 중 하나의 값을 가집니다.
- Retain (Bit 0): 이 플래그가
1로 설정되면 현재 메시지는 유지(Retained) 메시지입니다. 즉, 브로커가 이 메시지를 저장하고, 해당 토픽에 새로 구독하는 클라이언트에게 즉시 전송합니다.0이면 일반 메시지입니다.
고정 헤더 다음에는 현재 패킷의 남은 부분의 바이트 수를 나타내는 남은 길이(Remaining Length) 필드가 옵니다.
가변 헤더 (Variable Header)
PUBLISH 패킷의 가변 헤더는 순서대로 다음 필드들을 포함합니다:
- 토픽 이름 (Topic Name): UTF-8 인코딩 문자열로, 메시지가 발행될 정보 채널을 나타냅니다.
- 패킷 식별자 (Packet Identifier): 2바이트 길이의 부호 없는 정수로, 전송 중인 메시지를 고유하게 식별합니다. QoS 등급이 1 또는 2일 때만
PUBLISH패킷에 이 필드가 포함됩니다. - 속성 (Properties):
PUBLISH패킷에서 사용할 수 있는 다양한 속성들이 포함될 수 있습니다. 각 속성은 메시지의 동작을 세부적으로 제어하거나 추가 정보를 제공합니다. 다음 표는 주요 속성들을 나열합니다.
| 식별자 (Identifier) | 속성 이름 (Property Name) | 유형 (Type) |
|---|---|---|
| 0x01 | 페이로드 형식 지시자 (Payload Format Indicator) | 단일 바이트 |
| 0x02 | 메시지 만료 간격 (Message Expiry Interval) | 4바이트 정수 |
| 0x23 | 토픽 별칭 (Topic Alias) | 2바이트 정수 |
| 0x08 | 응답 토픽 (Response Topic) | UTF-8 문자열 |
| 0x09 | 상관 데이터 (Correlation Data) | 이진 데이터 |
| 0x26 | 사용자 속성 (User Property) | UTF-8 문자열 쌍 |
| 0x0B | 구독 식별자 (Subscription Identifier) | 가변 길이 정수 |
| 0x03 | 콘텐츠 유형 (Content Type) | UTF-8 문자열 |
페이로드 (Payload)
PUBLISH 패킷의 페이로드에는 클라이언트가 전송하고자 하는 애플리케이션 메시지의 실제 내용이 담겨 있습니다. 이 필드는 JSON, ProtoBuf 등 어떤 형식의 데이터도 포함할 수 있으며, MQTT는 페이로드의 내용에 대해 특정 형식을 강제하지 않습니다.
PUBACK 패킷 구조
고정 헤더 (Fixed Header)
PUBACK 패킷의 고정 헤더 중 첫 바이트의 상위 4비트는 PUBACK 패킷임을 나타내는 4 (0b0100)로 고정됩니다. 하위 4비트는 예약되어 있으며 항상 0으로 설정됩니다.
고정 헤더 다음에는 현재 패킷의 남은 부분의 바이트 수를 나타내는 남은 길이(Remaining Length) 필드가 옵니다.
가변 헤더 (Variable Header)
PUBACK 패킷의 가변 헤더는 순서대로 다음 필드들을 포함합니다:
- 패킷 식별자 (Packet Identifier):
PUBLISH패킷과 달리,PUBACK패킷에는 패킷 식별자가 필수로 존재합니다. 이 필드는 어떤 QoS 1PUBLISH패킷에 대한 응답인지를 발행 클라이언트에게 알려줍니다. - 이유 코드 (Reason Code): 단일 바이트의 부호 없는 정수로,
PUBLISH요청의 처리 결과를 발행 클라이언트에게 전달합니다. 발행 거부 사유(예: 권한 없음) 등을 명시할 수 있습니다. 다음 표는PUBACK패킷에서 사용 가능한 주요 이유 코드들을 보여줍니다.
| 값 (Value) | 이유 코드 이름 (Reason Code Name) | 설명 (Description) |
|---|---|---|
| 0x00 | 성공 (Success) | 메시지가 성공적으로 수신되었음을 나타냅니다. |
| 0x10 | 매칭되는 구독자 없음 (No matching subscribers) | 메시지는 수신되었으나, 해당 토픽에 매칭되는 구독자가 없습니다. |
| 0x80 | 불특정 오류 (Unspecified error) | 구체적인 오류 원인을 밝히지 않거나, 프로토콜 사양에 적합한 이유 코드가 없는 경우 사용됩니다. |
| 0x83 | 구현 특정 오류 (Implementation specific error) | PUBLISH 패킷은 유효하지만, 수신 측의 구현에서 받아들여지지 않았습니다. |
| 0x87 | 권한 없음 (Not authorized) | PUBLISH 패킷이 서버의 권한 검사를 통과하지 못했습니다. 클라이언트가 해당 토픽에 발행할 권한이 없을 수 있습니다. |
| 0x90 | 유효하지 않은 토픽 이름 (Topic Name invalid) | 토픽 이름의 형식이 올바르지만, 수신 측에서 허용되지 않습니다. |
| 0x91 | 사용 중인 패킷 식별자 (Packet identifier in use) | PUBLISH 패킷의 패킷 ID가 이미 사용 중입니다. 이는 일반적으로 클라이언트와 서버 간의 세션 상태 불일치 또는 구현 오류를 의미합니다. |
| 0x97 | 할당량 초과 (Quota exceeded) | 서버가 설정한 발행 할당량을 초과했음을 나타냅니다. |
| 0x99 | 유효하지 않은 페이로드 형식 (Payload format invalid) | 페이로드의 형식이 Payload Format Indicator 속성이 나타내는 형식과 일치하지 않습니다. |
- 속성 (Properties):
PUBACK패킷에서 사용할 수 있는 속성은Reason String과User Property등이 있습니다.
| 식별자 (Identifier) | 속성 이름 (Property Name) | 유형 (Type) |
|---|---|---|
| 0x1F | 이유 문자열 (Reason String) | UTF-8 문자열 |
| 0x26 | 사용자 속성 (User Property) | UTF-8 문자열 쌍 |
페이로드 (Payload)
PUBACK 패킷은 페이로드를 포함하지 않습니다.
PUBREC, PUBREL, PUBCOMP 패킷 구조
PUBREC, PUBREL, PUBCOMP 패킷은 기본적으로 PUBACK 패킷과 매우 유사한 구조를 가집니다. 주요 차이점은 고정 헤더의 패킷 유형 필드 값과 각 패킷에서 사용 가능한 이유 코드에 있습니다.
PUBREC패킷은 고정 헤더의 패킷 유형 필드 값이5 (0b0101)입니다. QoS 2 메시지 흐름에서PUBLISH패킷에 대한 첫 번째 확인 응답으로 사용되며,PUBACK과 동일한 이유 코드를 사용할 수 있습니다.PUBREL패킷은 고정 헤더의 패킷 유형 필드 값이6 (0b0110)이며,QoS 2흐름에서 메시지 수신자가 메시지 처리를 진행할 준비가 되었음을 송신자에게 알립니다.PUBREL패킷의 고정 헤더에는 DUP 플래그가 비트 3에, QoS 플래그가 비트 2-1에 있지만, 이들은 모두 0으로 예약되어 있으며, 비트 0만1로 설정됩니다 (0b01100010 = 0x62).PUBCOMP패킷은 고정 헤더의 패킷 유형 필드 값이7 (0b0111)이며,QoS 2흐름의 마지막 단계로, 메시지 전송 및 처리가 완전히 완료되었음을 확인합니다.
PUBREL과 PUBCOMP 패킷에서 사용 가능한 이유 코드는 다음과 같습니다:
| 값 (Value) | 이유 코드 이름 (Reason Code Name) | 설명 (Description) |
|---|---|---|
| 0x00 | 성공 (Success) |
|
| 0x92 | 패킷 식별자를 찾을 수 없음 (Packet Identifier not found) | 알 수 없는 패킷 식별자가 수신되었음을 나타냅니다. 이는 일반적으로 서버와 클라이언트의 세션 상태 불일치를 의미할 수 있습니다. |