import Foundation protocol SignalingClientDelegate: AnyObject { func signalingDidConnect() func signalingDidDisconnect() func signaling(didFail error: String) func signaling(didReceive message: SignalMessage) } /// 信令 WebSocket 客户端(URLSessionWebSocketTask 实现)。 /// 连接成功后自动发送 REGISTER(deviceType = CONTROLLER), /// 所有回调均已切换到主线程。 final class SignalingClient: NSObject { weak var delegate: SignalingClientDelegate? private let serverUrl: String private let deviceId: String private var session: URLSession? private var task: URLSessionWebSocketTask? private var manuallyClosed = false init(serverUrl: String, deviceId: String) { self.serverUrl = serverUrl self.deviceId = deviceId super.init() } func connect() { guard let url = URL(string: serverUrl) else { notifyError("无效的服务器地址: \(serverUrl)") return } manuallyClosed = false let config = URLSessionConfiguration.default config.timeoutIntervalForRequest = 15 session = URLSession(configuration: config, delegate: self, delegateQueue: nil) task = session?.webSocketTask(with: url) task?.resume() receiveLoop() } func disconnect() { manuallyClosed = true task?.cancel(with: .normalClosure, reason: "Disconnecting".data(using: .utf8)) task = nil session?.invalidateAndCancel() session = nil } func send(_ message: SignalMessage) { guard let task else { return } guard let data = try? JSONEncoder().encode(message), let json = String(data: data, encoding: .utf8) else { return } task.send(.string(json)) { error in if let error { NSLog("[Signaling] send error: \(error.localizedDescription)") } } } var isConnected: Bool { task != nil } // MARK: - Private private func registerDevice() { var msg = SignalMessage() msg.type = "REGISTER" msg.fromDeviceId = deviceId msg.deviceType = "CONTROLLER" send(msg) } private func receiveLoop() { task?.receive { [weak self] result in guard let self else { return } switch result { case .success(let wsMessage): if case .string(let text) = wsMessage, let data = text.data(using: .utf8), let message = try? JSONDecoder().decode(SignalMessage.self, from: data) { DispatchQueue.main.async { self.delegate?.signaling(didReceive: message) } } self.receiveLoop() case .failure(let error): if !self.manuallyClosed { self.notifyError(error.localizedDescription) } } } } private func notifyError(_ text: String) { DispatchQueue.main.async { self.delegate?.signaling(didFail: text) } } } // MARK: - URLSessionWebSocketDelegate extension SignalingClient: URLSessionWebSocketDelegate { func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didOpenWithProtocol protocol: String?) { registerDevice() DispatchQueue.main.async { self.delegate?.signalingDidConnect() } } func urlSession(_ session: URLSession, webSocketTask: URLSessionWebSocketTask, didCloseWith closeCode: URLSessionWebSocketTask.CloseCode, reason: Data?) { guard !manuallyClosed else { return } DispatchQueue.main.async { self.delegate?.signalingDidDisconnect() } } }