투케이2K

577. (ios/swift5) [간단 소스] NWConnection 사용해 Broker 브로커 MQTT 연결 방법 본문

IOS

577. (ios/swift5) [간단 소스] NWConnection 사용해 Broker 브로커 MQTT 연결 방법

투케이2K 2024. 12. 1. 00:22

[개발 환경 설정]

개발 툴 : XCODE

개발 언어 : SWIFT5

 

[소스 코드]

 

// --------------------------------------------------------------------------------------
[개발 및 테스트 환경]
// --------------------------------------------------------------------------------------

- 언어 : Swift


- 개발 툴 : Xcode


- NWConnection 설명 :

  >> NWConnection 는 로컬 엔드포인트와 원격 엔드포인트 간의 양방향 데이터 연결 (TCP , UDP) 을 수행할 수 있습니다

  >> NWConnection 를 사용하기 위해서는 import Network 패키지 호출 정의가 필요합니다

  >> NWParameters 설정 가능 옵션 : 

   - tls
   - tcp
   - dtls
   - udp
   - quic
   - quicDatagram

// --------------------------------------------------------------------------------------






// --------------------------------------------------------------------------------------
[소스 코드]
// --------------------------------------------------------------------------------------

// --------------------------------------------------------------
// MARK: - [전역 변수 선언]
// --------------------------------------------------------------
private static let ACTIVITY_NAME = "C_Broker_Mqtt_NMConnection_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 연결 수행
// --------------------------------------------------------------
static var MQTT_CONNECT_LOG = ""
func observableMqttConnect(url: String, clientId: String, completion: @escaping (Bool)->()) {
    
    /*
    // -----------------------------------------
    [observableSocketConnect 메소드 설명]
    // -----------------------------------------
    1. IP , PORT 사용해 웹 소켓 연결 수행
    // -----------------------------------------
    2. 호출 방법 :
        
        C_Broker_Mqtt_NMConnection_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_NMConnection_Client_Module.MQTT_CONNECT_LOG)"])
            }
            
        }
        
    // -----------------------------------------
    3. 필요 import :
        
        import Network
    // -----------------------------------------
    */


    // [변수 선언]
    C_Broker_Mqtt_NMConnection_Client_Module.connection = nil
    C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG = ""
    self.workItem = nil
    C_Broker_Mqtt_NMConnection_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_NMConnection_Client_Module.connection = NWConnection(to: .url(URL(string: url)!), using: self.parames)


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

                        if sendResult == true {
                            
                            // ---------------------------------------------
                            // MARK: [연결 완료 플래그 값 변경]
                            // ---------------------------------------------
                            C_Broker_Mqtt_NMConnection_Client_Module.connectFlag = true
                            
                            
                            // ---------------------------------------------
                            // MARK: [실시간 메시지 수신 상태 확인]
                            // ---------------------------------------------
                            // self.receiveData()
                            
                            
                            // ---------------------------------------------
                            // MARK: [특정 토픽 구독 수행 실시]
                            // ---------------------------------------------
                            /*
                            C_Broker_Mqtt_NMConnection_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_NMConnection_Client_Module.SUBSCRIBE_ERROR_LOG)"])
                                }
                                
                            }
                            // */
                            
                            
                            // ---------------------------------------------
                            // [콜백 반환]
                            // ---------------------------------------------
                            completion(true)
                            return
                            
                        }
                        else {
                            S_Log._F_(description: "MQTT 연결 완료 에러 메시지", data: ["\(C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG)"])
                            
                            // [에러 메시지 삽입]
                            C_Broker_Mqtt_NMConnection_Client_Module.SEND_ERROR_LOG = C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG
                            
                            // [콜백 반환]
                            if C_Broker_Mqtt_NMConnection_Client_Module.connectFlag == false {
                            
                                completion(false)
                                
                            }
                            
                            // [객체 초기화]
                            if C_Broker_Mqtt_NMConnection_Client_Module.connection != nil {
                                
                                C_Broker_Mqtt_NMConnection_Client_Module.connection = nil
                                C_Broker_Mqtt_NMConnection_Client_Module.connectFlag = false
                                
                            }
                            
                            return
                        }
                        
                    }
                    
                case .failed(let error):
                    C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG = "[Fail] : Connection Fail State"
                    
                    S_Log._D_(description: "MQTT 연결 실패", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG)",
                        "Description :: \(error)"
                    ])
                    
                    // [콜백 반환]
                    if C_Broker_Mqtt_NMConnection_Client_Module.connectFlag == false {
                        
                        completion(false)
                        
                    }
                    
                    // [객체 초기화]
                    if C_Broker_Mqtt_NMConnection_Client_Module.connection != nil {
                        
                        C_Broker_Mqtt_NMConnection_Client_Module.connection = nil
                        C_Broker_Mqtt_NMConnection_Client_Module.connectFlag = false
                        
                    }
                    
                    return
                    
                case .cancelled:

                    C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG = "[Cancel] : Connection Cancel State"
                    
                    S_Log._D_(description: "MQTT 연결 실패", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG)"
                    ])
                    
                    // [콜백 반환]
                    if C_Broker_Mqtt_NMConnection_Client_Module.connectFlag == false {
                        
                        completion(false)
                        
                    }
                    
                    // [객체 초기화]
                    if C_Broker_Mqtt_NMConnection_Client_Module.connection != nil {
                        
                        C_Broker_Mqtt_NMConnection_Client_Module.connection = nil
                        C_Broker_Mqtt_NMConnection_Client_Module.connectFlag = false
                        
                    }
                    
                    return
                    
                default:
                    break
                }
            }

            
            // ---------------------------------------------
            // [연결 시작]
            // ---------------------------------------------
            C_Broker_Mqtt_NMConnection_Client_Module.connection?.start(queue: .global())
            
            
            // ---------------------------------------------
            // MARK: [MQTT 연결 타임 아웃 처리]
            // ---------------------------------------------
            self.workItem = DispatchWorkItem {
                
                if C_Broker_Mqtt_NMConnection_Client_Module.connection != nil && C_Broker_Mqtt_NMConnection_Client_Module.connectFlag == false {
                    
                    C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG = "[Time Out] : Mqtt Connection TimeOut"
                    
                    S_Log._D_(description: "MQTT 연결 실패", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG)"
                    ])
                    
                    // [MQTT 연결 취소]
                    C_Broker_Mqtt_NMConnection_Client_Module.connection?.cancel()
                    
                }
                
            }
            
            let delay = DispatchTime.now() + self.CONNECT_TIME_OUT // [특정 시간 후에 실행]
            
            DispatchQueue.main.asyncAfter(deadline: delay, execute: self.workItem!)
            
        }
        else {
            C_Broker_Mqtt_NMConnection_Client_Module.MQTT_CONNECT_LOG = "[Error] : Input Data Is Null"
            
            S_Log._D_(description: "MQTT 연결 에러", data: [
                "M_LOG :: \(C_Broker_Mqtt_NMConnection_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_NMConnection_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_NMConnection_Client_Module.SETTING_ERROR_LOG)"])
            }
            
        }
        
    // -----------------------------------------
    3. 필요 import :
        
        import Network
    // -----------------------------------------
    */
    
    
    // [변수 선언]
    C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG = ""
    
    
    // [로직 처리 수행]
    DispatchQueue.global().sync {

        if C_Broker_Mqtt_NMConnection_Client_Module.connection != nil && C_Util().stringNotNull(str: clientId) == true {
            
            S_Log._F_(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_NMConnection_Client_Module.connection?.send(content: Data(packet), completion: .contentProcessed { error in
                if let error = error {
                    C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG = "[Error] : Mqtt Setting Send Connection Packet Error"
                    
                    S_Log._D_(description: "MQTT 연결 요청 패킷 전송 에러", data: [
                        "M_LOG :: \(C_Broker_Mqtt_NMConnection_Client_Module.SEND_ERROR_LOG)",
                        "Description :: \(error)"
                    ])
                    
                    // [콜백 반환]
                    completion(false)
                    return
                    
                } else {
                    S_Log._D_(description: "MQTT 연결 요청 패킷 전송 성공", data: nil)
                    
                    // [콜백 반환]
                    completion(true)
                    return
                    
                }
            })
            
        }
        else {
            C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG = "[Error] : Setting Input Send Message Is Null"
            
            S_Log._D_(description: "MQTT 연결 옵션 지정 및 연결 완료 요청 에러", data: [
                "M_LOG :: \(C_Broker_Mqtt_NMConnection_Client_Module.SETTING_ERROR_LOG)"
            ])
            completion(false) // [콜백 반환]
            return
        }
        
    }

}

// --------------------------------------------------------------------------------------

 

반응형
Comments