九哥聊Kestrel网络编程第二章:开发一个Fiddler

推荐序

之前在.NET 性能优化群内交流时,我们发现很多朋友对于高性能网络框架有需求,需要创建自己的消息服务器、游戏服务器或者物联网网关。但是大多数小伙伴只知道 DotNetty,虽然 DotNetty 是一个非常优秀的网络框架,广泛应用于各种网络服务器中,不过因为各种原因它已经不再有新的特性支持和更新,很多小伙伴都在寻找替代品。

这一切都不用担心,在.NET Core 以后的时代,我们有了更快、更强、更好的 Kestrel 网络框架,正如其名,Kestrel 中文翻译为红隼(hóng sǔn) 封面就是红隼的样子,是一种飞行速度极快的猛禽。Kestrel 是 ASPNET Core 成为.NET 平台性能最强 Web 服务框架的原因之一,但是很多人还觉得 Kestrel 只是用于 ASPNET Core 的网络框架,但是其实它是一个高性能的通用网络框架。

为了让更多的人了解 Kestrel,和多个千星.NET 开源项目作者九哥[1]一拍即合,计划写一系列的文章来介绍它;本文是第二篇,通过 kestrel 实现一个类似 Fiddler 的抓包软件。

由于公众号排版问题,在 PC 端浏览更佳

1 文章目的

本文讲解基于 kestrel 开发类似 Fiddler 应用的过程,让读者了解 kestrel 网络编程里面的 kestrel 中间件和 http 应用中间件。由于最终目的不是输出完整功能的产品,所以这里只实现 Fiddler 最核心的 http 请求和响应内容查看的功能。本文章是KestrelApp 项目[2]里面的一个 demo 的讲解,希望对您有用。

2 开发顺序

  1. 代理协议 kestrel 中间件

  2. tls 协议侦测 kestrel 中间件

  3. 隧道和 http 协议侦测 kestrel 中间件

  4. 请求响应分析 http 中间件

  5. 反向代理 http 中间件

  6. 编排中间件创建服务器和应用

3 传输层与 kestrel 中间件

所谓传输层,其目的是为了让应用协议数据安全、可靠、快速等传输而存在的一种协议,其特征是把应用协议的报文做为自己的负载,常见的 tcp、udp、quic、tls 等都可以理解为传输层协议。 比如 http 协议,常见有如下的传输方式:

  1. http over tcp

  2. http over tls over tcp

  3. http over quic over udp

3.1 Fiddler 的传输层

Fiddler 要处理以下三种 http 传输情况:

  1. http over tcp:直接 http 请求首页

  2. http over proxy over tcp:代理 http 流量

  3. http over tls over proxy over tcp:代理 https 流量

3.2 Kestrel 的中间件

kestrel 目前的传输层基于 tcp 或 quic 两种,同时内置了 tls 中间件,需要调用ListenOptions.UseHttps()来使用 tls 中间件。kestrel 的中间件的表现形式为:Func<ConnectionDelegate, ConnectionDelegate>,为了使用读者能够简单理解中间件,我在KestrelFramework里定义了 kestrel 中间件的变种接口,大家基于此接口来实现更多的中间件就方便很多:

/// <summary>
/// Kestrel的中间件接口
/// </summary>
public interface IKestrelMiddleware
{/// <summary>/// 执行/// </summary>/// <param name="next"></param>/// <param name="context"></param>/// <returns></returns>Task InvokeAsync(ConnectionDelegate next, ConnectionContext context);
}

4 代理协议 kestrel 中间件

Filddler 最基础的功能是它是一个 http 代理服务器, 我们需要为 kestrel 编写代理中间件,用于处理代理传输层。http 代理协议分两种:普通的 http 代理和 Connect 隧道代理。两种的报文者是遵循 http1.0 或 1.1 的文本格式,我们可以使用 kestrel 自带的HttpParser<>来解析这些复杂的 http 文本协议。

4.1 代理特征

在中间件编程模式中,Feature是一个很重要的中间件沟通桥梁,它往往是某个中间件工作之后,留下的财产,让之后的中间件来获取并受益。我们的代理中间件,也设计了 IProxyFeature,告诉之后的中间件一些代理特征。

/// <summary>
/// 代理Feature
/// </summary>
public interface IProxyFeature
{/// <summary>/// 代理主机/// </summary>HostString ProxyHost { get; }/// <summary>/// 代理协议/// </summary>ProxyProtocol ProxyProtocol { get; }
}/// <summary>
/// 代理协议
/// </summary>
public enum ProxyProtocol
{/// <summary>/// 无代理/// </summary>None,/// <summary>/// http代理/// </summary>HttpProxy,/// <summary>/// 隧道代理/// </summary>TunnelProxy
}

4.2 代理中间件的实现

/// <summary>
/// 代理中间件
/// </summary>
sealed class KestrelProxyMiddleware : IKestrelMiddleware
{private static readonly HttpParser<HttpRequestHandler> httpParser = new();private static readonly byte[] http200 = Encoding.ASCII.GetBytes("HTTP/1.1 200 Connection Established\r\n\r\n");private static readonly byte[] http400 = Encoding.ASCII.GetBytes("HTTP/1.1 400 Bad Request\r\n\r\n");/// <summary>/// 解析代理/// </summary>/// <param name="next"></param>/// <param name="context"></param>/// <returns></returns>public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context){var input = context.Transport.Input;var output = context.Transport.Output;var request = new HttpRequestHandler();while (context.ConnectionClosed.IsCancellationRequested == false){var result = await input.ReadAsync();if (result.IsCanceled){break;}try{if (ParseRequest(result, request, out var consumed)){if (request.ProxyProtocol == ProxyProtocol.TunnelProxy){input.AdvanceTo(consumed);await output.WriteAsync(http200);}else{input.AdvanceTo(result.Buffer.Start);}context.Features.Set<IProxyFeature>(request);await next(context);break;}else{input.AdvanceTo(result.Buffer.Start, result.Buffer.End);}if (result.IsCompleted){break;}}catch (Exception){await output.WriteAsync(http400);break;}}}/// <summary>/// 解析http请求/// </summary>/// <param name="result"></param>/// <param name="request"></param>/// <param name="consumed"></param>/// <returns></returns>private static bool ParseRequest(ReadResult result, HttpRequestHandler request, out SequencePosition consumed){var reader = new SequenceReader<byte>(result.Buffer);if (httpParser.ParseRequestLine(request, ref reader) &&httpParser.ParseHeaders(request, ref reader)){consumed = reader.Position;return true;}else{consumed = default;return false;}}/// <summary>/// 代理请求处理器/// </summary>private class HttpRequestHandler : IHttpRequestLineHandler, IHttpHeadersHandler, IProxyFeature{private HttpMethod method;public HostString ProxyHost { get; private set; }public ProxyProtocol ProxyProtocol{get{if (ProxyHost.HasValue == false){return ProxyProtocol.None;}if (method == HttpMethod.Connect){return ProxyProtocol.TunnelProxy;}return ProxyProtocol.HttpProxy;}}void IHttpRequestLineHandler.OnStartLine(HttpVersionAndMethod versionAndMethod, TargetOffsetPathLength targetPath, Span<byte> startLine){method = versionAndMethod.Method;var host = Encoding.ASCII.GetString(startLine.Slice(targetPath.Offset, targetPath.Length));if (versionAndMethod.Method == HttpMethod.Connect){ProxyHost = HostString.FromUriComponent(host);}else if (Uri.TryCreate(host, UriKind.Absolute, out var uri)){ProxyHost = HostString.FromUriComponent(uri);}}void IHttpHeadersHandler.OnHeader(ReadOnlySpan<byte> name, ReadOnlySpan<byte> value){}void IHttpHeadersHandler.OnHeadersComplete(bool endStream){}void IHttpHeadersHandler.OnStaticIndexedHeader(int index){}void IHttpHeadersHandler.OnStaticIndexedHeader(int index, ReadOnlySpan<byte> value){}}
}

5 tls 协议侦测 kestrel 中间件

Fiddler 只监听了一个端口,要同时支持非加密和加密两种流量,如果不调用调用ListenOptions.UseHttps(),我们的程序就不支持 https 的分析;如果直接调用ListenOptions.UseHttps(),会让我们的程序不支持非加密的 http 的分析,这就要求我们有条件的根据客户端发来的流量分析是否需要开启。

我已经在KestrelFramework内置了TlsDetection中间件,这个中间件可以根据客户端的实际流量类型来选择是否使用 tls。在 Fiddler 中,我们还需要根据客户端的tls握手中的sni使用 ca 证书来动态生成服务器证书用于 tls 加密传输。

/// <summary>
/// 证书服务
/// </summary>
sealed class CertService
{private const string CACERT_PATH = "cacert";private readonly IMemoryCache serverCertCache;private readonly IEnumerable<ICaCertInstaller> certInstallers;private readonly ILogger<CertService> logger;private X509Certificate2? caCert;/// <summary>/// 获取证书文件路径/// </summary>public string CaCerFilePath { get; } = OperatingSystem.IsLinux() ? $"{CACERT_PATH}/fiddler.crt" : $"{CACERT_PATH}/fiddler.cer";/// <summary>/// 获取私钥文件路径/// </summary>public string CaKeyFilePath { get; } = $"{CACERT_PATH}/fiddler.key";/// <summary>/// 证书服务/// </summary>/// <param name="serverCertCache"></param>/// <param name="certInstallers"></param>/// <param name="logger"></param>public CertService(IMemoryCache serverCertCache,IEnumerable<ICaCertInstaller> certInstallers,ILogger<CertService> logger){this.serverCertCache = serverCertCache;this.certInstallers = certInstallers;this.logger = logger;Directory.CreateDirectory(CACERT_PATH);}/// <summary>/// 生成CA证书/// </summary>public bool CreateCaCertIfNotExists(){if (File.Exists(this.CaCerFilePath) && File.Exists(this.CaKeyFilePath)){return false;}File.Delete(this.CaCerFilePath);File.Delete(this.CaKeyFilePath);var notBefore = DateTimeOffset.Now.AddDays(-1);var notAfter = DateTimeOffset.Now.AddYears(10);var subjectName = new X500DistinguishedName($"CN={nameof(Fiddler)}");this.caCert = CertGenerator.CreateCACertificate(subjectName, notBefore, notAfter);var privateKeyPem = this.caCert.GetRSAPrivateKey()?.ExportRSAPrivateKeyPem();File.WriteAllText(this.CaKeyFilePath, new string(privateKeyPem), Encoding.ASCII);var certPem = this.caCert.ExportCertificatePem();File.WriteAllText(this.CaCerFilePath, new string(certPem), Encoding.ASCII);return true;}/// <summary>/// 安装和信任CA证书/// </summary>public void InstallAndTrustCaCert(){var installer = this.certInstallers.FirstOrDefault(item => item.IsSupported());if (installer != null){installer.Install(this.CaCerFilePath);}else{this.logger.LogWarning($"请根据你的系统平台手动安装和信任CA证书{this.CaCerFilePath}");}}/// <summary>/// 获取颁发给指定域名的证书/// </summary>/// <param name="domain"></param>/// <returns></returns>public X509Certificate2 GetOrCreateServerCert(string? domain){if (this.caCert == null){using var rsa = RSA.Create();rsa.ImportFromPem(File.ReadAllText(this.CaKeyFilePath));this.caCert = new X509Certificate2(this.CaCerFilePath).CopyWithPrivateKey(rsa);}var key = $"{nameof(CertService)}:{domain}";var endCert = this.serverCertCache.GetOrCreate(key, GetOrCreateCert);return endCert!;// 生成域名的1年证书X509Certificate2 GetOrCreateCert(ICacheEntry entry){var notBefore = DateTimeOffset.Now.AddDays(-1);var notAfter = DateTimeOffset.Now.AddYears(1);entry.SetAbsoluteExpiration(notAfter);var extraDomains = GetExtraDomains();var subjectName = new X500DistinguishedName($"CN={domain}");var endCert = CertGenerator.CreateEndCertificate(this.caCert, subjectName, extraDomains, notBefore, notAfter);// 重新初始化证书,以兼容win平台不能使用内存证书return new X509Certificate2(endCert.Export(X509ContentType.Pfx));}}/// <summary>/// 获取域名/// </summary>/// <param name="domain"></param>/// <returns></returns>private static IEnumerable<string> GetExtraDomains(){yield return Environment.MachineName;yield return IPAddress.Loopback.ToString();yield return IPAddress.IPv6Loopback.ToString();}
}

6 隧道和 http 协议侦测 kestrel 中间件

经过KestrelProxyMiddleware后的流量,在 tls 解密(如果可能)之后,一般情况下都是 http 流量了,但如果你在 qq 设置代理到我们这个伪 Fildder 之后,会发现部分流量流量不是 http 流量,原因是 http 隧道也是一个通用传输层,可以传输任意 tcp 或 tcp 之上的流量。所以我们需要新的中间件来检测当前流量,如果不是 http 流量就回退到隧道代理的流程,即我们不跟踪不分析这部分非 http 流量。

6.1 http 流量侦测

/// <summary>
/// 流量侦测器
/// </summary>
private static class FlowDetector
{private static readonly byte[] crlf = Encoding.ASCII.GetBytes("\r\n");private static readonly byte[] http10 = Encoding.ASCII.GetBytes(" HTTP/1.0");private static readonly byte[] http11 = Encoding.ASCII.GetBytes(" HTTP/1.1");private static readonly byte[] http20 = Encoding.ASCII.GetBytes(" HTTP/2.0");/// <summary>/// 传输内容是否为http/// </summary>/// <param name="context"></param>/// <returns></returns>public static async ValueTask<bool> IsHttpAsync(ConnectionContext context){var input = context.Transport.Input;var result = await input.ReadAtLeastAsync(1);var isHttp = IsHttp(result);input.AdvanceTo(result.Buffer.Start);return isHttp;}private static bool IsHttp(ReadResult result){var reader = new SequenceReader<byte>(result.Buffer);if (reader.TryReadToAny(out ReadOnlySpan<byte> line, crlf)){return line.EndsWith(http11) || line.EndsWith(http20) || line.EndsWith(http10);}return false;}
}

6.2 隧道回退中间件

/// <summary>
/// 隧道传输中间件
/// </summary>
sealed class KestrelTunnelMiddleware : IKestrelMiddleware
{private readonly ILogger<KestrelTunnelMiddleware> logger;/// <summary>/// 隧道传输中间件/// </summary>/// <param name="logger"></param>public KestrelTunnelMiddleware(ILogger<KestrelTunnelMiddleware> logger){this.logger = logger;}/// <summary>/// 执行中间你件/// </summary>/// <param name="next"></param>/// <param name="context"></param>/// <returns></returns>public async Task InvokeAsync(ConnectionDelegate next, ConnectionContext context){var feature = context.Features.Get<IProxyFeature>();if (feature == null || feature.ProxyProtocol == ProxyProtocol.None){this.logger.LogInformation($"侦测到http直接请求");await next(context);}else if (feature.ProxyProtocol == ProxyProtocol.HttpProxy){this.logger.LogInformation($"侦测到普通http代理流量");await next(context);}else if (await FlowDetector.IsHttpAsync(context)){this.logger.LogInformation($"侦测到隧道传输http流量");await next(context);}else{this.logger.LogInformation($"跳过隧道传输非http流量{feature.ProxyHost}的拦截");await TunnelAsync(context, feature);}}/// <summary>/// 隧道传输其它协议的数据/// </summary>/// <param name="context"></param>/// <param name="feature"></param>/// <returns></returns>private async ValueTask TunnelAsync(ConnectionContext context, IProxyFeature feature){var port = feature.ProxyHost.Port;if (port == null){return;}try{var host = feature.ProxyHost.Host;using var socket = new Socket(SocketType.Stream, ProtocolType.Tcp);await socket.ConnectAsync(host, port.Value, context.ConnectionClosed);Stream stream = new NetworkStream(socket, ownsSocket: false);// 如果有tls中间件,则反回来加密隧道if (context.Features.Get<ITlsConnectionFeature>() != null){var sslStream = new SslStream(stream, leaveInnerStreamOpen: true);await sslStream.AuthenticateAsClientAsync(feature.ProxyHost.Host);stream = sslStream;}var task1 = stream.CopyToAsync(context.Transport.Output);var task2 = context.Transport.Input.CopyToAsync(stream);await Task.WhenAny(task1, task2);}catch (Exception ex){this.logger.LogError(ex, $"连接到{feature.ProxyHost}异常");}}
}

7 请求响应分析 http 中间件

这部分属于 asp.netcore 应用层内容,关键点是制作可多次读取的 http 请求 body 流和 http 响应 body 流,因为每个分析器实例都可以会重头读取一次请求内容和响应内容。

7.1 http 分析器

为了方便各种分析器的独立实现,我们定义 http 分析器的接口

/// <summary>
/// http分析器
/// 支持多个实例
/// </summary>
public interface IHttpAnalyzer
{/// <summary>/// 分析http/// </summary>/// <param name="context"></param>/// <returns></returns>ValueTask AnalyzeAsync(HttpContext context);
}

这是输到日志的 http 分析器

public class LoggingHttpAnalyzer : IHttpAnalyzer
{private readonly ILogger<LoggingHttpAnalyzer> logger;public LoggingHttpAnalyzer(ILogger<LoggingHttpAnalyzer> logger){this.logger = logger;}public async ValueTask AnalyzeAsync(HttpContext context){var builder = new StringBuilder();var writer = new StringWriter(builder);writer.WriteLine("[REQUEST]");await context.SerializeRequestAsync(writer);writer.WriteLine("[RESPONSE]");await context.SerializeResponseAsync(writer);this.logger.LogInformation(builder.ToString());}
}

7.2 分析 http 中间件

我们把请求 body 流和响应 body 流保存到临时文件,在所有分析器工作之后再删除。

/// <summary>
/// http分析中间件
/// </summary>
sealed class HttpAnalyzeMiddleware
{private readonly RequestDelegate next;private readonly IEnumerable<IHttpAnalyzer> analyzers;/// <summary>/// http分析中间件/// </summary>/// <param name="next"></param>/// <param name="analyzers"></param>public HttpAnalyzeMiddleware(RequestDelegate next,IEnumerable<IHttpAnalyzer> analyzers){this.next = next;this.analyzers = analyzers;}/// <summary>/// 分析代理的http流量/// </summary>/// <param name="context"></param>/// <returns></returns>public async Task InvokeAsync(HttpContext context){var feature = context.Features.Get<IProxyFeature>();if (feature == null || feature.ProxyProtocol == ProxyProtocol.None){await next(context);return;}context.Request.EnableBuffering();var oldBody = context.Response.Body;using var response = new FileResponse();try{// 替换response的bodycontext.Response.Body = response.Body;// 请求下个中间件await next(context);// 处理分析await this.AnalyzeAsync(context);}finally{response.Body.Position = 0L;await response.Body.CopyToAsync(oldBody);context.Response.Body = oldBody;}}private async ValueTask AnalyzeAsync(HttpContext context){foreach (var item in this.analyzers){context.Request.Body.Position = 0L;context.Response.Body.Position = 0L;await item.AnalyzeAsync(context);}}private class FileResponse : IDisposable{private readonly string filePath = Path.GetTempFileName();public Stream Body { get; }public FileResponse(){this.Body = new FileStream(filePath, FileMode.Open, FileAccess.ReadWrite);}public void Dispose(){this.Body.Dispose();File.Delete(filePath);}}
}

8 反向代理 http 中间件

我们需要把请求转发到真实的目标服务器,这时我们的应用程序是一个 http 客户端角色,这个过程与 nginx 的反向代理是一致的。具体的实现上,我们直接使用 yarp 库来完成即可。

/// <summary>
/// http代理执行中间件
/// </summary>
sealed class HttpForwardMiddleware
{private readonly RequestDelegate next;private readonly IHttpForwarder httpForwarder;private readonly HttpMessageInvoker httpClient = new(CreateSocketsHttpHandler());/// <summary>/// http代理执行中间件/// </summary>/// <param name="next"></param>/// <param name="httpForwarder"></param>public HttpForwardMiddleware(RequestDelegate next,IHttpForwarder httpForwarder){this.next = next;this.httpForwarder = httpForwarder;}/// <summary>/// 转发http流量/// </summary>/// <param name="context"></param>/// <returns></returns>public async Task InvokeAsync(HttpContext context){var feature = context.Features.Get<IProxyFeature>();if (feature == null || feature.ProxyProtocol == ProxyProtocol.None){await next(context);}else{var scheme = context.Request.Scheme;var destinationPrefix = $"{scheme}://{feature.ProxyHost}";await httpForwarder.SendAsync(context, destinationPrefix, httpClient, ForwarderRequestConfig.Empty, HttpTransformer.Empty);}}private static SocketsHttpHandler CreateSocketsHttpHandler(){return new SocketsHttpHandler{Proxy = null,UseProxy = false,UseCookies = false,AllowAutoRedirect = false,AutomaticDecompression = DecompressionMethods.None,};}
}

9 编排中间件创建服务器和应用

9.1 kestrel 中间件编排

这里要特别注意顺序,传输层套娃。

/// <summary>
///  ListenOptions扩展
/// </summary>
public static partial class ListenOptionsExtensions
{/// <summary>/// 使用Fiddler的kestrel中间件/// </summary>/// <param name="listen"></param>public static ListenOptions UseFiddler(this ListenOptions listen){// 代理协议中间件listen.Use<KestrelProxyMiddleware>();// tls侦测中间件listen.UseTlsDetection(tls =>{var certService = listen.ApplicationServices.GetRequiredService<CertService>();certService.CreateCaCertIfNotExists();certService.InstallAndTrustCaCert();tls.ServerCertificateSelector = (context, domain) => certService.GetOrCreateServerCert(domain);});// 隧道代理处理中间件listen.Use<KestrelTunnelMiddleware>();return listen;}
}

9.2 http 中间件的编排

public static class ApplicationBuilderExtensions
{/// <summary>/// 使用Fiddler的http中间件/// </summary>/// <param name="app"></param>public static void UseFiddler(this IApplicationBuilder app){app.UseMiddleware<HttpAnalyzeMiddleware>();app.UseMiddleware<HttpForwardMiddleware>();}
}

9.3 创建应用

我们可以在传统的 MVC 里创建伪 fiddler 的首页、下载证书等 http 交互页面。

public static void Main(string[] args)
{var builder = WebApplication.CreateBuilder(args);builder.Services.AddFiddler().AddControllers();builder.WebHost.ConfigureKestrel((context, kestrel) =>{var section = context.Configuration.GetSection("Kestrel");kestrel.Configure(section).Endpoint("Fiddler", endpoint => endpoint.ListenOptions.UseFiddler());});var app = builder.Build();app.UseRouting();app.UseFiddler();app.MapControllerRoute(name: "default",pattern: "{controller=Home}/{action=Index}/{id?}");app.Run();
}

10 留给读者

如果让您来开发个伪 Fiddler,除了本文的方法,您会使用什么方式来开发呢?

参考资料

[1]

九哥: https://www.cnblogs.com/kewei/

[2]

KestrelApp项目: https://github.com/xljiulang/KestrelApp

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

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

相关文章

apple tv 开发_如何跨多台Apple TV同步Apple TV的主屏幕

apple tv 开发If you have more than one Apple TV in your household, you probably know how annoying it is when you have to install Apple TV apps multiple times on each device. However, with the release of tvOS 11, that’s no longer the case. 如果您的家庭中有…

这些故事说的都是你——译者带你读《硅谷革命》

作者 | 薛命灯 作为《硅谷革命》的译者之一&#xff0c;同时也是一个拥有十余年软件开发和架构经验的工程师&#xff0c;当时我在决定是否接受重译这本书的时候&#xff0c;几乎是不假思索地答应了郭蕾&#xff08;本书重启版发起人之一&#xff09;的提议&#xff0c;只因他的…

runc容器逃逸漏洞最强后续:应对之策汇总与热点疑问解答

美国时间2019年2月11日晚&#xff0c;runc通过oss-security邮件列表披露了runc容器逃逸漏洞CVE-2019-5736的详情。runc是Docker、CRI-O、Containerd、Kubernetes等底层的容器运行时&#xff0c;此次安全漏洞无可避免地会影响大多数Docker与Kubernetes用户&#xff0c;也因此为整…

OOD之问题空间到解空间—附FP的建模

通常会被问到&#xff0c;什么事OOD&#xff0c;然后大部分人期待的答案比较死板&#xff0c;继承、封装、多态&#xff01;懂这个的人多的去了&#xff0c;有什么好问&#xff1f;回答出来的人是否拿着Java又去做一些面向过程的勾当&#xff1f; 计算机革命起源于机器&#xf…

com surrogate_什么是“ COM Surrogate”(dllhost.exe),为什么它在我的PC上运行?

com surrogateIf you poke around in your Task Manager, there’s a good chance you’ll see one or more “COM Surrogate” processes running on a Windows PC. These processes have the file name “dllhost.exe”, and are part of the Windows operating system. You’…

云计算时代,互联网金融背后的想象空间

本文讲的是云计算时代&#xff0c;互联网金融背后的想象空间&#xff0c;【IT168评论】阿里巴巴在纽交所的开市钟史无前例的由八位合作伙伴敲响&#xff0c;可见阿里对互联网时代构筑起生态系统的坚持。这其中&#xff0c;由余额宝所敲开的互联网金融热潮的热度持续不减&#x…

JavaScript数据结构与算法——集合

1.集合数据结构 集合是一组无序且唯一&#xff08;不能重复&#xff09;的项组成的。这个数据结构使用了和有限集合相同的数学概念。 2.创建集合 function Set() {// 这里使用对象而不是数组来表示集合 // js对象中不允许一个键值指向两个不同属性&#xff0c;也保证了集合中的…

php用两个栈来实现队列

php用两个栈来实现队列 一、总结 我主要的问题是不知道的是题目描述&#xff0c;题目和贵的代码之间的关系&#xff0c;以及返回值 思路&#xff1a;A栈做入队操作&#xff0c;B栈做出队操作&#xff0c;入队的时候元素直接入A&#xff0c;出队的时候判断B栈是否为空&#xff0…

facebook 邀请好友_如何查看紧急情况下您的Facebook朋友是否安全

facebook 邀请好友Facebook’s Safety Check feature lets you check in during an emergency to confirm you’re safe. If you have friends or family in an area that you haven’t heard from, though, you may want to ask them directly. Here’s how to ask someone to…

【您有一封来自阿里云的邀请函】阿里云成都客户服务中心20+职位虚席以待,来吧,成就最好的自己!...

如果你不想辜负这个科技的时代&#xff0c;相信它会因你而不同。如果你不想仅做年度大戏的观众&#xff0c;相信自己会成为主角。如果你不想淹没在枯燥与苟且中&#xff0c;相信工作有诗和远方。那么&#xff0c;不要犹豫&#xff0c;加入我们&#xff01;在这&#xff0c;你已…

A - A Secret -扩展KMP

题目大意&#xff1a;给你两个字符串A,B&#xff0c;现在要你求B串的后缀在A串中出现的次数和后缀长度的乘积和为多少。题解&#xff1a;扩展KMP模板题&#xff0c;将A和B串都逆序以后就变成了求前缀的问题了&#xff0c;扩展KMP求处从i位置开始的最长公共前缀存于数组。最后通…

.NET 代码优化 聊聊逻辑圈复杂度

本文属于 dotnet 代码优化系列博客。相信大家都对圈复杂度这个概念很是熟悉&#xff0c;本文来和大家聊聊逻辑的圈复杂度。代码优化里面&#xff0c;一个关注的重点在于代码的逻辑复杂度。一段代码的逻辑复杂度越高&#xff0c;那么维护起来的难度也就越大。衡量代码的逻辑复杂…

GO语言基础条件、跳转、Array和Slice

1. 判断语句if 1. 条件表达式没有括号&#xff08;这点其他语言转过来的需要注意&#xff09; 2. 支持一个初始化表达式&#xff08;可以是并行方式&#xff0c;即&#xff1a;a, b, c : 1, 2, 3) 3. 左大括号必须和条件语句或 else 在同一行 4. 支持单行模式 5. 初始化语句中的…

干式真空泵原理_如何安装干式墙锚在墙壁上悬挂重物

干式真空泵原理If you ever plan to mount something to the wall that’s even remotely heavy, you’ll need to use drywall anchors if a stud isn’t available. Here are the different types of drywall anchors, and how to use each one. 如果您打算将甚至更重的东西安…

sharding-jdbc学习

sharding-jdbc的全局id生成策略是通过雪花算法来实现的。 sharding-jdbc也是一个数据的中间件&#xff0c;可实现读写分离和分库分表&#xff0c;比mycat要简单些。 nginx与ribbon实现负载均衡的区别&#xff1a;nginx是实现服务器端的负载均衡&#xff0c;ribbon是实现客户端即…

像go 一样 打造.NET 单文件应用程序的编译器项目bflat 发布 7.0版本

现代.NET和C#在低级/系统程序以及与C/C/Rust等互操作方面的能力完全令各位刮目相看了&#xff0c;有人用C#开发的64位操作系统: GitHub - nifanfa/MOOS: C# x64 operating system pro...&#xff0c;截图要介绍的是一个结合Roslyn和NativeAOT的实验性编译器bflat &#xff1a;h…

添加dubbo.xsd的方法

整合dubbo-spring的时候&#xff0c;配置文件会报错 因为 阿里关闭在线的域名了.需要本地下载xsd文件 所以&#xff0c;需要下载本地引入。 解决方式&#xff1a; 在dubbo的开源项目上找到xsd文件&#xff1a; https://github.com/alibaba/dubbo Idea使用本地xsd Setting…

Spring Cloud Feign注意点

2019独角兽企业重金招聘Python工程师标准>>> 1、只要在启动类中加入EnableFeignClients注解&#xff0c;才会扫描FeignClient注解 2、Feign主要是通过接口调用&#xff0c;底层其实也是HttpClient/OkHttp 1&#xff09;提供一个Feign接口&#xff0c;加入对应的rest…

.gitkeep是什么? .gitignore和.gitkeep之间的区别(译)

你是不是在git工程里遇到过.gitkeep文件&#xff1f;如果你通过angular脚手架来生成angular2或者angular4工程&#xff0c;你会发现.gitkeep文件在./src/app/assets文件夹里。你对着个文件感到奇怪吗&#xff1f;我们都知道我们的老朋友.gitignore。你也许会觉得它是.gitignore…

扫描PDF417崩溃的原因找到:手机摄像头分辨率低

换孩子姥姥华为手机解决了。 能扫pdf417码了转载于:https://www.cnblogs.com/strongdady/p/9049155.html