使用GCDAsyncSocket接收数据遇问题,求可用Swift示例
我之前也踩过GCDAsyncSocket在Swift下的坑,确实官方和社区的Swift示例偏少,给你整理了一套完整的可运行示例,同时解决接收数据时常见的粘包/拆包、代理回调UI更新、连接状态管理等问题:
GCDAsyncSocket Swift 完整接收数据示例
完整代码实现
import UIKit import CocoaAsyncSocket class DiscoveryViewControllerTest: UIViewController, GCDAsyncSocketDelegate { let host = "192.168.55.1" let port: UInt16 = 4000 let cmdDeviceInformation = "?0600\r" let cmdDeviceIStandByeExit = "?060B\r" let cmdDeviceIStandByeEnter = "?060A\r" var mSocket: GCDAsyncSocket! // 用于缓存未处理的分段数据(解决TCP粘包/拆包问题) var receivedDataBuffer = Data() override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) setupSocket() connectToHost() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) disconnectSocket() } // MARK: - Socket 初始化 private func setupSocket() { // 初始化socket,指定主队列处理代理回调(方便直接更新UI) mSocket = GCDAsyncSocket(delegate: self, delegateQueue: DispatchQueue.main) } // MARK: - 连接服务器 private func connectToHost() { do { try mSocket.connect(toHost: host, onPort: port, withTimeout: 10) } catch let error { print("连接失败: \(error.localizedDescription)") } } // MARK: - 发送命令 func sendCommand(_ command: String) { guard let data = command.data(using: .ascii) else { print("命令转Data失败") return } // 发送数据,-1表示无超时 mSocket.write(data, withTimeout: -1, tag: 0) // 发送后立即启动接收监听 mSocket.readData(withTimeout: -1, tag: 0) } // MARK: - 断开连接 private func disconnectSocket() { mSocket.disconnect() receivedDataBuffer.removeAll() } // MARK: - GCDAsyncSocket Delegate func socket(_ sock: GCDAsyncSocket, didConnectToHost host: String, port: UInt16) { print("成功连接到 \(host):\(port)") // 连接成功后发送第一个命令 sendCommand(cmdDeviceInformation) } func socket(_ sock: GCDAsyncSocket, didRead data: Data, withTag tag: Int) { // 追加新数据到缓存 receivedDataBuffer.append(data) // 处理缓存中的完整响应 processReceivedBuffer() // 必须再次调用readData,否则会停止接收后续数据 sock.readData(withTimeout: -1, tag: 0) } func socketDidDisconnect(_ sock: GCDAsyncSocket, withError error: Error?) { if let error = error { print("连接异常断开: \(error.localizedDescription)") } else { print("主动断开连接") } receivedDataBuffer.removeAll() } // MARK: - 处理缓存数据,拆分完整响应 private func processReceivedBuffer() { guard let bufferString = String(data: receivedDataBuffer, encoding: .ascii) else { print("数据转ASCII字符串失败,清空缓存") receivedDataBuffer.removeAll() return } // 按协议约定的\r分割数据 let responseSegments = bufferString.components(separatedBy: "\r") for (index, segment) in responseSegments.enumerated() { if index == responseSegments.count - 1 { // 最后一段可能是不完整的,保留到缓存 receivedDataBuffer = segment.data(using: .ascii) ?? Data() } else if !segment.isEmpty { // 处理完整的响应内容 handleCompleteResponse(segment) } } } // MARK: - 业务逻辑处理 private func handleCompleteResponse(_ response: String) { print("收到完整响应: \(response)") // 根据不同命令的响应做对应处理 switch response { case let info where info.hasPrefix("?0600"): print("设备信息响应: \(info)") // 这里可以更新UI,比如显示设备信息 DispatchQueue.main.async { // self.deviceInfoLabel.text = info } case let status where status.hasPrefix("?060A"): print("设备进入待机状态") case let status where status.hasPrefix("?060B"): print("设备退出待机状态") default: print("未知响应类型: \(response)") } } }
核心问题解析
1. 解决TCP粘包/拆包问题
TCP是流式传输,数据不会按发送的边界拆分,所以必须自己维护缓存receivedDataBuffer,按协议中的分隔符(这里是\r)拆分出完整的响应。未处理的不完整数据留在缓存中,等待下一次接收后合并处理。
2. 代理队列与UI更新
初始化Socket时指定delegateQueue: DispatchQueue.main,这样所有代理回调都在主队列执行,不用额外切换队列就可以直接更新UI,避免线程问题。
3. 持续接收数据
每次didRead回调处理完数据后,必须调用sock.readData(withTimeout: -1, tag: 0),否则Socket会停止监听后续的 incoming data,这是很多新手容易踩的坑。
4. 连接状态管理
在viewWillDisappear中主动断开连接并清空缓存,避免页面销毁后Socket还保持连接,造成内存泄漏或无效资源占用。
内容的提问来源于stack exchange,提问作者Pascal
相关产品推荐
相关产品推荐

