iOS 13 URLSessionWebSocketTask 当前连接状态

mre*_*dig 5 websocket ios swift urlsession

我已经URLSessionWebSocketDelegate设置并实施了

urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?)
Run Code Online (Sandbox Code Playgroud)

然而,如果服务器正确断开连接,它似乎只会影响委托。如果连接断开或服务器不提供断开连接代码,则委托函数永远不会运行。我还尝试检查URLSessionWebSocketTask属性closeCode以及closeReason断开连接之前和之后,除非服务器发送正确的断开连接代码,否则它们不会更改。

虽然我可以控制服务器并且可以确定故意断开连接有代码,但如果网络连接中断会发生什么?或者如果服务器宕机了?我认为委托函数仍然应该被调用。

有趣的是,我的监听函数出现错误,错误内容如下:

Error Domain=NSPOSIXErrorDomain Code=57 "Socket is not connected" UserInfo={NSErrorFailingURLStringKey=[urlomitted], NSErrorFailingURLKey=[urlomitted]
Run Code Online (Sandbox Code Playgroud)

因此,虽然如果我看到错误 57,我可以手动断开套接字,但我不知道这是否适用于所有不同的断开连接变体。

我认为我要么做错了什么,要么这是一个错误,但我找不到其他人有同样的问题,所以我想我应该在提交雷达之前询问。

Ham*_*ini 7

完全按照我在示例中所做的操作,它可以正常工作,没有任何问题。它管理连接状态,当您需要再次重新连接时,它使用 connect 方法重新初始化套接字。

@available(iOS 13.0, *)
class NativeWebSocket: NSObject, WebSocketProvider, URLSessionDelegate, URLSessionWebSocketDelegate {
    var delegate: WebSocketProviderDelegate?
    private var socket: URLSessionWebSocketTask!
    private var timeout: TimeInterval!
    private var url: URL!
    private(set) var isConnected: Bool = false

    init(url: URL, timeout: TimeInterval) {
        self.timeout = timeout
        self.url = url
        super.init()
    }

    // do not move create socket to init method because if you want to reconnect it never connect again
    public func connect() {
        let configuration = URLSessionConfiguration.default
        let urlSession = URLSession(configuration: configuration, delegate: self, delegateQueue: OperationQueue())
        let urlRequest = URLRequest(url: url, timeoutInterval: timeout)
        socket = urlSession.webSocketTask(with: urlRequest)
        socket.resume()
        readMessage()
    }

    func send(data: Data) {
        socket.send(.data(data)) { error in
            self.handleError(error)
        }
    }

    func send(text: String) {
        socket.send(.string(text)) { error in
            self.handleError(error)
        }
    }

    private func readMessage() {
        socket.receive { result in
            switch result {
            case .failure:
                break
            case .success(let message):
                switch message {
                case .data(let data):
                    self.delegate?.webSocketDidReciveData(self, didReceive: data)
                case .string(let string):
                    self.delegate?.webSocketDidReciveData(self, didReceive: string.data(using: .utf8)!)
                @unknown default:
                    print("un implemented case found in NativeWebSocketProvider")
                }
                self.readMessage()
            }
        }
    }

    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        isConnected = true
        delegate?.webSocketDidConnect(self)
    }

    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        isConnected = false
    }

    /// never call delegate?.webSocketDidDisconnect in this method it leads to close next connection
    func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
        completionHandler(.useCredential, URLCredential(trust: challenge.protectionSpace.serverTrust!))
    }

    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
        if let error = error {
            handleError(error)
        }
    }

    func closeConnection() {
        socket.cancel(with: .goingAway, reason: nil)
    }

    /// we need to check if error code is one of the 57 , 60 , 54 timeout no network and internet offline to notify delegate we disconnected from internet
    private func handleError(_ error: Error?) {
        if let error = error as NSError? {
            if error.code == 57 || error.code == 60 || error.code == 54 {
                isConnected = false
                closeConnection()
                delegate?.webSocketDidDisconnect(self, error)
            } else {
                delegate?.webSocketReceiveError(error)
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


小智 1

不确定这是一个好方法,但是下面的两个解决方案确实捕获了我的代码中服务器/连接事件的丢失。

a) 您可以在 WebSocket 接收函数中检测到它,如下所示:

func receiveMessage() {
        webSocketTask?.receive { result in
            defer { self.receiveMessage() }
            switch result {
            case .failure(let error):
                print("Error in receiving message: \(error)")
                let code = (error as NSError).code
                if code == 60 || code == 57 || code == 54 {
                    // deal with error
                }
            case .success(let message):
                switch message {
                case let .string(string):
                    // deal with strings message
                case let .data(data):
                    // deal with data message
                @unknown default:
                    // deal with unknown message
                }
            }
        }
    }  
Run Code Online (Sandbox Code Playgroud)

b) 或者您可以添加一个 urlSession 实例方法来URLSessionWebSocketDelegate捕获服务器/连接错误的一般丢失。

extension BlipSocket: URLSessionWebSocketDelegate {
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) {
        DispatchQueue.main.async {
            self.isConnected = true
        }
    }
    func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) {
        DispatchQueue.main.async {
            self.isConnected = false
        }
    }
    func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError: Error?) {
        DispatchQueue.main.async {
            self.connectionError = true
        }
    }
}
Run Code Online (Sandbox Code Playgroud)