투케이2K

147. (TWOK/UTIL) [Ios/Swift] C_Broker_Mqtt_NWConnection_Client_Module : MQTT 통신 수행 유틸 파일 본문

투케이2K 유틸파일

147. (TWOK/UTIL) [Ios/Swift] C_Broker_Mqtt_NWConnection_Client_Module : MQTT 통신 수행 유틸 파일

투케이2K 2024. 12. 6. 18:24

[설 명]

프로그램 : Ios / Swift

설 명 : C_Broker_Mqtt_NWConnection_Client_Module : MQTT 통신 수행 유틸 파일

 

[소스 코드]

 

import Foundation
import UIKit
// -----------------------------------------
//MARK: [네트워크 소켓 통신 사용]
import Network
// -----------------------------------------

class C_Broker_Mqtt_NWConnection_Client_Module {



    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 1. NMConnection Broker Mqtt 통신 클라이언트 모듈
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 2. NWConnection : ios 12 이상 부터 사용 가능한 애플에서 지원하는 네트워크 프레임 워크입니다
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 3. NWConnection 는 로컬 엔드포인트와 원격 엔드포인트 간의 양방향 데이터 연결 (TCP , UDP) 을 수행할 수 있습니다
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 4. NWConnection 를 사용하기 위해서는 import Network 패키지 호출 정의가 필요합니다
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 5. NWParameters 설정 가능 옵션 : tls , tcp , dtls , udp , quic , quicDatagram
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 6. 애플 공식 참고 사이트 : https://developer.apple.com/documentation/network/nwconnection
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 7. 온라인 MQTT 테스트 사이트 : https://testclient-cloud.mqtt.cool/
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 8. Aws IoT Core MQTT 디바이스 통신 프로토콜 및 port 종류 확인 :
     * https://blog.naver.com/kkh0977/223580748811?trackingCode=blog_bloghome_searchlist
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */





    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * //  TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableMqttConnect : 엔드포인트 사용해 MQTT 연결 수행
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableMqttSetting : MQTT 연결 옵션 지정 및 연결 완료 요청 수행
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : closeMqttConnect : MQTT 연결 종료 수행
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableSubscribe : 특정 토픽 TOPIC 구독 수행 실시
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : receiveData : 실시간 MQTT 메시지 수신 상태 확인
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : observableSendData : 실시간 MQTT 메시지 전송 수행
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     *
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */
    
    
    
    
    
    
    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [소스 코드 사용 방법]
     * // --------------------------------------------------------------------------------------------------------------------------------------
     DispatchQueue.main.async { // [비동기 요청]
         
         // [변수 선언 수행]
         let url = "tcp://broker.mqtt.cool:1883"
         
         
         // -----------------------------------------
         // [MQTT 연결 실시]
         // -----------------------------------------
         // 1. 정의 된 URL 과 고유한 클라이언트 ID 사용해 MQTT 연결 수행
         // -----------------------------------------
         // 2. receiveData 실시간 메시지 수신 상태 감지 등록 : 내려온 response 에서 topic 과 payload 를 파싱하기 위해 패킷 분해 필요
         // -----------------------------------------
         // 3. 특정 토픽과 QOS 설정 지정해 subscribe 구독 수행 실시 : 구독을 수행하기 위한 MQTT 버전 및 QOS 설정 패킷 전송
         // -----------------------------------------
         C_Broker_Mqtt_NWConnection_Client_Module().observableMqttConnect(url: url, clientId: "twok1234"){(connectResult) in
             
             S_Log._F_(description: "MQTT 연결 확인 수행", data: ["\(connectResult)"])

             if connectResult == true {
                 
                 
                 // -----------------------------------------
                 // [실시간 메시지 전송 수행]
                 // -----------------------------------------
                 // 1. 특정 토픽과 QOS 설정 지정해 publish 메시지 전송 수행 실시 : MQTT 버전 및 QOS 설정 패킷 전송
                 // -----------------------------------------
                 // 2. receiveData 실시간 메시지 수신 상태에서 토픽과 페이로드 파싱해서 정보 확인
                 // -----------------------------------------
                 DispatchQueue.main.asyncAfter(deadline: .now() + 5) { // [5초 시간 설정]
                  
                     C_Broker_Mqtt_NWConnection_Client_Module().observableSendData(topic: "hello", message: "twok", qos: 0){(sendResult) in
                         
                         S_Log._F_(description: "MQTT 실시간 메시지 전송 확인", data: ["\(sendResult)"])
                         
                     }
                     
                 }
                 
             }
             else {
                S_Log._F_(description: "MQTT 연결 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)"])
             }
             
         }
     }
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [전역 변수 선언]
    // -----------------------------------------------------------------------------------------
    private static let ACTIVITY_NAME = "C_Broker_Mqtt_NWConnection_Client_Module"
    
    let CONNECT_TIME_OUT = 10.0 // [연결 타임 아웃 시간]

    lazy var tcpOptions: NWProtocolTCP.Options = {
        let options = NWProtocolTCP.Options()
        options.noDelay = true // [딜레이 비활성]
        options.connectionTimeout = Int(self.CONNECT_TIME_OUT) // [connection timed out]
        return options
    }()

    lazy var parames: NWParameters = {
        let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
        if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
            // isOption.version = .v4 // [ipv4 방식 : 192.168.1.1]
        }
        parames.preferNoProxies = true
        return parames
    }()
    
    static var connection: NWConnection? = nil // [MQTT 통신 수행 객체]
    var workItem: DispatchWorkItem? = nil // [연결 타임 아웃 핸들러]
    static var connectFlag = false // [연결 완료 플래그 값]
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - observableMqttConnect : 엔드포인트 사용해 MQTT 연결 수행
    // -----------------------------------------------------------------------------------------
    // 1. 정의 된 URL 과 고유한 클라이언트 ID 사용해 MQTT 연결 수행
    // -----------------------------------------------------------------------------------------
    // 2. receiveData 실시간 메시지 수신 상태 감지 등록 : 내려온 response 에서 topic 과 payload 를 파싱하기 위해 패킷 분해 필요
    // -----------------------------------------------------------------------------------------
    // 3. 특정 토픽과 QOS 설정 지정해 subscribe 구독 수행 실시 : 구독을 수행하기 위한 MQTT 버전 및 QOS 설정 패킷 전송
    // -----------------------------------------------------------------------------------------
    static var MQTT_CONNECT_LOG = ""
    func observableMqttConnect(url: String, clientId: String, completion: @escaping (Bool)->()) {
        
        /*
        // -----------------------------------------
        [observableSocketConnect 메소드 설명]
        // -----------------------------------------
        1. IP , PORT 사용해 웹 소켓 연결 수행
        // -----------------------------------------
        2. 호출 방법 :
         
         C_Broker_Mqtt_NWConnection_Client_Module().observableMqttConnect(url: "tcp://broker.mqtt.cool:1883", clientId: "twok1234"){(connectResult) in
             
             S_Log._F_(description: "MQTT 연결 확인 수행", data: ["\(connectResult)"])

             if connectResult == true {
                
                // [MQTT 연결 완료 로직 처리]
             }
             else {
                S_Log._F_(description: "MQTT 연결 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)"])
             }
             
         }
         
        // -----------------------------------------
        3. 필요 import :
         
         import Network
        // -----------------------------------------
        */


        // [변수 선언]
        C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
        C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = ""
        self.workItem = nil
        C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
        
        
        // [로직 처리 수행]
        DispatchQueue.main.async {
            S_Log._D_(description: "MQTT 연결 수행", data: [ "URL :: \(url)", "CLIENT_ID :: \(clientId)" ])
            
            
            // [인풋 데이터 널 체크]
            if C_Util().stringNotNull(str: url) == true && C_Util().stringNotNull(str: clientId) == true {

                // ---------------------------------------------
                // MARK: [MQTT 연결 및 연결 상태 확인]
                // ---------------------------------------------

                // [연결 객체 생성]
                C_Broker_Mqtt_NWConnection_Client_Module.connection = NWConnection(to: .url(URL(string: url)!), using: self.parames)


                // [연결 상태 업데이트 핸들러 설정]
                C_Broker_Mqtt_NWConnection_Client_Module.connection?.stateUpdateHandler = { state in
                    switch state {
                    case .ready:
                        C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = "[Success] : Connection Ready State"
                    
                        S_Log._W_(description: "MQTT 연결 준비 완료", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)",
                            "URL :: \(url)",
                            "CLIENT_ID :: \(clientId)"
                        ])
                        
                        
                        // ---------------------------------------------
                        // MARK: [MQTT 연결 옵션 값 지정 최종 연결 완료 요청 수행]
                        // ---------------------------------------------
                        C_Broker_Mqtt_NWConnection_Client_Module().observableMqttSetting(clientId: clientId){(sendResult) in
                            S_Log._W_(description: "MQTT 연결 완료 상태 확인", data: ["\(sendResult)"])

                            if sendResult == true {
                                
                                // ---------------------------------------------
                                // MARK: [연결 완료 플래그 값 변경]
                                // ---------------------------------------------
                                C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = true
                               
                                
                                // ---------------------------------------------
                                // MARK: [실시간 메시지 수신 상태 확인]
                                // ---------------------------------------------
                                self.receiveData()
                                
                                
                                // ---------------------------------------------
                                // MARK: [특정 토픽 구독 수행 실시]
                                // ---------------------------------------------

                                C_Broker_Mqtt_NWConnection_Client_Module().observableSubscribe(topic: "hello", qos: 0){(sendResult) in
                                    
                                    S_Log._W_(description: "MQTT 토픽 구독 완료 상태 확인", data: ["\(sendResult)"])

                                    if sendResult == true {
                                       
                                    }
                                    else {
                                       S_Log._E_(description: "MQTT 토픽 구독 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG)"])
                                    }
                                    
                                }
                                
                                
                                // ---------------------------------------------
                                // [콜백 반환]
                                // ---------------------------------------------
                                completion(true)
                                return
                                
                            }
                            else {
                               S_Log._E_(description: "MQTT 연결 완료 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG)"])
                                
                                // [에러 메시지 삽입]
                                C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG = C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG
                                
                                // [콜백 반환]
                                if C_Broker_Mqtt_NWConnection_Client_Module.connectFlag == false {
                                
                                    completion(false)
                                    
                                }
                                
                                // [객체 초기화]
                                if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil {
                                    
                                    C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
                                    C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
                                    
                                }
                                
                                return
                            }
                            
                        }
                        
                    case .failed(let error):
                        C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = "[Fail] : Connection Fail State"
                        
                        S_Log._E_(description: "MQTT 연결 실패", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)",
                            "Description :: \(error)"
                        ])
                        
                        // [콜백 반환]
                        if C_Broker_Mqtt_NWConnection_Client_Module.connectFlag == false {
                         
                            completion(false)
                            
                        }
                        
                        // [객체 초기화]
                        if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil {
                            
                            C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
                            C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
                            
                        }
                        
                        return
                        
                    case .cancelled:
                        C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = "[Cancel] : Connection Cancel State"
                        
                        S_Log._E_(description: "MQTT 연결 실패", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)"
                        ])
                        
                        // [콜백 반환]
                        if C_Broker_Mqtt_NWConnection_Client_Module.connectFlag == false {
                         
                            completion(false)
                            
                        }
                        
                        // [객체 초기화]
                        if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil {
                            
                            C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
                            C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
                            
                        }
                        
                        return
                        
                    default:
                        break
                    }
                }

                
                // ---------------------------------------------
                // [연결 시작]
                // ---------------------------------------------
                C_Broker_Mqtt_NWConnection_Client_Module.connection?.start(queue: .global())
                
                
                // ---------------------------------------------
                // MARK: [MQTT 연결 타임 아웃 처리]
                // ---------------------------------------------
                self.workItem = DispatchWorkItem {
                    
                    if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil && C_Broker_Mqtt_NWConnection_Client_Module.connectFlag == false {
                        
                        C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = "[Time Out] : Mqtt Connection TimeOut"
                        
                        S_Log._E_(description: "MQTT 연결 실패", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)"
                        ])
                        
                        // [MQTT 연결 취소]
                        C_Broker_Mqtt_NWConnection_Client_Module.connection?.cancel()
                        
                    }
                    
                }
                
                let delay = DispatchTime.now() + self.CONNECT_TIME_OUT // [특정 시간 후에 실행]
                
                DispatchQueue.main.asyncAfter(deadline: delay, execute: self.workItem!)
                
            }
            else {
                C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG = "[Error] : Input Data Is Null"
                
                S_Log._E_(description: "MQTT 연결 에러", data: [
                    "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.MQTT_CONNECT_LOG)"
                ])
                completion(false) // [콜백 반환]
                return
                
            }
        }
        
    }
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : observableMqttSetting : MQTT 연결 옵션 지정 및 연결 완료 요청 수행
    // -----------------------------------------------------------------------------------------
    static var SETTING_ERROR_LOG = ""
    func observableMqttSetting(clientId: String, completion: @escaping (Bool)->()) {
        
        /*
        // -----------------------------------------
        [observableMqttSetting 메소드 설명]
        // -----------------------------------------
        1. MQTT 연결 옵션 지정 및 연결 완료 요청 수행
        // -----------------------------------------
        2. 호출 방법 :
         
         C_Broker_Mqtt_NWConnection_Client_Module().observableMqttSetting(clientId: "twok1234"){(sendResult) in
             
             S_Log._F_(description: "MQTT 연결 완료 상태 확인", data: ["\(sendResult)"])

             if sendResult == true {
                
             }
             else {
                S_Log._F_(description: "MQTT 연결 완료 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG)"])
             }
             
         }
         
        // -----------------------------------------
        3. 필요 import :
         
         import Network
        // -----------------------------------------
        */
        
        
        // [변수 선언]
        C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG = ""
        
        
        // [로직 처리 수행]
        DispatchQueue.global().sync {

            if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil && C_Util().stringNotNull(str: clientId) == true {
                
                S_Log._D_(description: "MQTT 연결 옵션 지정 및 연결 완료 요청 수행", data: nil)
                
                
                /*
                // ---------------------------------------------
                // MARK: [최종 연결 요청 패킷 보내기]
                // ---------------------------------------------
                1. MQTT 버전 설명 :

                  - 0x03 : MQTT 3.1   : MQIsdp
                  - 0x04 : MQTT 3.1.1 : MQTT
                  - 0x05 : MQTT 5.0   : MQTT
                // ---------------------------------------------
                2. Connect Flags 타입 종류 :

                  - Bit : Name
                  - 7 : User Name Flag
                  - 6 : Password Flag
                  - 5 : Will Retain
                  - 4 : Will QoS
                  - 3 : Will Flag
                  - 2 : Clean Session
                  - 1 : Reserved
                // ---------------------------------------------
                3. Keep Alive : 클라이언트와 브로커간 연결 상태 유지 값 : 해당 값 동안 들어온 패킷이 없으면 서버는 연결 중단 상태로 인지

                  - Keep Alive 설정의 최대값은 65535초, 즉 18시간 12분 15초
                  - 60 초 : 0x3C
                  - 180 초 : 0xB4
                // ---------------------------------------------
                4. QoS : Application Message의 신뢰성을 보장하는 레벨입니다 (0, 1, 2)

                  - 숫자가 커질수록 높은 신뢰성을 보장하지만 그만큼의 리소스를 소모합니다
                // ---------------------------------------------
                */


                // ---------------------------------------------
                var packet = [UInt8]()
                packet.append(0x10) // [CONNECT Control Packet type]
                let variableHeader: [UInt8] = [
                    0x00, 0x04, // [Length of "MQTT 3.1.1"]
                    0x4D, 0x51, 0x54, 0x54, // ["MQTT"] : "MQTT".utf8
                    0x04,       // [Protocol Level (4 for MQTT 3.1.1)]
                    0x02,       // [Connect Flags (Clean session)]
                    0x00, 0xB4  // [Keep Alive (180 seconds)] : 클라이언트와 브로커간 연결 상태 유지 값 : Hex To Dec
                ]
                let clientIdBytes = Array(clientId.utf8) // [클라이언트 아이디]
                let payload = UInt16(clientIdBytes.count).bigEndianBytes + clientIdBytes
                let remainingLength = UInt8(variableHeader.count + payload.count)
                packet.append(remainingLength)
                packet.append(contentsOf: variableHeader)
                packet.append(contentsOf: payload)
                // ---------------------------------------------
                
                
                C_Broker_Mqtt_NWConnection_Client_Module.connection?.send(content: Data(packet), completion: .contentProcessed { error in
                    if let error = error {
                        C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG = "[Error] : Mqtt Setting Send Connection Packet Error"
                        
                        S_Log._E_(description: "MQTT 연결 요청 패킷 전송 에러", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG)",
                            "Description :: \(error)"
                        ])
                        
                        // [콜백 반환]
                        completion(false)
                        return
                        
                    } else {
                        S_Log._W_(description: "MQTT 연결 요청 패킷 전송 성공", data: nil)
                        
                        // [콜백 반환]
                        completion(true)
                        return
                        
                    }
                })
                
            }
            else {
                C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG = "[Error] : Setting Input Send Message Is Null"
                
                S_Log._E_(description: "MQTT 연결 옵션 지정 및 연결 완료 요청 에러", data: [
                    "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SETTING_ERROR_LOG)"
                ])
                completion(false) // [콜백 반환]
                return
            }
            
        }

    }
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : closeMqttConnect : MQTT 연결 종료 수행
    // -----------------------------------------------------------------------------------------
    func closeMqttConnect() {
        
        if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil {
            S_Log._E_(description: "MQTT 연결 종료 수행 [IF]", data: nil)
            
            C_Broker_Mqtt_NWConnection_Client_Module.connection?.cancel()
            C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
            C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
        }
        else {
            S_Log._E_(description: "MQTT 연결 종료 수행 [ELSE]", data: nil)
        }
        
    }
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : observableSubscribe : 특정 토픽 TOPIC 구독 수행 실시
    // -----------------------------------------------------------------------------------------

    static var SUBSCRIBE_ERROR_LOG = ""
    func observableSubscribe(topic:String, qos: UInt8, completion: @escaping (Bool)->()) {
        
        /*
        // -----------------------------------------
        [observableSubscribe 메소드 설명]
        // -----------------------------------------
        1. 특정 토픽 TOPIC 구독 수행 실시
        // -----------------------------------------
        2. 호출 방법 :
         
         C_Broker_Mqtt_NWConnection_Client_Module().observableSubscribe(topic: "hello", qos: 0){(sendResult) in
             
             S_Log._F_(description: "MQTT 토픽 구독 완료 상태 확인", data: ["\(sendResult)"])

             if sendResult == true {
                
             }
             else {
                S_Log._F_(description: "MQTT 토픽 구독 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG)"])
             }
             
         }
         
        // -----------------------------------------
        3. 필요 import :
         
         import Network
        // -----------------------------------------
        */
        
        
        // [변수 선언]
        C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG = ""
        
        
        // [로직 처리 수행]
        DispatchQueue.global().sync {

            if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil && C_Util().stringNotNull(str: topic) && qos >= 0 {
                
                S_Log._D_(description: "MQTT 특정 토픽 Subscribe 구독 수행", data: ["TOPIC :: \(topic)", "QOS :: \(qos)"])
                

                // ---------------------------------------------
                // MARK: [패킷 데이터 전송 수행]
                // ---------------------------------------------
                //*
                var packet = [UInt8]()
                
                // [Fixed Header] : [고정 헤더 영역]
                packet.append(0x82) // [SUBSCRIBE control packet type]

                // [Variable Header] : [가변 헤더 영역]
                let packetIdentifier: UInt16 = 1 // [Packet Identifier (고유 ID)]
                let packetIdentifierMSB = UInt8((packetIdentifier >> 8) & 0xFF)
                let packetIdentifierLSB = UInt8(packetIdentifier & 0xFF)
                var variableHeader: [UInt8] = [packetIdentifierMSB, packetIdentifierLSB]

                // [Payload] : [메시지 전송 페이로드]
                let topicBytes = self.encodeString(topic) // [Topic 이름 UTF-8 인코딩]
                let qosByte = qos // [QoS Level]
                var payload: [UInt8] = topicBytes + [qosByte]

                // [Remaining Length]
                let remainingLength = variableHeader.count + payload.count
                packet.append(contentsOf: self.encodeRemainingLength(remainingLength))

                // [Variable Header + Payload 추가]
                packet.append(contentsOf: variableHeader)
                packet.append(contentsOf: payload)
                // */
                // ---------------------------------------------
                

                C_Broker_Mqtt_NWConnection_Client_Module.connection?.send(content: Data(packet), completion: .contentProcessed { error in
                    if let error = error {
                        C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG = "[Error] : Mqtt Topic Send Message Error"
                        
                        S_Log._E_(description: "MQTT 특정 토픽 구독 에러", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG)",
                            "Description :: \(error)"
                        ])
                        
                        // [콜백 반환]
                        completion(false)
                        return
                        
                    } else {
                        S_Log._W_(description: "MQTT 특정 토픽 구독 성공", data: [ "TOPIC :: \(topic)" ])
                        
                        // [콜백 반환]
                        completion(true)
                        return
                        
                    }
                })
                
            }
            else {
                C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG = "[Error] : Topic Input Send Message Is Null"
                
                S_Log._E_(description: "MQTT 특정 토픽 구독 에러", data: [
                    "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SUBSCRIBE_ERROR_LOG)"
                ])
                
                // [콜백 반환]
                completion(false)
                return
            }
            
        }

    }
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : receiveData : 실시간 MQTT 메시지 수신 상태 확인
    // -----------------------------------------------------------------------------------------
    static var RECEIVE_ERROR_LOG = ""
    private func receiveData() {
        
        if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil {
            S_Log._D_(description: "MQTT 실시간 메시지 수신 감지 수행", data: nil)
            
            C_Broker_Mqtt_NWConnection_Client_Module.connection?.receive(minimumIncompleteLength: 1, maximumLength: 1024, completion: { data, _, isComplete, error in
                
                // ---------------------------------------------
                // MARK: [실시간 메시지 수신 확인]
                // ---------------------------------------------
                if let data = data, !data.isEmpty {
                    let message = String(data: data, encoding: .utf8) ?? ""
                    
                    let emptyCheck = message.replaceAll(_string: " ", _replace: "")
                    if C_Util().stringNotNull(str: emptyCheck) == true {
                        
                        // ---------------------------------------------
                        // MARK: [데이터 패킷 파싱 수행 및 토픽 , 메시지 구분 수행]
                        // ---------------------------------------------
                        let bytes = [UInt8](data)

                        // [PUBLISH 패킷인지 확인 (Packet Type: 0x3)]
                        /*
                        let packetType = bytes[0] >> 4
                        if packetType == 3 {
                            handlePublishPacket(data: bytes)
                        }
                        // */
                        
                        // [Variable Header: Topic 이름 파싱]
                        let topicLength = (UInt16(bytes[2]) << 8) | UInt16(bytes[3])
                        let topicStartIndex = 4
                        let topicEndIndex = topicStartIndex + Int(topicLength)
                        let topic = String(bytes: bytes[topicStartIndex..<topicEndIndex], encoding: .utf8) ?? "Unknown Topic"

                        // [Payload: 메시지 내용]
                        let payloadStartIndex = topicEndIndex
                        let payload = String(bytes: bytes[payloadStartIndex...], encoding: .utf8) ?? "Unknown Payload"
                     
                        
                        // ---------------------------------------------
                        // MARK: [로그 출력 수행]
                        // ---------------------------------------------
                        S_Log._W_(description: "[Ing] ---- [MQTT 실시간 메시지 수신 감지] ---- [Ing]", data: [
                            "TOTAL :: \(message)",
                            "TOPIC :: \(topic)",
                            "MSG :: \(payload)"
                        ])
                        
                        
                        // ---------------------------------------------
                        // MARK: [로직 분기 처리]
                        // ---------------------------------------------
                        
                    }
                }

                
                // ---------------------------------------------
                // MARK: [계속해서 실시간 메시지 수신 처리]
                // ---------------------------------------------
                if isComplete {
                    C_Broker_Mqtt_NWConnection_Client_Module.RECEIVE_ERROR_LOG = "[Error] : Connection closed by Broker"
                    
                    S_Log._E_(description: "MQTT 실시간 메시지 수신 감지 에러", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.RECEIVE_ERROR_LOG)"
                    ])
                    
                    // ---------------------------------------------
                    // MARK: [MQTT 연결 종료 및 객체 초기화]
                    // ---------------------------------------------
                    C_Broker_Mqtt_NWConnection_Client_Module.connection?.cancel()
                    //C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
                    //C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
                    
                } else if let error = error {
                    C_Broker_Mqtt_NWConnection_Client_Module.RECEIVE_ERROR_LOG = "[Error] : let error catch"
                    
                    S_Log._E_(description: "MQTT 실시간 메시지 수신 감지 에러", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.RECEIVE_ERROR_LOG)",
                        "Description :: \(error)"
                    ])
                    
                    // ---------------------------------------------
                    // MARK: [MQTT 연결 종료 및 객체 초기화]
                    // ---------------------------------------------
                    C_Broker_Mqtt_NWConnection_Client_Module.connection?.cancel()
                    //C_Broker_Mqtt_NWConnection_Client_Module.connection = nil
                    //C_Broker_Mqtt_NWConnection_Client_Module.connectFlag = false
                    
                } else {
                    
                    // ---------------------------------------------
                    // [계속해서 데이터 수신]
                    // ---------------------------------------------
                    self.receiveData()
                    
                }
            })
            
        }
        else {
            S_Log._E_(description: "MQTT 실시간 메시지 수신 감지 에러 :: connection is null", data: nil)
        }
        
    }
    
    
    

    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : observableSendData : 실시간 MQTT 메시지 전송 수행
    // -----------------------------------------------------------------------------------------
    // 1. 특정 토픽과 QOS 설정 지정해 publish 메시지 전송 수행 실시 : MQTT 버전 및 QOS 설정 패킷 전송
    // -----------------------------------------------------------------------------------------
    // 2. receiveData 실시간 메시지 수신 상태에서 토픽과 페이로드 파싱해서 정보 확인
    // -----------------------------------------------------------------------------------------
    static var SEND_ERROR_LOG = ""
    func observableSendData(topic:String, message:String, qos: UInt8, completion: @escaping (Bool)->()) {
        
        /*
        // -----------------------------------------
        [observableSendData 메소드 설명]
        // -----------------------------------------
        1. 실시간 MQTT 메시지 전송 수행
        // -----------------------------------------
        2. 호출 방법 :
         
         C_Broker_Mqtt_NWConnection_Client_Module().observableSendData(topic: "hello", message: "twok", qos: 0){(sendResult) in
             
             S_Log._F_(description: "MQTT 실시간 메시지 전송 확인", data: ["\(sendResult)"])

             if sendResult == true {
                
             }
             else {
                S_Log._F_(description: "MQTT 실시간 메시지 전송 에러 메시지", data: ["\(C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG)"])
             }
             
         }
         
        // -----------------------------------------
        3. 필요 import :
         
         import Network
        // -----------------------------------------
        */
        
        
        // [변수 선언]
        C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG = ""
        
        
        // [로직 처리 수행]
        DispatchQueue.global().sync {

            if C_Broker_Mqtt_NWConnection_Client_Module.connection != nil && C_Util().stringNotNull(str: topic)
                && C_Util().stringNotNull(str: message) == true && qos >= 0 {
                
                S_Log._D_(description: "MQTT 클라이언트 >> 특정 토픽 메시지 전송 수행", data: ["TOPIC :: \(topic)", "QOS :: \(qos)", "MSG :: \(message)"])
                

                // ---------------------------------------------
                // MARK: [패킷 데이터 전송 수행]
                // ---------------------------------------------
                /*
                var packet = [UInt8]()
                packet.append(0x30) // [PUBLISH Control Packet type]
                let topicBytes = Array(topic.utf8)
                let topicLength = UInt16(topicBytes.count).bigEndianBytes
                let messageBytes = Array(message.utf8)
                let remainingLength = UInt8(topicLength.count + topicBytes.count + messageBytes.count)
        
                packet.append(remainingLength)
                packet.append(contentsOf: topicLength)
                packet.append(contentsOf: topicBytes)
                packet.append(contentsOf: messageBytes)
                // */
                // ---------------------------------------------
                //*
                var packet = [UInt8]()
                let qosFlag = (qos & 0x03) << 1 // [QoS 는 고정 헤더의 2~3번째 비트에 위치]
            
                // [PUBLISH 고정 헤더 (Control Packet Type + Flags)]
                packet.append(0x30 | qosFlag)
            
                let topicBytes = Array(topic.utf8)
                let topicLength = UInt16(topicBytes.count).bigEndianBytes
                let messageBytes = Array(message.utf8)
            
                var remainingLength = topicLength.count + topicBytes.count + messageBytes.count
            
                // [QoS가 1 이상이면 Packet Identifier가 필요]
                if qos > 0 {
                    remainingLength += 2
                }
            
                packet.append(contentsOf: self.encodeRemainingLength(remainingLength))
                packet.append(contentsOf: topicLength)
                packet.append(contentsOf: topicBytes)
            
                // [Packet Identifier (QoS 1 이상일 때 추가)]
                if qos > 0 {
                    let packetId = UInt16.random(in: 1...UInt16.max)
                    packet.append(contentsOf: packetId.bigEndianBytes)
                }
            
                packet.append(contentsOf: messageBytes)
                // */
                // ---------------------------------------------
                

                C_Broker_Mqtt_NWConnection_Client_Module.connection?.send(content: Data(packet), completion: .contentProcessed { error in
                    if let error = error {
                        C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG = "[Error] : Mqtt Publish Send Message Error"
                        
                        S_Log._E_(description: "MQTT 실시간 메시지 전송 에러", data: [
                            "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG)",
                            "Description :: \(error)"
                        ])
                        completion(false) // [콜백 반환]
                        return
                        
                    } else {
                        S_Log._W_(description: "MQTT 실시간 메시지 전송 성공", data: [
                            "message :: \(message)"
                        ])
                        completion(true) // [콜백 반환]
                        return
                        
                    }
                })
                
            }
            else {
                C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG = "[Error] : Publish Input Send Message Is Null"
                
                S_Log._E_(description: "MQTT 실시간 메시지 전송 에러", data: [
                    "M_LOG :: \(C_Broker_Mqtt_NWConnection_Client_Module.SEND_ERROR_LOG)"
                ])
                completion(false) // [콜백 반환]
                return
            }
            
        }

    }
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [유틸 파일] : Remaining Length 인코딩
    // -----------------------------------------------------------------------------------------
    private func encodeRemainingLength(_ length: Int) -> [UInt8] {
        var value = length
        var encodedBytes: [UInt8] = []

        repeat {
            var digit = UInt8(value % 128)
            value /= 128
            if value > 0 {
                digit |= 0x80
            }
            encodedBytes.append(digit)
        } while value > 0

        return encodedBytes
    }
    
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [유틸 파일] : UTF-8 문자열을 MQTT 형식으로 인코딩
    // -----------------------------------------------------------------------------------------
    private func encodeString(_ string: String) -> [UInt8] {
        let utf8Bytes = Array(string.utf8)
        let length = UInt16(utf8Bytes.count)
        return [UInt8(length >> 8), UInt8(length & 0xFF)] + utf8Bytes
    }

    
} // [클래스 종료]






/**
* // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
* TODO [UInt16] : [Extension]
* // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
* */
private extension UInt16 {
    var bigEndianBytes: [UInt8] {
        return [
            UInt8((self >> 8) & 0xFF),
            UInt8(self & 0xFF)
        ]
    }
}

 

반응형
Comments