管道是如何随着WebHost的开启被构建出来的?

注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的。要深刻了解这个管道是如何被构建出来的,我们就必须对WebHost和它的创建者WebHostBuilder这个重要的对象具有深刻的理解。[本文已经同步到《ASP.NET Core框架揭秘》之中]

目录
一、WebHost
    WebHostOptions
    构建管道的三个步骤
二、WebHostBuilder
    WebHost的创建
    几个常用的扩展方法

一、WebHost

顾名思义,WebHost被作为Web应用的宿主,应用的启动和关闭都是通过启动或者关闭对应WebHost的方式来实现的。这里所说的WebHost是对所有实现了IWebHost接口的所有类型及其对应对象的统称。IWebHost接口具有如下三个基本成员,其中Start方法用于启动宿主程序。我们编程中通常会调用它的一个扩展方法Run来启动WebHost,实际上背后调用的其实还是这个Start方法。当WebHost启动之后,注册的服务器变开始了针对请求的监听,所以WebHost需要具有与服务器相关的一些特性,这些特性就保存在通过属性ServerFeatures返回的特性集合中。

   1: public interface IWebHost : IDisposable
   2: {    
   3:     void Start();
   4:     IFeatureCollection     ServerFeatures { get; }
   5:     IServiceProvider       Services { get; }
   6: }

我们多次提到ASP.NET Core管道在构建和进行请求处理过程中广泛使用到了依赖注入。依赖注入只要体现在:ASP.NET Core框架以及应用程序会根据需要注册一系列的服务,这些服务会在WebHost启动的时候被用来创建一个ServiceProvider对象,管道在进行请求处理过程所需的任何服务对象都可以从这个ServiceProvider对象中获取。IWebHost接口的Services属性返回的就是这么一个ServiceProvider对象。

具有如下定义的WebHost类是对IWebHost接口的默认实现,我们默认使用的WebHost就是这么一个对象。一般来说,WebHost是通过对应的WebHostBuilder创建的,当后者通过调用构造函数创建一个WebHost对象的时候,需要提供四个参数,它们分别是直接注册到WebHostBuilder上面的服务(appServices)和由此创建的ServiceProvider(hostingServiceProvider),针对WebHost的选项设置(options)和配置(config)。

   1: public class WebHost : IWebHost
   2: {
   3:     public IFeatureCollection     ServerFeatures { get; }
   4:     public IServiceProvider       Services { get; }
   5:  
   6:     public WebHost(
   7:         IServiceCollection     appServices,
   8:         IServiceProvider       hostingServiceProvider,
   9:         WebHostOptions         options,
  10:         IConfiguration         config);
  11:  
  12:     public void Dispose();
  13:     public void Start();
  14: }

WebHostOptions

顾名思义,一个WebHostOptions对象为构建的WebHost对象提供一些预定义的选项设置。这些选项设置很重要,它们决定由WebHost构建的管道进行内容加载以及异常处理等方面的行为。至于它具体携带着哪些选项设置,我们只需要看看这个类型具有怎样的属性成员。

   1: public class WebHostOptions
   2: {
   3:     public string     ApplicationName { get; set; }
   4:     public bool       DetailedErrors { get; set; }
   5:     public bool       CaptureStartupErrors { get; set; }
   6:     public string     Environment { get; set; }        
   7:     public string     StartupAssembly { get; set; }
   8:     public string     WebRoot { get; set; }
   9:     public string     ContentRootPath { get; set; }
  10:  
  11:     public WebHostOptions()
  12:     public WebHostOptions(IConfiguration configuration) 
  13: }

如下面的代码片段所示,WebHostOptions具有七个属性成员。这些属性都是可读可写的,我们可以调用默认无参构造函数创建一个空的WebHostOptions对象,通过手工为这些属性赋值的方式来设置对应的选项。除此之外,我们可以将这些选项设置定义在配置中,并利用对应的Configuration对象来创建一个WebHostOptions对象。

构建管道的三个步骤

一般我们开启了作为应用宿主的WebHost,由注册的服务器和中间件构成的整个管道被构建起来,服务器开始绑定到基地址进行请求的监听。接下来我们就来着重聊聊WebHost在开启过程中都做了些什么。总的来说,WebHost的整个开启过程大体上可以分为如下三个步骤:

  • 注册服务:获取Startup对象并利用它完成服务的注册。
  • 中间件注册:利用获取的Startup对象完成中间件的注册。
  • 设置并开启服务器:获取注册到WebHostBuilder上的服务器并为之设置监听地址,最后启动服务器。

接下来我们按照这个步骤定义一个同名的类型来模式真实WebHost的实现逻辑。如下面的代码片段所示,这个模拟的WebHost和真正的WebHost的构造函数具有完全一致的参数列表,我们定义了对应的字段来保存这些参数值。除此之外,我们会创建一个ApplicationLifetime对象并将其注册到提供个ServiceCollection,在WebHost开启和关闭之后我们会利用它发送相应的通知。

   1: public class WebHost : IWebHost
   2: {
   3:     private IServiceCollection   _appServices;
   4:     private IServiceProvider     _hostingServiceProvider;
   5:     private WebHostOptions       _options;
   6:     private IConfiguration       _config;
   7:     private ApplicationLifetime  _applicationLifetime;
   8:  
   9:     public WebHost(IServiceCollection appServices, IServiceProvider hostingServiceProvider, WebHostOptions options, IConfiguration config)
  10:     {
  11:         _appServices                 = appServices;
  12:         _hostingServiceProvider      = hostingServiceProvider;
  13:         _options                     = options;
  14:         _config                      = config;
  15:         _applicationLifetime         = new ApplicationLifetime();
  16:         appServices.AddSingleton<IApplicationLifetime>(_applicationLifetime);
  17:     }
  18:     …
  19: }
  20:  

我们接下来看WebHost除Start方法之外的其他成员的定义。只读属性Services返回一个ServiceProvider对象,我们将在完成所有服务注册工作之后利用ServiceCollection对象创建这个对象,所以只要实现具有相关的服务注册,我们就能够利用它得到对应的服务对象。只读属性ServerFeatures返回服务器的特性集合,而服务器本身则直接利用上述这个ServiceProvider获得。当MyWebHost对象因Dispose方法的调用而被回收之后,我们会对ServiceProvider实施回收 工作。在实施回收的前后,我们利用ApplicationLifetime发送相应的信号。

   1: public class WebHost : IWebHost
   2: {    
   3:     private ApplicationLifetime _applicationLifetime;
   4:     public IServiceProvider Services { get; private set; }
   5:     public IFeatureCollection ServerFeatures
   6:     {
   7:         get { return this.Services.GetRequiredService<IServer>()?.Features; }
   8:     }
   9:     public void Dispose()
  10:     {
  11:         _applicationLifetime.StopApplication();
  12:         (this.Services as IDisposable)?.Dispose();
  13:         _applicationLifetime.NotifyStopped();
  14:     }
  15: }
  16:  

真正开启WebHost的实现体现在如下所示的代码片段中。我们直接利用WebHostBuilder提供ServiceProvider获取一个Startup对象,并调用其ConfigureServices方法完成服务的注册,作为参数的ServiceCollection对象也是由WebHostBuilder提供的。当所有的服务注册工作完成之后,我们利用最新的ServiceCollection对象创建一个ServiceProvider对象,并利用此对象对Services属性进行赋值。在后续管道构建过程,以及管道在处理请求过程中所使用的服务均是从这个ServiceProvider中提取的。

   1: public class WebHost : IWebHost
   2: {
   3:     private IServiceCollection   _appServices;
   4:     private IServiceProvider     _hostingServiceProvider;
   5:     private WebHostOptions       _options;
   6:     private IConfiguration       _config;
   7:     private ApplicationLifetime  _applicationLifetime;
   8:  
   9:     public void Start()
  10:     {
  11:         //注册服务
  12:         IStartup startup = _hostingServiceProvider.GetRequiredService<IStartup>();
  13:         this.Services = startup.ConfigureServices(_appServices);
  14:            
  15:         //注册中间件
  16:         Action<IApplicationBuilder> configure = startup.Configure;
  17:         configure = this.Services.GetServices<IStartupFilter>().Reverse().Aggregate(configure, (next, current) => current.Configure(next));
  18:         IApplicationBuilder appBuilder = this.Services.GetRequiredService<IApplicationBuilder>();
  19:         configure(appBuilder);
  20:  
  21:         //为服务器设置监听地址
  22:         IServer server = this.Services.GetRequiredService<IServer>();
  23:         IServerAddressesFeature addressesFeature = server.Features.Get<IServerAddressesFeature>();
  24:         if (null != addressesFeature && !addressesFeature.Addresses.Any())
  25:         {
  26:             string addresses = _config["urls"] ?? "http://localhost:5000";
  27:             foreach (string address in addresses.Split(';'))
  28:             {
  29:                 addressesFeature.Addresses.Add(address);
  30:             }
  31:         }
  32:  
  33:         //启动服务器
  34:         RequestDelegate application = appBuilder.Build();
  35:         ILogger logger = this.Services.GetRequiredService <ILogger<MyWebHost>>();
  36:         DiagnosticSource diagnosticSource = this.Services.GetRequiredService<DiagnosticSource>();
  37:         IHttpContextFactory httpContextFactory = this.Services.GetRequiredService<IHttpContextFactory>();
  38:         server.Start(new HostingApplication(application, logger, diagnosticSource, httpContextFactory));
  39:  
  40:         //对外发送通知
  41:         _applicationLifetime.NotifyStarted();
  42:     }
  43: }
  44:  

当服务注册结束并成功创建出ServiceProvider之后,接下来的工作就是注册中间件了。通过上面的介绍我们知道,中间件的注册既可以利用Startup来完成,也可以利用注册的StartupFilter来实现,为此我们利用最新构建的ServiceProvider获取所有注册的StartupFilter,并结合之前提取的Startup对象创建了一个用于注册中间的委托链(最终体现为一个Action<IApplicationBuilder>对象)。我们最终执行这个委托链完成了对所有中间件的注册,执行过程中作为参数的ApplicationBuilder对象同样是通过ServiceProvider提取出来的。

再此之后,我们利用ServiceProvider提取出注册在WebHostBuiler上的服务器。如果服务器的监听地址尚未指定,我们在开启服务器之前必须指定。通过前面对服务器的介绍,我们知道监听地址保存在服务器的一个名为ServerAddressesFeature的特性中,而用户设置的监听地址则保存在配置中,对应的Key为“urls”,所以我们将从配置中提取的地址列表添加到ServerAddressesFeature特性中。如果监听地址不曾配置,我们会为之指定一个默认的地址,即“http://localhost:5000”。

一切就绪的服务器通过调用Start方法开启,该方法接收一个HttpApplication对象作为参数。通过前面的介绍我们知道这个HttpApplication对象可以视为对所有注册中间件和应用的封装,服务器将接收到的请求传递给它作后续处理。我们默认创建的HttpApplication是一个HostingApplication对象,而构建过程中需要提供四个对象,它们分别是代表中间件链表的RequestDelegate对象,用于日志记录和诊断的Logger和DiagnosticSource,以及用来创建HTTP上下文的HttpContextFactory,除了第一个通过调用ApplicationBuilder的Build方法创建之外,其余的都是通过ServiceProvider提取的。在服务器被成功开启之后,我们利用ApplicationLifetime对外发送应用启动的通知。

二、WebHostBuilder

顾名思义,WebHostBuilder就是WebHost的创建者,所谓的WebHostBuilder是对所有实现了IWebHostBuilder接口的类型以及对应对象的统称。如下面的代码片段所示,IWebHostBuilder接口除了用来创建WebHost的核心方法Build之外,还具有其他一些额外的方法。

   1: public interface IWebHostBuilder
   2: {
   3:     IWebHost Build();
   4:     IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices); 
   5:     IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory);
   6:     IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging);
   7:     string GetSetting(string key);
   8:     IWebHostBuilder UseSetting(string key, string value);
   9: }

ConfigureServices方法让我们可以直接将我们所需的服务注册到WebHostBuilder上面。ASP.NET Core具有两种注册服务的途径,一种是将服务注册实现在启动类的ConfigureServices方法中,另一种服务注册的方式就是调用这个方法。对于前者,服务实际上是在开启WebHost的时候调用Startup对象的ConfigureServices进行注册的;至于后者,注册的服务将直接提供给创建的WebHost。UseLoggerFactory 和ConfigureLogging方法与日志记录有关,前者帮助我们设置一个默认的LoggerFactory,后者则对LoggerFactory进行相关设置,最重要的设置就是添加相应的LoggerProvider。GetSetting和UseSetting以键值对的形式获取和设置一些配置。

WebHost的创建

ASP.NET Core定义了一个名为WebHostBuilder的类型作为对IWebHostBuilder接口的默认实现,我们同样采用定义模拟类型的形式来说明WebHostBuilder创建WebHost的实现原理。我们将这个模拟类型命名为,如下的代码片段展示了除Build方法之外的所有成员的定义。

   1: public class WebHostBuilder : IWebHostBuilder
   2: {
   3:     private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>();
   4:     private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>();
   5:     private ILoggerFactory _loggerFactory = new LoggerFactory();
   6:     private IConfiguration _config = new ConfigurationBuilder().AddEnvironmentVariables("ASPNETCORE_").Build();
   7:  
   8:     public IWebHostBuilder ConfigureLogging(Action<ILoggerFactory> configureLogging)
   9:     {
  10:         _configureLoggingDelegates.Add(configureLogging);
  11:         return this;
  12:     }
  13:  
  14:     public IWebHostBuilder ConfigureServices(Action<IServiceCollection> configureServices)
  15:     {
  16:         _configureServicesDelegates.Add(configureServices);
  17:         return this;
  18:     }
  19:  
  20:     public string GetSetting(string key)
  21:     {
  22:         return _config[key];
  23:     }
  24:  
  25:     public IWebHostBuilder UseLoggerFactory(ILoggerFactory loggerFactory)
  26:     {
  27:         _loggerFactory = loggerFactory;
  28:         return this;
  29:     }
  30:  
  31:     public IWebHostBuilder UseSetting(string key, string value)
  32:     {
  33:         _config[key] = value;
  34:         return this;
  35:     }
  36:     ...
  37: }
  38:  

如上面的代码片段所示,我们创建了一个Configuration类型的字段(_config)来体现应用默认使用的配置,它默认采用环境变量(用于过滤环境变量的前缀为“ASPNETCORE_”)作为配置源,GetSetting和UseSetting方法操作的均为这个对象。另一个字段_loggerFactory表示默认使用的LoggerFactory,UseLoggerFactory方法指定的LoggerFactory用来对这个字段进行赋值。ConfigureLogging和ConfigureServices方法具有类似的定义,调用它们提供的委托对象都保存在一个集合之中,以待后用。

我们实现WebHostBuilder的核心方法Build来创建一个WebHost对象。通过上面的定义我们知道一个WebHostBuilder能够最终运行起来需要从ServiceProvider提供很多必需的服务,而这些服务最初都必需通过WebHostBuilder来注册,所以Build方法除了调用构造函数创建并返回一个WebHost对象之外,余下的工作就是注册这些必需的服务。我们可以简单列一列那些服务是必需的,如下所示的是一个不完全列表。

  • 用于注册服务和中间件的Startup对象。
  • 用来创建Logger的LoggerFactory对象
  • 构建中间件链表的ApplicationBuilder对象
  • 创建HTTP上下文的HttpContextFactory对象
  • 用户实现诊断功能的DiagnosticSource对象
  • 用来保存承载环境的HostingEnvironment对象

如下所示的定义在WebHostBuilder中的Build方法的定义。在这个方法中,我们按照上述这些系统服务以及用户服务(通过调用ConfigureServices方法注册的服务)的注册之后,创建并返回了一个WebHost对象。

   1: public class WebHostBuilder : IWebHostBuilder
   2: {
   3:     private List<Action<ILoggerFactory>> _configureLoggingDelegates = new List<Action<ILoggerFactory>>();
   4:     private List<Action<IServiceCollection>> _configureServicesDelegates = new List<Action<IServiceCollection>>();
   5:     private ILoggerFactory _loggerFactory = new LoggerFactory();
   6:     private IConfiguration _config = new ConfigurationBuilder().AddInMemoryCollection().Build();
   7:  
   8:     public IWebHost Build()
   9:     {
  10:         //根据配置创建WebHostOptions
  11:         WebHostOptions options = new WebHostOptions(_config);
  12:  
  13:         //注册服务IStartup
  14:         IServiceCollection services = new ServiceCollection();
  15:         if (!string.IsNullOrEmpty(options.StartupAssembly))
  16:         {
  17:             Type startupType = StartupLoader.FindStartupType(options.StartupAssembly, options.Environment);
  18:             if (typeof(IStartup).GetTypeInfo().IsAssignableFrom(startupType))
  19:             {
  20:                 services.AddSingleton(typeof(IStartup), startupType);
  21:             }
  22:             else
  23:             {
  24:                 services.AddSingleton<IStartup>(_ => new ConventionBasedStartup(StartupLoader.LoadMethods(_, startupType, options.Environment)));
  25:             }
  26:         }
  27:  
  28:         //注册ILoggerFactory
  29:         foreach (var configureLogging in _configureLoggingDelegates)
  30:         {
  31:             configureLogging(_loggerFactory);
  32:         }
  33:         services.AddSingleton<ILoggerFactory>(_loggerFactory);
  34:  
  35:         //注册服务IApplicationBuilder,DiagnosticSource和IHttpContextFactory
  36:         services
  37:             .AddSingleton<IApplicationBuilder>(_ => new ApplicationBuilder(_))
  38:             .AddSingleton<DiagnosticSource>(new DiagnosticListener("Microsoft.AspNetCore"))
  39:             .AddSingleton<IHttpContextFactory, HttpContextFactory>()
  40:             .AddOptions()
  41:             .AddLogging()
  42:             .AddSingleton<IHostingEnvironment, HostingEnvironment>()
  43:             .AddSingleton<ObjectPoolProvider, DefaultObjectPoolProvider>();          
  44:                       
  45:         //注册用户调用ConfigureServices方法设置的服务
  46:         foreach (var configureServices in _configureServicesDelegates)
  47:         {
  48:             configureServices(services);
  49:         }
  50:  
  51:         //创建MyWebHost
  52:         return new WebHost(services, services.BuildServiceProvider(), options, _config);
  53:     }  
  54: }
  55:  

虽然上面提供的WebHost和WebHostBuilder仅仅是WebHost和WebHostBuilder的模拟类。为了让读更加易于理解,我们刻意剔除了很多细节的东西,但是两者从实现原理角度来讲是完全一致的。不仅如此,我们自定义的这两个类型甚至可以执行运行的。

几个常用的扩展方法

WebHostBuilder在内部使用了配置,环境变量是默认采用的配置源,它的两个方法GetSetting和UseSetting以键值对的形式实现对配置项的获取和设置。除了UseSettings方法之外,我们还可以调用WebHostBuilder如下这个扩展方法UseConfiguration来进行配置项的设置,这个方法会将保存在指定Configuration中的配置原封不动地拷贝过来,它最终调用的依旧是UseSettings方法。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder UseConfiguration(this IWebHostBuilder hostBuilder, IConfiguration configuration);
   4: }

WebHostBuilder在创建WebHost的时候需要提供一个WebHostOptions对象,该对象最初是根据当前配置创建的。为了方便设置针对WebHostOptions的配置项,ASP.NET Core为我们定义了如下一系列的扩展方法,这些方法最终调用的也是这个UseSettings方法。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder CaptureStartupErrors(this IWebHostBuilder hostBuilder, bool captureStartupErrors);
   4:     public static IWebHostBuilder UseContentRoot(this IWebHostBuilder hostBuilder, string contentRoot);
   5:     public static IWebHostBuilder UseEnvironment(this IWebHostBuilder hostBuilder, string environment);
   6:     public static IWebHostBuilder UseStartup(this IWebHostBuilder hostBuilder, string startupAssemblyName);
   7:     public static IWebHostBuilder UseWebRoot(this IWebHostBuilder hostBuilder, string webRoot);
   8:     public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
   9: }
  10:  

虽然服务器是必需的,但是WebHostBuilder并没有专门定义一个用于注册服务的方法,这是因为服务器也是作为一项基本的服务进行注册的。但是我们可以调用如下一个扩展方法UseServer实现针对服务器的注册,至于另一个扩展方法UseUrls,我们可以调用它来为注册的服务器设置监听地址。

   1: public static class HostingAbstractionsWebHostBuilderExtensions
   2: {
   3:     public static IWebHostBuilder UseServer(this IWebHostBuilder hostBuilder, IServer server);
   4:     public static IWebHostBuilder UseUrls(this IWebHostBuilder hostBuilder, params string[] urls);
   5: }
作者:蒋金楠 
微信公众账号:大内老A

管道是如何随着WebHost的开启被构建出来的?的更多相关文章

  1. 学习ASP.NET Core, 怎能不了解请求处理管道[6]: 管道是如何随着WebHost的开启被构建出来的?

    注册的服务器和中间件共同构成了ASP.NET Core用于处理请求的管道, 这样一个管道是在我们启动作为应用宿主的WebHost时构建出来的.要深刻了解这个管道是如何被构建出来的,我们就必须对WebH ...

  2. ASP.NET Core框架揭秘(持续更新中…)

    之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...

  3. ASP.NET Core框架揭秘(持续更新中…)

    之前写了一系列关于.NET Core/ASP.NET Core的文章,但是大都是针对RC版本.到了正式的RTM,很多地方都发生了改变,所以我会将之前发布的文章针对正式版本的.NET Core 1.0进 ...

  4. 通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的?

    在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下来我们需要了解的是这样一个管道是如何被构建起来的.总的来说,管道由一个服务器和一个HttpApplication构成 ...

  5. Core管道中的处理流程3

    通过重建Hosting系统理解HTTP请求在ASP.NET Core管道中的处理流程[下]:管道是如何构建起来的? 在<中篇>中,我们对管道的构成以及它对请求的处理流程进行了详细介绍,接下 ...

  6. 爬虫框架Scrapy 之(四) --- scrapy运行原理(管道)

    解析后返回可迭代对象 这个对象返回以后就会被爬虫重新接收,然后进行迭代 通过scrapy crawl budejie -o xx.josn/xx.xml/xx.csv 将迭代数据输出到json.xml ...

  7. 引入 Tinker 之后如何在 Debug 模式下开启 Instant Run

    在<Tinker + Bugly + Jenkins 爬坑之路>一文中讲了在接入 Tinker 之后,Jenkins 中的一些坑,由此,热修复算告一段落,但是,在直接 Run 模式运行时, ...

  8. ASP.NET Core中如影随形的”依赖注入”[上]: 从两个不同的ServiceProvider说起

    我们一致在说 ASP.NET Core广泛地使用到了依赖注入,通过前面两个系列的介绍,相信读者朋友已经体会到了这一点.由于前面两章已经涵盖了依赖注入在管道构建过程中以及管道在处理请求过程的应用,但是内 ...

  9. .NET Core多平台开发体验[1]: Windows

    微软在千禧年推出 .NET战略,并在两年后推出第一个版本的.NET Framework和IDE(Visual Studio.NET 2002,后来改名为Visual Studio),如果你是一个资深的 ...

随机推荐

  1. 64位 Windows 用了 32位编译平台 编译不过 MySQL API

    发生在一周前的事情了,当时想感受下 MySQL C API ,就写了几个小例子.虽然是在 Windows(我的工作电脑是 64位 Windows) 上面,但是不想用 VS ,只想用文本软件写好代码后用 ...

  2. Stop logging "internal dummy connection" in Apache

    Apache 2.x keeps child processes alive by creating internal connections which appear in the log file ...

  3. codevs 3314 魔法森林

    传送门 3314 魔法森林  时间限制: 3 s  空间限制: 256000 KB  题目等级 : 大师 Master 题解   题目描述 Description 为了得到书法大家的真传,小E同学下定 ...

  4. [转载]IOCP模型的总结

    原文:IOCP模型的总结 IOCP(I/O Completion Port,I/O完成端口)是性能最好的一种I/O模型.它是应用程序使用线程池处理异步I/O请求的一种机制.在处理多个并发的异步I/O请 ...

  5. TCP点对点穿透探索--失败

    TCP点对点穿透探索 点对点穿透是穿透什么 点对点穿透,需要实现的是对NAT的穿透.想实现NAT的穿透,当然要先了解NAT到底是什么,以及NAT是用来干什么的.NAT全称Network Address ...

  6. zk 10之:Curator之三:服务的注册及发现

    Service Discovery 我们通常在调用服务的时候,需要知道服务的地址,端口,或者其他一些信息,通常情况下,我们是把他们写到程序里面,但是随着服务越来越多,维护起来也越来越费劲,更重要的是, ...

  7. android TextView selector点击样式改变

    1.selector 从单词的意思来说:选择器,就是对你的目标的控制.selector主要是用在ListView的item单击样式和TextView和Button的点击样式. 2.主要属性介绍: an ...

  8. AngularJs(Part 4)--Modules depending on other Modules

    Angular does an excellent job of managing object dependencies. it can even take care of module depen ...

  9. 廖雪峰的java教程

    F:\教程\0-免费下载-廖雪峰 公司电脑地址: G:\学习中\廖雪峰的java教程 廖雪峰java课程地址: https://www.feiyangedu.com/category/JavaSE 0 ...

  10. Linux系统下使用split命令分割大文件 (转载)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://snailwarrior.blog.51cto.com/680306/140531 ...