netinitialize_ASP.NET初始化流程分析2

上一篇讲了从创建应用程序域到创建ISAPIRuntime实例的过程,本篇继续讲Asp.net处理第一次请求的必要的初始化过程。

ISAPIRuntime分析

ISAPIRuntime在System.Web.Hosting中实现,它的ProcessRequest是我们处理web请求的入口。

public int ProcessRequest(IntPtr ecb, intiWRType) {

IntPtr pHttpCompletion=IntPtr.Zero;if (iWRType ==WORKER_REQUEST_TYPE_IN_PROC_VERSION_2) {

pHttpCompletion=ecb;

ecb=UnsafeNativeMethods.GetEcb(pHttpCompletion);

}

ISAPIWorkerRequest wr= null;try{bool useOOP = (iWRType ==WORKER_REQUEST_TYPE_OOP);

wr=ISAPIWorkerRequest.CreateWorkerRequest(ecb, useOOP);

wr.Initialize();

String wrPath=wr.GetAppPathTranslated();

String adPath=HttpRuntime.AppDomainAppPathInternal;if (adPath == null ||StringUtil.EqualsIgnoreCase(wrPath, adPath)) {

HttpRuntime.ProcessRequestNoDemand(wr);return 0;

}else{

HttpRuntime.ShutdownAppDomain(ApplicationShutdownReason.PhysicalApplicationPathChanged, SR.GetString(SR.Hosting_Phys_Path_Changed, adPath, wrPath));return 1;

}

}catch(Exception e) {try{

WebBaseEvent.RaiseRuntimeError(e,this);

}catch{}if (wr != null && wr.Ecb ==IntPtr.Zero) {if (pHttpCompletion !=IntPtr.Zero) {

UnsafeNativeMethods.SetDoneWithSessionCalled(pHttpCompletion);

}if (e isThreadAbortException) {

Thread.ResetAbort();

}return 0;

}throw;

}

}

注意方法的IntPtr类型的参数ecb, 它是一个非托管的指针,用于传递一些必须的数据,以及最终将Response的内容返回给非托管环境ISAPI(异步方式),然后呈现给Client用户。方法中调用ISAPIWorkerRequest的静态方法CreateWorkerRequest而创建ISAPIWorkerRequest对象实例,参数分别为ecb和代表WorkerRequest类型的int参数iWRType,通过判断ecb和type类型的具体内容,来决定创建什么类型的WorkerRequest(上述类型的ISPAIWorkerRequest都继承于HttpWorkerRequest),上面的代码可以看出对不同版本的IIS进行了不同的包装,通过其Initialize方法来初始化一些基本的信息(比如:contentType, querystring的长度,filepath等相关信息)。然后调用HttpRuntime.ProcessRequestNoDemand(wr)转入HttpRuntime处理请求,最终体现在调用ProcessRequestInternal方法上。

HttpRuntime分析

Httpruntime在System.Web下实现,我们来看其处理请求的ProcessRequestInternal方法。

private voidProcessRequestInternal(HttpWorkerRequest wr) {

Interlocked.Increment(ref_activeRequestCount);if(_disposingHttpRuntime) {try{

wr.SendStatus(503, "Server Too Busy");

wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType,"text/html; charset=utf-8");byte[] body = Encoding.ASCII.GetBytes("

Server Too Busy");

wr.SendResponseFromMemory(body, body.Length);

wr.FlushResponse(true);

wr.EndOfRequest();

}finally{

Interlocked.Decrement(ref_activeRequestCount);

}return;

}

HttpContext context;try{

context= new HttpContext(wr, false);

}catch{try{

wr.SendStatus(400, "Bad Request");

wr.SendKnownResponseHeader(HttpWorkerRequest.HeaderContentType,"text/html; charset=utf-8");byte[] body = Encoding.ASCII.GetBytes("

Bad Request");

wr.SendResponseFromMemory(body, body.Length);

wr.FlushResponse(true);

wr.EndOfRequest();return;

}finally{

Interlocked.Decrement(ref_activeRequestCount);

}

}

wr.SetEndOfSendNotification(_asyncEndOfSendCallback, context);

HostingEnvironment.IncrementBusyCount();try{try{

EnsureFirstRequestInit(context);

}catch{if (!context.Request.IsDebuggingRequest) {throw;

}

}

context.Response.InitResponseWriter();

IHttpHandler app=HttpApplicationFactory.GetApplicationInstance(context);if (app == null)throw newHttpException(SR.GetString(SR.Unable_create_app_object));if (EtwTrace.IsTraceEnabled(EtwTraceLevel.Verbose, EtwTraceFlags.Infrastructure)) EtwTrace.Trace(EtwTraceType.ETW_TYPE_START_HANDLER, context.WorkerRequest, app.GetType().FullName, "Start");if (app isIHttpAsyncHandler) {

IHttpAsyncHandler asyncHandler=(IHttpAsyncHandler)app;

context.AsyncAppHandler=asyncHandler;

asyncHandler.BeginProcessRequest(context, _handlerCompletionCallback, context);

}else{

app.ProcessRequest(context);

FinishRequest(context.WorkerRequest, context,null);

}

}catch(Exception e) {

context.Response.InitResponseWriter();

FinishRequest(wr, context, e);

}

}

该方法中创建了熟悉的HttpContext并同时创建了HttpRequest与HttpResponse

internal HttpContext(HttpWorkerRequest wr, boolinitResponseWriter) {

_wr=wr;

Init(new HttpRequest(wr, this), new HttpResponse(wr, this));if(initResponseWriter)

_response.InitResponseWriter();

PerfCounters.IncrementCounter(AppPerfCounter.REQUESTS_EXECUTING);

}

然后通过HttpApplicationFactory的GetApplicationInstance静态方法,获取我们熟悉的HttpApplication对象实例(注:HttpApplication对象是继承IHttpAsyncHandler,而IHttpAsyncHandler又继承于IHttpHandler),然后执行调用BeginProcessRequest方法。至此正式进入了HttpApplication对象的创建以及大家熟知的HttpApplication以后的生命周期了。

HttpApplicationFactory分析

HttpApplicationFactory在System.Web下实现。

查看HttpApplicationFactory用来创建Httpapplication的GetApplicationInstance方法。

internal staticIHttpHandler GetApplicationInstance(HttpContext context) {if (_customApplication != null)return_customApplication;if(context.Request.IsDebuggingRequest)return newHttpDebugHandler();

_theApplicationFactory.EnsureInited();

_theApplicationFactory.EnsureAppStartCalled(context);return_theApplicationFactory.GetNormalApplicationInstance(context);

}

该方法有三个步骤:首先是EnsureInited,会检查是否已经初始化,如果没有会调用Init方法先获取global.asax文件的完整路径,然后调用CompileApplication()对global.asax进行编译,Init方法如下。

private voidInit() {if (_customApplication != null)return;try{try{

_appFilename=GetApplicationFile();

CompileApplication();

}finally{

SetupChangesMonitor();

}

}catch{throw;

}

}

然后是EnsureAppStartCalled方法如果未开始启动会调用FireApplicationOnStart。

private voidEnsureAppStartCalled(HttpContext context) {if (!_appOnStartCalled) {lock (this) {if (!_appOnStartCalled) {using (newDisposableHttpContextWrapper(context)) {

WebBaseEvent.RaiseSystemEvent(this, WebEventCodes.ApplicationStart);

FireApplicationOnStart(context);

}

_appOnStartCalled= true;

}

}

}

}private voidFireApplicationOnStart(HttpContext context) {if (_onStartMethod != null) {

HttpApplication app=GetSpecialApplicationInstance();

app.ProcessSpecialRequest(context, _onStartMethod, _onStartParamCount,this, EventArgs.Empty, null);

RecycleSpecialApplicationInstance(app);

}

}

这里创建特定的HttpApplication实例,触发ApplicationOnStart事件(会执行global.asax中的Application_Start方法)。然后在处理完事件以后就立即被回收掉,因为系统初始化只需要一次。

最后是GetNormalApplicationInstance,如果在有空闲的HttpApplication实例,就直接用,如果没有就新创建,然后调用InitInternal方法进行初始化相关的内容,最后返回该HttpApplication实例。

privateHttpApplication GetNormalApplicationInstance(HttpContext context) {

HttpApplication app= null;lock(_freeList) {if (_numFreeAppInstances > 0) {

app=(HttpApplication)_freeList.Pop();

_numFreeAppInstances--;if (_numFreeAppInstances <_minfreeappinstances>

_minFreeAppInstances=_numFreeAppInstances;

}

}

}if (app == null) {

app=(HttpApplication)HttpRuntime.CreateNonPublicInstance(_theApplicationType);using (newApplicationImpersonationContext()) {

app.InitInternal(context, _state, _eventHandlerMethods);

}

}

……returnapp;

}

HttpApplication分析

HttpApplication在System.Web下实现,首先查看HttpApplication的InitInternal方法,该方法用于初始化。

internal voidInitInternal(HttpContext context, HttpApplicationState state, MethodInfo[] handlers) {

Debug.Assert(context!= null, "context != null");

_state=state;

PerfCounters.IncrementCounter(AppPerfCounter.PIPELINES);try{try{

_initContext=context;

_initContext.ApplicationInstance= this;

context.ConfigurationPath=context.Request.ApplicationPathObject;using (newDisposableHttpContextWrapper(context)) {if(HttpRuntime.UseIntegratedPipeline) {

Debug.Assert(_moduleConfigInfo!= null, "_moduleConfigInfo != null");

Debug.Assert(_moduleConfigInfo.Count>= 0, "_moduleConfigInfo.Count >= 0");try{

context.HideRequestResponse= true;

_hideRequestResponse= true;

InitIntegratedModules();

}finally{

context.HideRequestResponse= false;

_hideRequestResponse= false;

}

}else{

InitModules();

Debug.Assert(null == _moduleContainers, "null == _moduleContainers");

}if (handlers != null)

HookupEventHandlersForApplicationAndModules(handlers);

_context=context;if (HttpRuntime.UseIntegratedPipeline && _context != null) {

_context.HideRequestResponse= true;

}

_hideRequestResponse= true;try{

Init();

}catch(Exception e) {

RecordError(e);

}

}if (HttpRuntime.UseIntegratedPipeline && _context != null) {

_context.HideRequestResponse= false;

}

_hideRequestResponse= false;

_context= null;

_resumeStepsWaitCallback= new WaitCallback(this.ResumeStepsWaitCallback);if(HttpRuntime.UseIntegratedPipeline) {

_stepManager= new PipelineStepManager(this);

}else{

_stepManager= new ApplicationStepManager(this);

}

_stepManager.BuildSteps(_resumeStepsWaitCallback);

}finally{

_initInternalCompleted= true;

context.ConfigurationPath= null;

_initContext.ApplicationInstance= null;

_initContext= null;

}

}catch{throw;

}

}

该代码主要有2个功能,一个是初始化大家熟悉的HttpModules,一个是通过BuildSteps执行多个生命周期事件的处理函数。通过上面的代码我们可以看出,每个功能都有一个特殊判断,判断IIS是否是IIS7的集成模式,如果是就有特殊的步骤,如果不是就走一般的步骤(两者直接的差异分别是:经典模式初始化HttpModules的时候会从网站配置的Modules里读取,集成模式会预加载CLR和大量Modules,比如加载服务器上设置的HttpModules;另外在BuildSteps的时候, IIS7集成模式走的是自己特殊的流程)。

总结一下,InitInternal方法的主要功能如下:

InitModules():根据Web.Config的设置,加载相应的HttpModules。

InitIntegratedModules():会加载IIS7集成模式下在服务器上设定的HttpModuels和Web.config里system.webserver下的HttpModuels。

HookupEventHandlersForAppplicationAndModules:绑定HttpApplication实例中相应的事件处理函数(在Global.asax中定义的事件处理函数)。

创建很多实现IExecutionStep接口的类的实例并添加到当前HttpApplication实例的_execSteps中(包括HttpModules中定义的周期事件处理函数和查找匹配的HttpHandler、执行HttpHandler的方法以及过滤输出等特殊事件),等待回调时执行。从这里我们可以看到HttpApplication是以异步的方式处理请求, 对请求的很多处理工作都放入了_execStep等待回调时执行。

在 HttpApplication的事件如下形式定义:

public eventEventHandler BeginRequest {

add { AddSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }

remove { RemoveSyncEventHookup(EventBeginRequest, value, RequestNotification.BeginRequest); }

}

所有的事件都是调用AddSyncEventHookup方法添加进去的,其中第一个参数是以Event+事件名称的值。

internal void AddSyncEventHookup(objectkey, Delegate handler, RequestNotification notification) {

AddSyncEventHookup(key, handler, notification,false);

}private void AddSyncEventHookup(object key, Delegate handler, RequestNotification notification, boolisPostNotification) {

ThrowIfEventBindingDisallowed();

Events.AddHandler(key, handler);if(IsContainerInitalizationAllowed) {

PipelineModuleStepContainer container=GetModuleContainer(CurrentModuleCollectionKey);if (container != null) {

SyncEventExecutionStep step= new SyncEventExecutionStep(this, (EventHandler)handler);

container.AddEvent(notification, isPostNotification, step);

}

}

}

经典模式下在初始化HttpModlue的时候通过调用Events.AddHandler方法,将事件添加到Events集合里,同时这个key就是Event+事件名称,而集成模式下这些事件是添加到另外一个地方的(通过将事件hanlder包装成SyncEventExecutionStep类型,然后调用container.AddEvent方法将事件添加到另外一个地方),也就是说if上面的Events集合是给经典模式用的,下面的Container里的数据是给集成模式用的,这些事件是存放在HttpApplication的ModuleContainers属性里,这个属性的类型是PipelineModuleStepContainer[],个数就是HttpModules的个数,也就是说每个HttpModule在HttpApplication上添加的事件都放在各自的PipelineModuleStepContainer容器里。

privatePipelineModuleStepContainer[] ModuleContainers {get{if (_moduleContainers == null) {

Debug.Assert(_moduleIndexMap!= null && _moduleIndexMap.Count > 0, "_moduleIndexMap != null && _moduleIndexMap.Count > 0");

_moduleContainers= newPipelineModuleStepContainer[_moduleIndexMap.Count];for (int i = 0; i < _moduleContainers.Length; i++) {

_moduleContainers[i]= newPipelineModuleStepContainer();

}

}return_moduleContainers;

}

}

StepManager分析

集成模式和经典模式(或IIS6)使用的是不同的StepManager,这个类的BuildSteps方法就是为了创建有序的ExecutionStep,其中包括各种事件的事情以及其它在各时间周期之间穿插的操作,最主要的操作,大家以前就应该知道的,比如哪个周期可以判定使用哪个HttpHandler,以及在哪个周期内执行这个HttpHandler的BeginProcessRequest方法。StepManager的具体实现类(ApplicationStepManager、PipelineStepManager)和HttpApplication类在同一个文件中定义。

ApplicationStepManager的BuildSteps方法(用于经典模式)

internal override voidBuildSteps(WaitCallback stepCallback ) {

ArrayList steps= newArrayList();

HttpApplication app=_application;bool urlMappingsEnabled = false;

UrlMappingsSection urlMappings=RuntimeConfig.GetConfig().UrlMappings;

urlMappingsEnabled= urlMappings.IsEnabled && ( urlMappings.UrlMappings.Count > 0);

steps.Add(newValidateRequestExecutionStep(app));

steps.Add(newValidatePathExecutionStep(app));if(urlMappingsEnabled)

steps.Add(new UrlMappingsExecutionStep(app)); //url mappings

app.CreateEventExecutionSteps(HttpApplication.EventBeginRequest, steps);

app.CreateEventExecutionSteps(HttpApplication.EventAuthenticateRequest, steps);

app.CreateEventExecutionSteps(HttpApplication.EventDefaultAuthentication, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostAuthenticateRequest, steps);

app.CreateEventExecutionSteps(HttpApplication.EventAuthorizeRequest, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostAuthorizeRequest, steps);

app.CreateEventExecutionSteps(HttpApplication.EventResolveRequestCache, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostResolveRequestCache, steps);

steps.Add(new MapHandlerExecutionStep(app)); //map handlerapp.CreateEventExecutionSteps(HttpApplication.EventPostMapRequestHandler, steps);

app.CreateEventExecutionSteps(HttpApplication.EventAcquireRequestState, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostAcquireRequestState, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPreRequestHandlerExecute, steps);

steps.Add(app.CreateImplicitAsyncPreloadExecutionStep());

steps.Add(new CallHandlerExecutionStep(app)); //execute handlerapp.CreateEventExecutionSteps(HttpApplication.EventPostRequestHandlerExecute, steps);

app.CreateEventExecutionSteps(HttpApplication.EventReleaseRequestState, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostReleaseRequestState, steps);

steps.Add(new CallFilterExecutionStep(app)); //filteringapp.CreateEventExecutionSteps(HttpApplication.EventUpdateRequestCache, steps);

app.CreateEventExecutionSteps(HttpApplication.EventPostUpdateRequestCache, steps);

_endRequestStepIndex=steps.Count;

app.CreateEventExecutionSteps(HttpApplication.EventEndRequest, steps);

steps.Add(new NoopExecutionStep()); //the last is always there_execSteps= newIExecutionStep[steps.Count];

steps.CopyTo(_execSteps);

_resumeStepsWaitCallback=stepCallback;

}private voidCreateEventExecutionSteps(Object eventIndex, ArrayList steps) {

AsyncAppEventHandler asyncHandler=AsyncEvents[eventIndex];if (asyncHandler != null) {

asyncHandler.CreateExecutionSteps(this, steps);

}

EventHandler handler=(EventHandler)Events[eventIndex];if (handler != null) {

Delegate[] handlers=handler.GetInvocationList();for (int i = 0; i < handlers.Length; i++) {

steps.Add(new SyncEventExecutionStep(this, (EventHandler)handlers[i]));

}

}

}

这个方法的完整功能归纳总结有以下几点:

对请求的Request进行验证,ValidateRequestExecutionStep。

对请求的路径进行安全检查,禁止非法路径访问(ValidatePathExecutionStep)。

如果设置了UrlMappings, 进行RewritePath(UrlMappingsExecutionStep)。

执行事件处理函数,比如将BeginRequest、AuthenticateRequest转化成可执行ExecutionStep在正式调用时候执行。

在这多个个事件操作处理期间,根据不同的时机加了4个特殊的ExecutionStep。

MapHandlerExecutionStep:查找匹配的HttpHandler

CallHandlerExecutionStep:执行HttpHandler的BeginProcessRequest

CallFilterExecutionStep:调用Response.FilterOutput方法过滤输出

NoopExecutionStep:空操作,留着以后扩展用

所有的ExecuteionStep都保存在ApplicationStepManager实例下的私有字段_execSteps里,而HttpApplication的BeginProcessRequest方法最终会通过该实例的ResumeSteps方法来执行这些操作。

PipelineStepManager的BuildSteps(用于集成模式)

internal override voidBuildSteps(WaitCallback stepCallback) {

Debug.Trace("PipelineRuntime", "BuildSteps");

HttpApplication app=_application;

IExecutionStep materializeStep= newMaterializeHandlerExecutionStep(app);

app.AddEventMapping(ttpApplication.IMPLICIT_HANDLER,

RequestNotification.MapRequestHandler,false, materializeStep);

app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,

RequestNotification.ExecuteRequestHandler,false, app.CreateImplicitAsyncPreloadExecutionStep());

IExecutionStep handlerStep= newCallHandlerExecutionStep(app);

app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,

RequestNotification.ExecuteRequestHandler,false, handlerStep);

IExecutionStep webSocketsStep= newTransitionToWebSocketsExecutionStep(app);

app.AddEventMapping(HttpApplication.IMPLICIT_HANDLER,

RequestNotification.EndRequest,true, webSocketsStep);

IExecutionStep filterStep= newCallFilterExecutionStep(app);

app.AddEventMapping(HttpApplication.IMPLICIT_FILTER_MODULE,

RequestNotification.UpdateRequestCache,false, filterStep);

app.AddEventMapping(HttpApplication.IMPLICIT_FILTER_MODULE,

RequestNotification.LogRequest,false, filterStep);

_resumeStepsWaitCallback=stepCallback;

}private void AddEventMapping(stringmoduleName,RequestNotification requestNotification,boolisPostNotification,IExecutionStep step) {

......PipelineModuleStepContainer container=GetModuleContainer(moduleName);

container.AddEvent(requestNotification, isPostNotification, step);

}

以上代码有2个地方和经典模式不相同:

集成模式没有使用MapHandlerExecutionStep来装载ExecutionStep(也就是查找对应的HttpHandler),而是通过MaterializeHandlerExecutionStep类来获得HttpHandler,方式不一样。

集成模式是通过HttpApplication的AddEventMapping方法来添加事件的,从而将事件加入到前面所说的ModuleContainers容器。

总结一下,在经典模式下,是用 Event+事件名称做key将所有事件的保存在HttpApplication的Events属性对象里,然后在BuildSteps里统一按照顺序组装,中间加载4个特殊的ExecutionStep,最后在统一执行;在集成模式下,是通过HttpModule名称+RequestNotification枚举值作为key将所有的事件保存在HttpApplication的ModuleContainers属性对象里,然后也在BuildSteps里通过伪造HttpModule名称加载那4个特殊的ExecutionStep,最后按照枚举类型的顺序,遍历所有的HttpModule按顺序来执行这些事件,可以自行编写一个自定义的HttpModuel来执行这些事件看看效果如何。

下面是总结一下处理第一次请求的大体处理流程。

84eca146f18cb122c3c3266a40ca3f9d.png

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
这段代码是一个网络初始化函数 `net_init()`,它用于配置网络相关的参数。 首先,在函数内部定义了一个长度为8字节的缓冲区 `buf`,用于存储MAC地址和IP地址等信息。 接下来,调用了 `netInitialize()` 函数,该函数用于初始化网络栈。 然后,调用了 `netSYS_SetHostName()` 函数,将主机名设置为 `host_name`。 注释掉了一行代码 `get_mac_addr(ip_addr)` 和 `printf("%s\n", mac_addr)`。这可能是获取MAC地址的代码和打印MAC地址的代码。 然后,调用了 `netMAC_aton()` 函数,将 `mac_addr` 的MAC地址转换为二进制表示,并存储在 `buf` 中。接着,使用 `netIF_SetOption()` 函数将转换后的MAC地址设置为网络接口的MAC地址。 接下来,通过判断 `DHCP_enabled` 的值是否为假(false),来确定网络配置模式是静态配置还是使用DHCP。如果为假,即静态配置模式,则继续执行以下代码: 首先,调用 `netDHCP_Disable()` 函数禁用DHCP功能。 然后,调用 `netIP_aton()` 函数将 `ip_addr` 的IP地址转换为二进制表示,并存储在 `buf` 中。接着,使用 `netIF_SetOption()` 函数将转换后的IP地址设置为网络接口的IP地址。 接下来,类似地,使用 `netIP_aton()` 函数将 `net_mask` 的子网掩码转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的子网掩码。 然后,使用 `netIP_aton()` 函数将 `def_gw` 的默认网关地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的默认网关。 接下来,使用 `netIP_aton()` 函数将 `pri_dns` 的主DNS服务器地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的主DNS服务器地址。 最后,使用 `netIP_aton()` 函数将 `sec_dns` 的备用DNS服务器地址转换为二进制表示,并通过 `netIF_SetOption()` 函数设置网络接口的备用DNS服务器地址。 这样,该函数完成了网络初始化的配置,包括设置主机名、MAC地址、IP地址、子网掩码、默认网关和DNS服务器地址等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值