200行代码,7个对象——让你了解ASP.NET Core框架的本质[3.x版]

2019年1月19日,微软技术(苏州)俱乐部成立,我受邀在成立大会上作了一个名为《ASP.NET Core框架揭秘》的分享。在此次分享中,我按照ASP.NET Core自身的运行原理和设计思想创建了一个 “迷你版” 的ASP.NET Core框架,并且利用这个 “极简” 的模拟框架阐述了ASP.NET Core框架最核心、最本质的东西。整个框架涉及到的核心代码不会超过200行,涉及到7个核心的对象。由于ASP.NET Core 3.X采用了不同的应用承载方式,所以我们将这个模拟框架升级到3.x版本。[本篇内容节选自即将出版的《ASP.NET Core 3框架解密》,感兴趣的朋友可以通过《“ASP.NET Core 3框架揭秘”读者群,欢迎加入》加入本书读者群,以便及时了解本书的动态。源代码从这里下载。]

目录
一、中间件委托链
     HttpContext
     中间件
     中间件管道的构建
二、服务器
     IServer
     针对服务器的适配
     HttpListenerServer
三、承载服务
     WebHostedService
     WebHostBuilder
     应用构建

一、中间件委托链

通过本篇文章,我将管道最核心的部分提取出来构建一个“迷你版”的ASP.NET Core框架。较之真正的ASP.NET Core框架,虽然重建的模拟框架要简单很多,但是它们采用完全一致的设计。为了能够在真实框架中找到对应物,在定义接口或者类型时会采用真实的名称,但是在API的定义上会做最大限度的简化。

HttpContext

一个HttpContext对象表示针对当前请求的上下文。要理解HttpContext上下文的本质,需要从请求处理管道的层面来讲。对于由一个服务器和多个中间件构成的管道来说,面向传输层的服务器负责请求的监听、接收和最终的响应,当它接收到客户端发送的请求后,需要将请求分发给后续中间件进行处理。对于某个中间件来说,完成自身的请求处理任务之后,在大部分情况下需要将请求分发给后续的中间件。请求在服务器与中间件之间,以及在中间件之间的分发是通过共享上下文的方式实现的。

如下图所示,当服务器接收到请求之后,会创建一个通过HttpContext表示的上下文对象,所有中间件都在这个上下文中完成针对请求的处理工作。那么一个HttpContext对象究竟会携带什么样的上下文信息?一个HTTP事务(Transaction)具有非常清晰的界定,如果从服务器的角度来说就是始于请求的接收,而终于响应的回复,所以请求和响应是两个基本的要素,也是HttpContext承载的最核心的上下文信息。

我们可以将请求和响应理解为一个Web应用的输入与输出,既然HttpContext上下文是针对请求和响应的封装,那么应用程序就可以利用这个上下文对象得到当前请求所有的输入信息,也可以利用它完成我们所需的所有输出工作。所以,我们为ASP.NET Core模拟框架定义了如下这个极简版本的HttpContext类型。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context)=> _context = context;Uri IHttpRequestFeature.Url=> _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers=> _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers=> _context.Response.Headers;Stream IHttpRequestFeature.Body=> _context.Request.InputStream;Stream IHttpResponseFeature.Body=> _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;}
}

如上面的代码片段所示,我们可以利用HttpRequest对象得到当前请求的地址、请求消息的报头集合和主体内容。利用HttpResponse对象,我们不仅可以设置响应的状态码,还可以添加任意的响应报头和写入任意的主体内容。

中间件

HttpContext对象承载了所有与当前请求相关的上下文信息,应用程序针对请求的响应也利用它来完成,所以可以利用一个Action<HttpContext>类型的委托对象来表示针对请求的处理,我们姑且将它称为请求处理器(Handler)。但Action<HttpContext>仅仅是请求处理器针对“同步”编程模式的表现形式,对于面向Task的异步编程模式,这个处理器应该表示成类型为Func<HttpContext,Task>的委托对象。

由于这个表示请求处理器的委托对象具有非常广泛的应用,所以我们为它专门定义了如下这个RequestDelegate委托类型,可以看出它就是对Func<HttpContext,Task>委托的表达。一个RequestDelegate对象表示的是请求处理器,那么中间件在模型中应如何表达?

public delegate Task RequestDelegate(HttpContext context);

作为请求处理管道核心组成部分的中间件可以表示成类型为Func<RequestDelegate, RequestDelegate>的委托对象。换句话说,中间件的输入与输出都是一个RequestDelegate对象。我们可以这样来理解:对于管道中的某个中间件(下图所示的第一个中间件)来说,后续中间件组成的管道体现为一个RequestDelegate对象,由于当前中间件在完成了自身的请求处理任务之后,往往需要将请求分发给后续中间件进行处理,所以它需要将后续中间件构成的RequestDelegate对象作为输入。

当代表当前中间件的委托对象执行之后,如果将它自己“纳入”这个管道,那么代表新管道的RequestDelegate对象就成为该委托对象执行后的输出结果,所以中间件自然就表示成输入和输出类型均为RequestDelegate的Func<RequestDelegate, RequestDelegate>对象。

中间件管道的构建

从事软件行业10多年来,笔者对架构设计越来越具有这样的认识:好的设计一定是“简单”的设计。所以在设计某个开发框架时笔者的目标是再简单点。上面介绍的请求处理管道的设计就具有“简单”的特质:Pipeline = Server + Middlewares。但是“再简单点”其实是可以的,我们可以将多个中间件组成一个单一的请求处理器。请求处理器可以通过RequestDelegate对象来表示,所以整个请求处理管道将具有更加简单的表达:Pipeline = Server + RequestDelegate(见下图12)。

表示中间件的Func<RequestDelegate, RequestDelegate>对象向表示请求处理器的RequestDelegate对象之间的转换是通过IApplicationBuilder对象来完成的。从接口命名可以看出,IApplicationBuilder对象是用来构建“应用程序”(Application)的,实际上,由所有注册中间件构建的RequestDelegate对象就是对应用程序的表达,因为应用程序的意图完全是由注册的中间件达成的。

public interface IApplicationBuilder
{RequestDelegate Build();IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);
}

如上所示的代码片段是模拟框架对IApplicationBuilder接口的简化定义。它的Use方法用来注册中间件,而Build方法则将所有的中间件按照注册的顺序组装成一个RequestDelegate对象。如下所示的代码片段中ApplicationBuilder类型是对该接口的默认实现。我们给出的代码片段还体现了这样一个细节:当我们将注册的中间件转换成一个表示请求处理器的RequestDelegate对象时,会在管道的尾端添加一个处理器用来响应一个状态码为404的响应。这个细节意味着如果没有注册任何的中间件或者所有注册的中间件都将请求分发给后续管道,那么应用程序会回复一个状态码为404的响应。

public class ApplicationBuilder : IApplicationBuilder
{private readonly IList<Func<RequestDelegate, RequestDelegate>> _middlewares = new List<Func<RequestDelegate, RequestDelegate>>();public RequestDelegate Build(){RequestDelegate next = context =>{context.Response.StatusCode = 404;return Task.CompletedTask;};foreach (var middleware in _middlewares.Reverse()){next = middleware.Invoke(next);}return next;}public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){_middlewares.Add(middleware);return this;}
}

二、服务器

服务器在管道中的职责非常明确:负责HTTP请求的监听、接收和最终的响应。具体来说,启动后的服务器会绑定到指定的端口进行请求监听。一旦有请求抵达,服务器会根据该请求创建代表请求上下文的HttpContext对象,并将该上下文分发给注册的中间件进行处理。当中间件管道完成了针对请求的处理之后,服务器会将最终生成的响应回复给客户端。

IServer

在模拟的ASP.NET Core框架中,我们将服务器定义成一个极度简化的IServer接口。在如下所示的代码片段中,IServer接口具有唯一的StartAsync方法来启动自身代表的服务器。服务器最终需要将接收的请求分发给注册的中间件,而注册的中间件最终会被IApplicationBuilder对象构建成一个代表请求处理器的RequestDelegate对象,StartAsync方法的参数handler代表的就是这样一个对象。

public interface IServer
{Task StartAsync(RequestDelegate handler);
}

针对服务器的适配

面向应用层的HttpContext对象是对请求和响应的抽象与封装,但是请求最初是由面向传输层的服务器接收的,最终的响应也会由服务器回复给客户端。所有ASP.NET Core应用使用的都是同一个HttpContext类型,但是它们可以注册不同类型的服务器,应如何解决两者之间的适配问题?计算机领域有这样一句话:“任何问题都可以通过添加一个抽象层的方式来解决,如果解决不了,那就再加一层。”同一个HttpContext类型与不同服务器类型之间的适配问题自然也可以通过添加一个抽象层来解决。我们将定义在该抽象层的对象称为特性(Feature),特性可以视为对HttpContext某个方面的抽象化描述。

如上图所示,我们可以定义一系列特性接口来为HttpContext提供某个方面的上下文信息,具体的服务器只需要实现这些Feature接口即可。对于所有用来定义特性的接口,最重要的是提供请求信息的IRequestFeature接口和完成响应的IResponseFeature接口。

下面阐述用来适配不同服务器类型的特性在代码层面的定义。如下面的代码片段所示,我们定义了一个IFeatureCollection接口来表示存放特性的集合。可以看出,这是一个以Type和Object作为Key和Value的字典,Key代表注册Feature所采用的类型,而Value代表Feature对象本身,也就是说,我们提供的特性最终是以对应类型(一般为接口类型)进行注册的。为了便于编程,我们定义了Set<T>方法和Get<T>方法来设置与获取特性对象。

public interface IFeatureCollection : IDictionary<Type, object> { }
public class FeatureCollection : Dictionary<Type, object>, IFeatureCollection { }
public static partial class Extensions
{public static T Get<T>(this IFeatureCollection features)  => features.TryGetValue(typeof(T), out var value) ? (T)value : default(T);public static IFeatureCollection Set<T>(this IFeatureCollection features, T feature){features[typeof(T)] = feature;return features;}
}

最核心的两种特性类型就是分别用来表示请求和响应的特性,我们可以采用如下两个接口来表示。可以看出,IHttpRequestFeature接口和IHttpResponseFeature接口具有与抽象类型HttpRequest和HttpResponse完全一致的成员定义。

public interface IHttpRequestFeature
{Uri Url { get; }NameValueCollection Headers { get; }Stream Body { get; }
}
public interface IHttpResponseFeature
{int StatusCode { get; set; }NameValueCollection Headers { get; }Stream Body { get; }
}

我们在前面给出了用于描述请求上下文的HttpContext类型的成员定义,下面介绍其具体实现。如下面的代码片段所示,表示请求和响应的HttpRequest与HttpResponse分别是由对应的特性(IHttpRequestFeature对象和IHttpResponseFeature对象)创建的。HttpContext对象本身则是通过一个表示特性集合的IFeatureCollection 对象来创建的,它会在初始化过程中从这个集合中提取出对应的特性来创建HttpRequest对象和HttpResponse对象。

public class HttpContext
{public HttpRequest Request { get; }public HttpResponse Response { get; }public HttpContext(IFeatureCollection features){Request = new HttpRequest(features);Response = new HttpResponse(features);}
}public class HttpRequest
{private readonly IHttpRequestFeature _feature;public Uri Url=> _feature.Url;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public HttpRequest(IFeatureCollection features)=> _feature = features.Get<IHttpRequestFeature>();
}public class HttpResponse
{private readonly IHttpResponseFeature _feature;public NameValueCollection Headers=> _feature.Headers;public Stream Body=> _feature.Body;public int StatusCode{get => _feature.StatusCode;set => _feature.StatusCode = value;}public HttpResponse(IFeatureCollection features)=> _feature = features.Get<IHttpResponseFeature>();
}

换句话说,我们利用HttpContext对象的Request属性提取的请求信息最初来源于IHttpRequestFeature对象,利用它的Response属性针对响应所做的任意操作最终都会作用到IHttpResponseFeature对象上。这两个对象最初是由注册的服务器提供的,这正是同一个ASP.NET Core应用可以自由地选择不同服务器类型的根源所在。

HttpListenerServer

在对服务器的职责和它与HttpContext的适配原理有了清晰的认识之后,我们可以尝试定义一个服务器。我们将接下来定义的服务器类型命名为HttpListenerServer,因为它对请求的监听、接收和响应是由一个HttpListener对象来实现的。由于服务器接收到请求之后需要借助“特性”的适配来构建统一的请求上下文(即HttpContext对象),这也是中间件的执行上下文,所以提供针对性的特性实现是自定义服务类型的关键所在。

对HttpListener有所了解的读者都知道,当它在接收到请求之后同样会创建一个HttpListenerContext对象表示请求上下文。如果使用HttpListener对象作为ASP.NET Core应用的监听器,就意味着不仅所有的请求信息会来源于这个HttpListenerContext对象,我们针对请求的响应最终也需要利用这个上下文对象来完成。HttpListenerServer对应特性所起的作用实际上就是在HttpListenerContext和HttpContext这两种上下文之间搭建起一座如下图所示的桥梁。

上图中用来在HttpListenerContext和HttpContext这两个上下文类型之间完成适配的特性类型被命名为HttpListenerFeature。如下面的代码片段所示,HttpListenerFeature类型同时实现了针对请求和响应的特性接口IHttpRequestFeature与IHttpResponseFeature。

public class HttpListenerFeature : IHttpRequestFeature, IHttpResponseFeature
{private readonly HttpListenerContext _context;public HttpListenerFeature(HttpListenerContext context) => _context = context;Uri IHttpRequestFeature.Url => _context.Request.Url;NameValueCollection IHttpRequestFeature.Headers => _context.Request.Headers;NameValueCollection IHttpResponseFeature.Headers => _context.Response.Headers;Stream IHttpRequestFeature.Body => _context.Request.InputStream;Stream IHttpResponseFeature.Body => _context.Response.OutputStream;int IHttpResponseFeature.StatusCode{get => _context.Response.StatusCode;set => _context.Response.StatusCode = value;}
}

创建HttpListenerFeature对象时需要提供一个HttpListenerContext对象,IHttpRequestFeature接口的实现成员所提供的请求信息全部来源于这个HttpListenerContext上下文,IHttpResponseFeature接口的实现成员针对响应的操作最终也转移到这个HttpListenerContext上下文上。如下所示的代码片段是针对HttpListener的服务器类型HttpListenerServer的完整定义。我们在创建HttpListenerServer对象的时候可以显式提供一组监听地址,如果没有提供,监听地址会默认设置“localhost:5000”。在实现的StartAsync方法中,我们启动了在构造函数中创建的HttpListenerServer对象,并且在一个无限循环中通过调用其GetContextAsync方法实现了针对请求的监听和接收。

public class HttpListenerServer : IServer
{private readonly HttpListener _httpListener;private readonly string[] _urls;public HttpListenerServer(params string[] urls){_httpListener = new HttpListener();_urls = urls.Any() ? urls : new string[] { "http://localhost:5000/" };}public async Task StartAsync(RequestDelegate handler){Array.ForEach(_urls, url => _httpListener.Prefixes.Add(url));_httpListener.Start();while (true){var listenerContext = await _httpListener.GetContextAsync();var feature = new HttpListenerFeature(listenerContext);var features = new FeatureCollection().Set<IHttpRequestFeature>(feature).Set<IHttpResponseFeature>(feature);var httpContext = new HttpContext(features);await handler(httpContext);listenerContext.Response.Close();}}
}

当HttpListener监听到抵达的请求后,我们会得到一个HttpListenerContext对象,此时只需要利用它创建一个HttpListenerFeature对象并且分别以IHttpRequestFeature接口和IHttpResponseFeature接口的形式注册到创建的FeatureCollection集合上。我们最终利用这个FeatureCollection集合创建出代表请求上下文的HttpContext对象,当将它作为参数调用由所有注册中间件共同构建的RequestDelegate对象时,中间件管道将接管并处理该请求。

三、承载服务

到目前为止,我们已经了解构成ASP.NET Core请求处理管道的两个核心要素(服务器和中间件),现在我们的目标是利用.NET Core承载服务系统来承载这一管道。毫无疑问,还需要通过实现IHostedService接口来定义对应的承载服务,为此我们定义了一个名为WebHostedService的承载服务。(关于.NET Core承载服务系统,请参阅我的系列文章《服务承载系统》)

WebHostedService

由于服务器是整个请求处理管道的“龙头”,所以从某种意义上来说,启动一个ASP.NET Core应用就是为启动服务器,所以可以将服务的启动在WebHostedService承载服务中实现。如下面的代码片段所示,创建一个WebHostedService对象时,需要提供服务器对象和由所有注册中间件构建的RequestDelegate对象。在实现的StartAsync方法中,我们只需要调用服务器对象的StartAsync方法启动它即可。

public class WebHostedService : IHostedService
{private readonly IServer _server;private readonly RequestDelegate _handler;public WebHostedService(IServer server, RequestDelegate handler){_server = server;_handler = handler;}public Task StartAsync(CancellationToken cancellationToken) => _server.StartAsync(_handler);public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
}

到目前为止,我们基本上已经完成了所有核心的工作,如果能够将一个WebHostedService实例注册到.NET Core的承载系统中,它就能够帮助我们启动一个ASP.NET Core应用。为了使这个过程在编程上变得更加便利和“优雅”,我们定义了一个辅助的WebHostBuilder类型。

WebHostBuilder

要创建一个WebHostedService对象,必需显式地提供一个表示服务器的IServer对象,以及由所有注册中间件构建而成的RequestDelegate对象,WebHostBuilder提供了更加便利和“优雅”的服务器与中间件注册方式。如下面的代码片段所示,WebHostBuilder是对额外两个Builder对象的封装:一个是用来构建服务宿主的IHostBuilder对象,另一个是用来注册中间件并最终帮助我们创建RequestDelegate对象的IApplicationBuilder对象。

public class WebHostBuilder
{public IHostBuilder HostBuilder { get; }public IApplicationBuilder ApplicationBuilder { get; }public WebHostBuilder(IHostBuilder hostBuilder, IApplicationBuilder applicationBuilder){HostBuilder = hostBuilder;ApplicationBuilder = applicationBuilder;}
}

我们为WebHostBuilder定义了如下两个扩展方法:UseHttpListenerServer方法完成了针对自定义的服务器类型HttpListenerServer的注册;Configure方法提供了一个Action<IApplication
Builder>类型的参数,利用该参数来注册任意中间件。

public static partial class Extensions
{public static WebHostBuilder UseHttpListenerServer(this WebHostBuilder builder, params string[] urls){builder.HostBuilder.ConfigureServices(svcs => svcs.AddSingleton<IServer>(new HttpListenerServer(urls)));return builder;}public static WebHostBuilder Configure(this WebHostBuilder builder, Action<IApplicationBuilder> configure){configure?.Invoke(builder.ApplicationBuilder);return builder;}
}

代表ASP.NET Core应用的请求处理管道最终是利用承载服务WebHostedService注册到.NET Core的承载系统中的,针对WebHostedService服务的创建和注册体现在为IHostBuilder接口定义的ConfigureWebHost扩展方法上。如下面的代码片段所示,ConfigureWebHost方法定义了一个Action<WebHostBuilder>类型的参数,利用该参数可以注册服务器、中间件及其他相关服务。

public static partial class Extensions
{public static IHostBuilder ConfigureWebHost(this IHostBuilder builder, Action<WebHostBuilder> configure){var webHostBuilder = new WebHostBuilder(builder, new ApplicationBuilder());configure?.Invoke(webHostBuilder);builder.ConfigureServices(svcs => svcs.AddSingleton<IHostedService>(provider => {var server = provider.GetRequiredService<IServer>();var handler = webHostBuilder.ApplicationBuilder.Build();return new WebHostedService(server, handler);}));return builder;}
}

在ConfigureWebHost方法中,我们创建了一个ApplicationBuilder对象,并利用它和当前的IHostBuilder对象创建了一个WebHostBuilder对象,然后将这个WebHostBuilder对象作为参数调用了指定的Action<WebHostBuilder>委托对象。在此之后,我们调用IHostBuilder接口的ConfigureServices方法在依赖注入框架中注册了一个用于创建WebHostedService服务的工厂。对于由该工厂创建的WebHostedService对象来说,服务器来源于注册的服务,而作为请求处理器的RequestDelegate对象则由ApplicationBuilder对象根据注册的中间件构建而成。

应用构建

到目前为止,这个用来模拟ASP.NET Core请求处理管道的“迷你版”框架已经构建完成,下面尝试在它上面开发一个简单的应用。如下面的代码片段所示,我们调用静态类型Host的CreateDefaultBuilder方法创建了一个IHostBuilder对象,然后调用ConfigureWebHost方法并利用提供的Action<WebHostBuilder>对象注册了HttpListenerServer服务器和3个中间件。在调用Build方法构建出作为服务宿主的IHost对象之后,我们调用其Run方法启动所有承载的IHostedSerivce服务。

class Program
{static void Main(){Host.CreateDefaultBuilder().ConfigureWebHost(builder => builder.UseHttpListenerServer().Configure(app => app.Use(FooMiddleware).Use(BarMiddleware).Use(BazMiddleware))).Build().Run();}public static RequestDelegate FooMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Foo=>");await next(context);};public static RequestDelegate BarMiddleware(RequestDelegate next)=> async context =>{await context.Response.WriteAsync("Bar=>");await next(context);};public static RequestDelegate BazMiddleware(RequestDelegate next)=> context => context.Response.WriteAsync("Baz");
}

由于中间件最终体现为一个类型为Func<RequestDelegate, RequestDelegate>的委托对象,所以可以利用与之匹配的方法来定义中间件。演示实例中定义的3个中间件(FooMiddleware、BarMiddleware和BazMiddleware)对应的正是3个静态方法,它们调用WriteAsync扩展方法在响应中写了一段文字。

public static partial class Extensions
{public static Task WriteAsync(this HttpResponse response, string contents){var buffer = Encoding.UTF8.GetBytes(contents);return response.Body.WriteAsync(buffer, 0, buffer.Length);}
}

应用启动之后,如果利用浏览器向应用程序采用的默认监听地址(“http://localhost:5000”)发送一个请求,得到的输出结果如下图所示。浏览器上呈现的文字正是注册的3个中间件写入的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/311218.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

逻辑回归(二)

逻辑回归 在学习逻辑回归之前我们先回顾一下线性回归。线性回归解决的是回归问题&#xff0c;简单来说就是&#xff0c;我们需要找到一个函数&#xff0c;这个函数需要尽可能的拟合所有训练集的样本点。 逻辑回归解决的是分类问题&#xff0c;它的目标是找到一个函数&#x…

ios 顶部tab滑动实现_iOS开发之多表视图滑动切换示例(仿头条客户端)

好长时间没为大家带来iOS开发干货的东西了&#xff0c;今天给大家分享一个头条新闻客户端各个类别进行切换的一个示例。在Demo中对所需的组件进行的简单封装&#xff0c;在封装的组件中使用的是纯代码的形式&#xff0c;如果想要在项目中进行使用&#xff0c;稍微进行修改即可。…

上元节的灯会(灭)-区间dp

题目背景 上元节的庙会上&#xff0c;牛宝靠自己的聪明才智成功破解了花灯阵&#xff0c;点亮了在场所有花灯&#xff0c;但他没料到的是这个游戏包含AB两个项目&#xff0c;A项目就是点亮所有花灯&#xff0c;而B项目则是熄灭所有花灯。不过点亮的是花灯阵&#xff0c;熄灭的…

Asp.Net Core 中IdentityServer4 授权中心之应用实战

一、前言查阅了大多数相关资料&#xff0c;搜索到的IdentityServer4 的应用文章大多是比较简单并且多是翻译官网的文档编写的&#xff0c;我这里在 Asp.Net Core 中IdentityServer4 的应用分析中会以一个电商系统架构升级过程中普遍会遇到的场景进行实战性讲述分析&#xff0c;…

交通标志识别项目教程

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 安装Anaconda 安装Pycharm 安装格式工厂 在上图中这个位置输入cmd回车&#xff0c;即可打开命令终端。用这样的方式打开命令终端…

内存超频trfc_这只是开始?四款DDR4内存超频效果对比

原标题&#xff1a;这只是开始&#xff1f;四款DDR4内存超频效果对比之前有人说DDR4内存的频率极限是5000MHz&#xff0c;但在最近结束的台湾电脑展上&#xff0c;有些内存的默认频率已经达到了4400MHz&#xff0c;看样子5000MHz的频率极限很快要实现了&#xff0c;由此也说明5…

素数-试除法和埃式筛选法模板

试除法&#xff1a; bool is_prime(int n) {if (n < 1) return false;for (int i 2;i<sqrt(n);i)//这样写更好!if (n % i0) return false;return true; }埃式筛选法&#xff1a; const int N 1e7; int prime[N 1]; bool vis[N 1];int n_prime(int n) {int k 0;mem…

C# 视频监控系统

去过工厂或者仓库的都知道&#xff0c;在工厂或仓库里面&#xff0c;会有很多不同的流水线&#xff0c;大部分的工厂或仓库&#xff0c;都会在不同流水线的不同工位旁边安装一台电脑&#xff0c;一方面便于工位上的师傅把产品的重要信息录入系统&#xff0c;便于公司系统数据采…

sklearn svm如何选择核函数_机器学习之支持向量机多种核模型对比

机器学习xueyifeiyun1989zx&#xff0c;公众号&#xff1a;围着围巾的小黑机器学习之监督学习实战前文我们提到机器学习中的监督学习&#xff0c;其中有一个模型是我们提到的但是没有训练测试的&#xff0c;叫做支持向量机(简称SVM)。支持向量机也是监督学习里面一个非常容易理…

7的序列-数论

题目背景 墨家机关城即将被攻陷&#xff0c;墨家家主无意间发现了一道逃生密道&#xff0c;但这道密道需要密码&#xff0c;机智的你决定参与密码的破译。 题目描述 密码门上有两行数字序列&#xff0c;数字均为非负整数&#xff0c;根据门上的古语&#xff0c;你需要求出满足…

程序员过关斩将--从每秒6000写请求谈起

点击上方“蓝字”关注我们菜菜哥&#xff0c;紧急求助呀怎么回事&#xff1f;产品经理砍你了&#xff1f;没有&#xff0c;只是写了个新项目&#xff0c;上线就被压垮了什么功能&#xff0c;这么强悍&#xff1f;一个记录用户观看视频进度信息的功能那如果用户基数大&#xff0…

批量将PPM格式图片转化为JPG格式

将PPM格式图片转化为JPG格式 做图像识别的时候数据集常常是ppm格式的&#xff0c;虽然不影响建模训练&#xff0c;但是我们电脑往往不支持ppm格式的图像展示。 比如到做交通标志识别的时候用到的BelgiumTS交通数据集或者德国GTSRB数据集 下载后得到都是ppm格式的图像。 格式转…

python数据收集整理教案_数据收集整理教案讲解学习

一、数据收集整理第一课时教学目标初步体验数据收集、整理、描述的过程&#xff0c;会用分类数数的方法将数据整理成简单的统计表&#xff0c;初步认识统计表&#xff0c;能正确填写统计表&#xff0c;能从中获得简单统计的结果。通过对学生身边有趣事例的调查活动&#xff0c;…

hdu1873 看病要排队-优先队列

Problem Description 看病要排队这个是地球人都知道的常识。 不过经过细心的0068的观察&#xff0c;他发现了医院里排队还是有讲究的。0068所去的医院有三个医生&#xff08;汗&#xff0c;这么少&#xff09;同时看病。而看病的人病情有轻重&#xff0c;所以不能根据简单的先来…

Magicodes.IE 2.2里程碑需求和建议征集

简介Magicodes.IE是导入导出通用库&#xff0c;支持Dto导入导出以及动态导出&#xff0c;支持Excel、Word、Pdf、Csv和Html。已加入NCC开源组织。Magicodes.IE 2.0发布Github&#xff1a;https://github.com/dotnetcore/Magicodes.IE码云&#xff08;手动同步&#xff0c;不维护…

启动azkaban报错_解决启动Azkaban报错问题:java.lang.NoSuchMethodError: com.google.comm

问题描述&#xff1a;启动Azkaban报错&#xff1a;java.lang.NoSuchMethodError:com.google.common.collect.ImmutableMap.toImmutableMap解决方法&#xff1a;从报错信息来看&#xff0c;是找不到toImmutableMap这个方法。首先找到类ImmutableMap对应的Jar包为guava&#xff0…

(一)tensorflow笔记:Tensor数据类型

常见的数据类型载体 listnp.arraytf.tensor list: 可以存储不同数据类型&#xff0c;缺点不适合存储较大的数据&#xff0c;如图片 np.array: 解决同类型大数据数据的载体&#xff0c;方便数据运算&#xff0c;缺点是在深度学习之前就设计好的&#xff0c;不支持GPU tf.ten…

吃鸡蛋-优先队列

题目描述 小林养了一只母鸡&#xff0c;一连 n 天&#xff0c;每天都可以生下若干个鸡蛋。在第 i 天&#xff0c;母鸡会生下 eggs[i] 个鸡蛋&#xff0c;这些鸡蛋将会在days[i] 天后&#xff08;也就是说&#xff0c;第 i days[i] 天时&#xff09;腐烂&#xff0c;变得无法食…

交通标志识别教程(二)

项目结构图 下载好项目压缩包后解压&#xff0c;得到以上的文件&#xff0c;首先将画红圈的文件删除&#xff08;如果有&#xff09; 安装软件 解压软件包 安装Anaconda 直接下一步&#xff0c;到了这个页面全部勾选&#xff0c;否则不会添加添加环境变量。 安装Pycharm …

深度长文:Power Automation 帮助企业实现数字化转型

01自动化始于您在Ignite 2019上&#xff0c;我们宣布将Flow更改为Power Automate&#xff0c;并在UI Flow连接器的公开预览中引入了机器人流程自动化&#xff08;RPA&#xff09;。我们对几种激动人心的功能感到兴奋&#xff0c;这些功能将在今年全面上市&#xff0c;并想花一点…