概览
Courier 是一个使用 MQTT 创建长连接的库,MQTT 是物联网消息的行业标准。长连接是在客户端和服务器之间建立的持久连接,用于双向通信。通过心跳包,长连接尽可能长时间保持连接状态,以实现即时更新。这也有利于在移动设备上节省电池和数据。
详细文档
详细文档可在此处找到 - https://gojek.github.io/courier-iOS/
端到端 courier 示例 - https://gojek.github.io/courier/docs/Introduction
入门指南
设置 courier,通过 iOS 设备与代理之间双向长连接订阅、发送和接收消息。
示例应用
您可以通过运行示例应用来连接到任何可以配置的代理。从方案中选择 CourierE2EApp
。
Courier 支持 Cocoapods 和 SPM 两种依赖管理器。它分为 5 个模块。
CourierCore
:包含 Courier 的公共 API,例如协议和数据类型。其他模块都依赖这个模块。如果您想在项目中实现接口而不添加 Courier 实现,则可以使用此模块。CourierMQTT
:使用 MQTT 实现CourierClient
和CourierSession
。此模块依赖MQTTClientGJ
。MQTTClientGJ
:开源库 MQTT-Client-Framework 的分支版本。它添加了连接和空闲超时等特性。它还修复了MQTTSocketEncoder
中出现的竞争条件崩溃和Connack
状态 5 在MQTTTransportDidClose
调用之前未完成解码的 bug。CourierProtobuf
:使用Protofobuf
实现ProtobufMessageAdapter
。它依赖于SwiftProtobuf
库,这是可选的,如果使用 protobuf 进行数据序列化,则可以使用它。CourierMQTTChuck
:可以用于检查底层 MQTT 连接的所有进出数据包。它拦截所有数据包,持久化它们,并提供一个用户界面来访问发送或接收的所有 MQTT 数据包。它还提供多个其他功能,如搜索、分享和清除数据。底层使用SwiftUI
。
Cocoapods
// Podfile
target 'Example-App' do
use_frameworks!
pod 'CourierCore'
pod 'CourierMQTT'
pod 'CourierProtobuf' #optional
pod 'CourierMQTTChuck' #optional
end
Swift包管理器(SPM)
简单地将包依赖添加到您的Package.swift文件中,并在必要的目标中依赖于CourierCore
和CourierMQTT
dependencies: [
.package(url: "https://github.com/gojek/courier-iOS", branch: "main")
]
实现IConnectionServiceProvider以提供认证的ConnectOptions
要连接到MQTT代理,您需要实现IConnectionServiceProvider。首先,您需要实现IConnectionServiceProvider/clientId
以返回一个唯一的字符串来标识您的客户端。这对于连接到代理的每个设备都必须是唯一的。
var clientId: String {
UIDevice.current.identifierForVendor?.uuidString ?? UUID().uuidString
}
接下来,您需要实现IConnectionServiceProvider/getConnectOptions(completion:)
方法。您需要提供一个ConnectOptions
实例,该实例将用于连接到代理。此方法提供了一个释放闭包,以便您可以从远程API异步检索证书。
func getConnectOptions(completion: @escaping (Result<ConnectOptions, AuthError>) -> Void) {
executeNetworkRequest { (response: ConnectOptions) in
completion(.success(connectOptions))
} failure: { _, _, error in
completion(.failure(error))
}
}
以下是您需要在ConnectOptions中提供的数据。
/// IP Host address of the broker
public let host: String
/// Port of the broker
public let port: UInt16
/// Keep Alive interval used to ping the broker over time to maintain the long run connection
public let keepAlive: UInt16
/// Unique Client ID used by broker to identify connected clients
public let clientId: String
/// Username of the client
public let username: String
/// Password of the client used for authentication by the broker
public let password: String
/// Tells broker whether to clear the previous session by the clients
public let isCleanSession: Bool
使用CourierClientFactory配置和创建MQTT CourierClient实例
接下来,我们需要创建使用MQTT作为其实现的CourierClient实例。初始化CourierClientFactory
实例,并调用CourierClientFactory/makeMQTTClient(config:)
。我们需要传递带有几个可自定义参数的MQTTClientConfig实例。
let clientFactory = CourierClientFactory()
let courierClient = clientFactory.makeMQTTClient(
config: MQTTClientConfig(
authService: HiveMQAuthService(),
messageAdapters: [
JSONMessageAdapter(),
ProtobufMessageAdapter()
],
autoReconnectInterval: 1,
maxAutoReconnectInterval: 30
)
)
MQTTClientConfig/messageAdapters
:我们需要传递一个MessageAdapter
数组。这将用于接收来自代理的消息时的序列化和将消息发送到代理。CourierMQTT
为符合Codable
协议的JSON(JSONMessageAdapter
)和Plist(PlistMessageAdapter
)格式提供了内置的消息适配器。您只能使用其中之一,因为它们都实现了 Codable 以避免任何冲突。要使用protobuf,请导入CourierProtobuf
并传递ProtobufMessageAdapter
。MQTTClientConfig/authService
:我们需要传递适用于IConnectionServiceProvider协议的实现,以向客户端提供ConnectOptions。MQTTClientConfig/autoReconnectInterval
失去连接时尝试重新连接代理的间隔。每次失败后,该间隔将乘以2,直到成功连接为止。上限由MQTTClientConfig/maxAutoReconnectInterval
决定。
在CourierClient中管理连接生命周期
要连接到代理,您只需调用 connect
方法
courierClient.connect()
要断开连接,只需调用 disconnect
方法
courierClient.disconnect()
要获取连接状态,您可以通过访问 CourierSession/connectionState 属性
courierClient.connectionState
您还可以使用 CourierSession/connectionStatePublisher
属性订阅 ConnectionState
发送者。Courier提供的可观察API与 Apple Combine
非常相似,尽管它是使用 RxSwift
内部实现的,因此我们可以支持 iOS 12
。
courierClient.connectionStatePublisher
.sink { [weak self] self?.handleConnectionStateEvent($0) }
.store(in: &cancellables)
由于MQTT支持QoS 1和QoS 2消息,以确保在无互联网连接并且用户重新连接到代理时能够可靠地传递消息,因此我们将这些消息持久保存在本地缓存中。要断开连接并删除所有缓存,您可以调用。
courierClient.destroy()
在使用Courier时,您需要记住以下几点
- Courier始终会在应用进入后台时断开连接,因为iOS不支持在后台长时间运行套接字连接。
- 如果应用程序中有要订阅的主题,Courier始终会在应用进入前台时自动重新连接。
- Courier使用Reachability框架处理 internet 连接不良或丢失时的重新连接。
- 如果没有任何活跃的订阅使用 Observable/Publisher,Courier将使用可配置的TTL持久保存QoS > 0的消息。
MQTT中的QoS级别
服务质量(QoS)级别是消息发送方和接收方之间的一项协议,用于定义特定消息的交付保证。MQTT中有3种QoS级别:
- 最多一次(0)
- 至少一次(1)
- 恰好一次(2)。
讨论MQTT中的QoS时,您需要考虑消息传递的两个方面:
- 从发布客户端到代理的消息传递。
- 从代理到订阅客户端的消息传递。
您可以在HiveMQ网站上了解更多关于MQTT中QoS的详细信息。
从代理订阅主题
要从代理订阅一个主题。我们可以调用 CourierSession/subscribe(_:)
并传递一个包含主题字符串和 QoS 枚举的元组。
courierClient.subscribe(("chat/user1", QoS.zero))
您还可以订阅多个主题,通过调用 CourierSession/subscribe(_:)
并传递一个包含主题字符串和 QoS 枚举元组的数组。
courierClient.subscribe([
("chat/user1", QoS.zero),
("order/1234", QoS.one),
("order/123456", QoS.two),
])
从已订阅主题接收消息
在您已订阅了主题之后,您需要通过调用 CourierSession/messagePublisher(topic:)
订阅消息发布者,并传递相关联的主题。该方法使用 Generic
将二进制数据序列化到类型。确保您已提供可以解码数据的关联消息适配器。
courierClient.messagePublisher(topic: topic)
.sink { [weak self] (note: Note) in
self?.messages.insert(Message(id: UUID().uuidString, name: "Protobuf: \(note.title)", timestamp: Date()), at: 0)
}.store(in: &cancellables)
该方法返回 AnyPublisher,您可以使用诸如 AnyPublisher/filter(predicate:)
或 AnyPublisher/map(transform:)
之类的操作符对其进行链式调用。
Courier 提供的 observable API 与 Apple Combine 非常相似,尽管它是在内部使用 RxSwift 实现的,因此我们支持 iOS 12,仅支持 filter
和 map
操作符。
从主题取消订阅
要从主题取消订阅。我们可以调用 CourierSession/unsubscribe(_:)
并传递一个主题字符串。
courierClient.unsubscribe("chat/user1")
您还可以订阅多个主题,通过调用 CourierSession/unsubscribe(_:)
并传递一个包含主题字符串和 QoS 枚举的元组的数组。
courierClient.unsubscribe([
"chat/user1",
"order/"
])
向代理发送消息
要将消息发送到代理,首先确保您提供了一个能够将您的对象编码为二进制数据格式的MessageAdapter
。例如,如果您有一个想要以JSON格式发送的数据结构,请确保它符合Encodable
协议,并在创建CourierClient
实例时在MQTTClientConfig
中传递JSONMessageAdapter
。
您只需调用CourierSession/publishMessage(_:topic:qos:)
函数,传入主题字符串和QoS枚举。这是一个throwing
函数,如果编码失败,它可能会抛出异常。
let message = Message(
id: UUID().uuidString,
name: message,
timestamp: Date()
)
try? courierService?.publishMessage(
message,
topic: "chat/1234",
qos: QoS.zero
)
监听Courier内部事件
要监听Courier系统事件,如CourierEvent/connectionSuccess
、CourierEvent/connectionAttempt
等,您可以使用CourierEvent
枚举中声明的更多事件,实现ICourierEventHandler
协议,并实现ICourierEventHandler/onEvent(_:)
方法。此方法将在任何Courier系统事件发生时被调用。
最后,确保对实例具有强引用,并调用CourierEventManager/addEventHandler(_:)
传入该实例。
courierClient.addEventHandler(analytics)
监控Courier MQTT数据包日志
CourierMQTTChuck
用于检查底层MQTT连接的所有发出或接收到的数据包。
它会截获所有数据包,并将它们持久保存,同时提供一个用户界面以访问所有发送或接收的MQTT数据包。它还提供诸如搜索、分享和清除数据等其他功能。
它使用SwiftUI,最低iOS部署版本为15,但仍可以在iOS 11上构建,以访问记录器而不显示视图。
使用
将依赖项添加到 CourierMQTTChuck
并简单地声明日志记录器如下
import CourierMQTTChuck
let logger = MQTTChuckLogger()
然后在 SwiftUI 视图中声明 MQTTChuckView
,传递 logger
。
.sheet(isPresented: $showChuckView, content: {
NavigationView {
MQTTChuckView(logger: logger)
}
})
贡献指南
阅读我们的贡献指南以了解我们的开发过程、如何提出错误修复和改进的方法,以及如何构建和测试你的Courier iOS库更改。
许可
除MQTTClientGJ外的所有Courier模块都遵循MIT许可证。MQTTClientGJ遵循Eclipse许可证。