源码解析.Net中Host主机的构建过程
前言
本篇文章着重讲一下在.Net中Host主机的构建过程,依旧延续之前文章的思路,着重讲解其源码,如果有不知道有哪些用法的同学可以点击这里,废话不多说,咱们直接进入正题
Host构建过程
下图是我自己整理的Host构建过程以及里面包含的知识点我都以链接的形式放上来,大家可以看下图,大概了解下过程(由于知识点过多,所以只能分上下两张图了):
图中标识的相关知识点连接如下(ps:与编号对应):
- 1 环境变量 点击这里
- 2 命令行参数 点击这里
- 3 默认配置 点击这里
- 4 用户机密数据 点击这里
- 5 默认logging 点击这里
- 6 使用承载启动程序集 点击这里
- 7 自定义配置 点击这里
- 8 依赖注入 点击这里
- 9 应用启动后台任务 点击这里
- 10 中间件构建 点击这里
- 11 线程spinwait 点击这里
以上就是笔者在源码阅读阶段,其总结的自我感觉重要的知识点在微软文档中的对应位置。
源码解析
这部分笔者根据上图中的四大块分别进行源码解析,可能篇幅比较长,其主要是对源代码增加了自己理解的注释,所以读者在阅读的过程中,要多注意源码中的注释(ps:展示出的代码不是全部代码,而只是重要代码哦,每个小节总结的点都是按照代码顺序解释)
初始化默认配置ConfigDefaultBuilder
public static IHostBuilder ConfigureDefaults(this IHostBuilder builder, string[] args)
{
//设置程序运行路径
builder.UseContentRoot(Directory.GetCurrentDirectory());
builder.ConfigureHostConfiguration(config =>
{
//添加获取环境变量的前缀
config.AddEnvironmentVariables(prefix: "DOTNET_");
//添加命令行参数
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
});
builder.ConfigureAppConfiguration((hostingContext, config) =>
{
//宿主机环境信息
IHostEnvironment env = hostingContext.HostingEnvironment;
//是否在文件改变时重新加载,默认是True
bool reloadOnChange = GetReloadConfigOnChangeValue(hostingContext);
//默认添加的配置文件(格外添加以环境变量为名称的文件)
config.AddJsonFile("appsettings.json", optional: true, reloadOnChange: reloadOnChange)
.AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true, reloadOnChange: reloadOnChange);
//如果是开发环境,并且应用程序的应用名称不是空字符串,则加载用户机密,默认true(主要是为了不同开发人员的配置不同)
if (env.IsDevelopment() && env.ApplicationName is { Length: > 0 })
{
var appAssembly = Assembly.Load(new AssemblyName(env.ApplicationName));
if (appAssembly is not null)
{
config.AddUserSecrets(appAssembly, optional: true, reloadOnChange: reloadOnChange);
}
}
//这里再次执行是为了让环境变量和命令行参数的配置优先级提高(后加载的key/value获取时优先级最高)
//添加其他环境变量
config.AddEnvironmentVariables();
//添加命令行参数
if (args is { Length: > 0 })
{
config.AddCommandLine(args);
}
})
.ConfigureLogging((hostingContext, logging) =>
{
//判断操作系统
bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows);
if (isWindows)
{
//添加过滤规则,捕获warning日志
logging.AddFilter<EventLogLoggerProvider>(level => level >= LogLevel.Warning);
}
//获取Logging配置
logging.AddConfiguration(hostingContext.Configuration.GetSection("Logging"));
//添加输出到控制台
logging.AddConsole();
//添加将debug日志输出到控制台
logging.AddDebug();
//添加写入的事件源
logging.AddEventSourceLogger();
if (isWindows)
{
//添加事件日志
logging.AddEventLog();
}
//添加链路追踪选项
logging.Configure(options =>
{
options.ActivityTrackingOptions =
ActivityTrackingOptions.SpanId |
ActivityTrackingOptions.TraceId |
ActivityTrackingOptions.ParentId;
});
})
.UseDefaultServiceProvider((context, options) =>
{
bool isDevelopment = context.HostingEnvironment.IsDevelopment();
//依赖注入相关校验
options.ValidateScopes = isDevelopment;
options.ValidateOnBuild = isDevelopment;
});
return builder;
}
源码总结:
- 设置程序执行路径以及获取环境变量和加载命令行参数。
- 根据环境变量加载appsettings.json,加载用户机密数据(仅开发环境)。
- 接着又加载环境变量和命令行参数(这里为什么又加载了一次呢?是因为这它们执行的顺序是不一样的,而后加载的会覆盖前面加载的Key/Value,前面加载主要是确定当前运行的环境变量以及用户自定义的命令行参数,后面是为确保通过key获取value的时候能够获取到准确的值)。
- 接下来就主要是配置默认Log,如果是开发环境,依赖注入相关的配置默认开启(验证scope是否被用于singleton,验证是否在调用期间就创建所有服务至缓存)。
初始化主机启动配置ConfigureWebHostDefaults
public GenericWebHostBuilder(IHostBuilder builder, WebHostBuilderOptions options)
{
_builder = builder;
var configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
//添加以ASPNETCORE_开头的环境变量(ps:判断当前环境是那个环境)
if (!options.SuppressEnvironmentConfiguration)
{
configBuilder.AddEnvironmentVariables(prefix: "ASPNETCORE_");
}
//这里主要加载环境变量
_config = configBuilder.Build();
_builder.ConfigureHostConfiguration(config =>
{
//将上面的配置加载进来
config.AddConfiguration(_config);
//通过配置和特性加载额外的Config(或者不加载配置),通过继承IHostingStartup无侵入性加载。
ExecuteHostingStartups();
});
//将上面Startup中Config的配置放到Build阶段加载
_builder.ConfigureAppConfiguration((context, configurationBuilder) =>
{
if (_hostingStartupWebHostBuilder != null)
{
var webhostContext = GetWebHostBuilderContext(context);
_hostingStartupWebHostBuilder.ConfigureAppConfiguration(webhostContext, configurationBuilder);
}
});
//增加注入的服务
_builder.ConfigureServices((context, services) =>
{
var webhostContext = GetWebHostBuilderContext(context);
var webHostOptions = (WebHostOptions)context.Properties[typeof(WebHostOptions)];
//注入一些其他服务
services.AddSingleton(webhostContext.HostingEnvironment);
services.AddSingleton((AspNetCore.Hosting.IHostingEnvironment)webhostContext.HostingEnvironment);
services.AddSingleton<IApplicationLifetime, GenericWebHostApplicationLifetime>();
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.WebHostOptions = webHostOptions;
options.HostingStartupExceptions = _hostingStartupErrors;
});
services.TryAddSingleton(sp => new DiagnosticListener("Microsoft.AspNetCore"));
services.TryAddSingleton<DiagnosticSource>(sp => sp.GetRequiredService<DiagnosticListener>());
services.TryAddSingleton(sp => new ActivitySource("Microsoft.AspNetCore"));
services.TryAddSingleton<IHttpContextFactory, DefaultHttpContextFactory>();
services.TryAddScoped<IMiddlewareFactory, MiddlewareFactory>();
services.TryAddSingleton<IApplicationBuilderFactory, ApplicationBuilderFactory>();
_hostingStartupWebHostBuilder?.ConfigureServices(webhostContext, services);
//可以通过配置的方式查找程序集加载StartUp,但是默认只会加载最后一个StartUp
if (!string.IsNullOrEmpty(webHostOptions.StartupAssembly))
{
try
{
var startupType = StartupLoader.FindStartupType(webHostOptions.StartupAssembly, webhostContext.HostingEnvironment.EnvironmentName);
UseStartup(startupType, context, services);
}
catch (Exception ex) when (webHostOptions.CaptureStartupErrors)
{
var capture = ExceptionDispatchInfo.Capture(ex);
services.Configure<GenericWebHostServiceOptions>(options =>
{
options.ConfigureApplication = app =>
{
capture.Throw();
};
});
}
}
});
}
internal static void ConfigureWebDefaults(IWebHostBuilder builder)
{
//提供.netCore 静态Web资产(ps:说实话这里不知道有什么用)
builder.ConfigureAppConfiguration((ctx, cb) =>
{
if (ctx.HostingEnvironment.IsDevelopment())
{
StaticWebAssetsLoader.UseStaticWebAssets(ctx.HostingEnvironment, ctx.Configuration);
}
});
//使用 Kestrel 配置反向代理
builder.UseKestrel((builderContext, options) =>
{
options.Configure(builderContext.Configuration.GetSection("Kestrel"), reloadOnChange: true);
})
.ConfigureServices((hostingContext, services) =>
{
//配置启动的Url
services.PostConfigure<HostFilteringOptions>(options =>
{
if (options.AllowedHosts == null || options.AllowedHosts.Count == 0)
{
var hosts = hostingContext.Configuration["AllowedHosts"]?.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
options.AllowedHosts = (hosts?.Length > 0 ? hosts : new[] { "*" });
}
});
services.AddSingleton<IOptionsChangeTokenSource<HostFilteringOptions>>(
new ConfigurationChangeTokenSource<HostFilteringOptions>(hostingContext.Configuration));
services.AddTransient<IStartupFilter, HostFilteringStartupFilter>();
//用来获取客户端的IP地址
if (string.Equals("true", hostingContext.Configuration["ForwardedHeaders_Enabled"], StringComparison.OrdinalIgnoreCase))
{
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
services.AddTransient<IStartupFilter, ForwardedHeadersStartupFilter>();
}
//添加路由配置
services.AddRouting();
})
//默认使用IIS
.UseIIS()
//使用进程内的托管模式
.UseIISIntegration();
}
这部分内容可能会多点,源码总结:
- 添加Memory缓存,添加ASPNETCORE_开头的环境变量。
- 根据用户的配置,来加载额外的StartUp中Config配置,但是它的参数是IWebHostBuilder,这部分可以参考微软文档StartUp的部分。
- 如果有存在这些配置的话,则统一放到Build阶段加载。
- 加载web主机需要的注入的服务,以及判断是否需要通过程序集来加载StartUp,并且添加一个程序启动时调用的服务(这里主要是构建HttpContext执行管道)。
- 引用Kestrel,继承路由和IIS,并且默认使用进程内托管。
- 加载用户自定义的其他配置,例如默认的调用UseStartup方法。
根据指定配置开始初始化主机Build
public class HostBuilder : IHostBuilder
{
private List<Action<IConfigurationBuilder>> _configureHostConfigActions = new List<Action<IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IConfigurationBuilder>> _configureAppConfigActions = new List<Action<HostBuilderContext, IConfigurationBuilder>>();
private List<Action<HostBuilderContext, IServiceCollection>> _configureServicesActions = new List<Action<HostBuilderContext, IServiceCollection>>();
private List<IConfigureContainerAdapter> _configureContainerActions = new List<IConfigureContainerAdapter>();
private IServiceFactoryAdapter _serviceProviderFactory = new ServiceFactoryAdapter<IServiceCollection>(new DefaultServiceProviderFactory());
private bool _hostBuilt;
private IConfiguration _hostConfiguration;
private IConfiguration _appConfiguration;
private HostBuilderContext _hostBuilderContext;
private HostingEnvironment _hostingEnvironment;
private IServiceProvider _appServices;
private PhysicalFileProvider _defaultProvider;
public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();
public IHostBuilder ConfigureHostConfiguration(Action<IConfigurationBuilder> configureDelegate)
{
_configureHostConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureAppConfiguration(Action<HostBuilderContext, IConfigurationBuilder> configureDelegate)
{
_configureAppConfigActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder ConfigureServices(Action<HostBuilderContext, IServiceCollection> configureDelegate)
{
_configureServicesActions.Add(configureDelegate ?? throw new ArgumentNullException(nameof(configureDelegate)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(IServiceProviderFactory<TContainerBuilder> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder UseServiceProviderFactory<TContainerBuilder>(Func<HostBuilderContext, IServiceProviderFactory<TContainerBuilder>> factory)
{
_serviceProviderFactory = new ServiceFactoryAdapter<TContainerBuilder>(() => _hostBuilderContext, factory ?? throw new ArgumentNullException(nameof(factory)));
return this;
}
public IHostBuilder ConfigureContainer<TContainerBuilder>(Action<HostBuilderContext, TContainerBuilder> configureDelegate)
{
_configureContainerActions.Add(new ConfigureContainerAdapter<TContainerBuilder>(configureDelegate
?? throw new ArgumentNullException(nameof(configureDelegate))));
return this;
}
public IHost Build()
{
//只能执行一次这个方法
if (_hostBuilt)
{
throw new InvalidOperationException(SR.BuildCalled);
}
_hostBuilt = true;
using var diagnosticListener = new DiagnosticListener("Microsoft.Extensions.Hosting");
const string hostBuildingEventName = "HostBuilding";
const string hostBuiltEventName = "HostBuilt";
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuildingEventName))
{
Write(diagnosticListener, hostBuildingEventName, this);
}
//执行Host配置(应用程序执行路径,加载_dotnet环境变量,获取命令行参数,加载预配置)
BuildHostConfiguration();
//设置主机环境变量
CreateHostingEnvironment();
//构建HostBuilderContext实例
CreateHostBuilderContext();
//构建程序配置(加载appsetting.json,环境变量,命令行参数等)
BuildAppConfiguration();
//构造容器,注入服务
CreateServiceProvider();
var host = _appServices.GetRequiredService<IHost>();
if (diagnosticListener.IsEnabled() && diagnosticListener.IsEnabled(hostBuiltEventName))
{
Write(diagnosticListener, hostBuiltEventName, host);
}
return host;
}
private static void Write<T>(
DiagnosticSource diagnosticSource,
string name,
T value)
{
diagnosticSource.Write(name, value);
}
private void BuildHostConfiguration()
{
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.AddInMemoryCollection();
foreach (Action<IConfigurationBuilder> buildAction in _configureHostConfigActions)
{
buildAction(configBuilder);
}
//本质是执行ConfigureProvider中的Load方法,加载对应配置
_hostConfiguration = configBuilder.Build();
}
private void CreateHostingEnvironment()
{
//设置环境变量
_hostingEnvironment = new HostingEnvironment()
{
ApplicationName = _hostConfiguration[HostDefaults.ApplicationKey],
EnvironmentName = _hostConfiguration[HostDefaults.EnvironmentKey] ?? Environments.Production,
ContentRootPath = ResolveContentRootPath(_hostConfiguration[HostDefaults.ContentRootKey], AppContext.BaseDirectory),
};
if (string.IsNullOrEmpty(_hostingEnvironment.ApplicationName))
{
_hostingEnvironment.ApplicationName = Assembly.GetEntryAssembly()?.GetName().Name;
}
//程序运行路径
_hostingEnvironment.ContentRootFileProvider = _defaultProvider = new PhysicalFileProvider(_hostingEnvironment.ContentRootPath);
}
private void CreateHostBuilderContext()
{
_hostBuilderContext = new HostBuilderContext(Properties)
{
HostingEnvironment = _hostingEnvironment,
Configuration = _hostConfiguration
};
}
private void BuildAppConfiguration()
{
//对于已经加载过的配置不再重新加载
IConfigurationBuilder configBuilder = new ConfigurationBuilder()
.SetBasePath(_hostingEnvironment.ContentRootPath)
.AddConfiguration(_hostConfiguration, shouldDisposeConfiguration: true);
//注意这里是AppConfig
foreach (Action<HostBuilderContext, IConfigurationBuilder> buildAction in _configureAppConfigActions)
{
buildAction(_hostBuilderContext, configBuilder);
}
_appConfiguration = configBuilder.Build();
//将新的配置赋值给config
_hostBuilderContext.Configuration = _appConfiguration;
}
private void CreateServiceProvider()
{
var services = new ServiceCollection();
services.AddSingleton<IHostingEnvironment>(_hostingEnvironment);
services.AddSingleton<IHostEnvironment>(_hostingEnvironment);
services.AddSingleton(_hostBuilderContext);
services.AddSingleton(_ => _appConfiguration);
services.AddSingleton<IApplicationLifetime>(s => (IApplicationLifetime)s.GetService<IHostApplicationLifetime>());
services.AddSingleton<IHostApplicationLifetime, ApplicationLifetime>();
services.AddSingleton<IHostLifetime, ConsoleLifetime>();
services.AddSingleton<IHost>(_ =>
{
return new Internal.Host(_appServices,
_hostingEnvironment,
_defaultProvider,
_appServices.GetRequiredService<IHostApplicationLifetime>(),
_appServices.GetRequiredService<ILogger<Internal.Host>>(),
_appServices.GetRequiredService<IHostLifetime>(),
_appServices.GetRequiredService<IOptions<HostOptions>>());
});
services.AddOptions().Configure<HostOptions>(options => { options.Initialize(_hostConfiguration); });
services.AddLogging();
//主要加载额外注入的服务
foreach (Action<HostBuilderContext, IServiceCollection> configureServicesAction in _configureServicesActions)
{
configureServicesAction(_hostBuilderContext, services);
}
//这里返回object,主要是为了保留扩展,让用户自定义的依赖注入框架能够运行。
object containerBuilder = _serviceProviderFactory.CreateBuilder(services);
foreach (IConfigureContainerAdapter containerAction in _configureContainerActions)
{
containerAction.ConfigureContainer(_hostBuilderContext, containerBuilder);
}
_appServices = _serviceProviderFactory.CreateServiceProvider(containerBuilder);
if (_appServices == null)
{
throw new InvalidOperationException(SR.NullIServiceProvider);
}
//可能是想先把IConfiguration加载到内存中
_ = _appServices.GetService<IConfiguration>();
}
}
在上面的两个小单元可以看出,所有的构造是以委托的方式,最后都加载到HostBuilder内部的委托集合中,源码总结:
- 加载环境变量和命令行参数。
- 构建HostingEnvironment对象。
- 构建HostBuilderContext对象,里面包含配置和执行环境。
- 加载appsettings.json,环境变量和命令行参数等。
- 注入一些必须的服务,加载日志配置,WebHost里面注入的服务,加载StartUp里面ConfigService里面的服务,以及其他的一些注入的服务,构建容器(最后那里获取IConfiguration猜测可能是想先缓存到根容器吧)。
运行主机Run
public async Task StartAsync(CancellationToken cancellationToken = default)
{
_logger.Starting();
using var combinedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, _applicationLifetime.ApplicationStopping);
CancellationToken combinedCancellationToken = combinedCancellationTokenSource.Token;
//应用程序启动和关闭事件
await _hostLifetime.WaitForStartAsync(combinedCancellationToken).ConfigureAwait(false);
combinedCancellationToken.ThrowIfCancellationRequested();
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
//主要是执行一些后台任务,可以重写启动和关闭时要做的操作
foreach (IHostedService hostedService in _hostedServices)
{
//立即执行的任务,例如构建管道就是在这里
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
//执行一些后台任务
if (hostedService is BackgroundService backgroundService)
{
_ = TryExecuteBackgroundServiceAsync(backgroundService);
}
}
//通知应用程序启动成功
_applicationLifetime.NotifyStarted();
//程序启动
_logger.Started();
}
源码总结:
- 监听程序的启动关闭事件。
- 开始执行Hosted服务或者加载后台执行的任务。
- 通过TaskCompletionSource来持续监听Token,hold住进程。
总结
通过模板来构建的.Net泛型主机,其实已经可以满足大部分的要求,并且微软保留大量扩展让用户来自定义,当然你也可以构建其他不同的主机类型(如:Web主机或者控制台程序启动项配置),想了解的可以点击这里。
以上就是笔者通过阅读源码来分析的程序执行流程,因为篇幅问题没有把所有代码都放出来,实在是太多了,所以只放了部分代码,主要是想给阅读源码的同学在阅读的时候找到思路,可能会有一点错误,还请评论指正。
源码解析.Net中Host主机的构建过程的更多相关文章
- 源码解析Android中View的measure量算过程
Android中的Veiw从内存中到呈现在UI界面上需要依次经历三个阶段:量算 -> 布局 -> 绘图,关于View的量算.布局.绘图的总体机制可参见博文< Android中View ...
- [源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer
[源码解析] PyTorch 分布式(11) ----- DistributedDataParallel 之 构建Reducer 目录 [源码解析] PyTorch 分布式(11) ----- Dis ...
- 源码解析.Net中IConfiguration配置的实现
前言 关于IConfituration的使用,我觉得大部分人都已经比较熟悉了,如果不熟悉的可以看这里.因为本篇不准备讲IConfiguration都是怎么使用的,但是在源码部分的解读,网上资源相对少一 ...
- 源码解析.Net中DependencyInjection的实现
前言 笔者的这篇文章和上篇文章思路一样,不注重依赖注入的使用方法,更加注重源码的实现,我尽量的表达清楚内容,让读者能够真正的学到东西.如果有不太清楚依赖注入是什么或怎么在.Net项目中使用的话,请点击 ...
- 源码解析.Net中Middleware的实现
前言 本篇继续之前的思路,不注重用法,如果还不知道有哪些用法的小伙伴,可以点击这里,微软文档说的很详细,在阅读本篇文章前,还是希望你对中间件有大致的了解,这样你读起来可能更加能够意会到意思.废话不多说 ...
- Spark 源码解析 : DAGScheduler中的DAG划分与提交
一.Spark 运行架构 Spark 运行架构如下图: 各个RDD之间存在着依赖关系,这些依赖关系形成有向无环图DAG,DAGScheduler对这些依赖关系形成的DAG,进行Stage划分,划分的规 ...
- QT源码解析(七)Qt创建窗体的过程,作者“ tingsking18 ”(真正的创建QPushButton是在show()方法中,show()方法又调用了setVisible方法)
前言:分析Qt的代码也有一段时间了,以前在进行QT源码解析的时候总是使用ue,一个函数名在QTDIR/src目录下反复的查找,然后分析函数之间的调用关系,效率实在是太低了,最近总结出一个更简便的方法, ...
- 源码解析C#中PriorityQueue(优先级队列)的实现
前言 前段时间看到有大佬对.net 6.0新出的PriorityQueue(优先级队列)数据结构做了解析,但是没有源码分析,所以本着探究源码的心态,看了看并分享出来.它不像普通队列先进先出(FIFO) ...
- multiprocessing 源码解析 更新中......
一.参考链接 1.源码包下载·链接: https://pypi.org/search/?q=multiprocessing+ 2.源码包 链接:https://pan.baidu.com/s/1j ...
随机推荐
- ETL数仓测试
前言 datalake架构 离线数据 ODS -> DW -> DM https://www.jianshu.com/p/72e395d8cb33 https://www.cnblogs. ...
- scrapy 使用crawlspider rule不起作用的解决方案
一直用的是通用spider,今天刚好想用下CrawlSpider来抓下数据.结果Debug了半天,一直没法进入详情页的解析逻辑.. 爬虫代码是这样的 # -*- coding: utf-8 -*- i ...
- 用传纸条讲 HTTPS
我和小宇早恋了,上课的时候老说话. 老师把我们的座位分得很远,我在第一排,她在最后一排,我们中间隔了很多人. 但我们还是想通过传纸条的方式交流. 我们中间的那些同学,虽然坏心思比较多,但好在可以保证将 ...
- [论文阅读] LCC-NLM(局部颜色校正, 非线性mask)
[论文阅读] LCC-NLM(局部颜色校正, 非线性mask) 文章: Local color correction using non-linear masking 1. 算法原理 如下图所示为, ...
- Linux连接工具final配置
Linux连接工具 putty .CRT.XShell 在terminal里面敲不太方便,所以需要一款连接工具 这是一款美观医用的网络服务管理软件 安装final shell Windows版下载地址 ...
- 算法竞赛中的常用JAVA API :HashSet 和 TreeSet(转载)
算法竞赛中的常用JAVA API :HashSet 和 TreeSet set set容器的特点是不包含重复元素,也就是说自动去重. HashSet HashSet基于哈希表实现,无序. add(E ...
- 一篇文章搞懂密码学基础及SSL/TLS协议
SSL协议是现代网络通信中重要的一环,它提供了传输层上的数据安全.为了方便大家的理解,本文将先从加密学的基础知识入手,然后展开对SSL协议原理.流程以及一些重要的特性的详解,最后会扩展介绍一下国密SS ...
- DVWA-全等级文件包含
DVWA简介 DVWA(Damn Vulnerable Web Application)是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用,旨在为安全专业人员测试自己的专业技能和工具提供合法 ...
- 蓝桥杯练习-各大OJ平台介绍
校赛准备的不够充分,简单题失分太遗憾, 有幸参加到省赛,这次先码一下练习平台,等学期结束忙完之后好好练习! 1.题库与网站资源题库-在线提交系统(Online Judge)简介 下面是几个比较大的 ...
- SQL 练习28
查询平均成绩大于等于 85 的所有学生的学号.姓名和平均成绩 SELECT Student.SId,Student.Sname,平均成绩 FROM Student , (SELECT sid,AVG( ...