利用现代网络技术提升性能和安全性
Boost performance and security with modern networking
2020年6月25日
一句话判断
如果你还在用 URLSession 做”万能”网络请求,这个 Session 会告诉你如何利用 Network.framework 的 TLS 1.3、Multipath TCP、IPv6 Happy Eyeballs 等能力来打造更快更安全的网络层。
这场 Session 讲了什么
Session 聚焦在 Apple 平台上的现代网络技术栈,以及如何在 App 中利用这些技术来提升性能和安全性。内容涵盖了四个主题。
第一,TLS 1.3 的全面采用。iOS 14 默认使用 TLS 1.3,它比 TLS 1.2 少了一个 RTT 的握手延迟,连接建立速度更快。第二,IPv6 和 Happy Eyeballs 算法。当服务器同时支持 IPv4 和 IPv6 时,系统会同时尝试两种连接,使用先成功的那个。第三,Multipath TCP(MPTCP)允许一个 TCP 连接同时使用 Wi-Fi 和蜂窝网络,在切换网络时不会断连。第四,Network.framework 的 NWConnection 和 NWListener 提供了对这些底层能力的直接控制。
值得深挖的点
TLS 1.3 的性能优势
TLS 1.3 将握手过程从两个 RTT 减少到一个 RTT(对于重连甚至可以 0-RTT)。在实际场景中,这意味着每次 HTTPS 请求可以节省 50-100ms 的延迟。更重要的是,TLS 1.3 移除了 TLS 1.2 中一些不安全的密码套件,安全性也有提升。你的 App 不需要做任何改动就能受益——只要你的服务器支持 TLS 1.3,iOS 14 会自动使用。
Multipath TCP 的使用场景
MPTCP 允许一个 TCP 连接同时绑定多个网络接口。最典型的场景是用户从 Wi-Fi 切换到蜂窝网络(比如走出家门)。传统 TCP 连接会断开,需要重新建立。MPTCP 则无缝切换到蜂窝网络继续传输,对应用层完全透明。这对视频通话、文件上传等长时间网络操作特别有价值。
代码片段
使用 Network.framework 建立 TLS 连接
场景:创建一个安全的 TLS 1.3 连接。
import Network
class SecureConnection {
private var connection: NWConnection?
func connect(to host: String, port: UInt16) {
let endpointHost = NWEndpoint.Host(host)
guard let endpointPort = NWEndpoint.Port(rawValue: port) else { return }
// 创建 TLS 参数
let tlsOptions = NWProtocolTLS.Options()
// 设置 TLS 最低版本为 1.2,首选 1.3
let minTLS = tlsOptions.minimumTLSProtocolVersion
// 系统默认会协商最高版本(TLS 1.3)
// 创建 TCP 参数
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.noDelay = true // 禁用 Nagle 算法,适合实时通信
// 创建连接参数
let parameters = NWParameters(
tls: tlsOptions,
tcp: tcpOptions
)
// 启用 Multipath(需要特定 entitlement)
// parameters.multipathServiceType = .handover
// 创建连接
connection = NWConnection(
host: endpointHost,
port: endpointPort,
using: parameters
)
connection?.stateUpdateHandler = { state in
switch state {
case .setup:
print("连接设置中")
case .waiting(let error):
print("等待中: \(error)")
case .preparing:
print("DNS 解析中")
case .ready:
print("连接就绪(TLS 握手完成)")
case .failed(let error):
print("连接失败: \(error)")
case .cancelled:
print("连接取消")
@unknown default:
break
}
}
connection?.start(queue: .global(qos: .userInitiated))
}
}
配置 HTTP/2 和 HTTP/3 支持
场景:通过 URLSession 启用最新的 HTTP 协议。
import Foundation
class ModernNetworkManager {
private let session: URLSession
init() {
// 配置 URLSession 使用现代网络特性
let config = URLSessionConfiguration.default
// 启用 HTTP/2(默认已开启)
config.httpShouldUsePipelining = true
// 等待连接建立后再发送请求
config.waitsForConnectivity = true
// 设置超时
config.timeoutIntervalForRequest = 30
config.timeoutIntervalForResource = 60
// 启用 cookies 和缓存
config.httpCookieAcceptPolicy = .always
config.requestCachePolicy = .reloadIgnoringLocalCacheData
session = URLSession(configuration: config)
}
func fetchJSON(from url: URL) async throws -> Data {
var request = URLRequest(url: url)
request.httpMethod = "GET"
request.setValue("application/json", forHTTPHeaderField: "Accept")
let (data, response) = try await session.data(for: request)
guard let httpResponse = response as? HTTPURLResponse,
(200...299).contains(httpResponse.statusCode) else {
throw NetworkError.invalidResponse
}
return data
}
}
使用 NWPathMonitor 监听网络变化
场景:在网络切换时自动调整请求策略。
import Network
class NetworkPathMonitor {
private let monitor = NWPathMonitor()
private let queue = DispatchQueue(label: "path-monitor")
var currentPath: NWPath?
var onPathChange: ((NWPath) -> Void)?
func start() {
monitor.pathUpdateHandler = { [weak self] path in
self?.currentPath = path
DispatchQueue.main.async {
self?.onPathChange?(path)
self?.handlePathChange(path)
}
}
monitor.start(queue: queue)
}
private func handlePathChange(_ path: NWPath) {
if path.status == .satisfied {
if path.usesInterfaceType(.wifi) {
print("当前使用 Wi-Fi")
// Wi-Fi 环境:可以加大请求量
} else if path.usesInterfaceType(.cellular) {
print("当前使用蜂窝网络")
// 蜂窝环境:考虑节省流量
}
if path.supportsIPv4 && !path.supportsIPv6 {
print("仅支持 IPv4")
} else if path.supportsIPv6 {
print("支持 IPv6(Happy Eyeballs 已激活)")
}
} else {
print("网络不可用")
}
}
func stop() {
monitor.cancel()
}
}
创建本地服务器(NWListener)
场景:在设备上启动一个本地 HTTP 服务器。
import Network
class LocalServer {
private var listener: NWListener?
func start(port: UInt16 = 0) {
let parameters = NWParameters.tcp
do {
listener = try NWListener(using: parameters, on: NWEndpoint.Port(rawValue: port)!)
} catch {
print("创建 Listener 失败: \(error)")
return
}
// 处理新连接
listener?.newConnectionHandler = { [weak self] connection in
self?.handleConnection(connection)
}
listener?.stateUpdateHandler = { state in
switch state {
case .ready:
if let port = self.listener?.port {
print("服务器启动,端口: \(port.rawValue)")
}
case .failed(let error):
print("服务器失败: \(error)")
default:
break
}
}
listener?.start(queue: .main)
}
private func handleConnection(_ connection: NWConnection) {
connection.start(queue: .global())
// 接收数据
connection.receive(minimumIncompleteLength: 1, maximumLength: 65536) {
data, context, isComplete, error in
if let data = data {
// 处理 HTTP 请求
self.processRequest(data: data, connection: connection)
}
}
}
private func processRequest(data: Data, connection: NWConnection) {
// 简单的 HTTP 响应
let response = "HTTP/1.1 200 OK\r\nContent-Length: 13\r\n\r\nHello, World!"
connection.send(content: response.data(using: .utf8), completion: .contentProcessed { _ in })
}
}
最佳实践
已有项目:检查你的服务器是否支持 TLS 1.3,如果不支持,尽快升级。TLS 1.3 的性能提升对移动端特别明显。如果你使用 URLSession,大部分优化是自动的。如果你使用 Network.framework,确保使用了正确的 TLS 参数。
新项目:新项目直接使用 Network.framework 作为底层网络层,URLSession 作为上层 HTTP 客户端。Network.framework 的 NWConnection 提供了更细粒度的控制,适合实时通信、WebSocket、和自定义协议场景。URLSession 适合标准的 REST API 调用。
还有什么值得关注
- HTTP/3(基于 QUIC 协议)在 iOS 14 中有实验性支持,需要手动启用。
NWConnection的expiration回调可以帮助检测连接空闲超时。- Apple 的 CDN(Apple Music、App Store 等)已经全面支持 TLS 1.3 和 HTTP/2。