IIS架构与HTTP请求处理流程

4 篇文章 0 订阅

 Windows 操作系统中的 IIS 负责提供互联网服务,一台运行了 IIS 的计算机可以看成是一台 Web 服务器。

       Windows XP SP2 中 IIS 主版本号为 5 , Windows 2003 Server 为 6 , Vista 和 Windows Server 2008 为 7 。对于 Windows 2003 Server ,其默认支持的 ASP.NET 版本为 1.1 ,因此必须单独安装 .NET Framework 2.0 以上版本[1] 。

       目前, IIS 6 是使用最为广泛的版本, IIS 5 已基本不在 Web 服务器上部署, IIS 6 与 IIS 5 相比在系统架构上有着较大的差异, IIS 7 与 IIS 6 相比,基本架构并没有根本性的变化,但在许多方面有新的增强和改进。本书选择 IIS 6/7 进行介绍,大部分内容也适合于 IIS 5 ,但 IIS 5 一些已过时的特性就不介绍了。

       首先,我们来仔细分辨一下三个很容易混淆的基本概念。

 

8.1.1网站、Web应用程序和虚拟目录

       在 IIS 中可以创建网站、 Web 应用程序和虚拟目录,以便与计算机网络上的用户共享信息。“网站”、“ Web 应用程序”和“虚拟目录”这三个概念的关系如 图 8 ‑1 所示。

 

网站,应用程序与虚拟目录

 

 

 

图  网站,应用程序与虚拟目录

 

       简而言之, 一个“网站(Web Site )”包含一个或多个“ Web 应用程序(Web Application )”,一个Web 应用程序包含一个或多个“虚拟目录(Virtual Directory )”,而虚拟目录则映射到 Web 服务器或远程计算机上的物理目录。

       图 8 ‑2 所示为运行 IIS 7 的一个 Web 服务器。

 

IIS 7中的网站,应用程序与虚拟目录

 

 

图  IIS 7 中的网站,应用程序与虚拟目录

 

        8‑2 中可以清楚地看到此 Web 服务器上有两个“网站”: Default Web Site 和 NewWebSite ,其中 Default Web Site 网站中有三个“ Web 应用程序”: HappyBookShopService 、 HappyBookShopWebSite 和 OnlineAlbum 。而 HappyBookShopWebSite 应用程序下的每一个子文件夹都是一个“虚拟目录”。最顶层的虚拟目录称为“根虚拟目录”,  8‑2 中 Web 应用程序 HappyBookShopWebSite 的根虚拟目录为“ /HappyBookShopWebSite ”。

       下面逐个剖析这三个概念。

1 网站( Web Site )

       网站是 Web 应用程序的容器,每个网站都有一个唯一的标识,这一标识由它的 IP 地址、端口和可选的主机头/ 主机名 组合而成, Web 服务器根据收到的 HTTP 请求中的这些信息来确定是对哪一个网站的请求 。

       在 IIS 7 中,将网站标识称为“ 网站绑定 ”, 图 8 ‑3 所示为 IIS 7 默认网站的“网站绑定”对话框。

 

IIS7.0中的网站绑定

 

 

图  IIS7.0 中的网站绑定

2 Web 应用程序( Web Application )

       Web 应用程序是一种在应用程序池( Application Pool )[3] 中运行并通过 HTTP 协议向用户提供信息服务(通常以 HTML 格式表达信息)的软件程序。创建 Web 应用程序时, Web 应用程序的名称将成为网站 URL 的一部分,用户可以通过 Web 浏览器发出针对该 URL 的 HTTP 请求。

使用Visual Studio 创建的“ASP.NET 网站”,其实是一个“Web 应用程序”,它并不等于IIS 中的“网站”。

       在 IIS 中,每个网站至少必须拥有一个 Web 应用程序(但不一定是 ASP.NET 应用程序,可以是其他类型的 Web 应用程序),它被称为“ Web 应用程序” 或“默认 Web 应用程序” ,除此之外,网站还可以包含一个或多个 ASP.NET (或其他种类) Web 应用程序。

       在 Windows XP SP2 中,使用 Visual Studio 创建的 ASP.NET 网站发布到本机 IIS 之后都是作为本机默认网站(即“ localhost ”所代表的网站)所承载的 Web 应用程序而运行的。

       Windows Server 和 Vista 可以为某个 ASP.NET 应用程序创建一个独立的 IIS 网站,此网站只承载这个唯一的 ASP.NET 应用程序,并且运行在一个独立的应用程序池中。许多商业网站都采用这种方法以获取较高的性能,同时将此网站与 Web 服务器上承载的其他网站相互隔离,以增强 Web 服务器的安全性。

虚拟目录( Virtual Directory)

      

    虚拟目录是在 IIS 中指定并映射到本地或远程服务器上的物理目录的目录名称。然后,此虚拟目录名称将成为 Web 应用程序 URL 的一部分,用户可以通过 Web 浏览器向 IIS 请求访问此 URL 所对应的物理目录中的资源。

       在 IIS 中,每个 Web 应用程序都必须拥有一个最顶层的虚拟目录,它被称为“根虚拟目录 ”。

       在 Visual Studio 中,可以在属性窗口中直接设定 ASP.NET 网站的根“虚拟路径”( 图 8 ‑4 )。

 

ASP.NET网站的虚拟路径

      

 

图  ASP.NET 网站的虚拟路径

 

       但要注意,这里设定的“虚拟路径”是 Visual Studio 自带的轻量级 Web 服务器“ ASP.NET Development Server ”的虚拟路径,而非 IIS 中的虚拟路径。如果使用 Visual Studio 完整版本(比如团队开发版),则可以使用一个“发布网站”的功能。在使用此功能发布网站时可以直接指定 ASP.NET 应用程序在 IIS 中所对应的根虚拟目录 图 8 ‑5 )。

 

定IIS中ASP.NET网站的根虚拟目录

 

 

图  设定 IIS 中 ASP.NET 网站的根虚拟目录

 

       图 8 ‑5 中将 ASP.NET 网站所对应的 IIS 根虚拟目录设为 MyNewWebSite 。

       一个 Web 应用程序可以拥有多个虚拟目录,这些虚拟目录都将成为 Web 应用程序根虚拟目录的子目录。

       可以很方便地在 IIS 中创建一个虚拟目录( 图 8 ‑6 )。

 

在IIS 7中创建一个虚拟目录

 

 

图  在 IIS 7 中创建一个虚拟目录

 

       给 Web 应用添加完虚拟目录之后,可以通过以下 URL 访问虚拟目录中的网页

 

    http:// 网站名称 /Web 应用程序根虚拟目录 / 新创建的子虚拟目录 /Web 网页名称

 

       在上面的例子中, Web 应用程序根虚拟目录为“ /MyNewWebSite ”,新加的虚拟目录为“ MyNewVirtualDir ”,它指向“ D:/MyDir ”目录。现假设 D:/MyDir 目录中有一个 ASP.NET 网页(不妨设为 Sample.aspx ),则通过以下 URL 可以访问此网页:

 

    http://localhost/MyNewWebSite/MyNewVirtualDir/Sample.aspx


[1] 建议在 Windows 2003 Server 上安装最新版本的 .NET Framework 以支持一些新技术,比如 AJAX 和 Silverlight 。

[2] 更具体来说,是 Windows Server 的一个系统核心组件: HTTP.SYS 负责这一工作。后面在介绍 IIS 架构时还将介绍 HTTP.SYS 组件。

[3]后文将应用程序池作更详细的介绍

[4]“虚拟目录”有时又被称为“虚拟路径”,两者代表同一概念。

[5]对于 Visual Web Developer 速成版,它没有提供“发布网站”的功能,但可以在“复制网站”对话窗口找到相应的按钮完成这一工作。或者更直接些,直接使用 IIS 管理器来完成这一工作。

  [6]在 IIS 5/6 中使用“虚拟目录创建向导”来创建虚拟目录,其方法是在 IIS 的某个 Web 应用程序节点上右击,从快捷菜单中选“新建”--> “虚拟目录”命令。 IIS 7 的操作是类似的,但它直接在快捷菜单的第一级中就提供了“添加虚拟目录”的命令。

 

8.1.2 应用程序池与工作者进程

 

       “应用程序池( Applicaion Pool  ”是 Windows Server 为提升 Web 服务的性能和可靠性而引入的一个 Web 程序隔离机制。

       一个应用程序池可包含一个或多个 Web 应用程序。 ASP.NET Web 应用程序必须运行在一个应用程序池中。

       在 Windows Server 中,一个或多个“工作者进程( Worker Process  ”为应用程序池提供服务,停止一个应用程序池将导致这些工作者进程被关闭,这时,所有发往此应用程序池中 Web 应用程序的 HTTP 请求将收到“ 503 服务不可用”的响应信息。

       图 8 ‑7 展示了一台 Web 服务器上正在运行的应用程序池。

 

应用程序池

 

图  IIS 7 中的应用程序池

 

       如 图 8 ‑7 所示,本台 Web 服务器上的设置了 3 个应用程序池,每个应用程序池中可运行多个 Web 应用程序,图中 DefaultAppPool 这一应用程序池运行了 5 个 Web 应用程序。每个应用程序池都可以加载特定版本的 .NET Framework ,以对 ASP.NET 应用程序提供支持。在 IIS 7 中,应用程序池有两种运行模式—— 经典模式和集成模式 

       经典模式下, IIS7 应用程序池运行方式同 IIS 6 [1] 

       集成模式下, IIS 7 直接预装载 .NET Framework ,从而为 ASP.NET 应用程序提供了更好的性能。

       在 IIS 管理器中可以随时启动和停止某个应用程序池。

       Windows Server 上运行应用程序池的系统帐号是“ NetworkService ”。在后面的章节中可以看到在部署 ASP.NET 网站时往往需要给此帐号设置特定的权限(比如允许 ASP.NET 应用程序向某文件夹中添加文件)。

       下面深入介绍一下 IIS 的系统架构以及 Windows Server 是如何响应 HTTP 请求的。

 

8.1.3 IIS的系统架构

       IIS 6 (运行于 Windows 2003 Server )的架构如 图 8 ‑8 所示。

 

 

 IIS 6架构

图  IIS 6 的架构

 

 

       从 图 8 ‑8 中可以看到, IIS 6 架构由以下几个部分组成:

(1)HTTP.SYS :运行于 Windows 核心( Kernel )的一个组件,它负责侦听( Listen )来自于外部的 HTTP 请求(通常来自网络中另一台计算机上的浏览器),根据请求的 URL 将其转发给相应的应用程序池,由运行于应用程序池中的工作者进程来响应此 HTTP 请求。当此 HTTP 请求处理完成时,它又负责将处理结果发送出去(其接收者通常为发出 HTTP 请求的浏览器)。

       为了提供更好的性能, HTTP.SYS 内部建立了一个缓冲区,将最近的 HTTP 请求处理结果保存起来,如果发现某个 HTTP 请求“不久以前”处理过了(即在缓冲区中可以找到),它就简单地直接从缓冲区中取出这些结果发回给客户端(通常为发出 HTTP 请求的浏览器)。

(2)InetInfo :在 IIS 5 时代, InetInfo 是 IIS 服务的主进程,在 IIS 6 中,它不再负责处理 HTTP 请求,但它继续负责管理除了 WWW 服务之外的其他互联网服务,比如用于文件传输的 FTP 服务和用于邮件收发的 SMTP 服务。

       InetInfo 内部维护了一个元数据库( Metabase ),在这个数据库中存入了一些重要的信息,这些信息对于维护各种互联网服务(比如 WWW 和 FTP )等是必不可少的。

(3)Worker Process :负责处理 HTTP 请求,被译为“工作者进程 ”,事实上,它是由一个可执行程序 W3WP.EXE 运行时所生成的一个进程[ 2] ,每一个工作者进程内部都可以管理一个或多个 ASP.NET 应用程序。 工作者进程运行于一个应用程序池( Application Pool )中。 IIS 6 可以创建多个应用程序池,并指定某个 ASP.NET 应用程序在特定的应用程序池中运行( 图 8 ‑9 )。

 

 

 设定程序池

 

图  设定 ASP.NET 网站所属的应用程序池(图截自 Windows 2003 Server )

 

       一般情况下,一个应用程序池只有一个工作者进程,但也可通过配置必要的参数让多个工作者进程同时运行在同一个应用程序池中,在这种情况下,这个应用程序池被称为“ Web Garden  Web 园) ”。

(4)WAS  Web Admin Service  :这是一个监控程序,它一方面可以存取放在 InetInfo 元数据库( Metabase )中的各种信息,另一方面也负责监控应用程序池( Application Pool )中的工作者进程的工作状态况,必要时它会关闭一个老的工作者进程并创建一个新的取而代之。

 


[1] IIS 6 中应用程序池的运行方式后文有详细介绍

[2]“进程( Process )”是一个具有一定独立功能的程序在一个数据集合上的一次动态执行过程。如用通俗的语言来表达,可以简单地将“进程”理解成一个正在运行的程序。

8.1.4 HTTP请求的处理过程

 

在了解了 IIS 的架构之后,来看一下 IIS 架构中的各个组成部分是如何相互配合处理 HTTP 请求的。

先来从总体上看看 HTTP 请求的处理过程( 图 8 ‑10 )。

 http请求处理过程

图  10 HTTP 请求的处理过程

 

图 8 ‑10 清晰地展示出了 HTTP 请求处理就是浏览器与 Web 服务器间“一问一答”的过程 

首先,浏览器尝试连接 Web 服务器的 80 端口,如果 Web 服务器可以响应此连接请求,就在浏览器与 Web 服务器间建立了一个通讯链路,在此“通道”上浏览器与 Web 服务器可以相互发送与接收信息。

假设浏览器向 Web 服务器发出一个读取某 ASP.NET 站点上的某个 ASPX 网页的请求。当此请求通过网络到达 Web 服务器时,此请求被 HTTP.SYS 组件所接收。 HTTP.SYS 系统组件会检查此 HTTP 请求的相关信息,根据其 URL 将此 HTTP 请求发送给运行在某个应用程序池中的工作者进程处理。如果同时有多个针对此 ASP.NET 站点的 HTTP 请求, HTTP.SYS 会将这些请求排队,加入到对应的应用程序池的 HTTP 请求队列中等待。

如果这是第一个对 ASP.NET 站点的 HTTP 请求,工作者进程会加载 aspnet_isapi.dll ,并将请求转给它, aspnet_isapi.dll 接着会装载 .NET CLR[1]  创建一个针对此 ASP.NET 站点的应用程序域[2] ,然后启动一个复杂的由多个步骤和组件参与的处理流程,当此处理流程结束,要发回给客户端的结果(通常是 HTML 代码,当然也可以是其他类型的资源,比如由程序动态生成的图片)已经生成,此结果被转发给 HTTP.SYS 。

注意: 对于以集成模式运行的IIS 7 ,.NET CLR 在应用程序池一启动就自动装载,从而避免了临时装载CLR 的花销。后继处理过程与IIS 6 基本一致。

HTTP.SYS 接收到请求的处理结果之后,将其缓存到缓冲区中,然后把处理结果发回给发出 HTTP 请求的浏览器。

       上述过程是对 IIS 处理 HTTP 请求全过程的粗略描述。 8.2 节将选取这个处理过程中的主要阶段,详细介绍针对 ASP.NET 网页的 HTTP 请求处理过程。

 

8.1.5 ISAPI扩展、ISAPI筛选器和程序映射

 

Http请求刚刚到达服务器的时候

当服务器接收到一个 Http请求的时候,IIS 首先需要决定如何去处理这个请求(NOTE:服务器处理一个.htm页面和一个.aspx页面肯定是不一样的么)。那IIS依据什么去处理呢?―― 根据文件的后缀名。

服务器获取所请求的页面(NOTE:也可以是文件,比如 jimmy.jpg)的后缀名以后,接下来会在服务器端寻找可以处理这类后缀名的应用程序,如果IIS找不到可以处理此类文件的应用程序,并且这个文件也没有受到服务器端的保护(NOTE:一个受保护的例子就是 App_Code中的文件,一个不受保护的例子就是你的js脚本),那么IIS将直接把这个文件返还给客户端。

能够处理各种后缀名的应用程序,通常被称为 ISAPI 应用程序(NOTE:Internet Server Application Programe Interface,互联网服务器应用程序接口)。虽然这 ISAPI 听上去还挺气派,也算是“应用程序”呢,但仔细看看它的全称就明白了:它实际上只是一个接口,起到一个代理的作用,它的主要工作是映射所请求的页面(文件)  和与此后缀名相对应的实际的处理程序。

        在 IIS 的文档中经常会提到两个术语: ISAPI 扩展和 ISAPI 筛选器。

1、 ISAPI 扩展

       “ ISAPI 扩展( ISAPI Extension  ”是一种可以添加到 IIS 中以增强 Web 服务器功能的程序,其载体为 DLL 文件。它通常直接负责响应 HTTP 请求。

       根据 HTTP 请求要访问的资源扩展名(通过 URL 获取), IIS 会选取特定的 ISAPI 扩展来处理这一请求,这一过程被称为“ 程序映射  。 而用于响应 HTTP 请求的这一 ISAPI 扩展被称为“ HTTP Handler HTTP 处理程序) ”。 图 8 ‑11 展示了 IIS 6 中的程序映射。

 

程序映射

图  11 程序映射( IIS 6 

 

       在 图 8 ‑11 中可以看到, IIS 指定对 ASP.NET 网页(其扩展名为 .aspx )的请求将由 aspnet_isapi.dll 处理( 图 8 ‑12 )。

 我们找到 .aspx 的应用处理程序,然后点“编辑”,会出现下面的画面:

编辑映射

 

图  12 aspnet_isapi.dll 负责处理对 ASP.NET 网页的请求

一路看到这里,可以看出,所有的.aspx文件实际上都是由 aspnet_isapi.dll 这个程序来处理的,当IIS把对于.aspx页面的请求提交给了aspnet_isapi.dll以后,它就不再关心这个请求随后是如何处理的了。现在我们应该知道:Asp.Net 只是服务器(IIS)的一个组成部分而已,它是一个 ISAPI扩展。

这里需要注意两点:

  • 当你修改“限制为”后,可以限制页面(文件)只能以某种特定方式访问
  • “确认文件是否存在”是实现 URL 地址映射的关键选项,我以后会专门讲述。

 

       IIS 7 中的程序映射与 IIS 6 略有不同。当 IIS 7 以“经典模式”运行时,与 IIS 6 一样使用 aspnet_isapi.dll 响应针对“ .aspx ”的请求。但当 IIS 7 以“集成模式”运行时,则使用托管处理程序( System.Web.UI.PageHandlerFactory )响应针对“ .aspx ”的请求( 图 8 ‑13 )。

 IIS7下的程序映射

图  13 IIS 7 集成模式下的程序映射

 

2、 ISAPI 筛选器

       “ISAPI筛选器(ISAPI Filter )  也是一种 DLL ,但它不负责生成 HTTP 请求,它的主要作用是响应某些特定的事件。当这些事件发生时 ISAPI 筛选器被调用,它可以修改传入或传出的 HTTP 数据。

在 IIS 7 中,使用“ HTTP 模块(HTTP Module )  取代了传统 ISAPI 筛选器的功能。

注意: ISAPI 扩展与ISAPI 筛选器名字很相近,但其在IIS 中的地位和所起的作用是不同的。

 

理解宿主环境(Hosting)

从本质上讲,Asp.Net 主要是由一系列的组成,这些类的主要目的就是将Http请求转变为对客户端的响应。HttpRuntime类是Asp.Net的一个主要入口,它有一个称作 ProcessRequest 的方法,这个方法以一个 HttpWorkerRequest 类作为参数。HttpRuntime 类几乎包含着关于单个 Http请求的所有信息:所请求的文件、服务器端变量、QueryString、Http 头信息 等等。Asp.Net 使用这些信息来加载、运行正确的文件,并且将这个请求转换到输出流中,一般来说,也就是HTML页面。

NOTE:二般来说,也可以是张图片。

当 Web.config文件的内容发生改变 或者 .aspx文件发生变动的时候,为了能够卸载运行在同一个进程中的应用程序(NOTE:卸载也是为了重新加载),Http请求被分放在相互隔离的应用程序域中。

NOTE:可能你以前就听过应用程序域,但是不了解怎么回事,应用程序域就是 AppDomain。

对于IIS来说,它依赖一个叫做 HTTP.SYS 内置驱动程序监听来自外部的 HTTP请求。在操作系统启动的时候,IIS首先在HTTP.SYS中注册自己的虚拟路径。

NOTE:实际上相当于告诉HTTP.SYS哪些URL是可以访问的,哪些是不可以访问的。举个简单的例子:为什么你访问不存在的文件会出现 404 错误呢?就是在这一步确定的。

如果请求的是一个可访问的URL,HTTP.SYS会将这个请求交给 IIS 工作者进程

NOTE:IIS6.0中叫做 w3wp.exe,IIS5.0中叫做 aspnet_wp.exe

每个工作者进程都有一个身份标识 以及 一系列的可选性能参数。

NOTE:可选性能参数,是指诸如 回收机制的设置、超时时间设置 等等。

接下来进行的事情就是上面讲述的 ISAPI 了。

 

除了映射文件与其对应的处理程序以外,ISAPI 还需要做一些其他的工作:

  1. HTTP.SYS中获取当前的Httq请求信息,并且将这些信息保存到 HttpWorkerRequest 类中。
  2. 在相互隔离的应用程序域AppDomain中加载HttpRuntime
  3. 调用 HttpRuntime的ProcessRequest方法。

接下来才是程序员通常编写的代码所完成的工作了,然后,IIS 接收返回的数据流,并重新返还给 HTTP.SYS,最后,HTTP.SYS 再将这些数据返回给客户端浏览器。

 

图3.Asp.Net 的宿主环境

理解管道(Pipeline)

在前面两章中,我们在一个相对比较低的层次上讨论了从发出Http请求到看到浏览器输出这转瞬即逝的十分之一秒内IIS和 Framework 所做的事情。但是我们忽略了一个细节:程序员编写的代码是如何在这一过程中衔接的,本章我们就来看看这个问题。

当Http请求进入 Asp.Net Runtime以后,它的管道由托管模块(NOTE:Managed Modules)和处理程序(NOTE:Handlers)组成,并且由管道来处理这个 Http请求。

图4. 理解 Http 管道

我们按编号来看一下这幅图中的数据是如何流动的。

1. HttpRuntime将Http请求转交给 HttpApplication,HttpApplication代表着程序员创建的Web应用程序。HttpApplication创建针对此Http请求的 HttpContext对象,这些对象包含了关于此请求的诸多其他对象,主要是HttpRequest、HttpResponse、HttpSessionState等。这些对象在程序中可以通过Page类或者Context类进行访问。、

2. 接下来Http请求通过一系列Module,这些Module对Http请求具有完全的控制权。这些Module可以做一些执行某个实际工作前的事情

3. Http请求经过所有的Module之后,它会被HttpHandler处理。在这一步,执行实际的一些操作,通常也就是.aspx页面所完成的业务逻辑。可能你会觉得在创建.aspx页面并没有体会到这一过程,但是,你一定知道,.aspx 页面继承自Page类,我们看一下Page类的签名:

public class Page : TemplateControl, IHttpHandler{
    // 代码省略
}

可以看到,Page类实现了IHttpHandler接口,HttpHandler也是Http请求处理的最底层。

4.HttpHandler处理完以后,Http请求再一次回到Module,此时Module可以做一些某个工作已经完成了之后的事情。

NOTE:注意我用红色标识的字,然后回想一下:Asp.Net 中是不是有众多的 Inserting 、Inserted 之类成对的事件?其实,这里讲述的就是为什么Asp.Net可以将一个Insert操作分成前后两部分,然后再分别进行事件拦截的幕后原理。

如果我们将注意力只集中在Http请求、HttpHandler和HttpModule上,不去考虑HttpContext和HttpApplication,那么图4.可以简化成下面这样:

图5.Http请求在HttpHandler 和 HttpModule 中的流动方向

 

 

HTTP请求 总体流向:

HTTP.SYS -> ISAPI ->HttpRuntime  | ->HttpApplication -> HttpModule -> HttpHandler

 

 

[1] CLR ( Comon Language Runtime ):通用语言运行时,是 .NET 的核心,可以将其看成是一台虚拟的专用于运行 .NET 程序的计算机。

[2] 应用程序域( Application Domain ): .NET 引入的一种代码隔离机制,一个托管进程可以拥有多个应用程序域,在应用程序域中可以装载程序集,创建特定类型的对象,调用对象的方法。

 

参考:

https://blog.csdn.net/bitfan/article/details/2986503

https://www.cnblogs.com/JimmyZhang/archive/2007/09/04/880967.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值