Boost performance and security with modern networking
System Frameworks 进阶 24m

利用现代网络技术提升性能和安全性

Boost performance and security with modern networking

2020年6月25日

在 Apple 官方观看视频

一句话判断

如果你还在用 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 的 NWConnectionNWListener 提供了对这些底层能力的直接控制。

值得深挖的点

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 中有实验性支持,需要手动启用。
  • NWConnectionexpiration 回调可以帮助检测连接空闲超时。
  • Apple 的 CDN(Apple Music、App Store 等)已经全面支持 TLS 1.3 和 HTTP/2。
WWDC 2020