투케이2K

548. (ios/swift5) [URLSession] [1] Websocket 웹 소켓 connect 연결 수행 및 ping 응답 확인 본문

IOS

548. (ios/swift5) [URLSession] [1] Websocket 웹 소켓 connect 연결 수행 및 ping 응답 확인

투케이2K 2024. 9. 13. 15:49

[개발 환경 설정]

개발 툴 : XCODE

개발 언어 : SWIFT5

 

[소스 코드]

import Foundation
import UIKit

class C_WebSocket_Urlsession_Client_Module : NSObject, URLSessionWebSocketDelegate {
    
    
    
    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [클래스 설명]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 1. 웹 소켓 통신 수행 클라이언트 모듈
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * 2. 테스트 웹소켓 참고 사이트 :
     *
     * https://www.toolfk.com/ko/tools/online-runwebsocket.html
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */
    
    
    
    
    
    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [소스 코드 사용 방법]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     DispatchQueue.main.async { // [비동기 요청]
         
         let tag_ = "웹 소켓 통신 요청"
         
         //let url_ = "wss://javascript.info/article/websocket/demo/hello"
         let url_ = "ws://192.168.0.15:8001"
         
         let header_ : Dictionary<String, Any> = [:]
         
         
         // -----------------------------------------------
         // MARK: [웹 소켓 연결 수행]
         // -----------------------------------------------
         C_WebSocket_Urlsession_Client_Module().startWebsocket(tag: tag_, url: url_, header: header_){(result, response) in
             
             S_Log._F_(description: "웹 소켓 연결 결과 확인", data: [response])
             
             if result == true {
                 
                 
                 // -----------------------------------------------
                 // MARK: [주기적 핑 체크 동작]
                 // -----------------------------------------------
                 C_WebSocket_Urlsession_Client_Module().pingCheck {(result, response) in }
                 
                 
                 // -----------------------------------------------
                 // MARK: [웹 소켓 종료 수행]
                 // -----------------------------------------------
                 DispatchQueue.main.asyncAfter(deadline: .now() + 30) { // [30초 시간 설정]
                     
                     C_WebSocket_Urlsession_Client_Module().closeWebSocket(){(result, response) in
                         
                         S_Log._F_(description: "웹 소켓 종료 결과", data: [response])
                         
                     }
                     
                 }
                 
             }
             
         }
         
     }
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * */





    /**
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * TODO [빠른 로직 찾기 : 주석 로직 찾기]
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : startWebsocket
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : WebSocket Open Delegate
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : WebSocket Timer Ping
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     * [SEARCH FAST] : WebSocket Close
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     *
     * // -----------------------------------------------------------------------------------------------------------------------------------------------------------------
     */
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [전역 변수 선언]
    // -----------------------------------------------------------------------------------------

    private let ACTIVITY_NAME = "C_WebSocket_Urlsession_Client_Module"
    
    var workItem: DispatchWorkItem? = nil
    let PING_TIME_OUT = 10.0
    
    var tag = "" // [태그 지정]
    
    static var webSocketTask: URLSessionWebSocketTask?  = nil // [웹소켓 태스크 지정]
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : startWebsocket
    // -----------------------------------------------------------------------------------------
    private let socketOperationQueue = OperationQueue()
    static var webSocketOpenFlag = false
    static var webSocketOpenMessage = ""
    
    func startWebsocket(tag: String, url: String, header: Dictionary<String, Any>?, callback: @escaping (Bool, String)->()) {
        
        /*
         // -----------------------------------------
         [requestQueriesHttp 메소드 설명]
         // -----------------------------------------
         1. 비동기 POST 방식 graphql HTTP 통신 수행 및 콜백 반환 실시
         // -----------------------------------------
         2. 호출 방법 :
         
         let tag_ = "웹 소켓 통신 요청"
         let url_ = "wss://javascript.info/article/websocket/demo/hello"
         let header_ : Dictionary<String, Any> = [:]
         
         C_WebSocket_Urlsession_Client_Module().startWebsocket(tag: tag_, url: url_, header: header_){(result, response) in
             
             S_Log._F_(description: "웹 소켓 연결 결과 확인", data: [response])
             
         }
         // -----------------------------------------
         3. 사전 설정 사항 :
         
         - 필요 info plist 설정
         [1] http 허용 : App Transport Security Settings >> Allow Arbitrary Loads >> YES
         // -----------------------------------------
         */
        
        
        // -----------------------------------------
        // [초기 값 초기화]
        // -----------------------------------------
        C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
        C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = "Start"
        self.workItem = nil
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [작업 큐에 추가]
        // -----------------------------------------
        self.socketOperationQueue.isSuspended = true
        let block = { callback(C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag, C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage) }
        self.socketOperationQueue.addOperation(block)
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [사전 방어 로직 체크]
        // -----------------------------------------
        if url.startsWith(_string: "http") == true || url.startsWith(_string: "ws") == true {
        }
        else {
            // [콜백 반환]
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [startWebsocket] :: [ERROR] :: Url Http Not Start"
            self.socketOperationQueue.isSuspended = false
            return
        }
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [url 선언 실시]
        // -----------------------------------------
        let urlData = String(describing: url)
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [Tag 지정 실시]
        // -----------------------------------------
        self.tag = String(describing: tag)
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [URLRequest 생성 실시]
        // -----------------------------------------
        let urlComponents = URLComponents(string: urlData)
        var requestURL = URLRequest(url: (urlComponents?.url)!)
        
        requestURL.httpMethod = "GET"
        
        requestURL.addValue("application/x-www-form-urlencoded; charset=utf-8;", forHTTPHeaderField: "Content-Type") // header settings
        requestURL.addValue("no-cache", forHTTPHeaderField: "Cache-Control") // header settings
        
        if C_Util().dicNotNull(dic_: header) == true {
            
            for key in header!.keys {
                
                requestURL.addValue("\(String(describing: header![key] ?? ""))", forHTTPHeaderField: "\(key)")
                
            }

        }
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [요청 로그 출력 실시]
        // -----------------------------------------
        S_Log._D_(description: self.ACTIVITY_NAME + " :: [startWebsocket] :: WebSocket Http [요청] 수행", data: [
            "TAG :: " + String(describing: tag),
            "TYPE :: GET >> REQUEST",
            "URL :: " + String(describing: urlData),
            "HEADER :: " + String(describing: header)
        ])
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [http 요쳥을 위한 URLSessionDataTask 생성]
        // -----------------------------------------
        let sessionConfig = URLSessionConfiguration.default
        //sessionConfig.timeoutIntervalForRequest = C_WebSocket_Urlsession_Client_Module.TIME_OUT // [커넥션 타임 아웃 설정]
        //sessionConfig.timeoutIntervalForResource = C_WebSocket_Urlsession_Client_Module.TIME_OUT // [리소스 읽기 , 쓰기]
        
        let session = URLSession(configuration: sessionConfig)
        
        S_FileManager.appHttpLogSave(description: String(describing: tag), request: requestURL, data: nil, response: nil, error: nil)
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [웹 소켓 Task 지정]
        // -----------------------------------------
        C_WebSocket_Urlsession_Client_Module.webSocketTask = session.webSocketTask(with: requestURL)
        
        if #available(iOS 15.0, *) {
            C_WebSocket_Urlsession_Client_Module.webSocketTask?.delegate = self
        } else {
            // [콜백 반환]
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [startWebsocket] :: [ERROR] :: Ios Version 15 Minor"
            self.socketOperationQueue.isSuspended = false
            return
        }
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [network 통신 실행]
        // -----------------------------------------
        C_WebSocket_Urlsession_Client_Module.webSocketTask!.resume()
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [작업 아이템 지정]
        // -----------------------------------------
        self.workItem = DispatchWorkItem {
            
            // [콜백 반환]
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [startWebsocket] :: [ERROR] :: Ping Timeout"
            self.socketOperationQueue.isSuspended = false
            return
        }
        
        let delay = DispatchTime.now() + self.PING_TIME_OUT // [특정 시간 후에 실행]
        
        DispatchQueue.main.asyncAfter(deadline: delay, execute: self.workItem!)
        // -----------------------------------------
        
    }
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : WebSocket Open Delegate
    // -----------------------------------------------------------------------------------------
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        
        
        // MARK: [Ping 타임 아웃 예약 작업 취소]
        if (self.workItem != nil){
            self.workItem?.cancel()
            self.workItem = nil
        }
        
        
        // MARK: [웹 소켓 상태 확인]
        switch webSocketTask.state {

        case .running:
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = true
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: The web socket is running"
        case .suspended:
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: The web socket is suspended"
        case .canceling:
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: The web socket is canceling"
        case .completed:
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: The web socket is completed"
        @unknown default:
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: The web socket Unknown state"
        }
        
        
        // MARK: [Task 에러 상태 확인]
        //*
        guard webSocketTask.error == nil else {
            
            C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
            C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: [webSocketTask.error] :: \(webSocketTask.error?.localizedDescription ?? "")"
        
            S_Log._E_(description: self.ACTIVITY_NAME + " :: [urlSession] :: WebSocket [didOpenWithProtocol] Open 열기 Error 확인", data: [
                "TAG :: " + String(describing: self.tag),
                "STATUS :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag),
                "MESSAGE :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage),
                "PROTOCOL :: " + String(describing: `protocol`)
            ])
            
            self.socketOperationQueue.isSuspended = false
            
            return
        }
        // */
        
        
        // MARK: [ping 체크 방식으로 연결 상태 확인]
        if C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag == true {
           
            C_WebSocket_Urlsession_Client_Module.webSocketTask?.sendPing(pongReceiveHandler: { error in
                guard error == nil else {
                    
                    C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
                    C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: [Ping Error] :: \(error?.localizedDescription ?? "")"
                
                    S_Log._E_(description: self.ACTIVITY_NAME + " :: [urlSession] :: WebSocket [didOpenWithProtocol] Open 열기 Error 확인", data: [
                        "TAG :: " + String(describing: self.tag),
                        "STATUS :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag),
                        "MESSAGE :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage),
                        "PROTOCOL :: " + String(describing: `protocol`)
                    ])
                    
                    self.socketOperationQueue.isSuspended = false
                    
                    return
                }
            
                
                C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = true
                C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = self.ACTIVITY_NAME + " :: [urlSession] :: [Ping Success] :: WebSocket Open"
                
                S_Log._W_(description: self.ACTIVITY_NAME + " :: [urlSession] :: WebSocket [didOpenWithProtocol] Open 열기 결과 확인", data: [
                    "TAG :: " + String(describing: self.tag),
                    "STATUS :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag),
                    "MESSAGE :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage),
                    "PROTOCOL :: " + String(describing: `protocol`)
                ])
                
                // [콜백 반환]
                self.socketOperationQueue.isSuspended = false
                
            })
            
        }
        else {
            
            S_Log._E_(description: self.ACTIVITY_NAME + " :: [urlSession] :: WebSocket [didOpenWithProtocol] Open 열기 Fail 확인", data: [
                "TAG :: " + String(describing: self.tag),
                "STATUS :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag),
                "MESSAGE :: " + String(describing: C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage),
                "PROTOCOL :: " + String(describing: `protocol`)
            ])
            
            // [콜백 반환]
            self.socketOperationQueue.isSuspended = false
            
        }
        
    }
    
    
    
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        
        S_Log._E_(description: self.ACTIVITY_NAME + " :: [urlSession] :: WebSocket [didCloseWith] Close 닫기 결과 확인", data: [
            "TAG :: " + String(describing: self.tag),
            "CLOSE_CODE :: " + String(describing: closeCode)
        ])
        
        // [콜백 반환]
        C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
        C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = "Close"
        self.socketOperationQueue.isSuspended = false

    }

    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : WebSocket Timer Ping
    // -----------------------------------------------------------------------------------------
    func pingCheck(callback: @escaping (Bool, String)->()) {
        //S_Log._F_(description: self.ACTIVITY_NAME + " :: WebSocket Ping [핑 체크] 수행", data: nil)
        
        // -----------------------------------------
        // [사전 방어 로직 작성]
        // -----------------------------------------
        if C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag == false || C_WebSocket_Urlsession_Client_Module.webSocketTask == nil {
            
            // [콜백 반환]
            callback(false, self.ACTIVITY_NAME + " :: [pingCheck] :: [ERROR] :: oepn false || socketTask is nil")
            return
        }
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [핑 체크 작업 예약]
        // -----------------------------------------
        if C_WebSocket_Urlsession_Client_Module.webSocketTask!.state == .running {
            print(self.ACTIVITY_NAME + " :: [pingCheck] :: WebSocket ping [핑 체크] :: WebSocket is connected == true")
            
            DispatchQueue.main.asyncAfter(deadline: .now() + 10) { // [반복 상태 체크 수행]
                self.pingCheck {(result, response) in }
            }
            
            // -----------------------------------------
            // [핑 체크 수행 실시]
            // -----------------------------------------
            C_WebSocket_Urlsession_Client_Module.webSocketTask?.sendPing(pongReceiveHandler: { error in
                guard error == nil else {
                    S_Log._E_(description: self.ACTIVITY_NAME + " :: [pingCheck] :: WebSocket [Ping Error] :: 핑 체크 에러 발생", data: [
                        "Error :: \(error?.localizedDescription ?? "")"
                    ])

                    // [콜백 반환]
                    callback(false, self.ACTIVITY_NAME + " :: [pingCheck] :: [Ping Error] :: \(error?.localizedDescription ?? "")")
                    return
                }
            
                
                S_Log._W_(description: self.ACTIVITY_NAME + " :: [pingCheck] :: WebSocket [Ping Success] :: 핑 체크 수행 성공", data: nil)

                
                // [콜백 반환]
                callback(true, "")
                return
                
            })
            
        } else {
            print(self.ACTIVITY_NAME + " :: [pingCheck] :: WebSocket ping [핑 체크] :: WebSocket is connected == false")
            
            // [콜백 반환]
            callback(false, self.ACTIVITY_NAME + " :: [pingCheck] :: [Ping Error] :: WebSocket is connected == false)")
            return
        }
        
    }
    
    
    
    
    
    // -----------------------------------------------------------------------------------------
    // MARK: - [SEARCH FAST] : WebSocket Close
    // -----------------------------------------------------------------------------------------
    func closeWebSocket(callback: @escaping (Bool, String)->()) {
        S_Log._D_(description: self.ACTIVITY_NAME + " :: [closeWebSocket] :: WebSocket Close 종료 수행", data: nil)
        
        // -----------------------------------------
        // [사전 방어 로직 작성]
        // -----------------------------------------
        if C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag == false || C_WebSocket_Urlsession_Client_Module.webSocketTask == nil {
            
            // [콜백 반환]
            callback(false, self.ACTIVITY_NAME + " :: [closeWebSocket] :: [ERROR] :: oepn false || socketTask is nil")
            return
        }
        // -----------------------------------------
        
        
        // -----------------------------------------
        // [웹 소켓 종료 수행 및 변수 초기화]
        // -----------------------------------------
        if C_WebSocket_Urlsession_Client_Module.webSocketTask != nil {
            C_WebSocket_Urlsession_Client_Module.webSocketTask?.cancel()
        }
        C_WebSocket_Urlsession_Client_Module.webSocketTask = nil
        C_WebSocket_Urlsession_Client_Module.webSocketOpenFlag = false
        C_WebSocket_Urlsession_Client_Module.webSocketOpenMessage = ""
        self.workItem = nil
        self.tag = ""
        
        // [콜백 반환]
        callback(true, self.ACTIVITY_NAME + " :: [closeWebSocket] :: [WebSocket Close] :: [웹 소켓 종료] :: Success")
        return
        
    }
    

    
} // [클래스 종료]
 

[결과 출력]

 

 

반응형
Comments