大多数浏览器和
Developer App 均支持流媒体播放。
-
联网改进 - 第 2 部分
利用 Bonjour 的改进、自定信息分帧处理程序以及最新的安全功能,让您的联网 app 更上一层楼。您还将了解如何通过收集指标来了解网络性能,以及如何最好地利用 Apple 平台上的现代联网框架。
资源
- Building a custom peer-to-peer protocol
- Collecting Network Connection Metrics
- Network
- URLSession
- 演示幻灯片 (PDF)
相关视频
WWDC22
WWDC21
WWDC19
-
下载
大家好 欢迎大家来到 《Advances in Networking》的第二部分 如果你没有来听第一部分 应该很快就能在 App 和网页上看到视频
我叫 Eric Kinnear 来自网络技术部 一会将加入讲演的是 我的同事 Tommy 和 Stuart 我们会介绍很多东西 首先 我们会介绍 如何更加充分地利用 Bonjour 搜索 我们会介绍 如何通过搭建框架协议 高效简便得传递消息 我们会看一些新的 升级的指标收集 最后我们会介绍一些 状态更新和一些操作 让你的 App 实现最佳联网性能
开始之前 我要提醒大家
如果你在用 URLSession 和 Network.framework 你可以充分利用 我们今天讲的一切东西 如果你还没有用 这就是为什么 你应该改用更现代的网络 API 我们直接从 Bonjour 搜索开始 Bonjour 能在网络上 发布和发现服务 每当你用隔空打印 连接隔空播放设备 用 HomeKit 让你的家自动化时 你都需要 Bonjour 每次你在连接时 却无需输入 IP 地址或主机名时 你都在用 Bonjour 如你所知 Bonjour 在所有的 Apple 平台上都可用 它在 Linux Android Chrome OS 上同样可用 Chromecast 就是用它发现设备的 你可能不知道 早在 2015 年 微软也悄悄将 Bonjour 支持 添加到 Windows 10 从那时起 Bonjour 的实施就已经成熟 这就意味着 Bonjour 现在在各大主平台均可用
今天 我们将分享一些 这个领域的最新进展 有时 你在一个网络 想要发现另一个网络上的设备 比如说你有一台设备 想要连接到 另一个网络上的打印机 二者之间有多个跳跃 现在 如果你在局域网上 发送多点传送数据包 你不会得到任何回应
Discovery Proxy 解决这个问题 现在你可以发送单点传送数据包到 Discovery Proxy 它会把多点传送数据包 发送到目标子网络接收响应 将结果代理给你 现在你就可以直接连接到打印机 看 我们已经 收到了文件 我们很高兴地宣布 客户端上这个的代码 已经在你的开发者版本里了 服务器实施和操作指南 都可以在 GitHub 上找到 我们来看一下 这对你的 App 来说意味什么
之前比较推荐的做法是 当你搜索时 你需要将域设置为 nil 一直都是如此 对于几乎每一种情况 这种做法都是正确的
以前你不觉得 有多大的不同 但是现在它开始有更大的影响 指定本地会直接阻止发现 任何远程或者代理服务 这也许不是你想要的结果 所以要好好检查 你的搜索调用是不是无意间 指定了一个域
当你创建 App 搜索服务时 让我们来看一下 Network.framework 的 一些新功能 它们使得 Bonjour 搜索更加简便 尤其是在 Swift 里
去年我们介绍了 NWListener 和 NWConnection 以及 Network.framework 比如 你可以让 NWListener 发布一个 Bonjour 服务 你可以让 NWConnection 连接到 Bonjour 服务端点 但是如果想要 发现可用服务 你就需要其他 Bonjour 搜索 API 一旦发现了一个 你就需要做很多工作 将它转换为 一个可用于连接的端点
今天 我们宣布 通过 NWBrowser 对象 Network.framework 自带搜索支持
NWBrowser 可以连接 并监听其他服务 覆盖整个工作流 从发布 到发现 再到连接 用的都是大家早已熟悉的 Network.framework 对象
浏览器在 Network.framework 中 用现代基于调度的 API 提供发现服务的功能 这个 API 已经优化 特别适合 Swift 它还包括可选择的 TXT 记录支持 如果你的 App 需要 你可以为每个发现的端点 请求 TXT 记录 让我们看一下如何使用 browser
我们可以用你想发现的 Bonjour 服务类 和一些 NWParameters 将它初始化 你可以通过 NWParameters 告诉它 你想如何搜索 和你处理其他所有的 Network.framework 对象一样 接着 你可以创建一个 browseResultsChangedHandler 它可以被调用传输已发现的 所有可用端点的列表 最后 就像其他 Network.framework 对象一样 你可以在想要接收回调的队列上 创建你的 browser 我们仔细来看一下 browseResultsChangedHandler 你有两个选择 其一 你可以 用一个句柄 接收更新里 所有变化的详单 这与低级 API 非常一致 你可以清楚地看到 所有的变化 端点可以被添加或移除 而且还可以 改变它们的内部细节 这些变化通过 Flag 声明 在这种情况下 随着端点在其他接口上被发现 我们会查看接口是否被添加或移除 你也可以选择 只用句柄查看最新的 发现结果列表 这么做要小心 因为随着可用端点列表改变 这个句柄会重复被调用 所以确保 你更新 App 的状态和其他东西的方式是恰当的
我们来看一个 正在运行的 NWBrowser 的例子 我们准备做一个 App 可以在两个设备之间 发现服务和安全连接的 App 在我们的例子中 我将做一个井字游戏 你也可以做其他很多东西
我们会用 NWListener 把游戏发布给周围玩家 我们会用 NWBrowser 搜索附近可用游戏 一旦用户选择一个 他们想加入的游戏 我们就会取其中一个搜索结果 传送给 NWConnection 回连到我们的监听器 在 Xcode 中看一下
App 已经在这儿了 我们已经写了一些代码 去处理不同的视图 为用户展示 一系列可玩的游戏 让他们创建游戏 诸如此类 所以这里就只关注浏览器本身 我已经有了一个类 PeerBrowser 我会用这个类 管理我的 NWBrowser 并且提供一个 PeerBrowserDelegate 它也会传送发现端点的列表 这样 UI 就可以 将它们展现给用户
首先 我要把我的 NWBrowser 作为一个实例属性 添加到 PeerBrowser 上
然后 当 PeerBrowser 被初始化时 它会立即调用 startBrowsing 我们要把这个填上
首先 我要创建 一些 NWParameters 它们和其他 Network.framework 对象一样 用来描述你想如何与网络互动 在我们的例子里 默认 parameter 就可以 但是要设置 includePeerToPeer 为真 这样 即使设备 不在同一个网络上 我们也能发现其他可玩的游戏
接着 我们创建 NWBrowser
搜索服务类型 _tictactoe._tcp 我们要确保 将域设置为 nil
用之前创建的 parameters 将其存入 PeerBrowser 接下来 我们设置一个 stateUpdateHandler 就像我们处理 其他 Network.framework 对象一样 用来接收浏览器状态的更新 有无错误 运行状况如何 诸如此类
然后我们设置 browseResultsChangedHandler
非常简单 我们将结果列表传送给代理 让它呈现在 UI 中 确保代理是这样编码的 这样 每当发生变化时 它就会刷新 UI 所以我们可以一直呈现 发现端点的最新列表
最后 在主队列启动浏览器 因为想在主队列接收更新 就是这样 通过这样一组代码 我们可以搭建一个 NWBrowser 让它通过 P2P 连接 发现附近的游戏 并为用户展示 一系列可玩的游戏 让用户自己选择连接哪个 一会我们再介绍 监听器和连接这些代码 这个例子的代码 可以从网站下载 在我们继续之前 我想先说一件事 我们已经有了 NWParameters 可以用在监听器和连接上 刚才我提到 我们想确保 设备间的连接是安全的 没有人能看到 我们在做什么 更没有人能干扰我们的操作
要实现这个目标 我们要为 NWParameters 定义一个扩展 并创建一个便利构造函数
它将密码设为字符串
我们会给游戏的创建人 一个密码 让其他想加入游戏的人输入密码 这样就生成了一个 预共享密码 用 TLS 确保连接安全 要实现这个 我们需要在构造函数中 创建 TCP 和 TLS 选项 从 TLS 开始
我们定义了一个函数 它创建了 TLS 选项 密码做好了 现在我们只是 默认 TLS 选项
接下来 我们用新的 CryptoKit 框架 这个框架今年刚出的 从那个密码中 生成一个验证密钥和验证码 把那个预共享密码 添加到设置协议选项 还要确保添加一个 TLS 密钥算法套件 用来支持预共享密码 现在可以回到 TLS 选项 在这儿我们可以做 TCP 选项
大部分情况下 我们会用默认 TCP 选项 但是也要保证 enableKeepalive 为真
接着我们用 刚才在下面创建的 TLS 选项 和基本上是默认的 TCP 选项 初始化 NWParameters 最后一件要做的事是 在这里设置 includePeerToPeer 这样即便不在同一个网络 我们的连接和监听器 也可以连接到附近设备 就是这样 我们试一下 这里 可以看到有两台设备 正在运行 App 我们已经准备好 UI 创建游戏了 浏览器已经开始搜索 正显示 搜索游戏中 因为我们还没找到游戏
如果我输入名字
点按创建游戏 你可以看到 我拿到了一个密码 通过发现的端点列表 在这里是我创建的游戏 browseResultsChangedHandler 被调用 我们把界面展示给用户 就是这么简单 如果我点按加入游戏 就会显示输入密码 现在我确认密码 可以看到我们创建了 一个预共享密码 用它回连到监听器 一切正常的话 页面翻转 游戏就可以玩了 就是这样
到现在为止 我们已经搭建了 App 的开头 在两个设备间建立了连接 我们用 NWListener 发布一个 Bonjour _tictactoe._tcp 服务 用 NWBrowser 搜索可玩的游戏 展示给用户 我们可以选取一个结果 从浏览器返回 直接传送给 NWConnection 回连到监听器 在两个设备之间 建立一个安全的连接
当然 为了玩游戏 两个设备要能交流 分享游戏状态 告诉彼此玩家们的操作 诸如此类 要演示这一点 我想请 Tommy 上台 带大家搭建自定义框架协议
好的 谢谢你 Eric
今天 我想给大家分享 一些新的方法 可以用你写的 自定义协议框架代码 来扩展网络连接 它和网络栈的 其他协议一样 在同一线程上运行 所以 要做完 Eric 开始做的那个游戏 我们需要为两个游戏定义一个 互相发送指令的方法 当一个玩家要操作时 他需要给另一端发送消息
这时我们就需要一个协议 我们的协议 看起来是这样的 它是个简单的 类-长度-值 也就是 TLV 协议
类型占用 4 字节 可能意味着走一步棋 玩家想要把一个给定符号 放在我们井字盘的给定位置 长度也占用 4 字节 声明其他消息 然后是消息体 在我们的例子中 它可能是把猴子脸放在第 1 行第 2 列 然后它会在 TLS 字节流上 像这样重复
你可能已经发现 尽管我们是在 TLS 字节流上运行的 这个字节流本身没有被结构化 但它在使用结构化消息 App 并不是 以字节流的方式思考 而是通过清晰的消息
几乎所有的联网 App 都会这么做
它们有一个 Header 或 Body 或分隔符 用来定义消息的界限
但是 传统通讯网络 API 比如套接字 读取连接上的消息 并不简单 你要在 App 中 亲自做这件事 在看这个问题之前 我们先来看一下 你的 App 和其他网络栈的关系 上面是你的 App
它通过 API 和网络栈交流
所以在 Network.framework 里 TLS 和 TCP 都在 App 的 同一共享线程里运行 这就是我们去年介绍的 用户空间网络栈
让我们近距离看一下 当 App 在一个字节流上方时 是如何读取信息的 如果我们有一个协议 和刚才井字游戏一样的协议 那你可能有一个固定长度的 Header 这里你可以简单地 准确读取 Header 的长度
收到 8 个字节 所以是固定长度 你知道会发生什么 当你读取完整长度时 栈会回调你 这就让你可以确定 剩余消息的长度 你可以准确读取 然后读取 Header-Body-Header-Body 如此循环往复 这很棒 但是你可能已经注意到 我们要循环多次 每条消息至少循环两次 如果你的协议更复杂 如果是一个 可变长度 Header 或者如果是分隔符 这甚至会变得更没效率 尽管对于你的 App 来说 写这个逻辑很简单
如果你特别重视效率 你有另一个选择 你可以一次接收很多内容 但是现在 你有其他几个问题要处理 首次 你要解决 没能在一个数据块里 接收一条完整消息 或者你在一口气 接收了几条消息 或者你只接收了 Header 的一部分 或者你只有长度域的两三个字节 你需要保存 重建域 然后重新解析出来
要做到完全正确 处理好每一种可能的特殊情况 是非常困难的 情况通常是这样的 一些小的 Bug 只有在用户使用 App 的时候 才会显现出来
好 情况看起来有些不妙 如何才能两全呢 如果能够既保证高效 又保证代码简单 易于验证和组合呢 我很高兴能分享给大家一个消息 现在 在 iOS 13 和 macOS Catalina 你可以自己写 在同一网络线程上运行的 协议代码 来解决这个问题 所以如果你要在 通信网络 API 里定义消息 这个方法 让这件事变得 前所未有得简单 谢谢 你要在 NWConnection 里 做这些事情 对于上方的 App 来说 你好像只是 在基本连接上 读写数据包一样
现在我们来看一下 这里依旧是 App 依旧在发送和接收 但是现在你的框架代码 正运行在 和 TLS 和 TCP 相同的线程中 所以现在你可以调用 receiveMessage 当你有一个 App 可以处理的 完整消息时 它会准确地得到一个回调 你可以多次重复这个过程 每个消息一个调用 调试就变得非常简单 很容易就知道发生了什么 所以 这很棒 现在你可能会问 这个框架协议 可以真正实现些什么 有什么限制 好消息是 基本上所有 数据包和编码 App 数据 去传输数据的 都可以被写为 框架协议 如果你进行一次握手 或者如果你想在连接上 实现 KeepAlive 连接 你甚至可以发送 和 App 数据不对应的 自己的消息 你在这里实现的协议 可以是标准 IETF 官方协议 也可以是为你的 App 自定义的 我们接下来就会给井字游戏 做一个自定义的协议 如果你想搭建一个协议 需要两步 首先 实现一条可重复使用的代码 定义你的消息框架 这是协议 然后把这个协议 添加到连接协议栈 你就可以用它建立连接 发送和接收消息 好了 进入第一步 我们开始实现 框架协议
你要做的是 创建一个类 让它遵循 ProtocolFramerImplementation 你可以在这个类里做很多事情 但是要记住 最重要的两件事是 handleOutput 发送消息 和 handleInput 解析消息 如果你可以做到这两件事 太好了 它就是一个 Framer 了
让我们看一下代码 这是协议 它要遵循 ProtocolFramerImplementation
我推荐你先创建一个定义对象 这是你协议的句柄 你可以在整个 App 的其他部分 使用这个句柄 它指的是 你可以添加到连接的协议 接下来你可以处理很多的 基本回调事件 这里最重要的是 start 每当协议载入到连接时 start 都会被调用 用来发起连接 如果你想做一个握手 和另一端交换什么东西 你可以在这里实现 如果你的协议很简单 像我们的井字游戏一样 不需要设置 只需立刻标记连接就绪
一旦你做了这个 现在你需要 handleOutput 和 handleInput 我们深入来看一下
handleOutput 看起来这样 每当 App 发送消息时 你就会被 handleOutput 调用 如果你需要的话 你会收到消息元数据 还有一些自定义值 以及 App 正尝试发送的 消息长度 所以 如果你有一个 Header-Body 协议 就像我们刚才用的
你可以先创建 Header 结构 试着序列化一些数据
所以这会包括类型 也许是从消息元数据中 得到的类型 还有传送给你 到 handleOutput 的长度 你可以组合这些数据 然后调用 writeOutput writeOutput 会将字节 排列到输出流 但是实际上它们还没有发出去 然后你需要写主体 在这个例子中 我们完全不需要转存 App 数据 我们可以只调用 writeOutputNoCopy
它让我们直接 将 App 字节排列到输出流 当我们从 handleOutput 返回时 所有的字节都会被 发送出去连接 好了 我们接着处理输入 处理输入和处理输出类似 但是略微复杂
每当你的 App 接收到连接上的新字节时 你会被 handleInput 调用 如果你做的是 Header-Body 类型协议 你有两件事要做 你需要解析 Header 然后你需要解析 Body 我们先解析 Header 这儿 我们的协议有一个 固定长度的 Header 正好是 8 个字节 我们做的是 调用 parseInput 开始检查 已经进入连接的字节流 我们可以用最小和最大 8 字节调用它 因为我们希望准确查看 8 字节 Header 如果成功了 你会在组块里被调用 你能看到真正的缓冲字节 解析你的值 如果需要的话 将它们存到本地变量 解析输入的返回值 表示你想用多少字节 增量输入光标 我处理完了这 8 个字节 我们不需要再看到它们了 不需要将它们传送给 App 继续 现在你可以处理 不是所有的 8 个字节 都可用的情况 在这种情况下 parseInput 函数会失效 你只需等待更多字节可用 handleInput 的返回值表明 要有足够的字节数量 然后你才能做更多工作 所以在这个例子中 我们是在告诉连接 在你再次唤醒我之前 确保有 8 个字节 如果你能成功读出 Header 你可以创建一个消息对象 可以和数据一起 传送给 App 这让你可以放入任何 你想要发送到 App 的 自定义值 类型 或其他指针 最后 在这种情况下 你可以调用 deliverInput 或者 deliverInputNoCopy 这可以让你将 下一段确定范围的字节 标记为应该 直接传送给 App 的 App 数据 它返回 Boolean 值 去显示是不是 所有的字节都是可用的 是否成功发送 连接需不需要再等 所以如果你需要的话 你真的可以 传送输入消息 兆甚至千兆字节长 那些字节作为信息的一部分 会持续流出 你无需等待所有的字节都可用 或者亲自处理这些
我知道代码很多 但是我们马上就可以 为井字游戏实施协议了
好 这就是之前 Eric 做的游戏 现在我要创建一个新类 命名为游戏协议 它会遵循 NWProtocolFramerImplementation 我已经为这个游戏定义了 两个不同的类型 我想要发送两个 不同的指令 一个是选择角色 游戏的第一步是 玩家需要选择 他们想用哪个表情符号 他们想当猴子还是当鸟
然当玩家选择了他们的角色后 就开始发送走棋操作 这就会变成更长的 Body 它会包括角色和行 列值
好 我记得 当我实施协议时 我要做的第一件事就是 创建一个定义 这是基于我的对象的句柄 它在系统中注册对象 让我可以在连接中使用这个
接下来 我要处理所有的基本回调 这里再一次 因为我不需要自己的握手 当我被启动调用时 我可以返回一个就绪的启动结果
接着 我们来处理 发送和打包消息
我将在这里定义 handleOutput 的实现
所以头部是一个 8 字节 Header 包括一个类型和长度 首先我要知道我的类型是什么 我从 App 发送的消息中 得知类型 我们稍后会看这个 我已经给 framer 消息 创建了一个自定义扩展 用来提取特定枚举类型 这样我就可以知道 是选棋还是走棋 有了类型 我就可以在 handleOutput 中 通过传输的类型和长度 实例化我的结构 我的游戏协议 Header
我已经写了代码 编码那份 类型和长度为 8 字节范围的数据 我调用 writeOutput 把那个数据加入到输出流队列中 现在我要做最后一件事 既然我已经写了 Header 现在就要写 Body 这里我只需调用 writeOutputNoCopy 表明下一个宽度的字节 是这个消息的 Body
一旦这个返回 这些字节就会被发送 我就可以处理更多的信息 不论是输入还是输出 好 写入就完成了 现在来看读取 我要处理输入
首先我要读出 解析 Header 先是一个固定大小的 Header 8 个字节
我将尝试解析出 最小 8 个字节 最大 8 个字节 每当有 8 个字节可用时 我都会被缓冲调用 这里我确认 缓冲是有效的 然后创建结构 将那 8 个字节解析为类型和长度域
一旦这一步成功了 就表明我想用 8 个字节增量光标 说明我已经使用了这些字节 这些字节没用了 现在我同样需要处理 没有成功解析 所有 8 个字节的情况 可能只有 5 个字节是可用的
所以解析失败 我将从处理输入返回 表明我需要 等待 8 个字节可用 然后才能继续 但是如果我真的过了这一步 我就知道 Header 是有效的 我可以用它 传送剩余的 App 数据
所以现在我要创建一个信息对象 我要将我的特定消息类型 存储在那个消息对象中
最后我会调用 deliverInputNoCopy 直接告诉连接 接下来的那些 每当有 是 App 数据 当你的 App 收到消息时 它们会准确接收到 那个组块 这就是我要做的 这就是完整的协议 接下来我会介绍 如何初始化 把它输入到游戏连接中去
好 好消息是 这部分比较简单 要把协议添加到连接 并重复使用它 你只需要选取 之前做的定义 用那个定义 创建一些协议选项 协议选项 是协议栈的组成部分 有 TCP 选项 TLS 选项 现在有自己的自定义协议选项 当你为连接 创建 parameters 时 比如说你在用 TLS 因为它可以确保通信安全
以及协议栈里的 TLS 你可以直接 将自己的协议 添加到 applicationProtocols 数组的顶部 你可以添加多个 运行多层框架 如果你想用 WebSocket 也是在这个地方 WebSocket 是一个今年 新的系统实施 你可以将它添加到你的连接
WebSocket 本身 作为协议框架被实施 和你现在可用的 使用的相同的 API 它向你展示了 一个框架协议有多强大 如果你不想自己写 那你可以用 WebSocket
在这里我想说的是 一些 App 在不同的情况下 需要使用不同的协议栈 框架协议真的是一个 在 App 和网络连接之间 建立合约的好方法 即便你在使用 不同的协议栈 依旧如此 为给大家举个例子 我们在 DNS 系统上使用这个 DNS 通常通过 UDP 发送数据包消息
但是偶尔 DNS 会和 TCP 一样 需要在流的上端运行 当它这么做时 会有一个 Body 格式 它只有一个非常基本的长度 用 TCP 编码 DNS
所以我们写了一个 framer 用来定义这个简单的数据包 这样在上方就有相同的代码 发送 DNS 数据包不需要考虑 它用的是 UDP 还是 TCP 它是同样的逻辑 这样你就无需担心太多 可以分开调试 App 的不同部分 我们已经将框架协议 添加到连接 现在准备发送和接收消息 我们可以使用自定义值 接下来我们就会在游戏里 用自定义值
Framer.Message 让你可以存储 任何对象类型的键值对 所以你可以添加到自定义值 去修饰你的发送操作 接收协议的消息 你可以创建一条消息 按你喜欢的方式设置好它 将它发送到 你正在传送数据的上下文上 所以每个发送操作 都已经有了内容和上下文 上下文会描述你想要如何发送数据 Framer.Message 只是 发送数据的新方式 接收也很相似 当调用 receiveMessage 时 在接收内容的同时 还能接收到上下文 它用来描述数据是如何被接收的 你可以用协议的定义 查看协议 framer 上下文会描述特定消息值 接下来我们就要 完成这个井字游戏了 好 我们已经做好了游戏协议 要把它加入到连接中 我将返回 Eric 刚才设置的参数 他已经用 TCP 和 TLS 设置好了有密码的连接
现在我要做的只是 把这两行加进去 我要基于游戏协议定义 创建一些选项 将它们插入到 我想要用在连接里的 App 协议数组 现在当两设备间启动连接时 它会准备就绪 在流上编码消息 现在我要在连接中 做几个便捷函数 让 App 更简单地 发送和接收 自定义消息类型
这里有一个连接对象 它用刚才 定义的参数 设置了一个 NWConnection
每当连接就绪时 它就开始从 peer 接收消息
我们需要接收下一个消息 来实施这个
我现在做的是 获取连接 在连接上 调用 receiveMessage 我将得到内容和上下文 我将用上下文 查看我的协议 特定的元数据 和游戏协议定义
这会给我消息对象 让我把消息类型 和数据一起 向上传送到 App 当然 接下来 如果我 成功接收到一条消息 我会调用 receiveNextMessage 再次重复这个过程
我还要给发送 定义一些 Helper 所以每当 App 判定 玩家是在选择角色时 我们可以创建一个消息 将所选的角色作为类型 添加到这个消息 并将其发送到上下文中 发送走棋操作 也是这样的 在这里创建一个便捷函数 声明我想发送走棋操作 然后只需获取 App 数据 将它向下传送给连接
这就是我们要做的 现在就可以玩游戏了 玩游戏需要两个人 Eric 请回到台上
好的 Eric 已经在这里 创建了一个游戏 但是我想自己创建一个
我们来做这一步 Eric 我们输入密码 5176 不要告诉别人
好了 现在我们已经设置了一个 安全的连接 它在用 TLS 当 Eric 选择一个角色时 他选择鸟 他就是在发送 选择符号信息 我调用 receiveMessage 收到 他选择了鸟 然后我要选择猴子 为什么不呢 现在 Eric 选择一个位置 然后选择一个符号
好的 他将走棋动作发送给了我 我收到一个消息 我知道他走了一步棋 我知道他把棋放在了哪儿 我要把猴子 放在上面 他下一步会怎么走呢
哦 看起来 他要赢了 我不知道 我玩不了这个 井字游戏太难了 好像鸟赢了 但是可以看出来 这种类型的游戏搭建起来非常简单 你还可以用这个 搭建更多的小游戏 还有很多其他的 App 我们很期待 你们的作品
好的 在我继续之前 关于框架协议 我想再说 最后一件事 很多人都在问 如何用技术 比如 在 NWConnection 中采用 STARTTLS STARTTLS 是来自 SMTP 邮箱协议的技术 它让你可以与较早的服务器交流 你并不知道 这个服务器 是否支持 TLS 和安全连接 你可以和它做一个初次握手 如果它支持 TLS 你可以部分通过你的连接添加它 之前没有什么好方法 来做这一步 但是现在我们有了框架协议 所以 如果你创建一个 STARTTLS 框架协议 将它添加到连接 当 App 启动时 你可以和服务器握手 确定它是否支持 TLS 然后你可以 在表明就绪之前 在框架协议上 动态添加协议到栈 这样 App 就会保持不变 不需要担心 部分添加 TLS 这个过程会自动 通过框架协议进行 所以我们觉得这是一个 干净利落的方法
好 我们继续 我们已经介绍了 Bonjour 如果做更好的 P2P 连接 以及使用广域发现 我们介绍了框架协议 但是现在我想退一步 看看如何收集 你的 App 里 连接的指标
收集指标非常重要
当你给 App 或服务器 添加新功能 以实现更好的效果时 它让你确认这些功能 确实在发挥作用 你确实实现了自己想要的效果 它可以帮助你发现 用户在真实使用中 可能遭遇的问题 这些问题在设计时并没有发现 今年我们有很多 新的指标 帮你更好地分析连接 URLSession 已经有了很多 很好的指标 但是其实还有更多 第一次 在 Network.framework 里 你可以检查连接 更全面地了解 它们的表现
在 URLSession 中 你已经得到一个 DNS TCP TLS 和 HTTP 消息的 时间细分
现在你可以 在你的 App 里自检 更多的连接属性 和个人请求和响应 正在发送的数据
在 Network.framework 里 你可以访问 连接建立报告 它总结了连接中发生的一切 以及数据传输报告 让你可以查看 连接过程中 各个时间段的性能 很多这些过程 同时进行 我们先从 URLSession 开始 我要提醒大家 URLSession 里所有的指标 在 didFinishCollecting 指标 代理调用里 都是可用的
这里有一些 你可以访问的新东西 连接的端点 本地和远程地址以及端口 你还可以查看 安全属性 有没有用 TLS 1.3 它是 TLS 最新的 最安全的 性能最好的版本 你还可以用它查看路径属性 它会告诉你 比如说 你的连接用的是 约束低数据模式网络 还是一个 比较贵的蜂窝数据网络
Network.framework 里的等价指标 是在建立报告里面的 每当连接进入稳定状态 你就可以使用这些指标 这会给你一个细分 你的 DNS 次数 协议和 TCP 和 TLS 的握手 以及你是否使用了代理 在代码里它看起来是这样的 获取连接 并调用 requestEstablishmentReport 这里有一个队列 它会把报告加入到队列里
有了这个 你就可以查看 连接花费的所有时间 你可以查看各个解决步骤 如果一个 Bonjour 名称连接到你 它可能会解析 Bonjour 名称 为主机名 再把主机名解析为地址 你可以查看 每个步骤的时间细分 你还可以查看 TCP 的 TLS 的 和往返次数的 单独计时 我想强调一点 这一点对于连接建立的 整体表现来说 非常重要 这一点就是它 解析 DNS 的时间 以及 DNS 解析的来源
很多服务器 只有很短的时间 在 DNS 记录上配置 它们这样做的目的是 当服务器发生故障 或者想在另一个 IP 地址上 平衡负载时 它可以快速改变 IP 地址记录 调整客户端 使用新地址
但是 缺点是 这会影响客户端性能
在很短的时间内 客户端需要往返 做 DNS 域名解析 请求你要连接的 主机名地址 对于要连接到 高延迟链接的客户端来说 这就特别糟糕 这将会延长连接时间 增加数百毫秒 甚至好几秒 最糟糕的是 大部分时间 服务器地址并没有改变 所以这就是一个 没用的往返
我们 去年发布了 Optimistic DNS 可以解决这个问题
Optimistic DNS 让你连接到 主机名的最后一个 已知良好的 IP 地址 同时请求主机名的当前地址
大部分情况下 都没有什么改变 连接还是建立在 之前的 IP 地址 如果有变化 你会得到一个新的 IP 地址 转而连接到新地址 我们一直在测试它 它真的是一个好方法 所以今年 对于使用 Network.framework 和 URLSession 的连接 它是默认开启的
当你查看 连接报告时 你可以通过查看来源 辨认你用的是不是 Optimistic DNS 如果是来自过期缓存 这就意味着 我们用的是 Optimistic DNS 我想向大家展示一下 如何使用指标 查看连接性能 以及 Optimistic DNS 和 TLS 1.3 的优点 好 我这里有一个 App 一款非常基本的 App 用来收集指标 只需要对一个给定网站运行 probe 好 有了 我点按“Run Probe” 连接上了 非常快 Wi-Fi 信号非常好 如果你想在一个 更现实的场景中测试它 或者看它在 不同网络条件下的表现 你现在可以到设备内部 在 Xcode 的模拟器面板中 访问设备条件 模拟不同的网络连接环境 这样你就可以看到 不同的情景下 用户的体验 这很棒
我们看一下如果是高延迟 DNS 链接 会发生什么
我点按 Start 可以看出它在运行 因为左上角有一个 灰色框
现在 再次运行 probe 很快 很好 你会发现 它来自过期缓存 也就是说 我们用的是 Optimistic DNS Optimistic DNS 是默认运行的 如果你觉得 它不适合你的服务器的话 但是你可以关掉它 再次运行 probe 你可以感受到几秒钟过去了
也许我有点夸张 希望你的用户不会遭遇 3 秒 DNS 延迟的情况 但是两种情况是很不一样的 现在我们尝试一个 更加现实的场景 比如常用的 3G 网络 启动 再一次运行 probe 不像第一次运行的时候 那么快了 总的来说 你可以看到 建立连接 要 600 毫秒 TLS 花了不到 300 毫秒 也就是说差不多一半 所以我们的服务器被配置成 支持 TLS 1.3 现在 TLS 1.3 通常只需要一个往返 就可以完成一次握手 这是很大的进步 但是如果你的服务器 不支持 TLS 1.3 如果它只支持 TLS 1.2 如果你 App 上使用的 API 不支持 TLS 1.3 你可能会看到这种情况 TLS 自己花去 500 毫秒 多进行一轮往返 你可以看到 连接时间已经超过了 四分之三秒 差不多要一秒了 如果你有很多连接 用户会感受到 更久的延迟 所以我们建议 每当你测试 App 时 通过网络连接模拟器 运行它 测试一些场景 验证 App 是否运行良好
另一类指标 和建立连接后的 数据传输有关
所以在 URLSession 中 现在你可以访问更多指标 数量相当于 你发送到请求的 Header 和 Body 的字节数 以及你从 请求的 Header 和 Body 收到的字节数 如果你选的是不同的 URL 在一个低数据模式网络环境 来下载较少的数据 这个就非常重要 你要用它确认 确实存储了用户的字节
在 Network.framework 中 你现在可以访问数据传输报告 这个报告总结了运行情况 包括字节 数据包 和给定时间段内的往返次数 多个这些过程 会同时运行 并且它们要与 App 活动相对应 所以如果你要突然发送一段流 把它放入数据传输报告里 它不如闲置时间报告有趣 你要做的是 在任何时间 你都可以在连接上 调用 startDataTransferReport 首先 收集连接的 运行情况数据 发完一堆数据后 调用 collect 这会总结所有数据 给你一个报告
如果你用的是多路径协议 它会给你一个细分 多路径线程 每条链接上正在使用的 数量细分 但是你们很多人 只对聚合路径报告有兴趣
这里你可以查看 发送和接收的数据包数 传输的字节数 以及你观察到的 往返时间详细信息
这就是指标 我们特别想看到大家 用更多的指标 并且提升 App 的性能 现在我想把 Stuart 请上台 他将为大家带来 好的建议和新的更新 谢谢你 Tommy
很荣幸 我能总结 我的同伴们 用两个小时介绍的 联网信息 首先 我要介绍 Mac 版的 iPad App
我知道你们很多人对此很感兴趣 说到联网在 Apple 平台上 没有多大的区别 你需要注意的一件事是 在你的 Xcode 设置里 当你选择 Mac 的复选框时 现在你可以看到更多新选项 默认允许传出连接 如果你也想要传入连接 你要勾选那个复选框
在 watchOS 上 我们有新的联网功能
用 AVFoundation 做音频流的 App 现在可以使用直接联网 只要用的是 URLSession 或 Network.framework Sockets 不可用
我们也介绍了 TLS 1.3 它会给你带来很多好处 TLS 1.3 联网性能更出色 TLS 1.2 一般需要两个往返 才能建立一个连接 TLS 1.3 基本只需一个往返
TLS 1.2 用的是加密算法 我们曾经认为 加密算法很好 但是出现了问题 这不只是一个学术问题 这些都是实践出来的
这些问题都不会出现在 TLS 1.3 中 TLS 1.3 中所有的加密算法 都支持相关数据的认证加密 增强安全性
最后 大家都知道 对于 Apple 来说 隐私非常重要 TLS 1.3 在隐私方面做得更好 在 TLS 1.2 中 很多 Header 域和证书 都是明文发送的 在 TLS 1.3 中都加密了 所以我们希望 大家都开始 在 App 上用 TLS 1.3 当然也要确保服务器已更新 支持 TLS 1.3 现在 大家都知道了 对于 Apple 来说隐私的重要性 我们意识到 接入 Wi-Fi 可能会暴露位置 从现在开始 要访问那个 Wi-Fi 信息 和获取其他位置信息一样 你需要同样的权限
第一步是 在 Xcode 里 添加访问 Wi-Fi 信息的能力 添加权限到你的项目 然后你的 App 必须满足 其他三个标准之一 其一 如果用户已经给了你的 App 位置访问权限 那么你可以访问 Wi-Fi 网络信息
其二 如果你的 App 是设备上 当前启用的 VPN App 你可以访问信息 最后 如果你的 App 是任何热点配置 App 它可以访问信息 但是只能访问 配置的网络信息
如果想要了解更多 可以看一下 Wi-Fi 框架 今天你已经听到过很多次了 最后我要再提一下 使用网络模拟工具的重要性
当你在 Mac 上开发 App 时 如果模拟器是千兆以太网 或在环回接口上 与本地服务器对话时 就会很简单 如果你的服务器没有延迟 带宽是无限的 那毫无疑问 性能会很好 但是如果你按这种条件搭建 App 那就大错特错了 当用户真正开始 使用你的 App 时 你就会发现性能并不好 如果你从 一开始开发 App 就养成习惯 到设备情况里选择现实的 网络连接环境 一直模拟真实的网络环境 检测运行你的 App 那么那些 Bug 就根本不会出现 多年来 我们一直向大家传递的 另一个信息是 避免启动时检查 使用蜂窝数据 或较贵的网络之类的约束 实现更好的控制 这样就简单多了 一旦你开始这样开发 App 你就会问自己 为什么之前要启动时检查 而且启动时检查 并不可靠 因为它们总是带来问题 我举个例子 来说明这一点 这个是我很喜欢的一个 App 因为要说明这个情况 我故意做了一个 夸张的例子 看看 如果这个 App 写的不好 会发生什么 这是在告诉用户 确保连接了 Wi-Fi 然后点按按钮 但是用户并不能 控制选择 哪条联网路径 他们一般会 找 Wi-Fi 栏 希望连接到最佳网络 但是今天你知道了
在测试之前 你不可能知道 Wi-Fi 情况是什么样 设备可能以为自己连接了 Wi-Fi 但是当它想用的时候 发现 Wi-Fi 不能用 现在 当 Wi-Fi Assist 把你从 Wi-Fi 转换到蜂窝数据时 那些 Wi-Fi 栏都消失了 但是已经晚了 你已经开始连接了 所以不要让用户猜测 不要只是 期待最优的网络 我来演示一下 这个 App 实际上是什么样 它将连接限制到 不允许访问蜂窝数据 在 iOS 13 里启动它 实际上 它使用 allowsExpensiveNetworkAccess 控件 让系统选择 哪个是较贵的网络
它还设置 waitsForConnectivity 为真 这意味着 App 不需要反复重试 系统会一直 耐心等待 直到成功连接
当 App 尝试连接时 如果没有 Wi-Fi 它的 taskIsWaitingForConnectivity 代理 会被调用 这时它会显示 UI 让用户选择 要么移步到有 Wi-Fi 的地方 要么按下按钮 使用蜂窝数据
一些关于弃用的消息
如果还有人在用 PAC 文件 用这种文件 或者 FTP URL 方案 要注意 我们已经不支持这些了
SPDY 曾是非常好的 实验协议 如果还有人用 SPDY 要注意 SPDY 现在已经被 HTTP 2 取代了 Apple 现在支持的是 HTTP 2 一切都应朝着这个方向发展 而且 Secure Transport 不支持 TLS 1.3 永远也不会支持 TLS 1.3 这也是选择 URLSession 或 Network.framework 的另一原因 所以 总的来说 今天早晨 我们介绍了广域 Bonjour 发现 以及如何发布一个 井字游戏 有些人可能会想 serviceType_tictactoe 是否注册到了 Ayana 答案是肯定的 你可以去网站上看看
Tommy 介绍了 搭建协议框架 收集指标 让你写 App 变得更容易 也更容易考量性能 今天早晨 我们还讨论了 低数据模式 要尊重用户 何时保存数据的需求 我们还讨论了 结合 URLSession 可以很好地链接异步操作 我们还讨论了 WebSocket 如果你的 App 是基于网页的 它用 WebSocket 和服务器交流 现在你可以在本地 iOS App 中 使用同一台服务器
Christoph Paasch 介绍了所有的移动改进 通过多路径 TCP 和 Wi-Fi Assist 实现 关于这一点 你们很多人会知道 ACM SIGCOMM 是世界上最重要的 网络研究学术会议
每年都有 网络系统奖 选出网络领域 最有影响力的成就 今年 今天 他们宣布 今年的奖颁给了 Christoph Paasch 以及多路径 TCP 团队的其他人 我们希望在明天的 网络实验室看到大家 如果你们有人现在在写 网络内核扩展的话 明天一定要去参加 《Network Extensions for Modern macOS》 这一场会议 谢谢大家 [掌声]
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。