You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.25 02:23:41