前言
在日常开发中,我们难免会遇到需要使用 socket 进行网络编程,跨平台通讯等场景,而如果选择使用 iOS 原生的 CFSocket 又略显得繁琐,不够优雅,那么又有没有一些优秀的第三方库呢?经过查找,我找来了两个比较优秀的第三方 socket 库,分别是 CocoaAsyncSocket 和 BlueSocket。
CocoaAsyncSocket 是使用 Objective-C 编写的,一个比较老牌、成熟的第三方 socket 库(Github 传送门),它在 Github 上已经获得了过万的 star,许多开发者也使用它进行网络通信开发。而 BlueSocket 则是由 IBM 开发、维护的一个纯 Swift 编写的第三方 socket 库(Github 传送门),但相比于 CocoaAsyncSocket,它还略显稚嫩,还没有得到广泛的使用,网上关于它的介绍和 Demo 也比较少。虽然二者之间存在不少差异,但这两个优秀的第三方库都能很优雅地处理 socket 的创建、监听、连接、数据传输等操作。下文就两者在 socket 的创建、连接、读写操作三方面进行简单的比较,通过对比,各位可以根据需求,选择最适合的 socket 库。
Socket 实例的创建
在创建 Socket 实例时,CocoaAsyncSocket 明显更简单,更清晰明了,只需查看 socket 所属的类便可得知它是走 TCP 还是 UDP 协议,而 BlueSocket 在创建的时候,则提供了四种不同的构造方法来创建一个 Socket 实例,可以根据项目的特定需要来选择对应的协议。在监听方法上,两者都可以选择监听端口还是地址。根据文档得知,CocoaAsyncSocket 已经做了 GCD 的封装,且是线程安全的,而 BlueSocket 则需要手动创建线程,如有不慎可能会导致线程安全问题。
socket 创建时的主要的差异可见下表:
CocoaAsyncSocket | BlueSocket | |
---|---|---|
线程管理 | 已封装 GCD,线程安全 | 需手动创建并管理线程 |
实例创建 | 较为单一,须指定类型 | 四种方法,自定义程度高 |
socket 缓冲区 | 可自定义读/写缓存 | 可自定义读缓存 |
代码复杂度 | 低 | 高 |
以下代码为两者创建 socket 实例并开始监听的概要步骤
CocoaAsyncSocket:
CocoaAsyncSocket 库使用 GCDAsyncSocket 和 GCDAsyncUdpSocket 来区分走 TCP 协议还是 UDP 协议,它们的操作大同小异,以下均使用 GCDAsyncUdpSocket 举例。
// 使用前需要提前确定好是使用 GCDAsyncUdpSocket 还是 GCDAsyncSocket
var socket: GCDAsyncUdpSocket?
let queue = DispatchQueue.global(qos: .default)
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: queue)
do {
try self.socket?.bind(toPort: UInt16) //监听端口
try self.socket?.beginReceiving() //开始接收数据
} catch {
print("Failed to create socket.")
}
BlueSocket:
BlueSocket 提供了四种创建 socket 实例的方法,默认缓冲区大小为 4096, 最小为 1024
//方法一
socket?.create() //创建了一个全部为默认配置的Socket实例。(family默认是 inet(ipv4)、type 默认是 stream、protocol默认是 .tcp)
//方法二
socket?.create(family family: ProtocolFamily, type: SocketType, proto: SocketProtocol) //可以自定义不同的协议
//方法三
socket?.create(connectedUsing signature: Signature) // 根据 socket signature 创建
//方法四
socket?.create(fromNativeHandle nativeHandle: Int32, address: Address?)
BlueSocket 支持的协议和数据类型:
Families:
IPV4: Socket.ProtocolFamily.inet
IPV6: Socket.ProtocolFamily.inet6
UNIX: Socket.ProtocolFamily.unix
Types:
Stream: Socket.SocketType.stream
Datagram: Socket.SocketType.datagram
Protocols:
TCP: Socket.SocketProtocol.tcp
UDP: Socket.SocketProtocol.udp
UNIX: Socket.SocketProtocol.unix
创建 socket 实例并监听的步骤:
func run() {
let queue = DispatchQueue.global(qos: .userInteractive)
queue.async { [unowned self] in
do {
// Create an IPV4 socket...
try self.listenSocket = Socket.create(family: .inet)
guard let socket = self.listenSocket else {return}
try socket.listen(on: self.port)
repeat {
let newSocket = try socket.acceptClientConnection()
//对 newSocket 进行下一步的操作
} while self.continueRunning
} catch let error {
guard let socketError = error as? Socket.Error else {
print("Unexpected error...")
return
}
if self.continueRunning {
print("Error reported:\n \(socketError.description)")
}
}
}
dispatchMain()
}
通过代码的对比,可看出 CocoaAsyncSocket 在创建过程中的代码短小精悍,清晰明了,而 BlueSocket 则可以实现更多的自定义设置。
Socket 的连接
在连接上,二者并无太大的差异,BlueSocket 少了通过 socket address
连接的方式,多了一个 socket signature
的连接方式。而 BlueSocket 的监听方检测到有连接请求时,可以选择接受连接请求,或者忽略,多线程的程序则可以一边接受请求,一边继续监听。详情可见文档中提到的 acceptClientConnection(invokeDelegate: Bool = true)
, invokeDelegateOnAccept(for newSocket: Socket)
和 acceptConnection()
。下面列出二者在连接 socket 时的相关代码。
CocoaAsyncSocket:
do {
// 方法一 通过 HostName 以及端口号进行连接
try self.clientSocket?.connect(toHost: String, onPort: UInt16)
// 方法二 通过 address 连接(在 NetService 的 didResolve 回调方法中可以获取)
try self.clientSocket?.connect(toAddress: Data)
} catch {
print("Failed to connect to socket.")
}
BlueSocket:
do {
let socket = try Socket.create(family: .inet)
try socket.connect(to: String, port: Int32)
} catch {
guard let socketError = error as? Socket.Error else {
print("Unexpected error ...")
return
}
print("Error: \(socketError.description)")
}
由于 CocoaAsyncSocket 封装了 delegate 回调方法,所以当 socket 连接成功时,走 TCP 协议或 UDP 协议的 socket 可以在它们对应的回调方法 didConnectToAddress
里拿到相关的变量。而 BlueSocket 则需要判断一下 isConnecte
再取得你所需要的值。
Socket 的读写操作
在读写操作的对比上,BlueSocket提供的写入、读取类型比较丰富,而 CocoaAsyncSocket 已经在底层封装好读写操作,只提供了写入 Data 类型的方法。但 CocoaAsyncSocket 可以通过 delegate 回调来获取 socket 收到的数据,也能获取发送成功与否的结果和原因,还能设置发送数据的超时时间等。CocoaAsyncSocket 在使用上更加清晰、优雅;而 BlueSocket 没有封装 delegate 方法,仍需要手动创建线程来监听 socket,如有不慎,容易造成线程安全问题。
它们在读写操作上的差异可以总结为下表:
CocoaAsyncSocket | BlueSocket | |
---|---|---|
是否有 delegate 回调 | 有 | 无 |
读写的数据类型 | 单一 | 丰富 |
线程管理 | 已封装,线程安全 | 手动创建和管理 |
代码复杂度 | 简单 | 较为复杂 |
读写数据的相关步骤如下
CocoaAsyncSocket:
发数据(回调方法中提供了数据发送成功与否的结果):
// 提供了三种写入方法,第一种需要在调用前连接上接收方的 socket
socket?.send(data: Data, withTimeout: TimeInterval, tag: Int)
socket?.send(data: Data, toAddress: Data, withTimeout: TimeInterval, tag: Int)
socket?.send(data: Data, toHost: String, port: UInt16, withTimeout: TimeInterval, tag: Int)
读数据:
// 在回调方法里面读取
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
}
BlueSocket:
写数据:
socket?.write(from data: Data) //发送Data类型数据。
socket?.write(from data: NSData) //发送NSData类型数据,未来可能会被弃用。
socket?.write(from string: String) //发送String类型数据。
socket?.write(from buffer: UnsafeRawPointer, bufSize: Int)
读数据:
// 对应的数据读取方法
socket?.read(into data: inout Data)
socket?.read(into data: NSMutableData)
socket?.readString()
socket?.read(into buffer: UnsafeMutablePointer<CChar>, bufSize: Int, truncate: Bool = false)
以下为第一种读取数据的概要步骤( socket 的监听需放入线程的死循环中):
var readData = Data(capacity: bufferSize)
let bytesRead = try socket.read(into: &readData)
if bytesRead > 0 {
guard let response = String(data: readData, encoding: .utf8) else {
print("Error decoding response...")
readData.count = 0
break
}
print("response:\(response)")
}
小结
CocoaAsyncSocket 是一个老牌的 socket 库,它有着高度封装、线程安全、多协议支持等优点,但它的相关命名风格遵循的是 Objective-C 的风格,在纯 Swift 的项目中,可读性会比较差,但在一些简单的业务场景,特别是配合 Bonjour 使用时,个人更推荐使用 CocoaAsyncSocket;而 BlueSocket 则是一个后来者,它有着纯正的 Swift 血统,同样支持多种协议,但它没有封装 delegate,监听 socket 时需要手动创建线程,代码相对于 CocoaAsyncSocket 而言也比较臃肿,想写的优雅一点,建议再进行一层封装。如果你的业务场景比较复杂,CocoaAsyncSocket 并不能满足你的某些自定义设置的需求,BlueSocket 也会是一个不错的选择。
参考来源
https://github.com/robbiehanson/CocoaAsyncSocket/wiki
https://github.com/IBM-Swift/BlueSocket/blob/master/README.md