大多数浏览器和
Developer App 均支持流媒体播放。
-
适用于现代 Mac 的网络扩展
了解 macOS 中新增的强大 API,您可以利用它们来创建无需使用内核扩展即可扩展和自定 macOS 联网功能的 app。
资源
相关视频
WWDC21
WWDC19
-
下载
(现代macOS网络扩展 将内核从网络内核扩展中移除)
大家好 欢迎参加 现代macOS网络扩展演讲 我是Jamie Wood 我是Apple的一名软件工程师 致力于因特网技术 今天能来到这里我感到非常激动 我要与大家分享一些非常棒、 非常强大的新API 我们已经添加到 macOS Catalina中 可以让你创建扩展并自定义 macOS网络功能的app 而不需要使用网络内核扩展
开始 我想感谢你们所提供的反馈 在过去几年的WWDC 我们要求你们指出错误 并给我们提供 关于你如何在你的app中 使用网络内核扩展的反馈
我们收到了大量很棒的反馈 我们接受了你们的反馈 我们提出了一组app类别 在macOS Mojave 以及更早的版本上 你确实需要使用网络内核扩展 来完全实施这些类别内的app
今天我想带你们一起 了解每一个app类别 并讲一下我们在macOS Catalina中 所添加的新API 那些新API会帮助你创建 属于这些类别内的app 而不需要使用网络内核扩展
让我们开始讲吧 首先我要讲内容过滤器app
内容过滤器app的一个例子就是 个人防火墙app 这些app在系统中流动 检查网络流量 并阻塞流量 这在某种程度上 被视为是一种恶意行为 内容过滤器app的另一个例子是 家长控制app 这种app主要关注网络浏览行为 并阻止访问 被视为不适合儿童看的网站
内容筛选器app的另一个例子是 一种不会主动阻止任何网络流量 而是只记录Mac上的 网络行为的app 从而稍后对那种无行为日志进行分析 比如决定何时传输一些敏感数据
在我们讲我们新添加的 能帮助你创建内容过滤器app的 API之前
我要先讲内容过滤器app的 一些特别的运行时要求 内容过滤器app中的 过滤网络流量的代码 有一些特定的运行时要求 代码需要无时无刻运行 并且甚至 当没有用户登录系统时也要运行 比如在家长控制app中 app需要发挥 阻止访问不合适的网站的功能 即使你的app实际上并没有在运行
在个人防火墙app中 app需要发挥 防止Mac遭受 来自网络的攻击的作用 即使没有用户登录到系统中 当你在内核扩展内 实施内容过滤器代码时 很明显满足运行时要求 因为你的代码是在内核中运行 因此它是一直在运行 并且甚至当没有用户登录到系统时 也在运行
因此 为了在用户空间中 满足这些运行时要求 我们在macOS Catalina中 引入了一种新技术 叫做系统扩展
现在你很可能对app扩展 非常熟悉了 app扩展是一堆你能 在macOS上使用的可执行代码 用于扩展和自定义macOS 用户体验的方方面面 那么系统扩展 与app扩展有许多相似之处 跟app扩展一样 系统扩展被打包到你的app内 它们完全由操作系统进行管理 这很棒 因为这意味着你不需要写任何 自定义安装器数据包 从而把系统扩展放到文件系统中的 某个位置 并且你不需要写卸载程序 从而当用户卸载你的app时 移除系统扩展
同时你不需要担心 何时开始和停止系统扩展 操作系统会根据需要运行系统扩展
app扩展与系统扩展的另一个 相似之处是 系统扩展的开发和调试非常简单 你可以使用用于开发 其它任何常规app的常规工具… Xcode、LLDB、 Instruments
这与内核扩展不同 众所周知 内核扩展的开发和调试 都非常困难 在你开发扩展时 你必须频繁地进行重启 要调试内核扩展 你必须有两台独立的机器 并且如果你设法把这两台机器 连接到一起 并将它们放到内核扩展代码中的 调试器中 单步执行你的源代码 是个非常冒险的提议 如果它能起作用的话
与app扩展不同 系统扩展的运行 与任何用户登录到系统中无关 因此系统扩展确实是 运行网络处理代码的理想场所 比如内容过滤器代码
要了解关于系统扩展 以及系统扩展的其它用例的 信息和详情 请参看本周稍早时候的一场演讲 系统扩展和DriverKit
好的 你可以使用系统扩展 实施属于这些类别的不同app 我在这里列出了这些类别… 内容过滤器app 透明代理app DNS代理app和VPN app 现在我想具体讲一下 我们所添加的API 这些API可以帮助你们实施 内容过滤器app
内容过滤器API 是在网络扩展框架中 我们在iOS 9中 首次引入了这些API 我们在 macOS Catalina中 把这些API带过来 并使它们在Mac上可用 并添加了一堆很棒的新改进 使这些API变得更强大
让我们看一下内容过滤器API 以及如何在app中使用它们
在你的主UI app中 你使用NEFilterManager 来创建内容过滤器配置 内容过滤器配置 在系统中注册你的内容过滤器 从而系统了解如何运行你的过滤器
你还创建一个系统扩展 实际过滤网络内容的代码 就要在系统扩展中运行 内容过滤器API 允许你在两个不同的层过滤网络内容 你可以在数据流层过滤内容 或在数据包层过滤内容
对于数据流层过滤 你创建任意数据过滤提供器的子类
一旦你的内容过滤器配置在系统中 进行了注册 并且过滤器已经启动并运行 系统将在系统上创建 网络数据的新TCP和UDP数据流
传给NEFilterDataProvider子类的 那些数据流 被表达为单独的 NEFilterFlowObjects
然后子类负责 对每个单独的数据流 做出允许或放弃的决定
你可以在数据流的生命周期中的 任意时间点上 对每个数据流做出这种决定 你可以在数据流首次打开时 就做出决定 或你可以等看到数据流中一定量的 数据之后再做决定
请注意 NEFilterDataProvider类 提供数据流的只读权限
你不能修改数据流 包括数据流中的任意数据
默认情况下 系统将把所有TCP和UDP数据 传到 NEFilterDataProvider子类中
如果这并不是你想要的… 比如 如果你正在写家长控制app 那么你只对Web流量感兴趣 你使用NEFilter设置 创建一组规则 告知系统你想要在过滤器中看到的 数据流
那么数据流层过滤就是这样运作的
如果你想在数据包层过滤流量
你要在系统扩展中 创建NEFilterPacketProvider的 一个子类
因为网络数据包会流过系统 系统将把那些数据包作为 独立数据包对象 传到你的 FilterPacketProvider子类中
你可以对每个独立数据包 做出允许或放弃的决定 好的 这是关于内容过滤器API 以及如何在你的app中使用它们的 简要概览
接下来我会给你简单演示一个app 它使用了系统扩展 和内容过滤器API 来实施一个防火墙
我的这个app功能很简单
我会提示用户 允许或拒绝8888端口上的 TCP连接 让我继续并运行app 让你看看它是如何运作的
这个app叫做Simple Firewall 继续并运行app 你可以看到这是我的UI指示器 红点表示内容过滤器 现在并没有在运行中 因此我要继续并点击开始
好的 我得到了来自系统的对话框 表明我的系统扩展 被阻止运行 现在系统扩展非常强大 它们可以让你在系统上做许多事 包括查看系统中流过的网络流量 那么…我们想确保 在允许系统扩展运行之前 我们得到了用户的允许 因此我要继续并打开安全性首选项
这将把我带到 安全性和隐私性首选项面板中 我要提供我的管理员认证信息 继续并点击允许 从而允许运行我的系统扩展
网络扩展框架还会提示用户 确认他们希望允许系统扩展 在Mac上过滤网络流量 那么继续并点击允许 好的 现在返回到 Simple Firewall中 我们可以看到内容过滤器正在运行 让我们继续并在我的本地Mac上 连接8888端口 看看会发生什么 我正在端口8888上 运行Web服务器 因此我要继续打开Safari 我已经把它添加到 我的本地Web服务器上 我点击它 网页开始加载 但你可以看到它停在这了 我非常确定 在Simple Firewall app中 有一个对话框告知我 在8888端口上有个新连接 并请求我允许或拒绝 那么我要点击允许 然后网页就加载出来了
很酷 我的app正在工作中 现在让我们继续并看一下 Simple Firewall中的一些代码 看看它是如何利用系统扩展 和内容过滤器API的 在这里你可以看到我的项目 我有两个不同的Targets 我有SimpleFirewall Target 这是我的主UI app 我有SimpleFirewallExtension Target 这是我的系统扩展
让我们先看一下app中的一些代码
我们要看一下 我的主视图控制器类的实施 我想先从 startFilter函数看起 当我在SimpleFirewall UI中 点击开始按钮时 调用这个函数
先获取系统扩展的 捆绑标识符 并使用那个标识符创建一个系统扩展 activationRequest
我把视图控制器对象设为 activationRequest的委托 从而当请求完成后 系统会通知视图控制器对象
一旦创建好 activationRequest 我就把它提交给 OSSystemExtensionManager 这就开始激活系统扩展 包括提示用户 允许SystemExtension 运行 如果必要的话
好的 一旦用户允许 SystemExtension运行
就会调用视图控制器的请求 didFinishWithResult函数 我要确保激活请求已经完成 我要继续并创建内容过滤器配置
我就是要在这里使用 AnyFilterManager 创建过滤器配置并在系统中进行注册
在这里你可以看到 我正在我的配置上设置一些细节
我把过滤器插座设为真 这表示我会在数据流层 过滤网络流量 我把过滤器数据包设为假 表明我不会在数据包层 过滤网络流量
我要继续并启动我的内容过滤器配置 然后在系统中注册配置 通过调用保存到首选项实现
因为启动了内容过滤器配置 这将导致系统开启 SystemExtension 并开启我的内容过滤器 让我们继续并看一下 在系统扩展内运行的 NEFilterDataProvider 子类的实施
这是子类 它叫做FilterDataProvider 我已经在这个类中重写了 三个不同的方法… StartFilter、StopFilter 和HandleNewFlow
首先让我们看一下 StartFilter
当系统开启内容过滤器时 调用这个函数
默认情况下 系统将把每一个TCP和UDP 数据流都传到我的内容过滤器中 我其实并不想这样 我只对流入的在我的Mac上 连接8888端口的 TCP连接感兴趣
因此我要创建一个 NEFilterSettingsObject 告知系统我想要看到哪些流量
现在我不关心TCP连接来自哪里 我也不关心 TCP连接要连接到我Mac上的 哪个地址 我要创建两个NEFilter规则 一个有通配符IPv4地址 另一个有通配符IPv6地址
对于我所创建的每一个过滤器规则 我都创建一个 NENetworkRuleObject 用于指定我想要查看的数据流的特征 我想让过滤器规则与之匹配
对于远程网络和远程前缀 我会传递无和零 这意味着我的过滤器规则 将匹配来自任何地方的流量 我不关心它来自哪里 对于本地网络 我传递一个我使用通配符地址创建的 NWHostEndPoint 和一个本地的8888端口
好的 那么这意味着我的过滤器规则 将匹配所有进来的数据流 并在任意地址上连接端口8888
我指定一个TCP协议 和一个流入方向 我继续并在过滤器数据的动作中 创建 NEFilterRuleObject 传入NENetworkRule 当有新的网络数据流入时
如果匹配我在系统上创建的 NENetworkRule
系统将根据过滤器数据动作 把那个数据流传到我的内容过滤器中
好的 一旦我创建好这些 NEFilterRules 我会继续并创建我的 NEFilterSettingsObject 传入规则并制定允许的默认动作 这意味着如果系统上创建了 新的数据流 并且它不匹配任何过滤器规则 我希望系统允许那个它流过 不要把它传给我的内容过滤器
我要继续并调用应用 从把我的过滤器设置应用到系统中 然后 当完成后 我就调用 StartFilterCompletionHandler 示意系统我的过滤器现在已经启动 并运行 并且已经准备好处理网络数据流了
现在让我们看一下 HandleNewFlow函数 当有新数据流创建 并匹配我的过滤器规则时 将调用这个函数
这个函数接受一个参数 即表示数据流的 NEFilterFlowObject 并返回一个新的数据流裁决 示意系统如何处理数据流
那么在这里我要做的就是 在字典中 把有关数据流的一些细节打包 并把那个字典发送到 我的UI app中 提示用户允许或拒绝数据流
现在获得用户的决策 很明显是一个非常异步化的过程 当我等待用户做出决策时 我要继续并给系统返回一个 暂停的裁决 这会告诉OS对这个数据流待决 不要采取进一步行动 除非我恢复数据流
一旦用户做出决策 我会创建一个新的数据流裁决 允许或拒绝 取决于用户所做出的决策 然后我就通过新裁决调用恢复数据流
好的 这是一个使用系统扩展 和内容过滤器API 来实施简单防火墙的一个示例app
接下来我要讲透明代理app
透明代理app的其中一个例子是 云安全app 这些app把通往指定网站的流量 转移到云服务中 那个云服务 对流量应用一些额外的安全性检查 比如额外的用户认证或授权
透明代理app的另一个例子是 对流量应用一些特殊转换的app 比如对网络流量 或对以某种特殊方式 从Web上下载的缓存资源 应用加密算法
透明代理app还可以在单一连接上 多路传输多个网络流量数据流 或它们可以使用一些 自定义的特殊协议 那会减少网络延迟 有许多关于透明代理app的 非常有意思的用例 因此我要很激动地告诉你们 在macOS Catalina中 我们已经在网络扩展框架中 引入了一些新API 允许你创建透明代理app 而不需要使用内核扩展
让我们继续并看一下这些API 它们在 NetworkExtensionFramework中 让我们看一下如何在你的app中 使用它们
在你的主UI app中 你使用任意透明代理管理器 来创建透明代理配置 并在系统中注册你的透明代理 因此你的系统知道如何运行 你的透明代理
你还要创建一个系统扩展 你的代理要在系统扩展中运行
这些API允许你在数据流层 代理网络数据的流动 为此 你要创建 NEAppProxyProvider的一个子类 与内容过滤器不同 默认情况下 系统不会给你的代理 转移任何数据流
因此你必须创建一组 NENetworkRules 指定你想要代理哪些数据流
一旦你的透明代理启动并运行 并且你已经安装了 NENetworkRules
随着匹配你规则的新的 TCP和UDP数据流的打开 这些数据流会被转移到你的 NEAppProxyProvider子类中 然后完全由你来处理 每个独立的数据流
你可以在另一个链接上 多路传输数据流 应用你的特殊转换 或任何其它你想要执行的操作 完全由你决定
那么这是如何在你的app中 使用透明代理API的一个简单概览
接下来让我们看一下 DNS代理app 现在DNS协议是一个强大的协议 非常强大而且非常有用 但它不是特别安全 因此欺骗DNS响应 和导致浏览器打开恶意网站非常简单 或通过查看某人发送的DNS查询 监视他们的因特网浏览活动也很简单
为了弥补这些不足 DNS代理 向DNS协议中应用额外的安全性
比如 app可能对DNS流量 应用一些加密 或在某种安全通道上应用 代理DNS流量 那么我很高兴地告诉你 在macOS Catalina中 我们引入了一些很棒的新API 允许你实施 DNS代理app 而不需要使用网络内核扩展
这些API在 NetworkExtension框架中 它们实际上是 在iOS 11中引入的 我们把它们带到 macOS Catalina中来 并让它们在Mac上可用 让我们看一下这些API 以及如何在你的app中使用它们
在你的主UI app中 你将使用 NEDNSProxyManager 创建你的DNS代理配置 并在系统中注册你的配置 从而系统知道如何运行 你的DNS代理
你创建一个系统扩展 DNS代理将在系统扩展中运行 并且你要把你的代理 作为NEDNSProxyProvider类 的子类进行实施
一旦你的DNS代理配置在系统中 进行了注册 你的系统扩展也正在运行 那系统将开始转移 对你的NEDNSProxyProvider 子类的所有DNS查询
然后完全由你来决定 如何处理每个DNS查询 你可以对它进行加密 你可以通过某种安全通道发送它 完全由你决定
好的 那么这是DNS代理API的 一个概览 接下来我要讲VPN app VPN app的一个经典用例是 允许公司为员工提供 安全地远程访问公司内网的方式
另一个用例 最近几年越来越流行了 即个人VPN app 这些app用于 安全地和异步地浏览因特网
我们实际上在macOS 10.10中 就在macOS上引入了VPN API
在这次发布中 我们改善了这些API 让它们更好用 让我们看一下VPN API 以及如何在你的app中使用它们
在你的主UI app中 你使用NETunnelProviderManager 创建VPN配置 并在系统中注册VPN客户端
你还创建一个系统扩展 你的VPN客户端代码 将在系统扩展中运行
你把VPN客户端 作为NEPacketTunnelProvider类的 一个子类进行实施 系统创建一个utun界面 响应你的 NEPacketTunnelProvider
NEPacketTunnelProvider 负责告诉系统 你想通过你的VPN路由哪些网络
一旦你指定了VPN的路由规则 并在系统中安装了路由规则 当IP数据包按照那些规则 被路由到你的utun界面时 那些数据包将被转到你的 NEPacketTunnelProvider中 你可以通过隧道连接 使用自定义隧道协议发送那些数据包
好的 这是VPN API 如何运作的简单概览 接下来我想讲一下 我们对VPN API 所做的一些改进 首先是 IncludeAllNetworks 这是一个可以设置在VPN配置上的 新标志 这在个人VPN app中尤其有用 在这些app中 在VPN隧道之外没有流量泄漏 非常重要 你希望你的全部流量都能通过VPN 是的
通过在配置上启动 IncludeAllNetworks 你可以实现这个功能 系统将通过VPN路由所有流量 并且如果VPN 由于某种原因暂时不可用… 比如 如果Mac正在切换 它所连接的WiFi网络 或如果你的VPN不管由于什么原因 暂时当掉了
在这些情境中 流量实际上会被释放 而不是被路由到VPN之外
现在如果你已经启动 IncludeAllNetworks 但你仍然想访问本地网络资源 比如打印机 你可以启动 ExcludeLocalNetworks 从而仍然允许访问本地网络资源
我们还对Per-App VPN 做了一些改进 我们添加了三个新的域名列表 你可以用于把流量 路由到Per-App VPN
它的运作方式是对于每一个列表 如果相应的app 创建了对主机的连接 并且那个主机域名 与列表中的一个域名相匹配 该连接的流量将通过 Per-App VPN进行路由 让我们看一个例子 如果你正在使用Mail app 并且你给Mail app 设置了两个账户… 你有私人邮箱账户 和公司邮箱账户
通过在邮箱域名数组中 指定公司邮件服务器的域名
当Mail打开对公司邮件服务器的 新连接时 将通过Per-App VPN 路由该连接
而对你私人邮件服务器的连接 将不通过 Per-App VPN进行路由
CalendarDomains和 ContactsDomains列表 使用方式相同 但它们是Calendar app 和Contacts app
好的 那么这是macOS上可用的 VPN API的简单概览 以及我们所做的一些改进 从而使你可以创建VPN app 而不需要使用网络内核扩展
接下来我想讲一下虚拟机app 这些app用于创建和管理虚拟机 实话实说 如果虚拟机不联网的话 它很可能不是特别有用
在macOS上 我们有 vmnet.framework 正好能实现这个功能 把虚拟机连接到互联网 在macOS 10.10中vmnet.framework 就引入到macOS上了 但我们在本次发布中做了一些改进 为你提供更多 把虚拟机连接到网络的方式
框架的运作方式是 给你提供一些 把虚拟机连接到网络的不同模式 我们对共享模式做了一些改进 你现在可以在共享模式中 使用IPv6了 你可以指定 你想要分配给虚拟机的IP范围 并且你可以在虚拟机和网络之间设置 端口转发规则
我们还添加了一个全新的模式 叫做桥接模式 在这个模式中 你的虚拟机会出现在本地网络中 就好像它们实际连接到本地网络一样
好的 这是用于把虚拟机 连接到网络的 虚拟机API的简单概览
接下来我想简单讲一下 使用自定义低层协议的app 其中一个例子是 需要与硬件通讯的app 比如摄像头或音频设备 该设备仅能接受某些低层协议 比如自定义链接层协议 或自定义IP协议
另一个使用自定IP协议的 app例子是 比如 需要使用某些高度优化了的协议 与本地网络上的其它机器 进行通讯的app
我很高兴地宣布 在macOS Catalina中 我们引入了一些新API 允许你使用自定义低层协议 在网络上进行通讯 而不需要使用内核扩展
首先让我们看一下 自定义IP协议的API
这是网络框架中的一个新API
它的运作方式是 在你的app中 你创建一种新的 NWParameters对象 给你的自定义IP协议 指定标识符编号 然后使用那个NWParameters对象 创建一个NWConnection 然后那个NWConnection 的使用方法 与你使用自定义IP协议 在网络上进行TCP或UDP NWConnection通讯一样
要了解更多关于 NWConnection的信息 请参看我们去年的演讲 关于网络框架的介绍
现在让我们看一个简短的代码示例 演示如何使用这个 自定义PI协议API 首先我要做的是用这个新构造函数 创建一个 NWParameters对象
它接受自定义IP协议的标识符编号 重点是 你必须在这里传递 自定义协议的标识符编号 你不能传递 系统已经能处理的协议的编号 比如TCP、UDP或ICMP
接下来我要创建通讯的目的地 并且创建NWConnection 传入目的地和我的参数
然后我就像使用其它 NWConnection一样 使用连接 开启连接 并开始使用我的自定义IP协议 发送/接收数据包
接下来让我们看一下 自定义链接层协议API 我们也把它们添加到了网络框架中
它们的运作方式是 在你的app中你创建一个 NWEthernetChannelObject 指定你想要使用的 自定义etherType 然后使用你的通道对象 通过以太网接口通讯 使用了你的自定义 etherType的数据包
让我们看一些代码 了解它的运作方式
首先我引用当前有线以太网接口 然后创建我的 NWEthernetChannel对象 传入接口和我的自定义 etherType
现在与自定义IP协议API一样 你必须在这里传递一个 自定义etherType 你不能传递一个系统已经能处理的 etherType 比如IP或IPv6
创建好通道之后 我在通道上设置一些回调代码块
当通道状态发生变更时 调用 stateUpdateHandler代码块 当通道准备好之后 我可以继续并开始发送和接收 使用了我自定义etherType 的数据包
当从网络中收到 使用了我自定义etherType 的新数据包时 将调用 receiveHandler代码块
当完全设置好我的通道之后 我就继续并启动它 从而我可以开始使用我的自定义 etherType进行通讯
很好 那么这是我们所添加的 新API的简单预览 这些新API可以让你使用自定义 低层协议在网络上进行通讯 而不需要使用内核扩展
好的 我们今天讲了很多内容 我们在macOS Catalina中 添加了许多很棒的新API 可以让你创建属于这些类别的app 而不需要使用网络内核扩展
现在我想简单讲一下 网络内核扩展的未来发展情况
网络内核扩展存在许多问题
首先是难于开发 我之前提到过 如果你测试一些新功能 你很可能需要反复重启很多次 同时如果你使用网络内核扩展 你需要频繁地使用一些 非常低级的概念 比如执行手动M-缓冲链操作 这个代码非常棘手 非常容易出错
同时内核扩展难于调试 你必须有两台独立的机器 正如我之前所提到过的那样 单步执行代码非常棘手 如果行得通的话
同时内核扩展中的稳定性问题 可能会给系统带来灾难性后果 如果你的内核扩展崩溃了 它不会仅仅当掉你的app 还会重启整个系统 这对于用户来说非常糟糕 而且会导致严重的数据丢失
因为内核扩展存在这么多问题 而且也因为我们已经在macOS上 实现了这个重要的里程碑 就是我们现在有这些API 你可以用于在macOS Catalina中 创建app 而不需要使用网络内核扩展了 我们现在不推荐你使用 网络内核扩展
你现有的网络内核扩展 可以继续在 macOS Catalina上运行 然而我们强烈鼓励你们 查看一下我们所添加的这些 很棒的新API 并开始在你们的app中采用它们 替换网络内核扩展的使用 尽快做到这一点很重要 因为不久之后 我们将从macOS中彻底移除 对网络内核扩展的支持
好的 今天我们讲了 许多强大的新API 我们把它们引入 macOS Catalina中 帮助你创建 能过滤网络内容的app 代理网络内容 隧道网络内容 给虚拟机联网 并使用自定义低层协议 在网络上进行通讯 而不需要使用网络内核扩展
这是个好消息 因为我们强烈鼓励你 在你的app中采用这些新API 因为现在已经不推荐使用 网络内核扩展了 并且不久的将来 我们将移除对它的支持
要获取更多信息 请查看本场演讲的网页 你可以找到我今天在这里所演示的 示例代码的链接
我们还有一场网络演讲 实际上马上就会开始 并且我们希望能在演讲上看到你们 我们可以回答你所有的疑问 感谢大家参加这场演讲 祝你度过愉快的一天
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。